feat(desktop): the Factory tab, a live view of every plan being planned, built, and shipped

Merged
DA
5d ago
+1357-37

Summary

  • Factory v1, PR 3 of 3, after #2597 (the Inbox slice) and #2601 (the model), both merged. Follows the v9 design on main's own components; what needs the developer is read from the Inbox. No new fetch, IPC, act kind, table, or server work.
  • The page (desktop-factory.tsx): stands in the plan's place beside the sidebar, as the Inbox and an agent's page do (STAND_IN.FACTORY in use-plans-tab.ts's one stand-in state, so opening any of them puts the others away). Plan → Build → Ship, each a card of sidebar rows with the plan's hover card (planCardOf). What waits on the developer is the foot of the step it holds up, lined up as one band: the Inbox's open questions under Build, in full, wearing the question glyph instead of the working mark (the agent is stopped), opening in the Inbox on that item; pull requests ready to merge under Ship, opening on GitHub. Where both wait, the one to take first says "start here". The summary line says what needs you (opens the Inbox) and how many agents are working.
  • Live, not a report: no range and no totals. A row that moves between steps slides once and one that arrives fades in (timing from the motion tokens via paneTiming, so reduced motion and capture runs hold still); times tick with the window's minute; a pull request seen turning merged stands in Ship for a minute.
  • Doors: a Factory row after the Inbox's in the sidebar; APP_COMMAND.FACTORY (⇧⌘F) in the Go menu, ⌘K and Keyboard shortcuts; Escape leaves it (EXIT_FACTORY); it is a place in back and forward. Opening Factory puts the Inbox or an agent's page away and the reverse; any plan, Home, a version history, or a pinned tab puts it away. The window's title says "Factory" while it stands.
  • Gated behind the Factory experiment (EXPERIMENT.FACTORY, #2699's API): the sidebar row stands only while it is on; useAppCommand(APP_COMMAND.FACTORY, …) is offered only then, which takes it out of ⌘K, frees ⇧⌘F, and makes the Go menu item inert; Factory's and Exit factory's APP_SHORTCUTS entries carry experiment, which keeps them off the Keyboard shortcuts page, out of Settings search, and out of the menu bar; back and forward pass over Factory while it is off (reachable); and Factory switched off while it stands, here or on another Mac, gives the plan's place back. Its toolbar wears a quiet "Experimental" badge (.integration-tool-badge) and Send feedback, which opens "Feedback on Factory" through #2709's useOpenFeedback (decisions 5a, 7a).
  • Bundle budget: no baseline moves here. After #2720's reset to 795,917 gz (ceiling 835,712), renderer/renderer.js with Factory measures 799,572, inside the slack. Factory's own code was trimmed earlier without changing anything drawn (bundled glyphs, the home page's New plan, leaner rows).
  • Not in v1 (per the thin plan): Ideas, Automations, merge-queue "Merging", and a merge action.

Evidence

  • Platform-independent checks: pnpm check:affected exit 0 (typecheck, lint, knip, build, 682 desktop tests); ./scripts/check.sh exit 0.
  • Tests: desktop-factory.test.ts (5, jsdom: placement and waits, every door, call and empty, the summary's door, Ship's count); use-plans-tab.test.ts (Factory and the Inbox in turn); and each gated surface absent while off and present while on: desktop-shell.test.ts (the sidebar row and the command, over main's document as the bridge hands it), app-menu.test.ts (Go › Factory via shortcutShown, and shownShortcutGroups).
  • macOS Electron verification (./scripts/verify.sh): not run: this was built on Linux with no Mac available. CI could not verify it either: CI builds nothing for the Mac and produces no visual evidence, so a green PR says nothing about the Mac. Per Dean, merges are not gated on a verify.sh run: he dogfoods the app and reports breaks, and a Mac break that lands is also caught at the release rehearsal on a tag (LUKE-159).

Visual evidence

Not verify.sh output. These are the real renderer components (DesktopShell with Factory open and its experiment on, fixture plans) rendered in jsdom and drawn by headless Chrome on Linux with the built styles.css; the ⌘ glyph draws as a box only because Linux lacks the Mac font.

DarkLight
Busybusy darkbusy light
Quietquiet darkquiet light
Emptyempty darkempty light
  • Fixture screenshots inspected: verify.sh not run (see above); the Linux renders above inspected.

Physical-device evidence

  • Screenshot or screen recording: not attached
  • Device/display configuration: not recorded

Invariants

  • ./scripts/check.sh green; renderer/UI PRs also ./scripts/verify.sh with evidence inspected. (CI) — check.sh exit 0; verify.sh not run (no Mac; not a merge gate per Dean).
  • JSON Schema goldens unchanged (LUKE_UPDATE_FIXTURES not run, or diff explained line by line). (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (manual; root AGENTS.md "Testing")
  • Gateway envelope goldens unchanged. (CI)
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges and the shims tools/oxlint/anti-slop/effect-edges.json records. (CI via anti-slop/no-run-promise-outside-edges)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch outside the files tools/oxlint/anti-slop/effect-edges.json records. (CI via anti-slop/no-raw-async-primitives)
  • No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via pnpm discarded-effect)
  • Test count equals the pre-PR count or the delta is listed. (manual: vitest run --reporter=json) — +12 desktop tests.
  • Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root AGENTS.md's "The permanent adaptors" with the reason it stays.
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording)
  • PRIVACY.md unchanged, or the change is a product decision called out in the PR body.
  • Package package.json deps match what the sources import by bare specifier; effect via catalog:. (CI)
  • Barrel/door rule: no Node-reaching Effect module (@effect/platform-node, effect/sql) behind a barrel the renderer or a web function opens. (CI: existing renderer node: grep plus a new grep for those specifiers in renderer bundles)

Notes

  • Blockers or follow-up verification: None; rebuilt on main after #2720 (one commit, since main's stand-in refactor reshaped every place Factory joins) and queued once CI is green.

🤖 Generated with Claude Code