docs/references/command/command-usage.md
How renderer and main code uses the command system. For the model and architecture, see README.md.
Import from the barrel only:
import { CommandContextMenu, CommandShortcut, CommandTooltip, useCommandHandler } from '@renderer/features/command'
Do not import subpaths such as @renderer/features/command/presentation from business
code. Keeping a narrow public API lets the runtime change without rewriting call
sites.
The renderer domain (src/renderer/features/command/) is intentionally not under
components/ — most files are runtime plumbing rather than generic UI. It owns no
business state: business surfaces contribute only the minimal context keys and
handlers they are responsible for.
src/shared/command.CommandProvider resolves a keypress to a CommandId; components supply the
behavior:
useCommandHandler('topic.create', handleCreateTopic, { enabled: canCreateTopic })
For the same command, the most recently mounted enabled handler wins; when it unmounts, the previous enabled handler becomes active again. A command with no registered handler never resolves (so the keypress falls through untouched).
While an editable target (
<input>,<textarea>, or acontenteditableelement) is focused, the dispatcher skips no-modifier shortcuts by design — plain keys (Escape, single letters) don't fire commands while the user is typing. Modifier shortcuts (Ctrl/Meta/Alt) still fire everywhere. Don't reach for a per‑component keydown listener to work around this; if a no-modifier command genuinely must fire inside an editor, that's a context‑key/enablement decision to discuss.
ContextKeyProvider is window‑local. Context keys are not persisted and are not
synced across windows. Base keys are provided automatically: platform,
feature.quick_assistant.enabled, feature.selection.enabled.
Business surfaces contribute scoped keys:
useCommandContextKey('chat.active', true)
Allowed renderer keys are defined by RendererCommandContextKey; add one only
when an existing command, shortcut, or menu contribution needs it. Scoped keys use
stack semantics — the latest mounted value wins, unmounting restores the previous.
undefined unsets a key; false and null are valid values.
Use CommandContextMenu for renderer context menus that participate in the
command system:
MenuRegistry in src/shared/command.extraItems / getExtraItems (type: 'item' for
actions, type: 'submenu' for nested groups).shortcutCommand on an extra item so the menu resolves the platform label
and user preference; shortcutLabel is an escape hatch for non‑command shortcuts.The same resolved menu model renders through the native adapter or Cherry UI based
on menu.presentation_mode. app.menu and tray.menu always stay native (main
process services).
Use these instead of assembling labels/shortcuts in feature components:
CommandShortcut — standalone shortcut badgeCommandTooltip — tooltip content including the command shortcutCommandButton — command‑backed buttonuseResolvedCommand — custom UI needing the command label, enabled state,
shortcut label, and execute callbacksrc/shared/command/definitions.ts — add an entry to
COMMAND_DEFINITIONS (id, titleKey, categoryKey, scope, optional
keybinding with a defaultBinding, optional enablement).shortcut.<commandId> through the
data‑classify pipeline — add an entry to
v2-refactor-temp/tools/data-classify/data/target-key-definitions.json
(type: "PreferenceTypes.PreferenceShortcutType", defaultValue: { binding, enabled }), then regenerate:
cd v2-refactor-temp/tools/data-classify && npm run generate:preferences
npx biome format --write src/shared/data/preference/preferenceSchemas.ts
preferenceSchemas.ts.)useCommandHandler(id, fn) in the
owning surface. Main‑scope: add a built‑in handler in
CommandService.registerBuiltInHandlers.MENU_CONTRIBUTIONS entry
in src/shared/command/menus.ts for the relevant MenuLocation.Renderer command tests live in src/renderer/features/command/__tests__/; shared
declarations in src/shared/command/__tests__/; main services in
src/main/services/__tests__/.
Prefer targeted checks first:
pnpm vitest run src/shared/command src/renderer/features/command
pnpm typecheck
Run broader suites when the change touches shared command behavior, main menu services, or cross‑window contracts.