Add a global command palette (⌘K / Ctrl+K)#42
Merged
Conversation
Add a keyboard-driven command palette that lets you search for and run any registered app action — the "run" surface that pairs with the read-only Keyboard Shortcuts reference. Every comparable desktop app (Claude Code Desktop, VS Code / Codex, Linear) ships one; OpenWork had the action registry, cmdk primitives, and translated labels, but nothing tied them together. Frontend-only. The palette lists the centralized action registry grouped by category, filters with cmdk's built-in fuzzy match, and dispatches the chosen action via the registry's execute() — exactly as if its hotkey were pressed. Opened with ⌘K / Ctrl+K or from the app menu. No backend / qwen-code change, and no new i18n keys (reuses commands.*, common.noResultsFound, and the existing shortcuts.action.* / shortcuts.category.* labels). - action registry: new app.commandPalette action (mod+k) - CommandPalette component (self-contained: registers its open handler, owns state, integrates with the modal stack) - extract ACTION_LABEL_KEYS to a shared actions/action-i18n module and reuse it in the Shortcuts settings page - AppMenu entry for discoverability - e2e: command-palette CDP assertion (open → filter → empty → clear → run Toggle Theme and assert the theme flips) - e2e harness: per-port isolated profile dirs so multiple assertions can each launch their own app instance without single-instance-lock collisions Closes #41
…close
Two issues surfaced from testing the palette:
- It listed context-scoped actions whose handler is disabled in the palette's
context (e.g. "Next Search Match" / "Previous Search Match", which need an
active in-conversation search, and the navigator/panel focus actions).
Selecting one was a dead no-op — clicking appeared to "do nothing". The
palette now filters to actions the registry reports as executable right now
(new ActionRegistry.canExecute), so only runnable commands show.
- It ran the selected action synchronously while the dialog was still tearing
down, so actions that open a panel or move focus (e.g. Search) could be
clobbered by the dialog's focus restoration. It now closes first and runs the
action on the next tick, in the app's restored-focus context.
The e2e assertion additionally verifies a known context-scoped action
("Next Search Match") is absent from the palette.
Collaborator
Author
|
@copilot resolve the merge conflicts in this pull request |
Collaborator
Author
|
@copilot resolve the merge conflicts in this pull request |
Copilot stopped work on behalf of
DragonnZhang due to an error
July 1, 2026 06:41
# Conflicts: # docs/loop/feature-ledger.md # e2e/app.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #41
What & why
Every comparable desktop app ships a command palette — a keyboard-driven
overlay to search for and run any command (⌘K in Claude Code Desktop / Linear,
⌘⇧P in VS Code / Codex desktop). OpenWork already had all the pieces — a
centralized action registry with an
execute(actionId)method, the cmdkcommand primitives, and translated action labels — but they were only
surfaced in read-only reference views (the Keyboard Shortcuts dialog and the
Settings → Shortcuts page). There was no way to run an action by searching for
it.
This adds a global command palette opened with ⌘K / Ctrl+K (and from the app
menu). It:
execute()— so the action runsexactly as if its hotkey were pressed,
Esc/ selection, integrated with the existing modal stack.Frontend-only. No backend / qwen-code change. No new i18n keys — the input
placeholder reuses
commands.searchCommands, the empty state reusescommon.noResultsFound, labels reuseshortcuts.action.*, and group headingsreuse
shortcuts.category.*.Changes
actions/definitions.ts— newapp.commandPaletteaction (mod+k).components/CommandPalette.tsx— the palette. Self-contained: it registersits own open handler, owns its open state, and joins the modal stack for
layered close. Mounted once in
App.tsx(inside the action + modal providers).actions/action-i18n.ts— extracted the action-ID → i18n-key map(
ACTION_LABEL_KEYS) plus a category-key helper into a shared module, and reuseit from Settings → Shortcuts (
pages/settings/ShortcutsPage.tsx) so the twosurfaces can't drift.
components/AppMenu.tsx— a “Command Palette” entry for discoverability.e2e/assertions/command-palette.assert.ts— new CDP assertion (below).e2e/app.ts— give each launched app instance its own profile dir keyed onthe unique debug port. Electron's single-instance lock is keyed on userData, so
a shared dir made a second assertion's launch defer to the first instance and
never get its own renderer target. Per-port dirs let multiple assertions run in
one
bun run e2e.Verification (DoD)
The
command-paletteassertion drives the real built app over CDP through thewhole path: presses Ctrl/Cmd+K and asserts the palette opens with more than one
action row; types
themeand asserts the list narrows (every visible rowcontains the query, count shrinks) with Toggle Theme surviving; types a
no-match query and asserts the empty state with zero rows; clears and asserts
the full list is restored; then selects Toggle Theme and asserts the palette
closes and the app theme actually flips (
documentElementdark⇄light) —proving the palette executes the action, not merely displays it.
bun run typecheck:all— introduces no new errors.packages/{core, shared, server-core, server, session-tools-core, ui}all pass; the only errorsare 11 pre-existing ones in
apps/electronunrelated to this change(
auto-update.ts, asettings-default-thinkingtest tuple, and two test filesimporting
vitest, which is in nopackage.json). None are in the files this PRtouches.
bun test— the failing set is byte-for-byte identical tomain(56 pre-existing failures:
BrowserCDP,BrowserPaneManager, i18n locale-paritysorted checks,
startWebuiHttpServer,resource-bundle, etc.). This changeadds zero new failures.
app bootsassertion).Part of the autonomous desktop-feature loop (
loop-bot).Generated by Claude Code