diff --git a/packages/app/src/session/commands/use-session-commands.tsx b/packages/app/src/session/commands/use-session-commands.tsx index 6975462681a3..d99c06088db8 100644 --- a/packages/app/src/session/commands/use-session-commands.tsx +++ b/packages/app/src/session/commands/use-session-commands.tsx @@ -319,7 +319,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { sessionCommand({ id: "session.new", title: language.t("command.session.new"), - keybind: "mod+shift+s", slash: "new", onSelect: (source) => command.trigger("tab.new", source), }), diff --git a/packages/app/src/settings/command.tsx b/packages/app/src/settings/command.tsx index 9e19c2459f72..bf76a8106ae9 100644 --- a/packages/app/src/settings/command.tsx +++ b/packages/app/src/settings/command.tsx @@ -3,6 +3,8 @@ import { useLanguage } from "@/runtime/i18n/language" import { useSettingsSurface } from "./surface" import type { SettingsRootTab } from "./surface" +export const settingsOpenCommand = { id: "settings.open", keybind: "mod+comma" } + export function useSettingsDialog(defaultValue?: SettingsRootTab) { const settings = useSettingsSurface() @@ -16,10 +18,9 @@ export function useSettingsCommand() { command.register("settings", () => [ { - id: "settings.open", + ...settingsOpenCommand, title: language.t("command.settings.open"), category: language.t("command.category.settings"), - keybind: "mod+comma", onSelect: show, }, ]) diff --git a/packages/app/src/shell/commands/desktop-menu.test.ts b/packages/app/src/shell/commands/desktop-menu.test.ts new file mode 100644 index 000000000000..98c3942fb8bd --- /dev/null +++ b/packages/app/src/shell/commands/desktop-menu.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from "bun:test" +import terminal, { terminalToggleCommand } from "@opencode/gui-extensions/terminal" +import { settingsOpenCommand } from "@/settings/command" +import { sidebarToggleCommand } from "@/shell/shell" +import { previousSessionCommand, nextSessionCommand } from "@/shell/titlebar/tab-strip" +import { goBackCommand, goForwardCommand, newTabCommand } from "@/shell/titlebar/titlebar" +import { exportLogsCommand } from "./desktop" +import { DESKTOP_MENU } from "./desktop-menu" + +test("every desktop menu command is one its owner registers", () => { + const registered = new Set([ + settingsOpenCommand.id, + exportLogsCommand.id, + newTabCommand.id, + sidebarToggleCommand.id, + goBackCommand.id, + goForwardCommand.id, + previousSessionCommand.id, + nextSessionCommand.id, + // The host publishes extension commands as `${extension}.${id}`. + `${terminal.id}.${terminalToggleCommand.id}`, + ]) + + const commands = DESKTOP_MENU.flatMap((menu) => menu.items ?? []).flatMap((entry) => + entry.type === "item" && entry.command ? [entry.command] : [], + ) + + expect(commands.filter((command) => !registered.has(command))).toEqual([]) +}) diff --git a/packages/app/src/shell/commands/desktop-menu.ts b/packages/app/src/shell/commands/desktop-menu.ts index fbe114492460..f2b134b81d8c 100644 --- a/packages/app/src/shell/commands/desktop-menu.ts +++ b/packages/app/src/shell/commands/desktop-menu.ts @@ -97,14 +97,8 @@ export const DESKTOP_MENU: DesktopMenu[] = [ { type: "item", labelKey: "desktop.menu.newSession", - command: "session.new", - accelerator: { macos: "Shift+Cmd+S" }, - }, - { - type: "item", - labelKey: "desktop.menu.openProject", - command: "project.open", - accelerator: { macos: "Cmd+O" }, + command: "tab.new", + accelerator: { macos: "Cmd+N" }, }, { type: "item", @@ -183,7 +177,12 @@ export const DESKTOP_MENU: DesktopMenu[] = [ id: "view", labelKey: "desktop.menu.view", items: [ - { type: "item", labelKey: "desktop.menu.toggleSidebar", command: "sidebar.toggle" }, + { + type: "item", + labelKey: "desktop.menu.toggleSidebar", + command: "sidebar.toggle", + accelerator: { macos: "Shift+Cmd+B" }, + }, { type: "item", labelKey: "desktop.menu.toggleTerminal", @@ -248,19 +247,6 @@ export const DESKTOP_MENU: DesktopMenu[] = [ command: "session.next", accelerator: { macos: "Option+Down" }, }, - { type: "separator" }, - { - type: "item", - labelKey: "desktop.menu.previousProject", - command: "project.previous", - accelerator: { macos: "Cmd+Option+Up" }, - }, - { - type: "item", - labelKey: "desktop.menu.nextProject", - command: "project.next", - accelerator: { macos: "Cmd+Option+Down" }, - }, ], }, { diff --git a/packages/app/src/shell/commands/desktop.tsx b/packages/app/src/shell/commands/desktop.tsx index 375a1917f1f8..97e7caecda8f 100644 --- a/packages/app/src/shell/commands/desktop.tsx +++ b/packages/app/src/shell/commands/desktop.tsx @@ -2,6 +2,8 @@ import { useLanguage } from "@/runtime/i18n/language" import { usePlatform } from "@/runtime/platform/platform" import { useCommand, type CommandOption } from "./command" +export const exportLogsCommand = { id: "logs.export" } + export function DesktopCommands() { const command = useCommand() const language = useLanguage() @@ -12,7 +14,7 @@ export function DesktopCommands() { if (platform.platform !== "desktop" || !platform.exportDebugLogs) return commands commands.push({ - id: "logs.export", + ...exportLogsCommand, title: language.t("command.logs.export"), category: language.t("command.category.settings"), onSelect: () => void platform.exportDebugLogs?.(), diff --git a/packages/app/src/shell/shell.tsx b/packages/app/src/shell/shell.tsx index 871cb01ebbf0..6a0229496a98 100644 --- a/packages/app/src/shell/shell.tsx +++ b/packages/app/src/shell/shell.tsx @@ -17,6 +17,8 @@ import { CollapsedSidebarToggle, paintCollapsedSlots } from "@/shell/sidebar-tog import { createPanelMotion, guardClosingFocus, replayPanelToggle } from "@/shell/panel-motion" import { windowDragEnabled } from "@/session/header/drag-area" +export const sidebarToggleCommand = { id: "sidebar.toggle", keybind: "mod+shift+b" } + export default function Layout(props: ParentProps) { const platform = usePlatform() const settings = useSettingsSurface() @@ -66,10 +68,9 @@ export default function Layout(props: ParentProps) { verticalTabs() ? [ { - id: "sidebar.toggle", + ...sidebarToggleCommand, title: language.t("command.sidebar.toggle"), category: language.t("command.category.view"), - keybind: "mod+shift+b", onSelect: toggleSidebar, }, ] diff --git a/packages/app/src/shell/titlebar/tab-strip.tsx b/packages/app/src/shell/titlebar/tab-strip.tsx index 9701522596d3..83a784d19c54 100644 --- a/packages/app/src/shell/titlebar/tab-strip.tsx +++ b/packages/app/src/shell/titlebar/tab-strip.tsx @@ -27,6 +27,10 @@ import { errorMessage } from "@/shell/layout/helpers" import { notifySessionTabsRemoved } from "./session-events" import type { SessionInfo } from "@opencode/client/promise" +export const previousSessionCommand = { id: "session.previous", keybind: "alt+arrowup" } + +export const nextSessionCommand = { id: "session.next", keybind: "alt+arrowdown" } + function SessionTabSlot(props: { tab: SessionTab id: string @@ -410,17 +414,15 @@ export function TitlebarTabStrip(props: { onSelect: () => selectAdjacentTab(1), }, { - id: "session.previous", + ...previousSessionCommand, category: language.t("command.category.session"), title: language.t("command.session.previous"), - keybind: "alt+arrowup", onSelect: () => selectAdjacentTab(-1), }, { - id: "session.next", + ...nextSessionCommand, category: language.t("command.category.session"), title: language.t("command.session.next"), - keybind: "alt+arrowdown", onSelect: () => selectAdjacentTab(1), }, { diff --git a/packages/app/src/shell/titlebar/titlebar.tsx b/packages/app/src/shell/titlebar/titlebar.tsx index 379163799bc2..95b5f9ba67c1 100644 --- a/packages/app/src/shell/titlebar/titlebar.tsx +++ b/packages/app/src/shell/titlebar/titlebar.tsx @@ -37,6 +37,12 @@ import { useSettingsDialog } from "@/settings/command" import { rootSession } from "@/shell/routes/session" import { TitlebarItems, useTitlebarItems } from "@/runtime/extension/titlebar-items" +export const newTabCommand = { id: "tab.new", keybind: "mod+t,mod+n" } + +export const goBackCommand = { id: "common.goBack", keybind: "mod+[" } + +export const goForwardCommand = { id: "common.goForward", keybind: "mod+]" } + const titlebarHeight = 36 const windowsTitlebarHeight = 44 // Includes the content inset; matches the native Windows overlay. @@ -141,17 +147,15 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) { command.register(() => [ { - id: "common.goBack", + ...goBackCommand, title: language.t("common.goBack"), category: language.t("command.category.view"), - keybind: "mod+[", onSelect: back, }, { - id: "common.goForward", + ...goForwardCommand, title: language.t("common.goForward"), category: language.t("command.category.view"), - keybind: "mod+]", onSelect: forward, }, ]) @@ -531,10 +535,9 @@ export function Titlebar(props: { verticalTabs?: { mount?: HTMLElement } }) { return [ { - id: "tab.new", + ...newTabCommand, category: "tab", title: language.t("command.session.new"), - keybind: "mod+t,mod+n", hidden: true, onSelect: openNewTab, }, diff --git a/packages/gui-extensions/package.json b/packages/gui-extensions/package.json index 25e2cc6d48e7..1abb076d735f 100644 --- a/packages/gui-extensions/package.json +++ b/packages/gui-extensions/package.json @@ -11,6 +11,7 @@ "./sdk": "./src/sdk/index.ts", "./sdk/main": "./src/sdk/main.ts", "./sdk/bridge": "./src/sdk/bridge.ts", + "./terminal": "./src/terminal/index.ts", "./updater": "./src/updater/contract.ts" }, "scripts": { diff --git a/packages/gui-extensions/src/terminal/index.ts b/packages/gui-extensions/src/terminal/index.ts index 1818147e1c0d..208dc1ca229b 100644 --- a/packages/gui-extensions/src/terminal/index.ts +++ b/packages/gui-extensions/src/terminal/index.ts @@ -1,6 +1,9 @@ import { Extension } from "../sdk" import en from "./i18n/en" +/** Published as `terminal.toggle`; the desktop menu names it. */ +export const terminalToggleCommand = { id: "toggle", bind: "ctrl+`" } + export default Extension.define({ id: "terminal", i18n: { diff --git a/packages/gui-extensions/src/terminal/renderer.tsx b/packages/gui-extensions/src/terminal/renderer.tsx index b574bb41dc68..ab3169dae207 100644 --- a/packages/gui-extensions/src/terminal/renderer.tsx +++ b/packages/gui-extensions/src/terminal/renderer.tsx @@ -1,6 +1,7 @@ import { createMemo, lazy, on, onCleanup, Suspense } from "solid-js" import { Command, createKeyed, MenuItem, onIdle, Panel, type MountedSession, type PanelTab, type Setup } from "../sdk" import type Terminal from "./index" +import { terminalToggleCommand } from "./index" import { createTerminalModel, type TerminalWorkspace } from "./model" import { terminalTab, type LocalPTY } from "./state" import { terminalTabLabel } from "./title" @@ -68,12 +69,11 @@ const setup: Setup = (ctx) => { ctx.add( Command, (): Command => ({ - id: "toggle", + ...terminalToggleCommand, title: ctx.t("command.toggle"), group: ctx.t("command.category.view"), section: "terminal", featured: true, - bind: "ctrl+`", slash: { name: "terminal", after: "open" }, editable: true, enabled: routed(),