FE-1486: Add the ad-hoc initial state and parameters form - #9285
Draft
kube wants to merge 28 commits into
Draft
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
3 tasks
kube
force-pushed
the
cf/fe-1486-ad-hoc-initial-state-and-parameters-form-component
branch
from
August 23, 2026 14:18
57ec861 to
f4f987c
Compare
3 tasks
kube
force-pushed
the
cf/fe-1486-ad-hoc-initial-state-and-parameters-form-component
branch
from
August 23, 2026 15:42
01473ec to
4d3338d
Compare
kube
force-pushed
the
cf/fe-1486-ad-hoc-initial-state-and-parameters-form-component
branch
from
August 24, 2026 01:03
4d3338d to
3c26c08
Compare
The shared component over FE-1485's state model. Coloured places render a token spreadsheet: one column per colour element, a gutter reading #n for fixed rows and i for template rows (each template's count in a strip beneath, its own Optimize included), and clickable column headers that share one value for the whole column — cells beneath render derived and out of the tab order, and clicking one opens the shared editor. Variables render at both scopes; net parameters are exposed directly; uncoloured places are one count slot. Every value slot opens the same anchored editor: an expression input, and where optimization is available an Optimize toggle that swaps the expression for Min/Max/Scale (and Step on integers) in place, backed by the core transitions so nothing is lost on toggle. An optimizable prop removes the Optimize surface entirely for the plain-simulation consumers. Storybook stories for both modes and an empty net; jsdom tests pin row mixing, share/un-share retention, the optimizable flag, and expression editing. No consumer is wired yet.
The token table follows the shared-column-values prototype: the row gutter cycles Fixed → Dynamic (blue) → count-Optimized (purple), dynamic rows carry a quiet strip showing the count or its bounds, column headers toggle shared values with a wash under the row tints and derived cells routing clicks to the shared editor, a phantom trailing row materializes on click, and the place total renders at the bottom. Every value slot opens a Monaco single-line editor bound to the form's ad-hoc LSP session; closed slots underline with the first synthesis error or diagnostic as their tooltip. Variables render as compact joined rows added from icon-only header buttons.
Panda's extractor cannot resolve interpolated constants, so the single-line editor's height, placeholder offset, and font utilities were silently missing; the values are now literal. The popover hosts the editor in a row so its flex: 1 keeps meaning width. Stories wrap the form in MonacoProvider and the header share-toggles restore the text box the Tooltip wrapper's zero line-height collapsed.
Variable-name errors render on the name input instead of an undisplayed slot. Boolean slots optimize coherently everywhere as a true/false choice with no bounds. The gutter cycle skips the count-Optimized stage in consumers without Optimize. Clicking the phantom row in a shared column routes to the shared value's editor instead of auto-opening a derived cell. updateCell pads rows shorter than the table so edits land.
The token table, Variables, and Parameters share the token spreadsheet's visual grammar: a bordered table with 28px rows, hairline delimitations, square type-select cells, and full-width clickable value cells. Cells navigate with arrows and Tab. The gutter opens a row-kind menu (Fixed / Dynamic count / Optimized count) instead of cycling. The value editor opens in place: a Monaco single-line editor at exactly the cell's position, the slot's path floating quietly above and the labeled purple Optimize pill below. Uncoloured places show their count on the place-name line, and the TOKENS subtitle is gone. Stories run the real language client, so type-checking works end to end in Storybook.
The overlay portals into the PortalContainerContext container, staying inside the Panda scope (document.body loses every token). A pointer click selects the cell; a second click, a double-click, or Enter opens the editor, and opening one editor closes any other. The floating path label and Optimize control fade in; the editor itself shows immediately.
… delete The floating path and Optimize bars span the editor's full width and sit flush against it, sharing its focus ring. Opening the editor focuses the expression editor (or the Min bound). The type Select neutralizes its base-input box to render as a plain cell. Variables and Parameters navigate with arrow keys like the token table. Row deletion is a quiet trashcan that appears on row hover or focus.
…page AdHocFormContext now carries the form state, the net context, and the Optimize availability, so per-slot components hold only their own value and location; the value editor computes its own attribution label from its target, and a derived cell joins to its column's slot so the shared value's problems surface where the user looks. The folder declares the ui.adhoc-form layer, and an authored architecture page documents the three shapes, the slot join, the type-checking loop, the vocabulary, the component split, and the invariants.
Every table in the form is now a full keyboard grid. Vertical arrows pass through the shared-values line and each dynamic row's count strip, so the line above a row (its count or bounds) is reachable and editable without a pointer. The gutter joins the grid as the left column: focusing it selects and highlights the whole row, Enter opens the row-kind menu, Delete removes the row. Variables gain a quiet phantom trailing row that materializes a fresh entry from the name or value cell, and the name cell adopts the cell selection model (focus selects, Enter or a second click edits, Escape leaves). Cmd/Ctrl+Z and Shift+Cmd/Ctrl+Z walk a form-level undo history; open text fields and Monaco keep their own stacks, and bursts of typing coalesce into one step. The in-place editor drops the blue rings and roundness: the path bar and the Optimize bar are dark slabs flush with a square, frameless Monaco (new CodeEditor frameless variant), framed by one neutral ring. Cell focus gains contrast (darker outline over a faint blue fill), and a new petrinaut-light Monaco theme strengthens the selection and occurrence highlights that vs-light renders too faintly at 12px.
Opening the menu moves focus into it, onto the checked kind (the Popover's initialFocusRef, plus a focus on ref-attach for environments that skip the Popover's open autofocus). ArrowUp/ArrowDown cycle the items, Home/End jump, Enter chooses, and Escape or Tab returns focus to the gutter that opened it. The checkmark is aria-hidden so an item's accessible name is its label; the gutter reports aria-expanded.
The form's components chain into one keyboard walk: each grid, section header, place header, or lone count registers as a zone with the form's navigation registry (use-form-navigation.ts), keeps owning its internal arrow movement, and hands focus to the next or previous zone in document order when a vertical move runs off its edge. Zones that cannot take focus (collapsed sections) are skipped. The token table's column headers join its grid as the top line; the type selects join the Variables grid (arrows move between cells while the menu is closed, Enter opens it) and, being required, no longer offer an empty row. The type column sits directly after the name in both Parameters and Variables, and Parameters moved to its own parameter-rows.tsx. Sections and place headers are focusable collapse toggles: Left collapses, Right expands, and a collapsed place is one line with its row count and token total. Focusing a value highlights its connections (green): the Variable and Parameter rows its expression reads, and — when the focus is itself a Variable or Parameter — every cell that reads it (dependency-highlight.ts; scenario./parameters. prefixes and bare per-place names per the form's vocabulary).
Turning Optimize on now replaces the expression editor with a small spreadsheet: one labeled square column per field (Min, Max, Step where definable, Scale), hairline-delimited, with no chrome of its own — the slab's dark path and Optimize bars stay its only header and footer. Each bound is an expression cell with the form's selection model: opening the slab selects Min, arrows move between the cells (the Scale select included), Enter opens a frameless in-place Monaco bound to the slot's LSP document, and Escape peels one layer — first out of the bound edit, then closing the slab. A bound may hold any expression; one that does not resolve to a constant keeps surfacing the existing synthesis error on the closed cell. The Scale select is required, so its dropdown offers no empty row.
…collapse The trailing action column is gone from every table. The gutter is the row's single control: its pop-up menu (extracted to gutter-menu.tsx, keyboard-first, destructive items red) carries Delete row alongside the row kinds in the token table, and Delete variable in the Variables list, whose rows now start with a #n gutter in the keyboard grid; the Delete key on a gutter still removes directly. The + buttons are gone too: a Variable is created from the quiet trailing 'Add a variable…' line — one cell spanning the row, reachable with ArrowDown from any cell of the row above, opening the fresh name in edit mode. The type select subtree is capped at the 28px cell height, so Variables rows match Parameters rows exactly. Collapsing a place animates with plain modern CSS: the content wrapper is a one-row grid whose track transitions 1fr to 0fr while the inner clips, and the collapsed content turns inert, staying out of focus, the accessibility tree, and the keyboard walk.
… below The form's optimizable flag becomes selection: "none" | "optimize" | "controls". Optimize keeps its meaning; controls relabels the same selection as Control, for a classical scenario exposing values as controls over the state; the stories gain one variation per mode. Focusing a type select now reports its row as the focused value, so dependencies highlight exactly as for any other cell. Add-lines show a quiet + in their gutter — the variables line included — and variable gutters show a variable glyph instead of a row number. The place's token total moves out of the spreadsheet, to plain text under it at the right.
…trips Variable gutters show the design system's function icon instead of a maths glyph. The place total and the collapsed summary drop their = prefix. The add-lines in Variables and the token table, and the strips carrying a dynamic row's count or bounds, render a little shorter than content rows (22px and 20px against 28px).
…ine gutters Focusing any cell of a row highlights the row's connections: variable and parameter rows report their row target from every cell (gutter, name, type, value, the selection toggle), and a token row's gutter highlights as if every cell in it were selected — the union of what its cells (shared values included) and its count read, via a new tokenRow focus target. A pointer click on a gutter now only selects the row; the menu opens from a three-dots affordance that appears on row hover or focus (Enter on the gutter still opens it, Delete still deletes). On add-lines, hovering the row brightens the + and the message, and the + gutter is itself a button creating the new entry.
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.
🌟 What is the purpose of this PR?
The visual half of the ad-hoc scenario feature (FE-778): the shared form for defining Initial State + Parameters inline, built to the ratified prototype (the "shared column values" variation) over the FE-1485 (#9284) shapes. Nothing is wired into a view yet — the three consumers follow as FE-1487/FE-1488/FE-1489 in stack #9286.
🔗 Related links
🔍 What does this change?
New component
ui/components/ad-hoc-scenario-form/in@hashintel/petrinaut, following the prototype's anatomy:token-table.tsx): one column per colour element, rendered in the token spreadsheet's visual grammar — a bordered table with 28px rows and hairline delimiters, full-width clickable value cells, and type selects that render as plain cells. The row gutter opens a row-kind menu (Fixed / Dynamic count / Optimized count), retaining the count across kind changes. Dynamic rows carry a strip above their cells (× scenario.n_satellites, or× 1 … 24when the count is optimized); a phantom trailing row materializes into a fixed row on click and opens the clicked cell's editor; the place total renders below the table at the right, resolved to a number unless a count is (transitively) optimized.use-form-navigation.ts), owns its internal arrows, and hands focus to the next zone in document order (collapsed sections are skipped). The gutter is the grid's left column and the row's single control — no trailing action column anywhere: focusing it selects the whole row and highlights the row's connections (dependencies and dependents, as if every cell in it were focused), a pointer click selects first — the menu opens with Enter or the trailing⋯dots affordance that appears on row hover/focus — and Delete removes the row; its pop-up menu (gutter-menu.tsx, keyboard-first: opens on the checked item, arrows cycle, Enter chooses, Escape returns to the gutter) carries the row kinds plus a red Delete row; Variables rows get a variable-glyph gutter whose menu carries Delete variable, and every add-line's gutter shows a quiet+that also creates the entry on click (the whole line brightens on hover).Sectiongains controlledopenand opt-inunmountOnCollapse). A collapsed place is one line: its name, row count, and token total; the collapse animates with a plain CSS grid-track transition (1fr → 0fr) and the content turns inert, staying out of focus and the keyboard walk.dependency-highlight.ts): focusing a value tints, in green, the Variable and Parameter rows its expression reads; focusing a Variable or Parameter tints every cell that reads it, following the form's vocabulary (scenario.<name>,parameters.<name>, bare per-place names).parameter-rows.tsxnow) and Variables. The type select is a grid cell — arrows move past it while closed, Enter opens it — and, beingrequired, its dropdown no longer offers an empty row.AdHocFormContext.dispatch(action), with the serializable action vocabulary (zod-schema'd, mirroring the net editor's mutation actions) and the pure reducerapplyAdHocActionin petrinaut-core (ad-hoc-actions.ts, on FE-1485: Synthesize ad-hoc scenarios from an inline definition #9284) —setExpression,setDomainField,toggleSelection, variable add/rename (optionally rewriting references)/retype/delete/duplicate, token-row add/delete/duplicate/set-kind, share/unshare. The UI'sstate.tshelpers are gone; components carry a target and dispatch against it.use-form-history.ts): the history is a stack of state snapshots with a cursor. Dispatch appends (or extends) an entry; undo and redo never dispatch — they move the cursor and replay the recorded snapshot, so redo after undo restores the identical state value. Typing bursts coalesce by identity, not time: consecutive dispatches with the sameadHocActionCoalescingKey(one slot's expression, one bound, a rename) collapse into one entry, and the burst goes cold on undo/redo or an external state change. Cmd/Ctrl+Z and Shift+Cmd/Ctrl+Z (or Ctrl+Y) work anywhere in the form; open text fields and Monaco keep their own stacks. The hook exposes theUndoRedoContextshape (canUndo/canRedo/history/currentIndex/goToIndex) for a future history UI.form-spreadsheet.tsx,gutter-cell.tsx,phantom-line.tsx,use-row-selection.ts): the table shell, the gutter cell (main button + dots + menu with its dismiss-refocus choreography), the trailing add-line, and the row-selection model are one implementation composed by the three tables (variable-rows,parameter-rows,token-table).aria-pressed); a shared column takes a tint (drawn as a background image so it composites under the row tints), its value cell sits directly below the header, and the cells beneath render dimmed, chevron-prefixed, and out of the tab order, with clicks routed to the shared value's editor.value-editor.tsx): a pointer click selects a cell (plain:focusstyling, so the selection shows for pointer and keyboard alike); a second click, a double-click, or Enter opens the editor in place — a Monaco single-line expression editor at exactly the cell's position, bound to the form's LSP session, with the cell's attribution path (Space › item 0 › x) on a dark bar above and the Optimize toggle on a dark bar below, both flush with a square, frameless editor (newCodeEditorframelessvariant) framed by one neutral ring. Opening focuses the expression editor (or the Min bound); opening one editor closes any other; the overlay portals throughPortalContainerContextto stay inside the Panda theme scope. Turning Optimize on replaces the expression editor with a small labeled bounds spreadsheet — square Min/Max/Scale cells (Step only on integers other than counts), each an expression cell with the same selection model, editing in a frameless in-place Monaco bound to the bound slot's LSP document (a bound that does not resolve to a constant keeps its synthesis error); Escape peels one layer, bound edit first, then the slab. Turning Optimize off restores the expression, bounds retained.use-ad-hoc-lsp-session.ts, exposed through the language-client context with no-op defaults) and a synthesis dry-run per change; a closed cell with a problem underlines in red and carries the first synthesis error or LSP diagnostic as its tooltip, joined throughadHocSlotKey. A cell superseded by a shared column joins to the column's key, so the shared value's problems render on the cells the user looks at. The stories run the real language client, so type-checking works end to end in Storybook; with the default no-op client (tests) the form renders without diagnostics.default (…)placeholders; an uncoloured place is its name and count on one line.AdHocFormContextcarries the form state, the net context, and the Optimize availability, so per-cell components hold only their own value and location; the value editor computes its attribution label from its target. Aselectionprop sets what selecting a value means:"none"(quick simulation, plain experiments) hides the toggles,"optimize"marks values for the optimizer, and"controls"relabels the same selection as Control, for a classical scenario exposing values as controls over the state — the stories carry one variation per mode. Two further props shape an embedding:withVariables={false}removes the Variables section, andbarerenders the exposed groups without the collapsible section chrome, for hosts with their own heading grammar — quick simulation combines both to sit inline in Simulation Settings (FE-1487: Quick simulation runs an ad-hoc scenario when none is selected #9287).ui.adhoc-formlayer, and an authored page (libs/@local/petrinaut-arch-docs/content/ui/ad-hoc-form.mdx) is the consolidated spec: the shapes, the row grammar, the editing model (actions/reducer/history), the interaction model, the keyboard contract, the slot join, the type-checking loop, the vocabulary, the component split, and the invariants.Fixes in passing:
code-editor.tsx's single-line height, placeholder offset, and font utilities were interpolated constants Panda's extractor cannot resolve, so the utilities were silently missing and single-line editors collapsed to a hairline (visible in the experiment drawer's parameter rows too); the values are now literal. A newpetrinaut-lightMonaco theme replaces stockvs-lighteverywhere, strengthening the selection and occurrence highlights that render too faintly at 12px.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
patch).📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
🛡 What tests cover this?
Twenty-five jsdom tests (Monaco mocked). The earlier ones cover materialization, the gutter menu and its keyboard navigation, sharing, Optimize gating, count editing, closed-cell errors, totals, Delete-on-gutter, the count strip, phantom variables with name-cell editing, and undo/redo. Four new ones cover the zone walk (grid → section header → grid, with a collapsed section skipped and reopened via Left/Right), column headers as the grid's top line plus place collapse to the one-line summary, arrow navigation through the type select and its dropdown offering no empty row, connection highlighting in both directions, the bounds spreadsheet (Min selected on open, arrows across the cells, in-place expression edit, layered Escape), gutter-menu deletion of rows and variables, select-first gutters with the dots affordance, row-wide connection highlighting from the gutter, and add-line gutter creation. Two pin the redux semantics: undo/redo replay reference-identical snapshots, and a typing burst in one slot is a single undo step keyed by the slot, not by a timer.
❓ How to test this?
turbo run test:unit --filter @hashintel/petrinautturbo run dev --filter @hashintel/petrinaut→ Components / AdHocScenarioForm. Six stories: the Satellites Launcher model (plain, non-optimizable, optimized, shared column), a two-field model, and an empty net.🐾 Next steps
FE-1487 wires quick simulation, FE-1488 the experiment drawer, FE-1489 the optimization drawer.
🤖 Generated with Claude Code