feat: hierarchical command palette - #185
Draft
seankmartin wants to merge 31 commits into
Draft
Conversation
seankmartin
commented
Jun 8, 2026
pattern now follows default viewer_setup binding
also fixes the lifetime and binding locations to be more consistent with the default viewer setup and the input event bindings to help panel
Also removes doc level palette key listener, this was designed for when inside a number element for e.g. but not worth also explicitly labels the command type as opposed to infer from optional properties
Extract the catalog — CommandCatalog, CommandCatalogContext, collectActionBindings, the CommandPaletteEntry types, and the tool/label helpers — from command_palette.ts into a new command_catalog.ts with no DOM or CSS dependencies. command_palette.ts keeps the Overlay-based CommandPalette UI and bindCommandPalette, importing the catalog and the stylesheet. Previously, importing CommandCatalog for its enumeration transitively pulled in command_palette.css and the Overlay class even when no palette was rendered. Splitting the modules lets the catalog be consumed (and unit-tested) without a DOM, and reused independently of the palette UI. - command_catalog.spec.ts (renamed from command_palette.spec.ts) now imports from command_catalog.js, so the catalog tests no longer depend on the palette module. - default_viewer_setup.ts imports CommandCatalog from command_catalog.js and bindCommandPalette from command_palette.js. No behavioural change.
refactor: split CommandCatalog into a DOM-free command_catalog module
Grouping is a presentation concern: the command palette and the help panel would reasonably group the same commands in different ways, so the section a command belongs to belongs to whoever is presenting it. A suggested binding on the command was only ever informational, and would drift from whatever binding is actually installed. The shortcut a consumer shows now always comes from the live input event bindings.
RenderedDataPanel registers its per-axis action listeners by iterating AXES_NAMES, so declaring the matching commands from a second local list of axis names left two places to keep in step. Import the same constant and fold the move and rotate generators into one pass over it.
Behaviour had nowhere to live on a plain data record, so each consumer re-derived it: the palette built the `action:<id>` CustomEvent itself, and the catalog translated the registry's `type` discriminant into its own `kind` discriminant to decide which branch to take. A Command now owns its id, label, optional description and how it runs. ActionCommand dispatches the DOM action, CallbackCommand runs a callback, and both take a CommandContext rather than a bare target so that more context (mouse position, originating layer) can be added later without touching every implementation. The registry stores instances and forwards each command's `changed` signal, which replaces the per-command WatchableValue subscription that backed the old `isAvailable`; that property is now a settable `enabled` on the command itself. The catalog's ActionCommandEntry and CommandEntry collapse into a single entry carrying the Command.
The registry lists the commands it was told about, and there is no way to make that list complete: a viewer embedded in another application, or driven from the Python integration, can bind an action without ever registering a command for it. Enumerating only the registry dropped those from the palette, which the previous catalog did show. The catalog now enumerates the registry first, so a registered command keeps its curated label and description, then adds an ActionCommand for each keyboard-bound action the registry does not know, labelled from its action id as before. Tool slots and layer-index actions stay excluded; the catalog contributes its own entries for those.
The command ids in default_commands.ts have to match the action ids the default input event bindings dispatch, and nothing checked that. A typo in either direction is silent: a command whose id no action listens for does nothing when invoked, and a bound action with no command loses its label and description. Assert both directions against the real binding maps, with the tool slots and layer-index actions excluded as dynamic, and the three actions that have no default binding listed explicitly.
Describe what each piece owns: a Command holds identity, presentation and behaviour; the registry holds which commands exist; the catalog turns that plus viewer state into an ordered list with shortcuts attached; the palette renders it. Records why the registry cannot be treated as the complete list of commands, and how a change flows from a registration through to a re-render.
feat: Add CommandRegistry and default command descriptions.
also binds to that command palette in the default setup
The command catalog is now constructed by the viewer, so default_viewer_setup no longer references CommandCatalog directly. The leftover import fails lint:check with no-unused-vars.
fix: remove unused CommandCatalog import
Instead they are directly CallbackCommands
instead store all command actions flat in a catalog, and leave the consumer (currently only palette) to group commands based on the old grouping information
since they can be used standalone
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.