Skip to content

cockpit v3: verb previews with Undo, sheets, since last look (W6, W8, W10) - #214

Merged
LittleChimera merged 2 commits into
stagingfrom
factory/v3-m2-web-steer
Nov 4, 2026
Merged

LittleChimera merged 2 commits into
stagingfrom
factory/v3-m2-web-steer

Conversation

@LittleChimera

@LittleChimera LittleChimera commented Nov 4, 2026 •

Copy link
Copy Markdown

Related issue

Closes # (cockpit v3 plan items W6, W8, W10; notes/factory-v3/final/plan.md, milestone 2 "Steer at the node")

Summary

Cockpit v3 can now steer where you are. Every structural verb on a node opens a preview under the header before anything changes: the parent's First / Then / Later columns with the change as a dashed ghost, the roll-up change, the effects and who approves. Apply has Undo for 10 minutes. Needs you, Find, the keys, the base env and Try it are sheets that open by address. Three seconds on a page marks your last look. Still behind ?cockpit=v3; v1 and v2 are unchanged (the shared top bar, palette, key list and toast only gain optional, v3-only parts).

ELI5: before, clicking "Start now" said "a preview here soon". Now it shows where the panel would move in its feature's columns, what its wait becomes, who approves, and a Start now button. If you change your mind, Undo stays on the node for 10 minutes.

[Start now s] ──► preview (POST …/n/{ref}/preview: ghost columns, roll-up Δ, approver)
                    │ Apply (preview_id + based_on = page.version)
                    ▼
                  POST …/n/{ref}/apply ──► toast "… started  [Undo]"  +  "Start now: Undo (9 min left)"
                                                         │
                                                         ▼
                                                POST …/undo/{token}

The server's verb routes (S12) are not on staging yet. Each client call sits behind the NodeSource seam, and SERVED_STEER adds it to the live source once its endpoint lands. Until then a live verb says it cannot be previewed and changes nothing. Team is the exception: it already runs on a live factory through the team editor's own routes. The sample factory implements every route, so stories and tests run the real components.

Plan bullets → what this PR does

W6. Verb previews and Undo (v3/verbs/, v3/nodeSteer.ts)

  • PreviewPanel + MiniColumns:
    • Heading with "nothing changes until you confirm".
    • The verb's own fields.
    • The parent's columns after the change (columns_after). The changed node is a dashed ghost; its old place (moved_from) is struck through. They are boxes in a grid; nothing joins them.
    • Label and value lines from the server's rollup_delta, then effects, then "Who approves" (approver_note or the approver).
    • Warnings, then Apply (the server's apply_label), Cancel esc, "Undo for 10 min after applying."
    • nodeWire.normalizePreview also reads api.md's open forms: rollup_delta as an object ({progress: {before, after}}), effects as sentences.
  • AddChildPreview:
    • Fields: title, order (the page's columns + Later), depends on (up to 5 siblings or nothing), because, held for.
    • Pull-forward: the server names the held child the title matches (pulls_forward). The banner says "… is already here, held for …. Adding it now pulls it forward into Then instead of making a second copy". The ghost moves out of Later and Apply reads "Pull forward and plan".
  • StartNowPreview: the node moves to First in its parent's columns (the ghost), and the lines show what its wait becomes, the team, the burn, what it unlocks and the risk.
  • TeamPanel: the team editor in a panel.
    • Every member has a vendor and a model picker. Nothing is preselected; "swap luz to codex" stages that one change.
    • Change / When / Cost ($/h before → after) / Who approves.
    • Live: the project's team spec, the catalog and bench, useTeamPreview (definition preview), a definition version on Apply (useDefinitionEdit), and Undo by re-pinning the version before (usePinDefinition), as the team editor does. A link opens the full editor.
    • Sample: the source's team() with the generic preview.
  • AutonomyPreview: an auto-merge switch for the subtree. The lines say what it covers now and later, the first effect, what is not affected (a human-merged base stays human-merged) and who approves.
  • PausePreview (Pause / Resume).
  • MovePreview: only the parents one level up that the server lists (parents) are offered. A pick shows both roll-ups changing. Apply stays off until another parent is picked. A move two levels up is the server's 422 wrong_level, shown as "Not applied: …".
  • NamePreview: a name and/or a parent one level up.
  • Any other verb (Start research) gets a plain preview.
  • Undo:
    • useApplyVerb shows the toast with Undo, which calls undo(token). u presses it while the toast shows.
    • The steer/UndoLatest pattern: "Start now: Undo (n min left)" stays beside the node's verbs for the rest of the window (useNodeUndo, undoLabel).
    • A 409 stale or any refusal reads "Not applied: …", and the page and its preview are read again.
  • Opening previews:
    • The verb bar (the open one is pressed) and the verb keys a s t u p m n, now bound. u yields to the toast's Undo while one is offered.
    • The why card's "Start it now instead".
    • The chat's plain words, / menu, quick verbs and offer card. They now open the preview on the page instead of a "soon" toast, and keep focus in the chat box.
    • ?verb=…&title|member|to= (handling moved from NodeRoute into the page). A folded implicit parent's redirect keeps the query.

W8. Sheets (v3/sheets/V3Sheets.tsx, opened by ?peek=; mounted by the v3 shell, the story kit and renderNode)

  • Needs you (?peek=needs[&tree=factory]):
    • Ranked items, each with its breadcrumb below the root, title · id · age, the text with "Holds …" (or "Holds nothing else."), every answer, and Open . Merges on one human-merged base are one item that answers on GitHub.
    • The asks that block nothing are one group, with "Apply all n".
    • The alarms the server closed, struck through with their causes and the real ask, and a link to Factory › Health.
    • "n more in Factory ›" switches to the other tree.
    • Opens from the header's "Open C-16" and the v3 top bar's new "n need you" pill. The pill's count is the sheet's when the source ranks Needs you.
    • Without the route (live, pre-S9), ?peek=needs keeps today's list.
    • Reads both api.md's shape and the S9 branch's (blocks_nothing as items with breadcrumbs, closed_alarms optional).
  • Find: ⌘K under v3 lists "Nodes" first: ref or title, state, level and the last two crumbs (GET …/find, {q, items} read too). Projects, PRs and tasks that the search finds open their node pages.
  • Keys: ? under v3 opens ?peek=keys. The list gains each verb's key, "Enter on an answer: never answers", and ⌘K as Find.
  • Base env (?peek=env, also from the v3 top bar's env chip): Deployed sha · branch · age, Health (smoke and checks), Follows, Sign-in, Holds (merged changes), Open the app / API / Factory › Environments.
  • Try it (?peek=try&at=<ref>): the passing steps by name with their acceptance step and "API only …", then Env, Flag (try_flag, shown only when sent) and Sign-in, then Open the app / Open the API. Opens from the roll-up's env chip ("in base env @sha · Try it ↗"), a tile's Try it badge and the acceptance block's "Try what passes".

W10. Since last look

  • useMarkSeen: after 3 s with the page in view (the timer pauses while the tab is hidden), seen(ref) sends PUT …/n/{ref}/seen. useVisitSince keeps the since line and the changes it showed for this visit, even when the page is read again. The next visit starts from the new mark.
  • Badges and since lines: rows now show what changed under the Since overlay ("fix(repl): offer the harness's skills in the slash menu and tint typed commands omnigent-ai/omnigent#9882 merged 13:25", or "2 new" with the list on hover). Tiles keep their change badge. The server's ISO since.at reads as "12:30" (today) or "Nov 2".

Deviations

  • No server routes yet. S9, S10, S12 and S13 are not on staging (checked before pushing). steerRoutes implements api.md's routes with tests, and SERVED_STEER is empty, with TODOs naming each endpoint. One line adds each route to the live source once its endpoint lands. Until then a live verb says "this server cannot preview it yet, so nothing changed", and Needs you keeps today's peek. The S9/S10 work on factory/v3-m2-views (not merged) was read, and the client accepts its shapes.
  • Autonomy (S13). The preview sends {auto_merge: boolean} as its args. The base the subtree merges into comes from the server's lines, not the client.
  • Shapes api.md leaves open. VerbPreview adds (client) fields: title, moved_from, approver_note, pulls_forward, parents, apply_label. rollup_delta and effects are read as lines ({label, html}). The S12 server can send them or the object form.
  • Add child. No dependency is suggested (the prototype preselected one from sample-only data).
  • Team. A viewer who cannot apply sees why and links to the full editor; the editor's "Propose to owners" is not repeated in the panel.
  • Lines and "nothing empty". The levels-rule sweep ignores icons (lucide glyphs inside a button) and still allows no line, path or polygon. "0 working" became "all idle".

Review fixes

  • Team Undo (live) undoes only what is in force. Before it re-pins v12, it reads the versions again. It goes ahead only while the version it made (v13) is the one in force, was made from v12 and is inside its 10 minutes, counted from v13's own time (undoLeft, as steer/UndoLatest). If someone landed v14 after it, the button goes away and the toast's Undo says "v13 is no longer in force; undo from Versions." Once it runs, from the toast or beside the verbs, it is forgotten.
  • A refused apply reads everything again. On 409 stale, 422 or 403, the node pages and the open preview are invalidated, so the preview is read again and Apply works once it settles. The live Team editor's refusal does the same; its edit already re-reads the team spec and the definition when it settles.
  • The "n need you" pill stays live. The ranked Needs you list and the product page behind the base env chip are invalidated whenever the snapshot's open asks change (a push frame re-read it) or the summary's poll answers.
  • "Since" stays for the whole visit. useVisitSince keeps the first read's rollup.since and each child's changed (merged with later reads). An apply, an answer or an Undo after the 3 s mark no longer removes them. A visit to another node starts over.
  • Apply all shows only when every quiet ask's first answer is the same (id or label) and none answers elsewhere. The answers run with Promise.allSettled, and one toast says which went through and which did not.
  • Nothing empty.
    • With nothing open, the sheet is titled "Nothing needs you" with one line naming the tree, and keeps the other tree's link.
    • Try it claims "in the base env" only when the node or its roll-up says so. Otherwise it says "Nothing here can be tried in the base env yet".
    • An unknown ref says so; a failed read says why.
    • No dangling "·" when an item has no id or age.
  • A blocked form asks no preview. useVerbPreview(…, {enabled}) debounces the arguments and the flag together. Add child without a title and the sample Team with nothing picked ask nothing.
  • Move and Name ask the preview with no to only to get its parents. If a server answers that read with an error, the panel offers no parent and says so, instead of showing an error.
  • The preview fields the web reads beyond api.md are now in docs/FACTORY.md (the S12 contract), together with the arguments the web sends. CLIENT_PREVIEW_FIELDS is checked against that list by a test. Name now sends its name as title, as the S12 branch reads it.
  • Move under a folded implicit parent. The current parent is the last crumb, folded or not, so the label names the right level and picking the real parent is not a change.
  • Cancel and Esc wait while applying, and the note says the change cannot be cancelled now.
  • Closing a sheet goes back over the entry it pushed, or replaces the address when the top bar opened it in place, so Back after Esc never reopens it.

Test Plan

  • npx tsc -b: clean. oxlint --deny-warnings on src/components/factory src/pages/factory: clean. Prettier: clean. pre-commit run --files <changed>: passed.
  • vitest run src/components/factory src/pages/factory src/lib: 233 files, 4139 tests, all passed. Full vitest run: 548 files, 10497 tests passed.
  • Review-fix tests:
    • TeamPanel.live.test.tsx: Undo is offered for v13 (8 min left), runs once and is gone. With v14 on top, no Undo is offered and the toast says to undo from Versions.
    • VerbPreviews.test.tsx: after the stale refusal, Apply applies; Move under a folded implicit parent; Add child without a title asks no preview; Cancel and Esc wait while applying.
    • CockpitV3.test.tsx: a new open decision in the snapshot raises the pill from 5 to 6, with no v3 write.
    • nodeSteer.test.tsx: the since line survives an apply after the mark; every client-only preview field comes through and is named in docs/FACTORY.md.
    • V3Sheets.test.tsx: Apply all gives one summary toast; no Apply all when the first answers differ; Nothing needs you; Try it without base env and with an unknown ref; Back after closing never reopens.
  • CI: the failing E2E UI shards fail only tests/e2e_ui/chat/test_composer_acceptance.py::test_status_counts_and_pr_share_workspace_bar, as on the server-only cockpit v3: level views and lenses, Needs you and Find, last-look marks (S8, S9, S10) #213. Pre-commit, server-rest, E2E, stores-crdb and stores-mysql fail as on every branch off this staging (cockpit v3: the real node API end to end (Home, the feature, the project and the task on a live server) #211, cockpit v3: check v3's own chat parts in the e2e; name the proposal kinds that hold work #212).
  • New tests:
    • v3/verbs/VerbPreviews.test.tsx (12):
      • Start now: ghost and struck columns, lines, apply, toast Undo → undo u-1.
      • The server's rollup_delta (object form) and approver shown, and Undo latest calls the token ("tok-1").
      • Pull-forward banner, columns and "Filed C-19"; ?verb=add&title= fills the title without stealing focus.
      • Team: nothing preselected, swap, cost line; "swap luz to codex" stages the one change.
      • Autonomy covers four projects and leaves main alone; Move offers features only and shows both roll-ups; Pause / Resume.
      • Keys s, Esc, u, and u as the toast's Undo.
      • A source without preview changes nothing; a stale apply is refused.
    • v3/verbs/TeamPanel.live.test.tsx: live Team through the definition preview, then PUT /definition with base_version, then Undo POST /definition/pin {version: 12}.
    • v3/sheets/V3Sheets.test.tsx (7):
      • Needs you ranked D-45, merges, D-49, with holds, the quiet group, closed alarms and the other tree; Apply all; the factory tree.
      • Base env, Try it from three openers, the keys, and no sheet without the route.
    • v3/nodeSteer.test.tsx (8):
      • Seen after 3 s (fake timers), and not before; the next visit has no since; rows' since badge; ISO since words.
      • Routes: the requests api.md names; nothing served yet; the S9 branch's Needs you / Find shapes; 409 kept; preview normalisation.
    • CockpitPalette.test.tsx (+2): v3 finds T-16 with its breadcrumb and opens /n/T-16; without a Find route a project hit opens its node page.
    • CockpitV3.test.tsx (+1): in the shell, "Open C-16" opens the v3 Needs you; the "5 need you" pill, the base env chip and its sheet; ? opens ?peek=keys.
    • levelsRule.test.tsx (+1): every verb's preview and every sheet has no line between nodes and no empty cell.
  • Updated: four NodeChat tests now expect the page's preview (data-label) instead of the page's onVerb action.
  • pytest tests/examples/test_factory_sample_tokens.py: 3 passed (FORBIDDEN_SAMPLE_TOKENS green).
  • Storybook build: ok. 15 captures at 1440 and 390, light and dark, with no horizontal overflow.

How to try it: just dev, open a factory with ?cockpit=v3. On a project, Team shows the editor (live today). Other verbs say they cannot be previewed until S12 lands. In Storybook, open Factory/Cockpit v3/Verb previews (StartNow, AddPullForward, TeamSwap, AutonomyAndMerge, Move, NameIt, each with an …Applied story that clicks Apply) and Factory/Cockpit v3/Sheets (NeedsYou, FindByRef, Keys, BaseEnv, TryIt). Also Factory/Cockpit v3/Node page › SinceLastLook / SeenNothingSince.

Demo

Prototype shots beside this PR's captures, each with what matches and what differs: https://claude.ai/artifact/7ULiGSSCbUek8SaFeHD3AR (private; ask Luka for access). Screenshots are not committed (repo rule), and the CLI cannot upload attachments. The captures are in wt/v3-tmp/v3-m2-web-steer/shots/.

Type of change

  • Bug fix
  • Feature
  • UI / frontend change
  • Refactor / chore
  • Docs
  • Test / CI
  • Breaking change

Test coverage

  • Unit tests added / updated
  • Integration tests added / updated
  • E2E tests added / updated
  • Manual verification completed
  • Existing tests cover this change
  • Not applicable

Coverage notes

Manual: every new story was captured in a real browser from the built Storybook (Playwright over the static build) at 1440 and 390, light and dark, and checked against the prototype's shots 02, 04, 08, 09, 14 and 15. No page scrolled sideways. The e2e checks (W11) come with milestone 3.

Changelog

Cockpit v3 previews every structural verb before it applies, with Undo for 10 minutes, and adds Needs you, Find, base env and Try it sheets and "since your last look"

🤖 Generated with Claude Code

… W10)

Steering at the node. Every structural verb (Add child, Start now, Team,
Autonomy & merge, Pause / Resume, Move, Name it / file under, and any
other verb as a plain preview) opens a preview under the header before
anything changes: the parent's First / Then / Later columns with the
change as a dashed ghost and its old place struck through (MiniColumns,
boxes in a grid, no lines), the roll-up change, the effects and who
approves. Apply sends the preview id and the page's facts version; the
toast offers Undo and the node keeps "Undo (n min left)" for the rest of
its 10 minutes (the steer/UndoLatest pattern). Add child pulls a held
child whose title matches forward instead of copying it; Move and Name it
offer only parents one level up. Verb keys (a s t u p m n) open the
previews, Esc cancels, and `u` stays the toast's Undo while one is
offered. The chat's plain words, `/` menu, quick verbs and offers open the
same previews on the page (never an apply), and keep focus in the box.

Team is the team editor in a panel: every member with a vendor and a
model, nothing preselected (words like "swap luz to codex" stage that one
change), the $/h before and after, from the definition's own preview; a
definition version on Apply and Undo by re-pinning the version before. It
works on a live factory today.

Sheets open from the address: Needs you (?peek=needs: ranked by what each
ask holds, merges on one base as one item, asks that block nothing grouped
with "Apply all", the closed alarms with their causes, the other tree's
count), the base env (?peek=env), Try it (?peek=try&at=<ref>: the passing
steps by name, the flag and the sign-in note; from the roll-up's env chip,
a tile's badge and "Try what passes") and the keys (?peek=keys). The v3 top
bar gains the "n need you" pill and the base env chip. Find extends the
⌘K palette: nodes by ref or title with their breadcrumbs, and the search's
projects, PRs and tasks open their node pages.

Since last look: three seconds on a page (in view) marks this person's
last look; the since line and children's changes it showed stay until the
next visit; rows show what changed with the Since overlay; the server's
ISO "since" reads as a time.

The steering routes sit behind the NodeSource seam: steerRoutes implements
api.md (POST …/n/{ref}/preview|apply, POST …/undo/{token}, GET
…/needs-you, GET …/find, PUT …/n/{ref}/seen) and SERVED_STEER attaches
each to the live source once its endpoint lands (TODOs name them). The
sample factory previews, applies and undoes every verb, ranks Needs you,
finds and keeps marks, so stories and tests run the real components.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the size/XL Pull request size: XL label Nov 4, 2026
…ds you count, since lines for the visit

- Team Undo on a live factory re-pins the version before only while the
  version it made is still in force (read again first, the window from that
  version's time); after a newer version it says "undo from Versions". It is
  forgotten once it ran, from the toast or beside the verbs.
- A refused apply (409 stale, 422, 403) reads the node pages and the preview
  again, so Apply works on what is there now; the live Team editor's refusal
  reads them too.
- The shell's ranked "n need you" and the base env chip read again when the
  snapshot's open asks change or the summary's poll answers.
- The "since" line and each child's changes stay for the whole visit, though
  an apply or answer reads the page again after the mark moved.
- Apply all only when every quiet ask's first answer is the same one; one
  summary toast. Nothing needs you, Try it and a missing node say what is so;
  no dangling separator.
- A blocked form asks no preview (Add child without a title, Team with
  nothing picked); Move and Name take the parent from the last crumb (also a
  folded implicit one); Cancel and Esc wait while applying; closing a sheet
  goes back over the entry it pushed (or replaces it), so Back never reopens it.
- docs/FACTORY.md lists the preview fields the web reads beyond api.md
  (CLIENT_PREVIEW_FIELDS, checked by a test) and the arguments it sends; Name
  sends its name as `title`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LittleChimera
LittleChimera merged commit 2e81ecd into staging Nov 4, 2026
44 of 50 checks passed
@LittleChimera
LittleChimera deleted the factory/v3-m2-web-steer branch November 4, 2026 03:22
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

❌ UI Snapshot doesn't match the committed baseline.

If this UI change is intentional, update the baseline — each path renders in the same pinned image, so the result matches this gate:

  • Label the PR (recommended): add the update-ui-snapshot label — the bot regenerates the baseline in the pinned image, pushes it back here, and re-runs the checks.
  • Locally with Docker: run tests/e2e_ui/visual/regen_baseline_docker.sh, review the PNG, then commit + push.

Diff PNGs (expected_=baseline, actual_=your render, diff_) are in the run artifact. Full guide: tests/e2e_ui/visual/README.md.

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

Labels

size/XL Pull request size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant