Skip to content

cockpit v3: lenses (lineage, timeline, money) and the factory tree (W7, W9) - #216

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

LittleChimera merged 2 commits into
stagingfrom
factory/v3-m2-lenses

Conversation

@LittleChimera

@LittleChimera LittleChimera commented Nov 4, 2026 •

Copy link
Copy Markdown

Related issue

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

Summary

Cockpit v3 gets its other lenses and the factory tree. Below a node's header its children now show in one of five lenses: Map, List, Lineage, Timeline or Money. The bar offers only lenses that have something to show, and none of them draws a line between nodes. The factory tree gets a Product | Factory switch in the top bar and real content in its areas: Health lists the alarms the server closed, Environments shows the base env, and Definition shows the levels and can rename them (previewed, saved as a version, with Undo). Everything stays behind ?cockpit=v3; v1 and v2 are unchanged.

ELI5: "how did the assistant feature go today?" is now one click (Timeline). There is one row per project. Blue means it worked, hatched means it waited, and amber means it waited on you. The stretch since you last looked is shaded. "Where did T-16 come from?" is Lineage: product → need (your ask, no user evidence) → feature (approved by C-11) → project → this task, then what is below it and what landed.

node page ── lens bar (only lenses with content) ──┬─ Map / List      (children, as before)
   ?lens=…   `v` = next lens                       ├─ Lineage  ← GET …/n/{ref}/lineage
                                                    ├─ Timeline ← GET …/n/{ref}/timeline  (tab appears once ≥ 1 lane)
                                                    └─ Money    ← GET …/n/{ref}/money     (≥ 2 children with cost/budget)
top bar ── Product | Factory 3 ── fx › Rules · Team · Environments (base env) · Health (closed alarms) · Definition (levels, Rename levels r)

Plan bullets → what this PR does

W7. Lenses (web/src/components/factory/v3/lenses/)

  • LineageLens (GET …/n/{ref}/lineage):
    • One row per ancestor, top to bottom, with its level, title (a link) and one line. A need's row shows its evidence ("your ask · no user evidence yet"), a feature's row shows its approval ("approved by C-11 · applied 06:01"), and every other row shows its compact status line. "the ask above" applies here as everywhere else.
    • The node itself is highlighted, with acceptance on a feature.
    • Below: the counts per level. Each count opens that level's view.
    • Results: merged → in base env → readouts, and the open changes.
    • Product tree only. A node with nothing below folds it into one "Lineage … show" line, as the prototype's task and run pages do. On a phone, a Lineage button next to "‹ parent" opens it as a sheet (?peek=lineage&at=<ref>).
  • TimelineLens (GET …/n/{ref}/timeline):
    • One lane per child. Work is a solid bar: blue while the node still works, green once it is done.
    • Waits are hatched: grey when the lane waits on another node or the server, amber only when a person is the cause. The labels sit on the card's colour so they stay readable over the hatch.
    • The last-look band shades each lane from last_look to now. The legend lists only the kinds present.
    • Ticks fall on round times on the viewer's clock, never crowding "now". The heading reads "Projects over time · today · shaded: since 12:30".
    • No lines between lanes: each lane is its own box, the band is drawn per lane, there is no "now" rule, and there is no SVG.
  • MoneyLens (GET …/n/{ref}/money): each child's cost against its budget, most first, as one bar per row, with $/h burn where present. Then the total (of the node's budget) and "$x more was spent here … that no child holds" (rest).
  • The lens bar shows only lenses with content (lensModel.useLensChoice):
    • Map when the children are tiles; List when there are children.
    • Lineage in the product tree.
    • Timeline once the server drew at least one lane. It is read when the page has non-run children, and the tab appears when lanes arrive.
    • Money when at least two children have a cost or a budget.
    • Timeline and Money are never offered on a level grouped under parents (Home), as in the prototype.
    • The lens lives in the address (?lens=timeline, replaced in place so Back stays per node). v steps to the next lens. A lens the view does not offer falls back to Map or List. Overlays show only with Map or List.
  • The live source reads …/lineage|timeline|money (S8, on staging) and GET …/levels. The sample source computes the same shapes (fixture/sampleLenses.ts, with the prototype's spans for the assistant's projects and tasks).
  • Levels rule as a test:
    • Lenses.test.tsx opens every lens every sample page offers, at desktop and phone width, and asserts no svg line/path/polyline/polygon, no SVG at all inside a lens, and no empty cell.
    • nodeWire.server.test.tsx renders the server's own timeline, money and lineage through the live source, with the same assertion.
    • levelsRule.test.tsx's sheet sweep adds the lineage sheet.

W9. Factory tree (v3/factory/FactoryAreas.tsx, shell/FactoryTabs.tsx, verbs/RenamePanel.tsx)

  • Product | Factory (TreeSwitch) in the v3 top bar, with the asks waiting in the factory tree (the fx page's needs.open). It takes the place of v2's places under v3 only. The current tree is the page node's root, read from the page's own query.
  • Rules: the rule proposals C-17 and C-18 with their asks (D-50 "Apply as trial", D-51). This was already served as items; there is now a story and a test.
  • Team: the working agents drawn and idle ones counted. This was already served; there is now a story.
  • Environments: a base env block with the deployed sha and how long ago, Health (smoke and checks), Follows, Sign-in, Holds (merged changes), and "Try it in the app ↗" / "API ↗". The setup project that blocks nothing stays an item.
  • Health: "Closed by the server, with causes". Each alarm shows what it asked (struck through), why it closed, the real ask ("The real ask is D-45 (Apply C-16).") and "open ". The Needs you sheet now uses the same rows (ClosedAlarmRows).
  • Definition: both trees' levels as the definition names them (key, name · plural, "hidden now: no node on it yet", "shows when debugging", …) and the definition version.
  • Rename levels (r, from Definition):
    • Every product level with its name and plural. Nothing changes until something is typed, and the plural follows a typed name until it is typed itself.
    • The preview says what changes ("every page, the level strip, previews and agent prompts …; keys, refs, URLs and data stay the same") and who approves.
    • Live: POST …/definition/preview, then PUT …/definition with base_version (the version GET …/levels read) and note "Rename levels". Undo re-pins the version before, only while the version it made is in force. That Undo is the Team panel's, now shared as useVersionUndo.
    • Sample: the verb flow (preview, apply, Undo restores the names).
    • Duplicate names are refused before anything is saved.
  • A verb key the page offers now wins over the shell's single keys (the node keys listen in the capture phase). r renames on Definition and still talks in the chat everywhere else; every other key behaves as before.

Server (small, for W9)

  • rename is offered on the Definition area only. It was in structure.verbs.item, so every factory item (C-17, base-env, D-48, members) showed a "Rename" button that did nothing. Now structure.verbs.area lists it, _verb_applies restricts it to fx:definition, and the label reads "Rename levels".
  • The Environments area carries the same env as the product root.
  • The lens wire fixture gains the levels, a project's timeline, the root's money and a factory lineage. The node page fixture gains fx:environments and fx:definition.

Deviations

  • Definition › Levels is a block on the Definition area, not an item under it. The server has no Levels item, and a new ref kind just for it would touch the ref grammar. The rename verb moved from item to area (defaults and golden updated). A stored definition that lists rename under item has it moved to area on read (definition_blocks.upgrade), so it still offers Rename levels on Definition. The Definition page carries levels: true and shows the levels from that (to every viewer), not from the verb.
  • v2's places under v3. The top bar shows Product | Factory instead of Home / Discovery / Projects / Teams / Steer, per spec §6. Those pages stay reachable from ⌘K and "Back to cockpit v2".
  • Timeline cost. The timeline is read on every node page with non-run children, so the tab only appears when there is content. The route reads the spans once per projection (memoized on the tree, dropped with it on a write or after 5 s), the client keeps it fresh for 15 s and never refetches it on window focus, and Home, which is grouped, never reads it.
  • Team area. There is no separate "Working now" block: the drawn agents are the area's items and the roll-up counts the idle ones, so a block would repeat them.
  • Sample alignment. The sample's Environments area is now fx:environments (the server's ref). The sample-only env:base and fx:levels items are gone. Closed alarms' causes no longer repeat the real ask.
  • g f / g r chords are not added: the Product | Factory switch and ⌘K cover them.

Review fixes (second commit)

  • Timeline: a long lane title is clipped with an ellipsis inside its 168px column at 1440 (max-w-full). Measured in the rebuilt Storybook: "Fix live search on sample-main" is 168px wide and ends at x=206, before the track at 218. At 390 it stays a row.
  • Rename levels on factories stored before this PR: upgrade() moves a stored structure.verbs.item rename to structure.verbs.area (tests in test_levels.py and test_nodes.py).
  • The base env carries area: "fx:environments", and the env sheet's "Factory › Environments" opens that area. ref stays the setup project. New test: clicking it renders v3-base-env.
  • The timeline probe reads the spans once per projection (node_lenses.projection_spans). New API test: four reads across pages hit the store once, and an invalidation reads again. refetchOnWindowFocus: false on the probe.
  • Rename: a name typed back as it was brings no plural, so an irregular plural stays. The panel closes before the pages refresh, and its preview is keyed outside the node keys, so Apply never re-posts a stale preview. New unit and render tests.
  • Lineage: the fold names the first ancestor below the root, with no level key in render code. Open changes read by their short sha or #number, as links (1 open: a097437).

Test Plan

  • Server:
    • uv run --frozen pytest -q -n 8 tests/server/factory tests/examples/test_factory_sample_tokens.py tests/runner/test_factory_node_tool.py: 4489 passed, after rebasing on cockpit v3: threads per node and structural verbs with preview, apply and Undo (S11, S12) #215. After the review fixes: tests/server/factory plus the sample-token scan, 4483 passed (the 300 ms tests included), and tests/runner/test_factory_node_tool.py, 9 passed. This includes the 300 ms performance tests and FORBIDDEN_SAMPLE_TOKENS.
    • New tests: test_nodes.py::test_the_environments_area_shows_the_base_env and ::test_levels_are_renamed_from_the_definition_area_only. The two wire fixtures were regenerated (OMNIGENT_UPDATE_NODE_PAGES=1).
  • Web:
    • tsc -b clean; oxlint --deny-warnings on src/components/factory src/pages/factory src/lib clean; prettier clean.
    • vitest run src/components/factory src/pages/factory src/lib: 235 files, 4166 tests, all passed (4169 after the review fixes). The full npm test after the review fixes: 550 files, 10527 passed.
    • New tests:
      • lenses/Lenses.test.tsx (12):
        • The bar offers only lenses with content: a feature gets all five; the shortlist gets no Timeline; the factory tree gets no Lineage; a task gets List and Lineage; a run's lineage folds.
        • The address keeps the lens; v steps through them.
        • Timeline lanes, kinds (amber only for the person-caused wait), band, legend and ticks. Money order and total. T-16's lineage rows and links. The phone Lineage sheet.
        • The levels-rule sweep over every lens on every page.
      • factory/FactoryTree.test.tsx (10): Health rows, causes and real ask; the Environments block; the root has no env block; Rules; the Definition levels and r; only Definition offers rename; rename preview → apply → Undo on the sample; duplicate names refused; args → definition changes; the live path (definition preview, then PUT …/definition with base_version 14, then the toast).
      • nodeWire.server.test.tsx (+2): the server's timeline, money and lineage through the live source; Environments and Definition on the server's pages.
      • CockpitV3.test.tsx (+3): Product | Factory with the factory's count and no v2 tabs; Health is the Factory's tree; r renames on Definition and opens the chat elsewhere.
  • pre-commit on the changed files: everything passes except pyrefly, which reports its 28 pre-existing errors, none in these files.
  • Storybook build ok. 20 captures at 1440 and 390, light and dark, with no horizontal overflow, in wt/v3-tmp/v3-m2-lenses/shots/.

How to try it: in Storybook, open Factory/Cockpit v3/Lenses (AgentAssistantTimeline, AgentAssistantMoney, T16Lineage, the phone and dark variants, T16LineageSheetPhoneDark) and Factory/Cockpit v3/Factory tree (Areas, RulesC17C18, HealthClosedAlarms, Environments, Team, DefinitionLevels, RenameLevels, RenamedEverywhere). Factory/Cockpit v3/Shell › V3FactoryTree, V3FactoryHealthDark and V3ServerTimeline render the server's own fixtures in the shell. On a live factory with ?cockpit=v3: open a feature and press v, or pick Timeline. Click Factory in the top bar, then Definition, then press r.

Demo

Captures beside the prototype's shots (timeline vs shot 05, lineage vs 12, the factory tree vs 16, Health vs 17), each with what matches and what differs: https://claude.ai/artifact/X6xUy1cdcye6Jto7qgJvyq (private; ask Luka for access). Screenshots are not committed (repo rule), and the CLI cannot upload attachments. The 20 captures are in wt/v3-tmp/v3-m2-lenses/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: Storybook stories checked in the browser at 1440 and 390, light and dark. The timeline matches the prototype's shot 05, the lineage matches shot 12, and the Health area matches shot 17. Captured with no horizontal overflow; the shell stories ran on the server's own fixtures.

Changelog

Cockpit v3: Lineage, Timeline and Money lenses on every node, and the factory tree (Health, Environments, Definition with Rename levels) behind Product | Factory

🤖 Generated with Claude Code

…7, W9)

W7. Below a node's header its children show in one lens, kept in the
address (?lens=; `v` steps to the next): Map, List, Lineage, Timeline and
Money. The bar offers a lens only when it has something to show: Lineage in
the product tree, Timeline once the server drew a lane, Money when two or
more children have a cost or a budget, neither on a level grouped under
parents. LineageLens reads top to bottom (each ancestor with its one line, a
need's evidence, what approved a feature, the node, the counts per level
below that open each level, the results); a node with nothing below folds it
into one line, and the phone's Lineage button opens it as a sheet
(?peek=lineage&at=). TimelineLens draws one lane per child as boxes: work
solid (blue, green once done), waits hatched, amber only when a person is
the cause, the stretch since the viewer's last look shaded, ticks on round
local times, nothing drawn between lanes and no SVG. MoneyLens lists cost
against budget, most first, the total and what no child holds. The live
source reads GET …/n/{ref}/lineage|timeline|money and GET …/levels; the
sample computes the same shapes.

W9. The factory tree: Product | Factory in the v3 top bar (the factory's
asks counted) in place of v2's places; Health lists the alarms the server
closed with their causes and the real ask; Environments shows the base env
(commit, health, follows, sign-in, holds, Try it in the app); Definition
shows both trees' levels as the definition names them and offers Rename
levels (r): the product's level names and plurals, previewed through the
definition's preview, saved as a version on the version read, Undo by
re-pinning the version before (the Team panel's Undo, now shared). A verb
key the page offers wins over the shell's single keys, so r renames on
Definition and talks everywhere else.

Server: rename is offered on the Definition area only (it was offered on
every factory item); the Environments area carries the base env as the
product root does. The lens fixture gains the factory's levels, a
project's timeline, the root's money and a factory lineage.

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
- Timeline: a long lane title is clipped with an ellipsis inside its
  168px column at desktop width (it ran into the track).
- Rename levels on factories stored before: `upgrade()` moves a stored
  `structure.verbs.item` `rename` to `structure.verbs.area`, so Definition
  offers it again; the Definition page carries `levels: true` and shows the
  levels from it (to every viewer), not from the verb.
- The base env carries `area: "fx:environments"`; the env sheet's
  "Factory › Environments" opens that area (`ref` stays the setup project).
- The timeline probe every page makes reads the spans once per projection
  (memoized on the tree, dropped with it) and never refetches on focus.
- Rename: a name typed back as it was brings no plural (an irregular one
  stays); the panel closes before the pages refresh, and its preview is
  keyed outside the node keys, so Apply never re-posts a stale preview.
- Lineage: the fold names the first ancestor below the root (no level key
  in render code); open changes read by their short sha or #number, as links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LittleChimera
LittleChimera merged commit 37845c3 into staging Nov 4, 2026
15 of 25 checks passed
@LittleChimera
LittleChimera deleted the factory/v3-m2-lenses branch November 4, 2026 05:56
@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