feat(hub-ui): assign keyboard shortcuts to dock group members - #282
Conversation
|
@SaKaNa-Y is attempting to deploy a commit to the NuxtLabs Team on Vercel. A member of the Team first needs to authorize it. |
b8e9a78 to
cb369f0
Compare
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.
cb369f0 to
4a56a98
Compare
|
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
◈ PR Lens
Architecture 9 components touched across 3 lanes. Inside the changed components — 2 viewsComponent 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. Component view — Headless client runtime command execution Headless client runtime in @devframes/hub providing deep command lookup, local execution, and RPC forwarding. Data flow
The other flows — 1 sequence
Drill down
|
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 unbindableEvery consumer of the command tree walked a single level of
children. Dock group members sit two levels below theDockscommand, 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:
walkCommandsvisits the tree depth-first with a'skip'/'stop'visitor signal, shared byfindCommandDeep,collectAllKeybindings, the shortcut-settings rows, and the palette's root flatten.filterCommandsByWhenkeeps its own recursion, since it rebuilds a cloned tree per level.The ceiling was also stated in the public type:
childrenon bothDevframeServerCommandInputandDevframeClientCommanddocumented "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 memberA 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.CommandsContextgainsopenPalette(atCommandId?)andpaletteScopeId. 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.switchEntryremains on the programmatic path used by boot restore andhub:docks:activate: last-opened member, thendefaultChildId, 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
mainat a55f3d5. Full-repo ESLint and Knip are clean; typechecks for@devframes/huband@devframes/hub-uipass; direct builds fordevframe,@devframes/hub, and every@devframes/hub-uioutput pass.Focused state tests: 4 files, 50 passing. Package suites:
@devframes/hubhas 164 passing / 1 skipped;@devframes/hub-uihas 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-serverfixture 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(showInPalettepruning, path/parent titles, scope-trail degradation), and group-activation cases instate/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/paletteScopeIdAPI in the Hub API reference. New prose and comments follow the Terms vocabulary from #298 (dock rail, a devframe rather than a bare "host").