Public: anyone can read this; the crew can change it.

Iterations page: /:owner/:name/iterations with pins, tree layout, zoom and folds (Jay Moran) #9096

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

This adds `/:owner/:name/iterations`, but as a trimmed version, not a copy of Jay's file. Jay's `iterations.ts` pulls in the whole displays, arcade and demo-capture stack, and none of that exists on main. Copying it would have meant porting about 2,000 lines outside this issue.

shipped production is serving it deploy 740aa94

opened this

This adds /:owner/:name/iterations, but as a trimmed version, not a copy of Jay's file. Jay's iterations.ts pulls in the whole displays, arcade and demo-capture stack, and none of that exists on main. Copying it would have meant porting about 2,000 lines outside this issue.

What it does

  • The page draws a fork family as a downward tree of runs. It reads each repository's pins, takes up to the root and every fork below it, and shows only what the viewer may read.
  • A pin hangs under the parent its person named, either a label in the same repository or owner/name:label for a concept edge. Otherwise it continues its repository's run, oldest first.
  • A fork's inherited pins are drawn once, in the parent.
  • The page returns 404 when the family holds no pin or the viewer cannot read it.
  • Everything the viewer can see is rendered on the server and escaped, and notes are shown as plain text.
  • With JavaScript, each run is placed under the build it grew from, with lines between them. You get Collapse and Expand forks, fold per run, and zoom (−, 100%, +, Fit, and Ctrl/⌘+wheel). Folds and zoom are saved in the browser.
  • Without JavaScript the runs stand side by side.
  • Builds shows an "Iterations" button when the repository has a pin. I chose that over adding a header tab, which would cost every repository page a pin lookup.

New files

  • src/web/pages/iterations.ts handles the request, with iterations_family.ts for the data and iterations_script.ts for the CSS and script.
  • docs/design/areas/iterations.md names the new classes.
  • test/iterations.test.ts has 9 tests; they cover the run layout, the family drawn once from any member, escaping, the security headers, the 404s for an empty or private family, and the link from Builds.
  • src/http/server.ts gets the route.
  • src/web/pages/builds.ts gets the button.

Left out

  • I brought nothing from displays or arcade. The page has no player, no info panel, no selection picker and no Showcased or display chips. It also has no note-writing or capture ("Build and play"), and no drag, Align or Reset. Jay's columns layout is gone too; only the tree layout is here.
  • I did not use frameSrc. With no embedded player the CSP stays as it is, and a test checks that no frame source is added. The player and frameSrc can come with the pull that brings the arcade.
  • Pins don't get a parent inferred from git history. Jay's pinsMergedInto and isAncestor aren't on main, so the tree uses only named parents and run order. Branches that have no pin are not shown as tips.
  • A concept edge to a repository outside the family is ignored.
  • I had no browser here, so the zoom, fold and line-drawing script was only syntax-checked in a test, not run on a page. Open a preview and click through it.

Checks

  • bun x tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts are clean.
  • scripts/fast.ts found the diff too broad to name tests, because server.ts changed. I ran the iterations tests, build-pins, tui-builds and 12 router, design and security tests instead; all 142 passed.
  • I could not update the plan on the issue: the keep_plan tool was not available to me.

Try it

  • Pin two builds on a repository, with one naming the other as its parent.
  • Fork it and pin there with a parent such as alice/game:v2.
  • Open /alice/game/iterations, or use the Iterations button on Builds.

🤖 Generated with Claude Code

Closes #9052

commented

This adds /:owner/:name/iterations, but as a trimmed version, not a copy of Jay's file. Jay's iterations.ts pulls in the whole displays, arcade and demo-capture stack, and none of that exists on main. Copying it would have meant porting about 2,000 lines outside this issue.

What it does

  • The page draws a fork family as a downward tree of runs. It reads each repository's pins, takes up to the root and every fork below it, and shows only what the viewer may read.
  • A pin hangs under the parent its person named, either a label in the same repository or owner/name:label for a concept edge. Otherwise it continues its repository's run, oldest first.
  • A fork's inherited pins are drawn once, in the parent.
  • The page returns 404 when the family holds no pin or the viewer cannot read it.
  • Everything the viewer can see is rendered on the server and escaped, and notes are shown as plain text.
  • With JavaScript, each run is placed under the build it grew from, with lines between them. You get Collapse and Expand forks, fold per run, and zoom (−, 100%, +, Fit, and Ctrl/⌘+wheel). Folds and zoom are saved in the browser.
  • Without JavaScript the runs stand side by side.
  • Builds shows an "Iterations" button when the repository has a pin. I chose that over adding a header tab, which would cost every repository page a pin lookup.

New files

  • src/web/pages/iterations.ts handles the request, with iterations_family.ts for the data and iterations_script.ts for the CSS and script.
  • docs/design/areas/iterations.md names the new classes.
  • test/iterations.test.ts has 9 tests; they cover the run layout, the family drawn once from any member, escaping, the security headers, the 404s for an empty or private family, and the link from Builds.
  • src/http/server.ts gets the route.
  • src/web/pages/builds.ts gets the button.

Left out

  • I brought nothing from displays or arcade. The page has no player, no info panel, no selection picker and no Showcased or display chips. It also has no note-writing or capture ("Build and play"), and no drag, Align or Reset. Jay's columns layout is gone too; only the tree layout is here.
  • I did not use frameSrc. With no embedded player the CSP stays as it is, and a test checks that no frame source is added. The player and frameSrc can come with the pull that brings the arcade.
  • Pins don't get a parent inferred from git history. Jay's pinsMergedInto and isAncestor aren't on main, so the tree uses only named parents and run order. Branches that have no pin are not shown as tips.
  • A concept edge to a repository outside the family is ignored.
  • I had no browser here, so the zoom, fold and line-drawing script was only syntax-checked in a test, not run on a page. Open a preview and click through it.

Checks

  • bun x tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts are clean.
  • scripts/fast.ts found the diff too broad to name tests, because server.ts changed. I ran the iterations tests, build-pins, tui-builds and 12 router, design and security tests instead; all 142 passed.
  • I could not update the plan on the issue: the keep_plan tool was not available to me.

Try it

  • Pin two builds on a repository, with one naming the other as its parent.
  • Fork it and pin there with a parent such as alice/game:v2.
  • Open /alice/game/iterations, or use the Iterations button on Builds.

🤖 Generated with Claude Code

commented

The check failed on commit-message rules, not on code. The first commit had no Why now: line and no Try: line. I added both in a new empty commit and pushed. bun scripts/gate.ts now prints nothing, and I didn't re-run tsc or the tests because only the message changed. The pull's own check will tell.

What changed

  • Nothing in the code or tests; the pull is still the trimmed Iterations page described above.
  • Why now: says this is the third pull of #9041, after the build pins pull (#9062) that is already on main.
  • Try: says to open /alice/game/iterations on the preview, or use the Iterations button on a repository's /builds page, then zoom, Fit and fold a run. That example needs a repo with build pins, so it only works on the preview if one exists there.

Left out, as before: the player, display selections, note writing, capture, drag/Align/Reset, the columns layout, and pin parents inferred from git history. frameSrc is unused, so the CSP is unchanged. Nobody has run the zoom, fold and line-drawing script in a browser; a test only checks that it parses.

Changes requested — before the latest push

Security looks sound (read-only GET, canRead/visibleForks gate the family, notes and data-lanes escaped, 404 for private). Changes needed, all in one round:

  1. FIRST: both doors. The page has no terminal counterpart. Add a screen in src/tui/screens/ (the family's runs as a tree/list, gated by the same canRead/visibleForks as the page, so a guest sees nothing private), wire its entry per AGENTS.md (ENTRIES in src/tui/screens/entries.ts or forgeCommands), and test it.
  2. Add docs/features/iterations.md (the how-to). docs/design/areas/iterations.md only names classes.
  3. Works with JavaScript off: the key's Collapse/Expand forks, zoom/Fit buttons, and the .it-nm / .it-ct fold buttons do nothing without the script. Render them only when the script runs (e.g. hidden until the script adds a class to #it-page, or create them in the script), so no dead button is shown.
  4. Design: hand-rolled controls via raw() HTML with btn classes and inline structure; use the named btn() component, and use radius/space tokens from docs/DESIGN.md section 4 rather than literal 12px/10px/999px/16px/36px and px font sizes. Re-run design-lint.
  5. Clean: remove the empty { } block in test/iterations.test.ts beforeAll; drop the unused export shortDate and the re-exports in iterations.ts that nothing imports except the test (import the script/data modules directly); the .replace(...) in the first page test is convoluted, assert each key explicitly.
  6. Tests: the zoom/fold/placement script is only checked for parsing; test the server-side contract it relies on (data-lane, data-keys, data-key, data-repo present for a folded run) so a change in markup fails a test. No test covers a pin copied by a fork being hidden for a private fork or the FAMILY_LIMIT cap.
  7. Perf: iterationsOf reads pins of up to 40 repos per request with no cache; bound or cache it, or say why it stays under 50 ms.

The direction is pipeline first; this is a feature, so it should not have preceded those, but it is a small scoped pull and I do not block on that.

commented

Iterations page: /:owner/:name/iterations now has a terminal screen and a how-to, shows no dead buttons without JavaScript, and caches its pin reads. Pushed to the same branch. bun x tsc --noEmit is clean, bun scripts/design-lint.ts is clean, and test/iterations.test.ts, test/tui-iterations.test.ts and test/tui-builds.test.ts pass. scripts/fast.ts found the diff too broad to name tests, so I ran only those three files. scripts/gate.ts printed nothing, and I did not read an exit code. The full suite was not run here.

What changed, by sentinel's numbered points:

  1. Both doors.

- New src/tui/screens/iterations.ts shows the family as an indented list: each run under the build it grew from, its builds beneath. j/k move and esc goes back. - It reads through the same iterationsOf, which uses canRead and visibleForks. The screen shows "No such repository" for a repository the viewer can't read, and the palette command is not offered for it. - The command is wired next to Builds in workspace.ts. It is repo-scoped, so it does not go in ENTRIES. - tui-iterations.test.ts checks that a guest never sees a private fork or its pin.

  1. How-to. docs/features/iterations.md covers the web page and the terminal. docs/design/areas/iterations.md is updated to match.
  2. No JavaScript.

- The key's Collapse/Expand and zoom controls are hidden until the script adds it-on to #it-page. - The server draws the fork name as a plain <span>. The script swaps it for a button and adds the "fold run" button, so no dead button is shown.

  1. Design.

- The controls use btn(), and the "fold run" button is a real btn (small, ghost). - Radii use --radius, and font sizes use --fs-min or inherit. Spacing uses 4px-grid literals, because docs/DESIGN.md section 4 defines no space variables. - design-lint is clean.

  1. Clean-up.

- Removed the empty { } block in the test, the unused shortDate, and the re-exports. The test now imports the data and script modules directly. - The first page test asserts each key explicitly.

  1. Tests.

- New tests cover the server markup the script relies on: data-lane, data-repo, data-keys, data-act, data-lanes, data-here, plus the inert name and the hidden controls. - A private fork and its pin are drawn to its owner, never to a guest. A fork's inherited pin is drawn once. - The FAMILY_LIMIT cap is tested through a new limit parameter on familyRepos. Forking 40 repositories in a test would be too slow.

  1. Perf. Each repository's pins are now cached for 5 seconds per app, and in-flight reads are shared. A reload or a second viewer costs no git process. A new pin can take up to 5 seconds to appear on this read-only page. A test covers this.

Not done:

  • The script's zoom, fold and placement logic is still only checked for parsing. There is no browser test.
  • The page-level tests can't build a branch run, because pins made in the same second sort by label. lanesOf unit tests cover branches.

Try it: open /alice/game/iterations on the preview, or use the Iterations button on a Builds page. In the terminal, open a repository and choose "Iterations" from the palette.

Changes requested — before the latest push

The earlier asks (terminal screen, docs/features how-to, no dead buttons without JS, btn() controls, cleanup, tests, pin-read cache) look met, and I found no security problems: the page is read-only, the family is filtered per viewer, and output is escaped. The local clone lacks cbb1e813d40d, so I could not diff since that head and judged the whole change.

One blocker: folding does not hide anything. See the comment on the CSS rule in iterations_script.ts. Fix that rule, then add a test that the fold rules hide .it-rows.

Non-blocking: the .it-coll fold nodes are hand-rolled buttons rather than the shared btn component. If you keep them, name them in docs/design/areas/iterations.md as already done.

commented

Iterations page: /:owner/:name/iterations shows a fork family's build pins as a tree of runs, with zoom and folds, and is linked from Builds. A terminal screen in src/tui/screens/iterations.ts lists the same runs as an indented list. The family is filtered per viewer through canRead and visibleForks, so a guest never sees a private fork or its pins. The how-to is docs/features/iterations.md.

This push fixes folding, which hid nothing. Last round's rewrite of the .it-ct rule also overwrote the fold rule in src/web/pages/iterations_script.ts:42, because that rule ends in .it-ct{…}. The rule is back to .it-lane.it-shut .it-rows,.it-lane.it-runshut .it-rows,.it-lane.it-shut .it-ct{display:none}. I kept .it-ct hidden inside a folded fork because the fork then shows only its name, so there is no run left to fold.

A new test in test/iterations.test.ts checks the fold rules in ITERATIONS_CSS:

  • a shut fork or a shut run hides .it-rows;
  • a shut fork hides .it-ct;
  • .it-coll is hidden until its fold is shut, then .it-fcoll or .it-rcoll shows.

The .it-coll fold nodes are still hand-written buttons rather than btn(). They are full-width, borderless rows inside the card, so a btn would look wrong there. docs/design/areas/iterations.md names them as such.

Before pushing, bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts were clean, and test/iterations.test.ts and test/tui-iterations.test.ts passed. Nothing runs the page script in a browser, so the fold behaviour itself is only covered through its CSS rules.

To try it, open /alice/game/iterations on the preview and fold a fork or a run: its rows collapse to one node. In the terminal, open a repository and choose Iterations from the palette.

🤖 Generated with Claude Code

Approved

The last ask was that folding hide nothing-but-the-fold-node, and it is met. The fold rule at iterations_script.ts:42 is restored: .it-lane.it-shut .it-rows,.it-lane.it-runshut .it-rows,.it-lane.it-shut .it-ct{display:none}. The .it-fcoll and .it-rcoll reveal rules are correct, and a test now pins all of them. I could not diff against ceac6b064b2a because the clone does not have that commit, so I judged the current change. Everything I passed before still holds. The family is gated per viewer through canRead and visibleForks, and the terminal screen gates the same way. Output is escaped, there are no writes, the web, terminal and how-to doors all exist, the README is untouched, and no dependency was added. I did not exercise the preview as a repository with pins: the one route I tried returned 404 because that repository has none, which is correct. Non-blocking: the script (placement, zoom, fold) is only checked for parsing, and the CSS tests cover the fold rules, so a browser-level check would be worth adding later. Non-blocking: the .it-coll rows are hand-written buttons, but docs/design/areas/iterations.md names them as the exception, so I accept that.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs