Skip to content

cockpit v3: switch and node routes, header and status line, level strip, children (W1-W4) - #206

Merged
LittleChimera merged 5 commits into
stagingfrom
factory/v3-web-shell
Nov 3, 2026
Merged

LittleChimera merged 5 commits into
stagingfrom
factory/v3-web-shell

Conversation

@LittleChimera

@LittleChimera LittleChimera commented Nov 3, 2026 •

Copy link
Copy Markdown

Related issue

Cockpit v3 build plan (notes/factory-v3/final/plan.md), web items W1 to W4. No GitHub issue.

Summary

Cockpit v3 (the work levels) behind the cockpit choice: ?cockpit=v3 puts the node pages inside the new cockpit's shell. v1 and v2 are unchanged, and v2 stays what someone who chose the new cockpit sees by default. The server has no node API yet, so the pages read a typed sample of the factory at 13:40 (same shape, neutral names). The page says it shows sample data. Server PR S4 swaps in the real endpoint by setting one preflight flag.

ELI5: every page is one node of a strict tree. The page shows the node's ancestors and the levels below it in one row, then the node's header (what it is, why it exists, the one line that says who or what holds it), then its children as tiles or rows. Nothing is drawn between nodes. A link to another tree is a chip.

/factory/:id/            → Home (v3: the product's node page)
/factory/:id/n/:ref      → one node + its children        (useNode → NodePage)
/factory/:id/n/:ref/:lvl → one level under it, grouped     (useLevel → LevelView)
                 NodeSource seam: preflight.node_api ? live API : sample engine (labelled)

Plan bullets → what this PR does

W1. The v3 switch and routes

  • shell/cockpitV2.ts: CockpitChoice gains "v3", and ?cockpit=v3 is honoured while the server offers the new cockpit. The server stores only v1 or v2 (UiPrefs.cockpit, and this PR makes no server changes), so this browser keeps a v3 choice, and it beats the stored v1/v2 until the person picks v1 or v2 again. useCockpitV2() adds v3. on stays true under v3 because v3 is built on v2's shell, so v2's places (Teams, Steer, Debug…) still work.
  • Routes n/:ref and n/:ref/:level (pages/factory/NodeRoute.tsx). Under v3, HomeRoute renders the product's node page. On v1 or v2, a v3 link goes to the closest page there: the project page, the PR page, else Home. The shell marks itself data-cockpit="v3", and shellRoutes knows the node page (presence node:<ref>).
  • useNode(ref) / useLevel(ref, level) / usePrefetchNode / useAnswerDecision (v3/nodeQueries.ts): TanStack Query, keyed by factory and ref, fresh for 5 s, prefetched on hover and focus of any node link.
  • The seam (v3/nodeSource.ts): a NodeSource with node, level and answer. liveNodeSource reads GET /v1/factory/factories/{fid}/n/{ref} and …/level/{level} and answers via POST /decisions/{id}/answer. It is used when preflight says node_api: true; otherwise the sample source is used. Stories and tests inject a source with NodeSourceProvider.
  • The sample (v3/fixture/): sampleData.ts is the prototype's 13:40 data with neutral names. sampleEngine.ts ports the prototype's engine and returns the api.md shapes: one parent per node, state as the worst active child, the blocker path (person-ending paths ranked by held weight), compact lines, why, chips, roll-ups, levels below, child pages with columns, and grouped level views. scaleSample.ts is the synthetic big product. Answers apply to the source's own copy, so Apply C-16 recomputes the whole page.
  • Stories: Factory/Cockpit v3/Shell (v3 on, dark, phone, a node link, a level link, v3 off).

W2. Header, status line, why card, chips

  • NodeHeader: at most five lines, then verbs. They are title · ref · level · kind · owner; why (safe markup, RichText); status; roll-up (ring, acceptance and build %, cost of budget, team "6 idle", base env, since, alarms closed); and chips. A line with nothing to say is left out.
  • StatusLine: StatusBox (full path, label colour, "why? ▾", the answer) and CompactStatus (the end of the path only). The wording lives in statusModel.ts. The "the ask above" rule: a compact line that ends at the header's decision says "the ask above" and shows no button, and a ghost drops its options when the header asks the same thing.
  • WhyCard (numbered, ends at the ask, the met-links note, "Start it now instead"), LinkChip and ChipList (at most 3, "+n", count badges open lists grouped under breadcrumbs, warn tone for links with no reason).
  • DecisionButtons: header (answer + "Open C-16"), tile (answer) and ghost (every option). Enter is swallowed on them, so an answer is always a click. External answers (merges a person makes) are links out.
  • Stories: Factory/Cockpit v3/Header covers moving, needs with a path, waits on (hop), held on purpose, not started for 7 h with met links (why open), done, later, proposed (ghost), root, and phone 390. Each comes in light and dark.

W3. Level strip

  • LevelStrip: ancestors with state dots; folded implicit ancestors in italics with no dot; the node underlined; each level below with count, "+n later", "n you" and a state bar, linking to n/:ref/:level; "Below · nothing yet" when there is a next level but no node; the Results cell ("7 merged · all in base env", or "nothing merged yet"). PhoneLevelRow: "‹ parent" and cells that scroll inside the row. Empty levels are omitted, and a level that only has later work says "4 later", never "0".
  • Stories: Factory/Cockpit v3/Level strip covers root, feature, folded project, project, task, run, factory tree, scale, and renamed levels (Outcome / Workstream from data), with phone variants.

W4. Children: tiles, rows, columns, level view

  • NodeTile (ring or glyph, compact status, its answer only when the node itself needs you and the header doesn't ask it, grandchildren as up to 12 dots else a stacked bar, badges), NodeRow, OrderColumns (First / Then / … / Later (held), up to 4 to a row, done folded per column), DoneLine, LaterBox (reasons and note), LevelView (grouped under parents with their lineage, all-done groups folded into one line, rows paged at 60), ChildrenView (tiles ≤ 12, rows 13–60 with a note, grouped by state past 60). Acceptance and evidence render above the children when the node has them.
  • Keys (useNodeKeys.ts): a roving tabindex over tile and row title links (one Tab stop; they are real links). j/k move DOM focus, Enter or l step in, h steps out, w opens why. The keys yield to g chords and to fields, menus and dialogs. KeyboardHelp lists them under v3.
  • Stories: Factory/Cockpit v3/Node page covers Home, Agent assistant, assistant-core tasks, scale tasks grouped (each in light, dark and phone), plus task, run, factory tree, and after Apply C-16.

Deviations

  • Small additions to the api.md shapes, marked (client) in nodeTypes.ts and now in notes/factory-v3/final/api.md too, so S4 builds to one contract. They put display names next to refs (segments and also-holds carry title), level_name/kind_name/glyph/tag on nodes and crumbs, detail and why_title on status lines, why as {html, tone}, chips_more, home on root pages, tiles/level_name/later_title/later_note/via_fold on child pages and level views, and noun on grandchild counts. Every one is optional: without them the page falls back to the level key, tiles, or the bare ref, and nodeWire.ts also reads also_holds and why as api.md's plain strings. Root counts is a list of {key, name, plural, count, later?} so the nouns come from the definition.
  • v3 choice is kept in the browser only. The server's UiPrefs.cockpit is Literal["v1","v2"]. A one-line server change can store v3 later.
  • Shell: v3 reuses v2's top bar, Room drawer and phone tabs. The prototype's top bar (Product | Factory, money, base env, Needs you) is not in W1–W4. The chat column is W5, so pages are full width for now. NodePage takes an aside for it.
  • Not yet wired, deliberately: verbs show a toast ("shows a preview here soon; nothing changed") until W6. "Talk" waits for W5. "Try it" is a non-clickable "in base env @sha" chip until W8. LaterBox has no "plan now" yet (W6). Map/List are the only lenses (W7). The change card and the run's last-turn steps are not built (they are node content outside W1–W4). In the sample, "Open C-16" shows the ask's text in a toast; on a live source it opens v2's Needs you panel.
  • ui/ChainCounts is not reused as a component: the strip follows its count-plus-bar pattern, with colours from ui/statusTokens (MEANING). Moving work is blue and done work is green, as in the prototype.
  • The sample is neutral: "Sample app (sample)", sample-org/sample-app, base sample-main, the competitors and the ads network renamed, and "you" in place of the owner's name. factory v2: scrub product names from factory fixtures and strings #194's FORBIDDEN_SAMPLE_TOKENS ratchet passes.

Review fixes (second round)

  • Past 60 children (or grouped_by_state), proposals get a "Proposed" state group and held work keeps its Later box; nothing is dropped.
  • Paging to the server: NodeSource.node and .level take a cursor (?children_page=, ?cursor=). Past the first page a "Next 60" footer reads children.next (or a level group's next) and appends the rows (useMoreChildren, useMoreInGroup, TanStack infinite queries that only fetch on the click).
  • Dark mode: every v3 surface is bg-card-solid. Dark mode's glass rule (index.css, top-level .bg-card) was replacing tile rings (the amber "needs you" edge, focus-within rings), the dashed no-reason chip and the strip's current cell. Measured in Chromium: the needs tile's box-shadow keeps the amber 1px ring, and the warn chip is dashed amber. The levels sweep now asserts that no element on any v3 page carries a bare bg-card.
  • The S4 seam: client fields are optional with fallbacks (no .toLowerCase() on a missing name), a root's level is its tree's root key (product, fx) instead of a root sentinel, the summary's badge flag is try_ready so NodeFull.try_it stays api.md's step list, and "+n" chips show a count even without chips_more. nodeWire.test.tsx renders sample pages stripped to api.md's bare shapes through liveNodeSource.
  • Sample answers: on sample data an answer's toast says "Sample data: nothing was answered…".
  • Nothing empty: the strip and the phone row filter out levels with count + later == 0, LevelCountWords renders nothing for them, and the sweep's zero check now matches "0 later" / "0 you" and opens every level under every node (not only under the roots). A probe test injects an empty level.
  • Names from data: the root's roll-up reads "3 outcomes · 1 study · 9 workstreams" when the levels are renamed.
  • Keys: w is bound only when the header offers "why? ▾"; the keys line lists j/k/step in only with nodes to step into, h only with a parent, w only with a why.
  • Phone roll-up: separators end their part (and counts use a non-breaking space before "·"), so a wrapped line never starts with "·"; the since part truncates on a phone.
  • Strip: the node's own cell shows its code ("T-16 · Debit each turn", "[Bug] A message cancelled while queued blocks every later message in its session omnigent-ai/omnigent#9876", a sha; not a priority like "P0"); labels ellipsize; below 1024 px the ancestors between the root and the parent fold into "…".
  • Bundle: the sample (data, engine, scale data) is a dynamic import() that loads only under v3 without node_api, so v1 and v2 do not ship it.
  • Leaving v3 / v1-v2 links: a T-n link on v1/v2 lands on its project's page (from the tasks list), an I-n link on factory: outcome-first initiatives — walking skeleton, acceptance runs on the base env, one progress view #205's initiative page; the person menu offers "Back to cockpit v2" whenever v3 is on.
  • Rebased on factory: outcome-first initiatives — walking skeleton, acceptance runs on the base env, one progress view #205 (one import conflict in FactoryPage.tsx, both routes kept). No migration, no server change.

Test Plan

  • npx vitest run src/components/factory src/pages/factory src/lib: 224 files, 4020 tests, all pass (after rebasing on factory: outcome-first initiatives — walking skeleton, acceptance runs on the base env, one progress view #205).
  • New tests:
    • v3/fixture/sampleEngine.test.ts: the api.md §8 contract cases on the sample. These are assistant-core "Not moving: T-16 → held for C-16 → needs you: Apply C-16 (D-45)" with no D-48; the panel's "Not started for 7 h: waits on assistant-core › T-16 → …" with the met-links note; the root's top blocker with "also holds"; Home grouped under needs; after Apply C-16 the feature ends at D-49; the implicit parent folding; and the one-parent check at both scales.
    • v3/NodePage.test.tsx: one place per decision's answer on every sample page; Enter never answers (user-event and a raw keydown) while a click does; "the ask above"; why and w; the chip list; strip counts and "you" from levels_below, with no zero for a later-only level; folded ancestors; the phone row; columns, done line, later box and ghost options; grouping and paging at scale; the keyboard walk root → I-1 → assistant-core → T-16 → a097437 → run and back up with h; one roving tab stop.
    • v3/levelsRule.test.tsx: no SVG line/path/polyline/polygon (and no canvas) on any v3 page, and no leaf cell that reads "0", "none" or "—". It runs on every node and every root level of the sample, on desktop and phone, after Apply with renamed levels, and on the synthetic big product.
    • v3/Children.test.tsx (tiles ≤ 12, rows 13–60 with a note, state groups past 60 paged) and v3/statusModel.test.ts (wording rules).
    • pages/factory/CockpitV3.test.tsx: ?cockpit=v3 turns v3 on without a PUT; v3 survives a reload over a stored v2; a /n/I-1 link survives a reload; /n/product/project opens the level; "Back to cockpit v2" saves v2 (from the page and from the person menu); v2 stays the default; a v3 link on v2 lands on the project page, a task's on its project, a feature's on its initiative; an answer on sample data says nothing was answered and posts no answer; ?cockpit=v3 is ignored when the server doesn't offer the new cockpit.
    • Review round: Children.test.tsx (61 children with a ghost and held work; the server's "Next 60"), NodePage.test.tsx (paging through the cursor, renamed roll-up nouns, w and the keys line, the strip's code, the amber edge class, an injected empty level), levelsRule.test.tsx (every level under every node, "0 later" caught, no bare bg-card), nodeWire.test.tsx (api.md's bare shapes through the live source; cursor URLs).
  • Overflow and lines sweep in Chromium over all 79 sample pages (each node, each root level, and the scale levels) at 390 and at 1440: no horizontal overflow and no SVG line or path.
  • tsc -b, oxlint --deny-warnings, prettier --check, pre-commit on the changed files, and tests/examples/test_factory_sample_tokens.py: all pass.

To try it: open any factory with ?cockpit=v3 while the server offers the new cockpit (OMNIGENT_FACTORY_COCKPIT_V2=1). Home becomes the product's node page (sample data). Then:

  • click a tile or press j and Enter to step in, and h to step out;
  • open a level from the strip (for example Tasks);
  • press "why? ▾" on assistant-panel-ux;
  • try Apply on the status line (the sample recomputes; Enter on it does nothing);
  • use "Back to cockpit v2" to leave.

Demo

Side-by-side page, prototype shots against this PR's Storybook captures at 1440 and 390, light and dark, with what matches and what differs for each: https://claude.ai/artifact/BDSmQkGgybAKRBTi8EX1dv (private; ask Luka for access). Screenshots are not committed (repo rule), and the CLI cannot upload attachments. The captures sit locally in wt/v3-tmp/v3-web-shell/shots/.

Second-round captures (dark Home and Agent assistant with the amber edges back, the phone roll-up, the strip at 800 px) are in wt/v3-tmp/v3-web-shell-fix/shots/.

Stories to open: Factory/Cockpit v3/Node page (Home, AgentAssistant, AssistantCoreTasks, ScaleTasksGrouped, AfterApplyC16, with Dark and Phone variants), Factory/Cockpit v3/Header, Factory/Cockpit v3/Level strip, Factory/Cockpit v3/Shell.

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 sample page was swept in Chromium at 390 and 1440 for horizontal overflow and SVG lines. Twelve screens were captured and compared with the prototype's shots (Demo link). The Playwright end-to-end checks are W11.

Changelog

Cockpit v3 preview behind ?cockpit=v3: the factory as levels, with one node per page, its status line and its children.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the size/XL Pull request size: XL label Nov 3, 2026
LittleChimera and others added 5 commits November 3, 2026 21:10
… children

Cockpit v3 (the work levels) behind the cockpit choice: `?cockpit=v3` puts
the node pages in the new cockpit's shell, v1 and v2 stay as they are, and
v2 stays the default for people who chose the new cockpit. The server
stores only v1 or v2, so this browser keeps a v3 choice.

- Routes `/factory/:id/n/:ref` and `/n/:ref/:level`; Home is the product's
  node page under v3. A v3 link opened on v1 or v2 lands on the closest
  page there (the project, the PR, else Home).
- `useNode` / `useLevel` (TanStack Query, 5 s fresh, read ahead on hover
  and focus) against the api.md shapes, through a `NodeSource` seam: the
  server's node API when preflight says `node_api`, else a sample factory
  (the shape of a real factory at 13:40, neutral names) computed by a port
  of the prototype's engine, labelled as sample on the page.
- NodeHeader (five lines, then verbs), StatusLine full and compact with
  "the ask above", WhyCard, LinkChip and ChipList, DecisionButtons (Enter
  never answers).
- LevelStrip and PhoneLevelRow: ancestors (folded implicit ones in
  italics), levels below with counts, "+n later", "n you" and a state bar,
  the Results cell; empty levels left out, never a zero cell.
- NodeTile, NodeRow, OrderColumns (First / Then / Later), DoneLine,
  LaterBox, LevelView (grouped under parents, done groups folded, 60-row
  pages), rows past 12, grouped by state past 60; acceptance and evidence
  above the children.
- Roving tabindex over tile and row links; j/k move focus, Enter or l step
  in, h steps out, w opens why; KeyboardHelp lists them under v3.
- Tests assert no SVG line or path between nodes and no "0" or "none"
  cell on every sample page, desktop and phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Past 60 children, proposals get a "Proposed" group and held work keeps
  its box; the server's next 60 are read with the page's cursor
  (`children_page`, a level group's `cursor`) instead of slicing what
  was fetched.
- Every v3 surface is `bg-card-solid`: dark mode's glass rule restyled
  bare `bg-card` and dropped tile state edges, focus rings and the dashed
  no-reason chip. The levels sweep asserts no bare `bg-card` remains.
- The node API seam matches api.md: client-only fields are optional with
  fallbacks (level key, tiles by default), `also_holds` and `why` may
  arrive as plain strings (`nodeWire.ts`), a root's level is its tree's
  root key, and the summary's badge flag is `try_ready` so `NodeFull.try_it`
  stays api.md's step list. Root counts carry their level names.
- On sample data an answer's toast says nothing was answered.
- Empty levels are filtered in the strip and the phone row, and the sweep
  now flags "0 later" and opens every level under every node.
- `w` and the keys line follow what the page offers; roll-up separators
  end their part, so a wrapped phone line never starts with "·"; the
  strip names the node by its code (T-16 · …) and folds middle ancestors
  below 1024 px.
- The sample engine loads in its own chunk, only when there is no node
  API. A task's v3 link on v1/v2 lands on its project; the person menu
  offers "Back to cockpit v2" while v3 is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LittleChimera
LittleChimera merged commit 9916928 into staging Nov 3, 2026
27 of 34 checks passed
@LittleChimera
LittleChimera deleted the factory/v3-web-shell branch November 3, 2026 20:25
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