Skip to content

FE-1560: Add a command registry with a host-provided palette contract - #9457

Merged
kube merged 9 commits into
mainfrom
claude/fe-1560-command-registry
Sep 3, 2026
Merged

FE-1560: Add a command registry with a host-provided palette contract#9457
kube merged 9 commits into
mainfrom
claude/fe-1560-command-registry

Conversation

@kube

@kube kube commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Before this PR, Petrinaut's user-invocable actions lived only in the keyboard handler and on toolbar buttons. Nothing enumerated them, so a host could not render a command palette over the editor, and the headless instance had no way to expose actions such as auto-layout to a host UI.

Adds a command registry the host owns. Components declare commands with a hook that registers and unregisters them, the headless instance registers its own imperatively, and the host renders the list and runs entries by id. Petrinaut ships no palette; a Storybook story and the demo website carry reference palettes.

9457.mp4

Links

Changes

Core

  • createCommandRegistry in @hashintel/petrinaut-core, the core.command-registry layer

    register replaces by id in place and returns a disposer that is inert once the id was re-registered.
    list returns one snapshot until the set changes, execute runs an entry by id, subscribe notifies on changes.

  • combineCommandRegistries merges several registries into one read view

    For a host palette fed by its own registry plus one per embedded component. Writes stay on the sources.

React bindings

  • CommandRegistryProvider, useCommand, useCommands, useCommandRegistry, and formatShortcutKeys from @hashintel/petrinaut/react, the react.commands layer

    Provider takes a host registry or creates one.
    useCommands is a useSyncExternalStore snapshot for palettes.
    formatShortcutKeys turns mod+shift+z into platform-aware keycap labels.

  • useCommand registers while mounted and its when option holds

    Re-registers only when the id, label, category, keywords, or shortcut change and reads the latest run through a ref, so palettes are not notified on every render.
    No-op outside a provider.

  • Wrapping the editor in the provider is the only wiring a host needs

Editor

  • Editor registers a starter set from a null-rendering EditorCommands leaf

    Undo and Redo when the handle provides history, Select and Pan tools, Add place, Add transition, and auto-layout in edit mode when not read-only, search, left sidebar and bottom panel toggles.
    Leaf keeps the undo/redo context subscription off the EditorView tree.
    shortcut strings are display metadata; the keyboard handler still binds the keys.

  • Redundant width-initializing effect removed from PropertiesPanel

    Its dependency is stable only under the React Compiler, so without the compiler it looped with "Maximum update depth exceeded".
    Provider already seeds the width.

Host examples

  • Storybook story Commands / Command palette with a palette over host commands and over the full editor
  • Demo website ⌘K palette registering a demo-owned command beside Petrinaut's

    HASH app untouched.

Review fixes

  • Auto-layout registers from EditorCommands through the mode-aware usePetrinautCommands

    Nothing writes to the registry during render, and the command follows edit mode and the active subnet.

  • PropertiesPanel reads its width from the editor store only

    Removes the local width state that disagreed with the persisted value after a reload.

  • useCommands passes a server snapshot to useSyncExternalStore
  • formatShortcutKeys returns no keycaps for an empty shortcut
  • Example palettes close on Escape from any element inside them
  • Sidebar toggle keeps a static label, so it holds its palette position

Known issues

  • Registry does not dispatch keys

    Consume-contract shortcut work reads chords from it when it lands.

Next steps

  • Bridge the zero-argument entries of instance.commands into the registry
  • Rework the HASH app's command palette against this registry

Test coverage

  • command-registry.test.ts:

    Register, list, execute, in-place replacement, snapshot stability and notifications, inert disposer after replacement, and combineCommandRegistries.

  • command-registry.test.tsx:

    Registration lifecycle, the when flip, latest run without re-notification, no-provider no-op, and useCommands live snapshots.

  • format-shortcut.test.ts:

    Apple and generic keycap labels, and the empty shortcut.

How to test

  • Open Petrinaut preview on Vercel
  • Press ⌘K or Ctrl+K
  • Expect Petrinaut's commands and the demo's "Create a new empty net" in one list
  • Type "place", Enter
  • Expect the canvas tool switched to Add place
  • Press ⌘K again, type "undo"
  • Expect Undo and Redo listed with keycaps

@kube kube self-assigned this Aug 30, 2026
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 3, 2026 5:02pm UTC
petrinaut Ready Ready Preview Sep 3, 2026 5:02pm UTC
petrinaut-docs Ready Ready Preview Sep 3, 2026 5:02pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 3, 2026 5:02pm UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps area/apps > hash.design Affects the `hash.design` design site (app) labels Aug 30, 2026
@kube
kube marked this pull request as ready for review August 30, 2026 19:25
@cursor

cursor Bot commented Aug 30, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Additive public API and optional provider wiring; command runs delegate to existing editor handlers. The PropertiesPanel change fixes a render loop rather than altering editing semantics.

Overview
Introduces a command registry so hosts can render their own command palettes over Petrinaut. Core adds createCommandRegistry() and combineCommandRegistries() with stable snapshots, in-place id replacement, and safe disposers. @hashintel/petrinaut/react exposes CommandRegistryProvider, useCommand (optional when), useCommands, useCommandRegistry, and display-only formatShortcutKeys.

The editor registers undo/redo, canvas tools, auto-layout, search, and panel toggles via a null-rendering EditorCommands leaf so palette metadata stays out of the main view tree; shortcuts remain editor-bound, not registry-bound. Petrinaut does not ship a palette—Storybook and the demo site add reference ⌘K/Ctrl+K palettes that list useCommands() and call execute(id).

The demo wraps the editor in CommandRegistryProvider and registers a host command (new empty net). PropertiesPanel now uses propertiesPanelWidth from editor context directly and drops a width-sync effect that could cause maximum update depth errors without the React Compiler.

Reviewed by Cursor Bugbot for commit dd48015. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread libs/@hashintel/petrinaut-core/src/command-registry.ts Outdated
Comment thread libs/@hashintel/petrinaut-core/src/instance.ts Outdated
Comment thread libs/@hashintel/petrinaut-core/src/command-registry.ts Outdated
kube added 8 commits September 2, 2026 03:00
Drop `find`, `unregister`, the shortcut-collision warning, and the silent
run-refresh snapshots: `register` now replaces by id in place and notifies,
and `useCommand` re-registers only when the id, label, category, keywords,
or shortcut change, reading the latest `run` through a ref.

Move the registry into `core.command-registry` and the React bindings into
`react.commands`, export the bindings from `@hashintel/petrinaut/react`
only, and add the arch-docs usage manual with a flow diagram. One changeset
covers both packages.
Comment thread libs/@hashintel/petrinaut-core/src/instance.ts Outdated
Comment thread libs/@hashintel/petrinaut/src/ui/petrinaut.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5b2f371. Configure here.

Comment thread libs/@hashintel/petrinaut/src/react/commands/command-registry.tsx
…ings

Move the auto-layout command into the editor's command set, through the
mode-aware `usePetrinautCommands`, so nothing writes to the registry during
render and the command respects edit mode and the active subnet;
`createPetrinaut` loses its `commandRegistry` option. Read the properties
panel width from the editor store only, give `useCommands` a server
snapshot, return no keycaps for an empty shortcut, close the example
palettes on Escape from any element inside them, and keep the sidebar
command's label static so it holds its palette position.
@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.05%. Comparing base (27b32c4) to head (dd48015).
⚠️ Report is 34 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9457      +/-   ##
==========================================
+ Coverage   61.03%   61.05%   +0.02%     
==========================================
  Files        1449     1449              
  Lines      144693   144718      +25     
  Branches     6692     6695       +3     
==========================================
+ Hits        88307    88356      +49     
+ Misses      55282    55254      -28     
- Partials     1104     1108       +4     
Flag Coverage Δ
apps.hash-api 15.41% <ø> (+0.73%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kube
kube added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 23b0f06 Sep 3, 2026
104 checks passed
@kube
kube deleted the claude/fe-1560-command-registry branch September 3, 2026 19:47
@hash-release hash-release Bot mentioned this pull request Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants