Skip to content

site: Factory — lifecycle figure redrawn as a pipeline + 3 diagram fixes - #60

Merged
p4gs merged 2 commits into
mainfrom
design/defense-factory-r2
Sep 15, 2026
Merged

p4gs merged 2 commits into
mainfrom
design/defense-factory-r2

Conversation

@p4gs

@p4gs p4gs commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Factory follow-up — the lifecycle figure redrawn as a pipeline, and three diagram-connector fixes

Two commits on top of #59 (already on main), per the principal's live review of the first interim ship.

The lifecycle figure — replaced, not tweaked

The home page's "One record covers the whole lifecycle" figure previously drew its six checkpoints along a perfect maze — a solvable labyrinth generated by a seeded depth-first carve. The concept behind it was always right (the checkpoints are PHASE_NAMES in PHASES order — Commit integrity, Dependencies, Build receipts, Code & build hardening, Ongoing posture, Distribution & publishing — already real SDLC order), but the maze shape read as a puzzle to solve, not a pipeline to follow.

It's now a directed, left-to-right circuit-trace through four zones grounded in what those six phases actually are: Repository (commit integrity) → Build (dependencies, build receipts, code & build hardening — the three build-time concerns, so this zone is proportionally wider) → Production (ongoing posture) → Registry (distribution & publishing). Same motion mechanism (pathLength=1, scroll-bound dash-offset), same six data-bound checkpoints, same honesty constraints (no copy says "walks," "passes," "in order," or "none is skipped" — a scan reads a clone once), same deterministic byte-identical build. A new test asserts the route never doubles back into a zone it has already left.

Three diagram-connector fixes (round-3 findings)

  • The explorer's outgoing wire now lands on the Record card's own centre-line instead of 490px into empty grid.
  • The ranked figure's first label no longer collides with its rank label ("strongest") at either width.
  • The explorer's phase control at 390 no longer hides four of six phases behind an uncued scroll.

Gates

bun test 892 pass / 0 fail / 1 skip · tsc --noEmit 0 · bun run build 0 (36 pages) · Factory-page greps clean (0 workstation paths, 0 "nobody could answer/check", 0 OpenAI references, 0 remaining maze markup) · five-design regression diff against main: 0 lines (main already carries Factory from #59; nothing outside Factory changed).

🤖 Generated with Claude Code

https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm

p4gs and others added 2 commits September 14, 2026 20:08
The concept above `mazeFigure()` was always right — "the road is the path CODE
takes, from a commit to a published package, and the checkpoints are where the
standard set's checks sit along it" — and the shape contradicted it.
`carve()` + `solve()` generated a PERFECT MAZE: an enclosed labyrinth over the
whole 12x8 lattice with exactly one path between any two cells. So the figure
read as a puzzle a reader has to solve rather than as a pipeline they can
follow. No legible direction, no start or end that said "commit → published
package", and no visible systems — a route free to double back into an earlier
column is a maze's defining property, and code does not double back into a
repository it has already left. That is a direct hit on this design's own
principle: the motion has to mean something, and the meaning has to be true.

WHAT IT IS NOW. A directed left-to-right circuit trace through the four real
systems the six phases already belong to, monotonically non-decreasing in
column by construction:

  Repository (SCM)  P1 Commit integrity
  Build (CI)        P2 Dependencies · P3 Build receipts · P4 Code & build hardening
  Production        P5 Ongoing posture
  Registry (CD)     P6 Distribution & publishing

`STAGE_ZONES` is NOT a new taxonomy — `PHASES` and `PHASE_NAMES` are untouched
and in their own order, and the table only says which system each existing phase
sits in. The zone WIDTHS are the phase counts, so Build is three lattice columns
to either neighbour's one (measured: 2/6/2/2 of 12), which is the figure saying
out loud that half the standard set is a build-time concern. A phase the table
does not name falls into the last zone rather than off the board, and
`zonesFor(n)` deals any n across the same weighted span, so the generator still
follows `PHASES.length` rather than a literal.

The jogs are seeded, and their DIRECTION alternates while only their size is
random. A free coin drifts: six steps of an unbiased walk put the entire run in
the top half of a board it is supposed to cross (measured — rows 3,1,2,3,2,4
before the fix). Alternating keeps the trace oscillating about the middle while
the seeded magnitude keeps it from reading as a regular zigzag. A jog is also
required at every step, because two checkpoints on one row give a flat run with
no corner, and a flat run is the serpentine the first cut of this figure was
rebuilt to stop being.

The board behind it is the frame, one full-height divider per system boundary,
and two dead stubs per zone. The stubs are what make it read as a board rather
than an empty box — they are unused traces that deliberately go nowhere, and
they sit in rows 0 and 7, which the trace never uses. The one line that means
anything is the blue one.

NOTHING ABOUT THE MOTION OR THE DATA CHANGED. Same `tracedFigure`, same
`pathLength="1"` with scroll-bound `stroke-dashoffset`, same `data-node-at` /
`data-reached`, same markup-carries-the-finished-state, same 532x374 viewBox,
same six checkpoints from `PHASE_NAMES` in `PHASES` order, same `legend: true`.
Only the shape the trace draws is new. The honesty constraint is untouched and
its test still passes: nothing near this figure says walks, passes, in order, or
none is skipped.

The four zone names are generic structural labels rather than per-repository
facts, so they are authored — and drawn as HTML over the drawing, not as SVG
text, for the reason the ranked figure already learned: an SVG label renders at
whatever the viewBox scales to, so a declared 14px measured ~8px at 390. They
sit in the empty band above the frame, where they cannot cross the trace or a
checkpoint at any width, and drop to 8px at 390 where a zone is 51px wide
against 80 at 1440.

Measured in a real browser with the lifecycle live (61-65 rAF ticks/s), at both
widths: four labels in JetBrains Mono, 0 overlaps, 0 crossing the frame, the
document never widens (scrollWidth === clientWidth at 390 and 1440). Mid-scroll
at progress 0.549 the trace is drawn through Build with 3 of 6 checkpoints lit
and the legend dimming the other three — the scroll binding is intact.
Determinism proven the way it matters: two consecutive builds, `diff -r` over
the whole of `dist/`, 0 differences.

`mazeRoute`/`MAZE_GRID` are gone, and so are `carve`, `solve` and `mazeWalls`;
`pipelineRoute`/`PIPELINE_GRID`/`STAGE_ZONES` replace them, and `.fy-maze`
becomes `.fy-pipeline`. The maze tests are rewritten rather than deleted — the
"perfect maze, every wall drawn" assertion is now its own inverse (a perfect
maze over this lattice draws 77 interior walls, and every one of them must be
gone), plus a new directedness test and a new test that the zones are the
phases regrouped.

889 pass / 0 fail / 1 pre-existing skip; tsc 0; build 0; dist byte-identical
across two builds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
THE OUTGOING WIRE POINTED AT EMPTY GRID. Round 2 top-anchored the explorer's
three columns so each head sits on its own content — correct, and the cost was
that the wire stayed in the CHECKS band, where it stretched to that band's
~2000px row. Its arrowhead's `inset-block-start: 50%` therefore resolved to
y=779, on bare dotted background, ~330px below the "One listing" Record card at
y 349-510. An arrow IS the claim that the middle column produces the right one,
and it was landing on nothing — while the inbound bracket beside it was drawn
correctly, which made the outbound half look worse.

The fix is structural rather than a number: the run moves into the OUT band, so
it shares one auto grid row with the Record card and 50% of that row IS the
card's vertical centre. Its SVG is taken out of flow (`position: absolute; inset:
0`) so the wire contributes no intrinsic height of its own — in flow, the SVG's
viewBox aspect would set the row and 50% would mean the wire's own midpoint
again, which is the defect. Nothing in the stylesheet has to be kept in step
with the card's content. Measured at 1440: arrowhead y **11626.0**, card centre
**11626.0**, delta **0.00px**, arrow inside the card's box, and the wire's right
edge meets the card's left edge at **0.0px**. The inbound bracket's arms still
land at **14.6 / 46.7 / 82.1%** of the input cards.

The 390 connector is a second element rather than the same one moved, because
reading order at that width has to stay checks-cards → connector → Record head,
and an element inside the out band comes after the out head. Each width paints
exactly one: `[data-flow-wire="stack"]` is `display: none` at >=768 and `block`
at <=767, and `[data-flow-wire="out"]` the reverse.

THE RANK WORD SAT IN THE LABEL BAND. "strongest", placed 10 units above its
rung, shared a horizontal band with the first node's centred label, and the
descenders of "The project's own build, signed" ran through its cap height —
40.5 x 4.0px at 1440 and 39.8 x 5.1px at 390, the only text overlap on the whole
site and, since round 2 centred that label on its node, at both widths.

Horizontal separation is not available here and it is worth saying why: node 1
sits 40 units right of the ladder's own left edge, so any label centred on it
reaches into the rank gutter at any width. So the separation is vertical, and
the rule is now the same for both words — the rank word hangs 14 units UNDER the
rung it names (110 and 270 against rungs at 96 and 256), clear of the rung's own
line, with the first speed label lifted 4 units further off its node. Measured:
**0 text overlaps on home at both widths** (per-line-box probe over every text
node, closed `<details>` and the visually-hidden idiom excluded), with the rank
word **22.3px** clear of the label above it at 1440 and **12.8px** at 390.

THE PHASE PICKER HID FOUR OF ITS SIX PHASES. At 390 `.fy-segmented` is a
scrollbar-less horizontal scroller: measured **2 of 6** labels fully visible,
under a rounded cap 20px inside the screen edge that reads as the end of the
control rather than as more to come. It is the site's one interactive device and
its own copy says "pick a phase", while the page's chart, sheet and directory
all say six. The `.table-scroll` containers two selectors away already had the
mask, the `data-overflow-*` attributes and the cue; the chapter rail already had
scroll-into-view. This gives the control all of it: same mask, same cue element,
and the checked label is scrolled fully into view on change, padded by the mask's
own 20px so a revealed label never lands under the fade that says there is more.

Measured at 390: `data-overflow-end="true"` with the cue visible while it
overflows, and after checking the sixth radio its label box **172.9-365.7** sits
fully inside the control's **20.0-370.0**, with `data-overflow-start` flipping to
true and `-end` to false at the end of the track. The radio semantics, keyboard
behaviour and the no-sliding-thumb rule are untouched — `role="radiogroup"`, one
checked input, the panel shown, and `transition-property: background-color,
color` with no thumb element. At 1440 the control does not overflow and the cue
computes `display: none`.

Two round-1 assertions pinned the old wire count and the old head padding; both
are updated rather than deleted, and the new count is two-sided (three wire
elements, exactly one of them the untagged inbound bracket).

892 pass / 0 fail / 1 pre-existing skip; tsc 0; build 0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017N6Qc2T7buVAXHZ9vYa1xm
@p4gs
p4gs merged commit 1ee8902 into main Sep 15, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant