Skip to content

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

Open
kube wants to merge 1 commit into
mainfrom
claude/fe-1544-spreadsheet-focus-layer
Open

FE-1544: Rebase the token spreadsheet on the worksheet focus layer#9418
kube wants to merge 1 commit into
mainfrom
claude/fe-1544-spreadsheet-focus-layer

Conversation

@kube

@kube kube commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Rebase the token spreadsheet (place initial state, scenario forms) on the worksheet keyboard-flow layer from #9411, replacing its hand-rolled focus and key routing. This fixes two shipping defects: Tab was intercepted everywhere and swallowed at the last cell, trapping keyboard users, and row focus went through an unscoped document.querySelector, so a scenario form with several per-place grids could focus the wrong grid.

🔗 Related links

🔍 What does this change?

  • Spreadsheet delegates movement, tab order, and the row-number lane to useFocusStops: each grid is one Tab stop with a roving tabindex, arrows walk cells and the gutter, and Tab leaves the grid instead of being trapped. The ~250-line hand-rolled key router, the focusedCell/selectedRow state, and every setTimeout/document.querySelector focus call are deleted.
  • Row selection follows the layer's useRowSelection grammar: the row whose gutter holds focus renders selected; Delete there removes the row, and the index-keyed rows keep focus in the lane afterwards.
  • Cell clicks follow the layer's select-first grammar (useSelectFirstActivation): the first click selects a cell, a click on the selected cell opens its editor. This also gives mouse users an editing path, which did not exist before (editing was keyboard-only).
  • Editor focus hand-off (open, commit-and-advance, cancel) runs through flushSync plus the grid's own target map instead of timers and document-wide queries.
  • The scenario form wraps its per-place grids in FocusRoot/FocusStack, so arrows flow from one place's spreadsheet into the next.
  • The grid declares role="grid"/row/gridcell/rowheader per the layer's contract, and the uuid overlay is revealed by CSS :focus-within instead of focus state.
  • Intentional behaviour changes: Enter/typing on a cell still edits, but Escape on a non-editing cell no longer blurs it; the row highlight clears when the gutter loses focus; boolean t/f/1/0 shortcuts now ignore modifier chords (Cmd+T reaches the browser again); printable keys typed into a cell no longer leak to the editor's global single-key shortcuts.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • With a row-number cell focused, single-letter editor shortcuts (v/h/n/t) still reach the canvas, as before this PR. Settling that globally is part of the wider focus-layer adoption.

🐾 Next steps

  • FE-1545 (internal) adopts the layer in the left sidebar lists and search.

🛡 What tests cover this?

  • New spreadsheet.test.tsx: roving tabindex, arrow movement and the gutter lane, Tab not intercepted, edit open/commit/advance/cancel, phantom-row materialization, gutter delete with focus retention, boolean key handling, select-first clicks, and cross-grid flow inside a FocusStack.
  • Existing focus-flow.test.tsx covers the underlying layer contract.

❓ How to test this?

  1. yarn workspace @hashintel/petrinaut storybook, open Components / Spreadsheet.
  2. Tab into the grid (one stop), walk cells and the row-number lane with arrows, press Enter or type to edit, Enter to commit and advance, Delete on a row number to remove the row, and fill the bottom phantom row to add one.
  3. Tab again: focus leaves the grid.
  4. In the app, open a scenario's Initial State section with two coloured places and arrow from one grid into the next.

The grid's visuals are unchanged; the existing docs screenshots stay valid.

🤖 Generated with Claude Code

@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 (UTC)
hash Ready Ready Preview Aug 28, 2026 8:16pm
petrinaut Ready Ready Preview Aug 28, 2026 8:16pm
petrinaut-docs Ready Ready Preview Aug 28, 2026 8:16pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Aug 28, 2026 8:16pm

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
Medium risk: large refactor of keyboard/focus behavior in a core editing surface with intentional shortcut and Escape/blur differences, though covered by new tests and existing focus-layer tests.

Overview
Replaces the token Spreadsheet’s custom focus and keyboard routing with the shared worksheet layer (useFocusStops, useRowSelection, useSelectFirstActivation), fixing Tab being trapped in the grid and row focus sometimes landing in the wrong grid when several per-place tables are open.

Each grid is now one Tab stop with roving tabindex, arrow keys move cells and the row-number gutter (and can flow between stacked grids), Tab leaves the grid, gutter Delete removes rows, and click-to-select then click-to-edit opens the cell editor (including for mouse users). The scenario form’s Initial State section wraps per-place spreadsheets in FocusRoot/FocusStack for that cross-grid flow. Editor open/commit uses flushSync and a local target map instead of setTimeout/document.querySelector; the grid adds role="grid" and UUID overlays show on :focus-within.

Adds spreadsheet.test.tsx, a patch changeset, and user-doc updates for simulation/scenarios keyboard behavior.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

1 participant