Skip to content

FE-1544: Rebase the token spreadsheet on the worksheet focus layer - #9418

Merged
kube merged 12 commits into
mainfrom
claude/fe-1544-spreadsheet-focus-layer
Sep 3, 2026
Merged

FE-1544: Rebase the token spreadsheet on the worksheet focus layer#9418
kube merged 12 commits into
mainfrom
claude/fe-1544-spreadsheet-focus-layer

Conversation

@kube

@kube kube commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Before this PR, the token spreadsheet behind the place State sub-view and the scenario forms routed focus by hand. Every cell was a tab stop, Tab was intercepted everywhere and swallowed at the last cell, and row focus went through an unscoped document.querySelector, so a scenario form with several per-place grids could focus the wrong grid.

Rebuilds the spreadsheet on the worksheet keyboard-flow layer. Each grid is one Tab stop, arrows walk cells and the row-number lane, a move past an edge flows into the neighbouring grid, and clicks follow the select-first grammar, which also gives mouse users an editing path.

9418.mp4

Links

Changes

  • Spreadsheet delegates movement, tab order, and the row-number lane to useFocusStops

    One Tab stop per grid with a roving tabindex, arrows walk cells and the gutter, Tab leaves the grid.
    Hand-rolled key router, focusedCell and selectedRow state, and the setTimeout and document.querySelector focus calls are deleted.

  • Row selection follows useRowSelection

    Row whose gutter holds focus renders selected. Delete there removes the row, and index-keyed rows keep focus in the lane.

  • Cell clicks are select-first through useSelectFirstActivation

    First click selects a cell, a click on the selected cell opens its editor. Editing was keyboard-only before.

  • Editor opens and closes through flushSync and the grid's own target map

    Focus lands on elements that exist.

  • Scenario form wraps its per-place grids in FocusRoot and FocusStack

    Arrows flow from one place's spreadsheet into the next.

  • Grid declares role="grid", row, gridcell, and rowheader
  • Uuid overlay revealed by CSS :focus-within instead of focus state
  • Behaviour changes

    Escape on a non-editing cell no longer blurs it.
    Row highlight clears when the gutter loses focus.
    Boolean t, f, 1, 0 keys ignore modifier chords, so Cmd+T reaches the browser again.
    Printable keys typed into a cell no longer reach the editor's single-key shortcuts.

Review fixes

  • Roving tab stop falls back to the first stop when the remembered row or column is gone

    useFocusStops computes it once per render, so a cleared marking or a dropped type element cannot take the grid out of the tab order.

  • Rows expose aria-selected

Known issues

  • Single-letter editor shortcuts v, h, n, t still reach the canvas while a row-number cell holds focus

    Same as before this PR. Settling that globally is part of the wider focus-layer adoption.

Test coverage

  • spreadsheet.test.tsx:

    Roving tabindex, arrow movement and the gutter lane, Tab not intercepted, edit open, commit, advance and cancel, phantom-row materialization, gutter delete with focus retention, boolean keys, select-first clicks, and cross-grid flow inside a FocusStack.

  • focus-flow.test.tsx:

    Fallback of the roving tab stop after the remembered row or column disappears, plus the existing movement contract.

How to test

  • Open Petrinaut preview on Vercel
  • Menu > Load example > Production with Machine Failure
  • Select a coloured place > Properties > State
  • Tab into the grid, arrows across cells and the row-number lane
  • Enter or type to edit, Enter to commit
  • Expect focus on the next cell
  • Delete on a row number
  • Expect the row removed and focus kept in the lane
  • Fill the bottom phantom row
  • Expect a new token row
  • Tab
  • Expect focus to leave the grid
  • Simulate > Scenarios > open a scenario > Initial state
  • ArrowDown past the last row of one place's grid
  • Expect focus in the next place's grid

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

vercel Bot commented Aug 28, 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 type/eng > backend Owned by the @backend team labels Aug 28, 2026
@kube
kube marked this pull request as ready for review August 28, 2026 20:28
@cursor

cursor Bot commented Aug 28, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large UI/focus refactor on a shared spreadsheet used for place state and scenarios; behavior changes (Tab, Escape, boolean shortcuts) could surprise users but risk to data/auth is low.

Overview
Replaces hand-rolled keyboard focus in the token Spreadsheet with the shared worksheet layer (useFocusStops, useRowSelection, useSelectFirstActivation). Each grid is now one Tab stop with a roving tabindex; arrow keys move between cells and the row-number gutter; Tab is no longer intercepted; edge moves can flow into a sibling grid via an enclosing FocusStack.

The scenario form’s per-place initial-state grids are wrapped in FocusRoot / FocusStack so vertical arrows continue from one place’s spreadsheet into the next. Editing uses select-first clicks, flushSync for editor open/close, and role="grid" semantics; UUID full-value display follows :focus-within instead of separate focus state.

useFocusStops now falls back to a valid tab stop when the remembered row or column disappears (e.g. after gutter Delete). Adds spreadsheet.test.tsx and extends focus-flow.test.tsx for that contract. User docs in simulation and scenarios describe the new grid interaction.

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

Comment thread libs/@hashintel/petrinaut/src/ui/components/spreadsheet.tsx 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.
kube added 4 commits September 3, 2026 18:48
…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.
Fold the three per-column text helpers into `fullText`/`displayText` over
one `isTextColumn` predicate, collapse `updateCell` and the editor
open/close paths, group the gutter's focus props, and list the layer's
consumers in the worksheet architecture page.
…ppears

`useFocusStops` computes the roving tab stop once per render and falls
back to the first stop when the remembered row or column is gone, so a
cleared marking or a dropped type element cannot take a grid out of the
tab order. Rows expose `aria-selected`, and the spreadsheet changeset is
dropped as an internal detail.
@kube
kube added this pull request to the merge queue Sep 3, 2026
Base automatically changed from claude/fe-1560-command-registry to main September 3, 2026 19:47
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