docs/references/command/command-usage.md
This guide covers the public renderer APIs and the files that must change when a new command is introduced. See Command System for the runtime model and process boundaries.
UI and hooks have separate category barrels:
import { CommandContextMenu, CommandPopupMenu, CommandTooltip } from '@renderer/components/command'
import { useCommandContextKey, useCommandHandler, useResolvedCommand } from '@renderer/hooks/command'
Business code should import those barrels, not files such as
@renderer/components/command/CommandMenus or
@renderer/hooks/command/useCommandRuntime. Shared contracts and pure resolution
logic live under @shared/types/command and @shared/utils/command respectively.
CommandProvider resolves a trigger to a CommandId; the owning surface supplies
the behavior:
useCommandHandler('topic.create', handleCreateTopic, { enabled: canCreateTopic })
Handlers use stack semantics. For the same command, the most recently mounted enabled handler runs. When it unmounts, the previous enabled handler becomes active again. A renderer command without an enabled handler does not resolve, so its keyboard event falls through unchanged.
Keep the handler with the business surface that owns the behavior. Do not move business state into the command runtime merely to make the action triggerable.
CommandContextKeyProvider supplies these base keys:
platformfeature.quick_assistant.enabledfeature.selection.enabledfeature.screenshot.enableduseCommandContextKey is the window-local extension point:
useCommandContextKey('chat.active', true)
Context keys are window-local, non-persistent, and stack-based. The latest mounted value wins; unmounting restores the previous value. No production surface currently registers an additional key; add or consume one only when a current command, keybinding, or menu contribution needs it.
Use CommandContextMenu for right-click surfaces and CommandPopupMenu for
click-triggered menus. Both resolve static MENU_CONTRIBUTIONS for their
location and can append caller-owned extraItems.
Custom items are appropriate for surface-local actions that do not need a stable
CommandId. They can use:
shortcutCommand to display an existing command's effective shortcut label;
this does not make the custom item's callback command-backed.shortcutLabel for a label that is not associated with a command.getExtraItems when a context menu must resolve items lazily.The same model renders through Cherry UI or a native popup according to
menu.presentation_mode. app.menu and tray.menu remain native regardless of
that Preference.
CommandTooltip adds the effective shortcut to tooltip content.CommandHint renders a compact command shortcut hint.useResolvedCommand exposes translated label, enabled state, shortcut label,
icon key, and an execute callback for custom UI.Use these APIs instead of reading shortcut Preferences or formatting bindings in a business component.
Before adding a CommandId, confirm that the action needs a shared identity: for
example, it has a configurable shortcut or more than one trigger surface. Keep a
single-surface action local when no cross-surface contract is needed.
Add the definition to src/shared/utils/command/definitions.ts. Set its id,
titleKey, categoryKey, and owner-aligned scope; add enablement or a
keybinding only when required. All current commands are owned by either main
or renderer; use both only for a concrete action with legitimate handlers in
both processes.
Add or reuse the English titleKey and categoryKey in
src/renderer/i18n/locales/en-us.json. When adding keys, run
pnpm i18n:sync and translate the generated entries in every locale.
If the command has a keybinding, add shortcut.<commandId> to
v2-refactor-temp/tools/data-classify/data/target-key-definitions.json with
type: "PreferenceTypes.PreferenceShortcutType", a matching { binding, enabled } default, and status: "classified". Regenerate the owned files:
cd v2-refactor-temp/tools/data-classify
npm run generate
Never edit src/shared/data/preference/preferenceSchemas.ts by hand.
Register the behavior with useCommandHandler in the owning renderer surface,
or add a main handler in CommandService that delegates to the owning service.
If an existing menu surface needs the command, add a MENU_CONTRIBUTIONS
entry in src/shared/utils/command/menus.ts. Do not add a contribution for a
reserved location without a current consumer.
Command behavior is covered in these directories and service tests:
pnpm test src/shared/utils/command src/renderer/components/command src/renderer/hooks/command
pnpm test src/main/services/__tests__/CommandService.test.ts \
src/main/services/__tests__/ShortcutService.test.ts \
src/main/services/__tests__/AppMenuService.test.ts \
src/main/services/__tests__/nativePopupMenu.test.ts
pnpm lint
For documentation-only edits, run pnpm docs:check instead.