From b50264525f76419f6272723550d76e51f23eaf1d Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 26 Jun 2026 13:07:56 -0300 Subject: [PATCH 1/3] feat(tabs): select tabs by index within active space --- package.json | 2 +- src/app/App.tsx | 6 ++- .../tabs/lib/pickTabBySpaceIndex.test.ts | 37 +++++++++++++++++++ src/modules/tabs/lib/useTabs.ts | 17 ++++++++- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 src/modules/tabs/lib/pickTabBySpaceIndex.test.ts diff --git a/package.json b/package.json index 1906318dc..9a17fab36 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.8.2", "license": "Apache-2.0", "type": "module", - "packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1", + "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b", "engines": { "node": ">=22" }, diff --git a/src/app/App.tsx b/src/app/App.tsx index e9c5bd211..080ed208b 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -639,7 +639,11 @@ export default function App() { "tab.close": handleCloseTabOrPane, "tab.next": () => stepSwitcher(1), "tab.prev": () => stepSwitcher(-1), - "tab.selectByIndex": (e) => selectByIndex(parseInt(e.key, 10) - 1), + "tab.selectByIndex": (e) => + selectByIndex( + parseInt(e.key, 10) - 1, + activeSpaceId ?? DEFAULT_SPACE_ID, + ), "space.next": () => cycleSpace(1), "space.prev": () => cycleSpace(-1), "space.overview": () => setSwitcherOpen(true), diff --git a/src/modules/tabs/lib/pickTabBySpaceIndex.test.ts b/src/modules/tabs/lib/pickTabBySpaceIndex.test.ts new file mode 100644 index 000000000..def7861f9 --- /dev/null +++ b/src/modules/tabs/lib/pickTabBySpaceIndex.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { pickTabBySpaceIndex, type Tab } from "./useTabs"; + +function term(id: number, spaceId: string): Tab { + return { + id, + kind: "terminal", + spaceId, + title: "shell", + paneTree: { kind: "leaf", id: id * 10 }, + activeLeafId: id * 10, + } as Tab; +} + +describe("pickTabBySpaceIndex", () => { + const tabs = [term(1, "a"), term(2, "b"), term(3, "b")]; + + it("Cmd+1 in space B returns B's first tab, not A's", () => { + expect(pickTabBySpaceIndex(tabs, 0, "b")?.id).toBe(2); + }); + + it("Cmd+2 in space B returns B's second tab", () => { + expect(pickTabBySpaceIndex(tabs, 1, "b")?.id).toBe(3); + }); + + it("Cmd+3 in space B returns undefined (does nothing)", () => { + expect(pickTabBySpaceIndex(tabs, 2, "b")).toBeUndefined(); + }); + + it("Cmd+1 in space A returns A's only tab", () => { + expect(pickTabBySpaceIndex(tabs, 0, "a")?.id).toBe(1); + }); + + it("returns undefined for an empty space", () => { + expect(pickTabBySpaceIndex(tabs, 0, "c")).toBeUndefined(); + }); +}); diff --git a/src/modules/tabs/lib/useTabs.ts b/src/modules/tabs/lib/useTabs.ts index 3abf5bba4..044578c35 100644 --- a/src/modules/tabs/lib/useTabs.ts +++ b/src/modules/tabs/lib/useTabs.ts @@ -148,6 +148,17 @@ function titleFromUrl(url: string): string { export const DEFAULT_SPACE_ID = "default"; +// Returns the tab at position `idx` within the given space, or undefined when +// idx is out of range or no matching space tab exists. +export function pickTabBySpaceIndex( + tabs: Tab[], + idx: number, + spaceId: string, +): Tab | undefined { + const pool = tabs.filter((t) => t.spaceId === spaceId); + return pool[idx]; +} + // Next active after close, scoped to the closing tab's space. null = last tab of // its space, which callers treat as "refuse to close". export function nextActiveInSpace( @@ -951,8 +962,10 @@ export function useTabs(initial?: Partial) { }, []); const selectByIndex = useCallback( - (idx: number) => { - const t = tabs[idx]; + (idx: number, spaceId?: string) => { + const t = spaceId + ? pickTabBySpaceIndex(tabs, idx, spaceId) + : tabs[idx]; if (t) setActiveId(t.id); }, [tabs], From 023baa17a029ac7e528470f02929a3586490a2ca Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 26 Jun 2026 13:32:29 -0300 Subject: [PATCH 2/3] feat(shortcuts): add ai toggle mini shortcut --- src/app/App.tsx | 10 ++++++++++ src/modules/ai/components/AiStatusBarControls.tsx | 3 +-- src/modules/shortcuts/shortcuts.ts | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 080ed208b..94da83746 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -284,6 +284,7 @@ export default function App() { const miniOpen = useChatStore((s) => s.mini.open); const miniPresence = usePresence(miniOpen, 200); const openMini = useChatStore((s) => s.openMini); + const toggleMini = useChatStore((s) => s.toggleMini); const focusInput = useChatStore((s) => s.focusInput); const openPanel = useChatStore((s) => s.openPanel); const panelOpen = useChatStore((s) => s.panelOpen); @@ -661,6 +662,13 @@ export default function App() { "blocks.next": () => navigateFocusedBlocks(1), "search.focus": () => searchInlineRef.current?.focus(), "ai.toggle": togglePanelAndFocus, + "ai.toggleMini": () => { + if (!hasComposer) { + void openSettingsWindow("models"); + return; + } + toggleMini(); + }, "ai.askSelection": askFromSelection, "settings.open": () => void openSettingsWindow(), "sidebar.toggle": toggleSidebar, @@ -686,7 +694,9 @@ export default function App() { splitActivePaneInActiveTab, focusNextPaneInTab, toggleSourceControl, + hasComposer, togglePanelAndFocus, + toggleMini, askFromSelection, toggleSidebar, toggleExplorerFocus, diff --git a/src/modules/ai/components/AiStatusBarControls.tsx b/src/modules/ai/components/AiStatusBarControls.tsx index 72578087b..e9d1698f7 100644 --- a/src/modules/ai/components/AiStatusBarControls.tsx +++ b/src/modules/ai/components/AiStatusBarControls.tsx @@ -170,9 +170,8 @@ export function AiStatusBarControls() { diff --git a/src/modules/shortcuts/shortcuts.ts b/src/modules/shortcuts/shortcuts.ts index 376adba45..df7dc0991 100644 --- a/src/modules/shortcuts/shortcuts.ts +++ b/src/modules/shortcuts/shortcuts.ts @@ -36,6 +36,7 @@ export type ShortcutId = | "view.zoomReset" | "view.zenMode" | "ai.toggle" + | "ai.toggleMini" | "ai.askSelection" | "settings.open" | "sidebar.toggle" @@ -240,6 +241,12 @@ export const SHORTCUTS: Shortcut[] = [ group: "AI", defaultBindings: [{ [MOD_PROP]: true, key: "i" }], }, + { + id: "ai.toggleMini", + label: "Toggle AI chat window", + group: "AI", + defaultBindings: [{ [MOD_PROP]: true, shift: true, key: "i" }], + }, { id: "ai.askSelection", label: "Ask AI about selection", From 2b644ff7a46ea67da6a1e7b08b13e4aed8aa6615 Mon Sep 17 00:00:00 2001 From: Roberto Date: Fri, 26 Jun 2026 17:52:52 -0300 Subject: [PATCH 3/3] feat: terminals in command-palette --- src/app/App.tsx | 6 ++++++ src/modules/command-palette/commands.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/app/App.tsx b/src/app/App.tsx index 94da83746..4ed06d654 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -995,6 +995,12 @@ export default function App() { openSpacesOverview: () => setSwitcherOpen(true), newSpace: () => void handleNewSpace(), switchSpace: (id) => useSpaces.getState().setActive(id), + terminalTabs: tabs.filter( + (t) => + t.kind === "terminal" && + t.spaceId === (activeSpaceId ?? DEFAULT_SPACE_ID), + ), + switchTab: (id) => setActiveId(id), }) : [], [ diff --git a/src/modules/command-palette/commands.ts b/src/modules/command-palette/commands.ts index 9f5e1c952..4680a1b03 100644 --- a/src/modules/command-palette/commands.ts +++ b/src/modules/command-palette/commands.ts @@ -24,6 +24,7 @@ import type { PaletteItem } from "./types"; export const COMMAND_GROUPS = [ "General", "Spaces", + "Terminals", "Tabs", "Panes", "Git", @@ -60,6 +61,8 @@ export type CommandPaletteActionContext = { openSpacesOverview: () => void; newSpace: () => void; switchSpace: (id: string) => void; + terminalTabs: { id: number; title: string; customTitle?: string }[]; + switchTab: (id: number) => void; }; const noop = () => {}; @@ -134,6 +137,15 @@ export function createCommandItems( sp.id === ctx.activeSpaceId ? "Current space" : undefined, run: () => ctx.switchSpace(sp.id), })), + ...ctx.terminalTabs.map((tab) => ({ + id: `terminals.switch.${tab.id}`, + title: tab.customTitle || tab.title, + group: "Terminals" as const, + keywords: ["terminal", "switch", "tab", tab.customTitle ?? "", tab.title], + icon: TerminalIcon, + disabledReason: tab.id === ctx.activeId ? "Current tab" : undefined, + run: () => ctx.switchTab(tab.id), + })), { id: "tab.new", title: "New terminal",