Skip to content

feat(hub-ui): assign keyboard shortcuts to dock group members - #282

Merged
antfu merged 5 commits into
devframes:mainfrom
SaKaNa-Y:feat/dock-group-shortcuts
Sep 3, 2026
Merged

feat(hub-ui): assign keyboard shortcuts to dock group members#282
antfu merged 5 commits into
devframes:mainfrom
SaKaNa-Y:feat/dock-group-shortcuts

Conversation

@SaKaNa-Y

@SaKaNa-Y SaKaNa-Y commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Resolves the "dock group members can never be assigned keyboard shortcuts" problem discussed in vitejs/devtools#501, following the approach @antfu proposed there: when a shortcut hits a group entry with no preferred member, the command palette offers the sub-docks so the flow stays keyboard-driven.

Two independent halves, one commit each.

fix: nested commands were unbindable

Every consumer of the command tree walked a single level of children. Dock group members sit two levels below the Docks command, so they had no row in Settings → Shortcuts to bind, no entry in the keybinding collector to fire, and no id the dispatcher could resolve. That depth-1 ceiling is the literal cause of "can never be assigned".

Traversal now lives in one place: walkCommands visits the tree depth-first with a 'skip'/'stop' visitor signal, shared by findCommandDeep, collectAllKeybindings, the shortcut-settings rows, and the palette's root flatten. filterCommandsByWhen keeps its own recursion, since it rebuilds a cloned tree per level.

The ceiling was also stated in the public type: children on both DevframeServerCommandInput and DevframeClientCommand documented "Two levels max (parent → children)". Both now read "nested arbitrarily deep", so the JSDoc a consumer hovers matches what the tree actually accepts.

The palette's flatten and drill-down stack move to state/palette.ts. showInPalette: 'without-children' now prunes a whole subtree rather than one level, and a row carries its full path for search while displaying only its immediate parent.

feat: activating a group no longer guesses a member

A group has no view of its own, so activating one by id used to fall back to whichever member came first. A group whose members are peers has no member worth picking.

Activating a group command now opens an unambiguous target directly: the member last opened in that group, the author's defaultChildId, or a lone visible member. With several visible peers and no preferred member, it opens the palette drilled into that group. Pressing the same shortcut again closes the palette; stepping back to the root list unscopes it, so the shortcut drills back in rather than toggling.

CommandsContext gains openPalette(atCommandId?) and paletteScopeId. Members hang directly off their group in the command tree — the dock rail's sub-category dividers have no counterpart there, since a category is not something you can run. A group with no visible member and no reachable preferred member registers no command at all.

switchEntry remains on the programmatic path used by boot restore and hub:docks:activate: last-opened member, then defaultChildId, then the first registered member. The dock rail opens the last-opened or default member and reveals the member popover when neither resolves.

Picking a member through the scoped palette also raises it into the recent-dock slot from #305, with no extra wiring: that feature watches the selection rather than the dock rail's click handlers, so a keyboard-driven pick lands in the same slot a popover pick does.

Verification

Rebased onto main at a55f3d5. Full-repo ESLint and Knip are clean; typechecks for @devframes/hub and @devframes/hub-ui pass; direct builds for devframe, @devframes/hub, and every @devframes/hub-ui output pass.

Focused state tests: 4 files, 50 passing. Package suites: @devframes/hub has 164 passing / 1 skipped; @devframes/hub-ui has 59 passing. Full Vitest run: 122 files passing, 1 failing; 1348 tests passing, 14 skipped.

The remaining failure is the existing Windows-only @devframes/plugin-code-server fixture handoff (ENOENT … Temp\dcs-dump-*\hashed). It reproduces outside this PR and is unrelated to the hub command changes.

New specs: state/keybindings.test.ts (traversal, skip/stop, deep lookup), state/palette.test.ts (showInPalette pruning, path/parent titles, scope-trail degradation), and group-activation cases in state/context.test.ts. New stories: Commands/Palette → ScopedToGroup, Views/Builtin/Settings → DeeplyNestedShortcuts.

Docs: an "Activating a group" section and a "Nested commands" section in the hub guide, plus the openPalette / paletteScopeId API in the Hub API reference. New prose and comments follow the Terms vocabulary from #298 (dock rail, a devframe rather than a bare "host").

@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

@SaKaNa-Y is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

@SaKaNa-Y
SaKaNa-Y marked this pull request as draft August 22, 2026 03:55
@SaKaNa-Y
SaKaNa-Y force-pushed the feat/dock-group-shortcuts branch from b8e9a78 to cb369f0 Compare August 27, 2026 08:36
@SaKaNa-Y
SaKaNa-Y marked this pull request as ready for review August 27, 2026 08:46
Every consumer of the command tree walked a single level of `children`, so
anything deeper was unreachable: a dock group's members had no row in the
shortcut settings to bind, no entry in the keybinding collector to fire, and
no id the dispatcher could resolve.

Traversal now lives in one place. `walkCommands` visits the tree depth-first
with a `'skip'`/`'stop'` visitor signal, and `findCommandDeep`,
`collectAllKeybindings`, the shortcut settings rows and the palette's root
flatten all share it. `filterCommandsByWhen` keeps its own recursion since it
rebuilds a cloned tree per level.

The palette's flatten and drill-down stack move to `state/palette.ts`, where
`showInPalette: 'without-children'` prunes a whole subtree rather than one
level, and a row carries its full path for search while displaying only its
immediate parent. Shortcut rows indent by nesting level instead of a boolean.
A group has no view of its own, so activating one by id — its shortcut, a
palette pick, an RPC activation — used to fall back to whichever member
happened to come first. That picked for the user, and a group whose members
are peers has no member worth picking.

Activating a group now opens an unambiguous target directly: the author's
`defaultChildId`, or a lone visible member. With several peer members it opens
the command palette drilled into that group, so the choice stays with the user
and the group is reachable by keyboard alone. Pressing the same shortcut again
closes the palette; stepping back to the root list unscopes it, so the
shortcut drills back in rather than toggling.

`CommandsContext` gains `openPalette(atCommandId?)` and `paletteScopeId`.
Members hang directly off their group in the command tree — the dock bar's
sub-category dividers have no counterpart there, since a category is not
something you can run. A group with no visible member and no reachable
`defaultChildId` registers no command at all.

`switchEntry` is unchanged, so boot restore and `hub:docks:activate` behave
as before.
@SaKaNa-Y
SaKaNa-Y force-pushed the feat/dock-group-shortcuts branch from cb369f0 to 4a56a98 Compare August 29, 2026 12:40
@SaKaNa-Y

Copy link
Copy Markdown
Contributor Author

Implemented the review follow-ups in e22596d. The command palette now executes actionable dock-group commands while preserving drill-down for ordinary command parents, keeps scope and breadcrumbs synchronized with the live command tree, and avoids stale registered actions. The headless client runtime now resolves nested client actions and default keybindings at arbitrary depth. Added regression coverage for group scope transitions, live command replacement, and grandchild commands. Validation: 50 focused tests passed; hub and hub-ui typechecks passed; ESLint and git diff checks passed.

…cuts

# Conflicts:
#	docs/content/8.references/6.hub-api.md
…cuts

# Conflicts:
#	docs/content/1.guide/16.hub.md
#	docs/content/8.references/6.hub-api.md
#	packages/hub-ui/src/client/state/keybindings.ts
@coldtea-pr-lens

coldtea-pr-lens Bot commented Sep 3, 2026

Copy link
Copy Markdown

◈ PR Lens

🟢 +1 new · 🟠 ~8 changed · 🔴 -0 removed · 2 flows · 19 files · commit 5f2deec


Architecture

Architecture diagram for devframes/devframe at 5f2deec

9 components touched across 3 lanes.

Open full size


Inside the changed components — 2 views

Component view — Dock UI command and palette subsystem

Internal modules in @⁠devframes/hub-ui handling scoped command palette drill-down, deep command tree traversal, dock group activation, and shortcut configuration.

Architecture view of Component view — Dock UI command and palette subsystem in devframes/devframe

Component view — Headless client runtime command execution

Headless client runtime in @⁠devframes/hub providing deep command lookup, local execution, and RPC forwarding.

Architecture view of Component view — Headless client runtime command execution in devframes/devframe

Data flow

Data flow diagram for devframes/devframe at 5f2deec

Activating an ambiguous dock group · Executing a deeply nested command

Open full size


The other flows — 1 sequence

Executing a deeply nested command

Sequence diagram of Executing a deeply nested command in devframes/devframe

Drill down
Client Runtimes & UI — 9 components
🟡 CHANGED Headless Client Runtime

Headless client host managing command registration, recursive command lookup, local action execution, and scoped palette state.

🟡 CHANGED Dock UI & Web Component

Dock web component and viewer hosting the command palette modal, dock navigation rail, and shortcut configuration settings.

🟡 CHANGED Command Palette Modal

Modal dialog providing fuzzy search across flattened nested commands, breadcrumb navigation trail, and scoped sub-command drill-down.

🟢 NEW Palette State & Scoping

Constructs breadcrumb trails, flattens arbitrary command trees for search, reconciles live trails, and determines drill-down vs execution actions.

🟡 CHANGED Dock Group Command Resolution

Maps dock entries directly into the command tree and resolves group activation to direct member opening or scoped palette selection.

🟡 CHANGED Keybindings & Command Tree Traversal

Walks arbitrarily nested command trees depth-first, locates commands by id, filters trees by when-clauses, and aggregates keybindings.

🟡 CHANGED UI Commands Context

Maintains reactive palette visibility and scope state, registers global keyboard listeners, and dispatches command executions.

🟡 CHANGED Settings Shortcuts View

Settings tab rendering all available commands indented by hierarchy depth for keyboard shortcut customization.

🟡 CHANGED Runtime Commands Registry

Headless command registry managing arbitrary nesting, local action execution, and RPC forwarding for server commands.


View

  • Architecture lens
  • Data flow lens
  • Expand every detail
  • Show unchanged neighbours

Tip

Show unchanged neighbours lists the components this change did not touch alongside the ones it did, so the drill-down shows what the changed code sits next to.

🪧 More tips
  • Run PR Lens on your own machine: npx skills add coldteadotai/pr-lens installs the agent skill. Then tell your coding agent: "Diagram the change you just made with PR Lens and attach it to the pull request."
  • Draw a diff before it is even a pull request: npx @coldtea/pr-lens-cli analyze --base origin/main reads the diff with your own model key, and npx @coldtea/pr-lens-cli render .pr-lens/graph.json draws the same lenses on your machine.
  • The boxes under View are live. Tick Architecture lens or Data flow lens to choose which diagrams appear, or Expand every detail to open every drill-down at once. The comment redraws in place a few seconds later.
  • GitHub will not let you zoom an image in a comment. The link under each diagram opens it full size on a page of its own, where you can.
  • The CLI's render picks up .github/pr-lens.yml automatically and applies your corrections (renames, exclusions, lane pins) at draw time.
  • Would you rather run it from CI on a key of your own? Add .github/workflows/pr-lens.yml with coldteadotai/pr-lens/packages/action@v0 and a model key in your repository secrets, say GEMINI_API_KEY. The Action asks Gemini by default, or OpenAI and any endpoint speaking /chat/completions through its provider input.
  • PR Lens is free for open source. A star on the repository is what keeps it going.
  • Push a new commit and the whole comment re-renders for the new head. An older run never overwrites a newer one, so a slow render cannot put a stale diagram back.
  • The diagrams follow your GitHub theme, so dark mode gets the dark render and light mode the light one, and the moving dots show this pull request's data in motion.

◈ Rendered by PR Lens · crafted with ❤️ by the Coldtea team · Something drawn wrong?

@antfu
antfu merged commit 1f97131 into devframes:main Sep 3, 2026
12 of 13 checks passed
@SaKaNa-Y
SaKaNa-Y deleted the feat/dock-group-shortcuts branch September 3, 2026 03:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants