feat(ui): the Steps tab lays a screen out in clusters and says a far link in words; a <Card/> is the Card its file imports (#1817)

* fix(ui): a screen's steps read as clusters, and a link too far to follow is said in words

The mobile app's /capture came back as a web: 100 boxes in a 1,227x5,588
ribbon, 113 lines drawn at rest crossing each other 652 times, each one
running over about five other boxes' names. Measured, not guessed — three
separate causes, very unequal.

The region grouping had nothing to divide there (98 of 100 boxes take their
region from one memoized component), so the picture fell back to a single
719px column. But the region dimension was not the lever. The lever was that
`packRegions` packed every step of one distance onto shared rows and wrapped
those rows at a fixed 720px, so a box and the thing it fires landed seven
lines apart: 70 of the 113 lines joined boxes ONE step apart. That is what
the crossings were made of.

So a region is now packed as CLUSTERS — a step, then the steps it sets in
motion on the line under it, stepped in — while the starting points that fire
nothing still share a line, because a screen's handlers are siblings and
giving each its own line turned a flat region into a column. A region's line
width is earned rather than fixed (sqrt(total * pitch), clamped 720..2600),
so a big screen comes out about as wide as it is tall.

Clustering makes most links local but not all: a step reached from two places
is drawn under whichever reached it first, so the other way in still crosses
the picture. Those are now said in WORDS at both ends — `-> resumeInference`
under the box that leads there, `<- CaptureView` under the box it arrives at,
capped at three with `+N more` — rather than drawn. This is not a hiding: the
link is stated, which says more than a line vanishing off the edge of the
screen does, and selecting the box draws every one of its real lines exactly
as before. It is the one at-rest cut that does not produce the "box that leads
somewhere and draws nothing" every earlier cut produced.

Also fixed while here, and predicted by the earlier region work: the in-region
row relaxation had no cycle guard, so a region holding one loop pushed 65 of
its boxes to rows 294-301 while the rest sat at 0-2. `forwardLinks` sets
cycle-closing links aside first, as the order reading's `withoutBackEdges`
already did.

/capture: 1,227x5,588 -> 2,279x4,356, at-rest crossings 652 -> 1,
lines-over-boxes 553 -> 26, with half the links still drawn as real lines and
every quiet box still one the screen itself fires directly. The order reading
is untouched (it keeps every line).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD

* fix(ui): a stub names its box without the mark the box wears for its kind

`← ⇠ onCaptureProgress +2` reads as two arrows arguing: the stub already
leads with a direction, and the box's own kind mark was competing with it.
Verified in the live canvas. The box keeps its mark, where nothing competes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD

* fix(ui): the screen's line into each of its parts stops sweeping the picture

Audited all 51 screens of the mobile app on this branch. 96 lines — the
screen's own stand-in line into each region — were 17% of everything drawn
and caused 79% of every crossing left. A screen with ten regions tiles them
into bands, so the line into a region two bands down travelled the height of
the whole picture.

Two causes, both fixed. The entry the line lands on was the walk's first
member of the region; clustering moves a step that fires something BELOW the
ones that fire nothing, so that box could sit lines down inside the region
and the line had to reach past everything above it. It now lands on the box
nearest the region's top-left that the screen actually leads to. And the
stand-in line is no longer exempt from the stub rule — when the region is
still too far to follow, the link is said in words like any other. The rest
of the anchor's fan stays quiet as before: it is already stood in for.

Across the 51 screens: crossings 47 -> 10, no screen above 10 (worst was 19,
now 2); lines-over-boxes 236 -> 182; boxes with neither a line nor a word
150 -> 135.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD

* fix(ui): a screen's parts fill the canvas instead of squaring off into rows

Audited the app's 28 regioned screens: the median canvas was 55% region and
45% nothing, and /home was 44% — 4,860px tall to hold about 2,160px of
picture. The cause is that regions were tiled a row at a time with each row
as tall as its tallest member, so one short region beside a tall one left the
rest of that row blank, and a reader scrolls through the blank.

Each region now goes as high as it can and then as far left as it can, over a
skyline of what is already placed. Reading order is untouched: regions are
still walked in the screen's own source order, so an earlier one is never
pushed below a later one — a short one just tucks under another short one
rather than waiting for the tall one beside it. Layering now comes from the
finished geometry rather than a band counter, since once regions drop
independently what a reader sees as one row IS one row.

/home 4,860px -> 3,584px, aspect 0.59 -> 0.94. Tallest screen in the app
4,860 -> 4,356. Crossings 10 -> 13 across all 51 screens, still none above 10.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD

* fix(ui): the width a picture wraps at is tried, not estimated

A region's line width came from sqrt(total * pitch) — the width at which
total/width lines come out square. That estimate is wrong for how these
pictures are drawn: a cluster spends lines on its own structure (a hub gets a
line to itself, and what it fires starts another), so it undercounts a
region's lines badly and wrapped /capture's 98 boxes into a 4,356px column.

Laying a picture out is cheap and exact, so the widths are tried instead:
layoutAt runs the whole pack at each of eight widths and the best finished
canvas wins (~2ms for the model, all eight included). It has to be scored on
the CANVAS, not per region — squaring each region off individually leaves
fewer of them side by side, which took /home from 3,584px to 5,624px while
every region looked better on its own.

Also measured and rejected while here: dropping a region's CLUSTERS side by
side the way the regions drop onto the canvas. Total height 42,084 -> 39,756px
(-6%), but lines-over-boxes 120 -> 134 and crossings 5 -> 8, because two
clusters side by side put each one's lines through the other. Height is cheap
to scroll; a crossed line is what made this picture unreadable. The reasoning
is recorded in the code so it is not re-tried blindly. Regions differ — they
sit far enough apart that few lines run between them.

Across the app's 51 screens: tallest picture 4,356 -> 3,796px, total height
45,744 -> 42,084px, lines-over-boxes 184 -> 120, crossings 13 -> 5, and no
screen is a tall ribbon any more.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTUFNN5bH3aPw2gi2LqbYD

* stuff

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-09-09 00:54:15 -05:00
committed by GitHub
co-authored by Claude Opus 5
parent 7a23648db9
commit 3ed73bc127
11 changed files with 1044 additions and 150 deletions
+50 -7
View File
@@ -550,16 +550,42 @@ the server names it on the step (`WireStep.region` — the fold's first node; th
in the screen body; the first-reaching parent's region for everything deeper — first reach wins, as `first` does, so a
shared store is one box in the region that got there first and every other region's way in is a link). Endpoints and
functions carry none: their rows already read in the code's order, and `view=order` is untouched. The viewer
(`steps-model.ts`'s `packRegions`) then lays each region out as its own small column — a box above what it sets in
motion, a line wrapping past ~720px — and tiles the columns into bands under a width budget aimed at a readable aspect,
in the order the walk met them: the screen's own source order, top of the screen to the left. **Within a region the
rows come from the region's own links** (longest lead-to path, settled by relaxation as the order reading's rows are),
never from distance to the anchor, which is flat inside a region: a handler and the store it calls are both one hop
from the screen, and side by side their line was a level arch, hidden at rest — the store looked wired to nothing.
(`steps-model.ts`'s `packRegions`) then lays each region out as its own small column, and tiles the columns into bands
under a width budget aimed at a readable aspect, in the order the walk met them: the screen's own source order, top of
the screen to the left. Regions are **not** squared off into rows — that made every row as tall as its tallest member,
leaving the median screen's canvas 55% region and 45% nothing (`/home` 44%: 4,860px to hold ~2,160px of picture, all of
it scrolled through). Each region instead goes as HIGH as it can and then as far LEFT as it can (a skyline over the
regions already placed, `floorAt`), which keeps the reading order — regions are placed in source order, so an earlier
one is never pushed below a later one — while a short region tucks under another short one. `/home`: 4,860px -> 3,584px,
aspect 0.59 -> 0.94. **Within a region the rows come from the region's own links**, never from distance to the
anchor, which is flat inside a region: a handler and the store it calls are both one hop from the screen, and side by
side their line was a level arch, hidden at rest — the store looked wired to nothing. Cycle-closing links are set
aside before the rows are settled (`forwardLinks`, the twin of the order reading's `withoutBackEdges`): relaxation
never converges on a cyclic graph, and one screen's 65 boxes had been pushed to rows 294-301 while the rest sat at
0-2. **A region is packed as CLUSTERS, not as rows**: a step, then the steps it sets in motion on the line under it,
stepped in by `CLUSTER_INDENT`; a step that fires nothing needs no cluster, so the region's own starting points that
lead nowhere still share one line (a screen's handlers are siblings, not a hierarchy — giving each its own line turned
a flat region into a column). Rows-then-wrap was the alternative and it failed for a measurable reason: it put every
step of one distance on the same rows and wrapped them at a fixed 720px, so a box and the thing it fires ended up
seven lines apart — 70 of 113 lines on `/capture` joined boxes ONE step apart and rendered seven lines apart, which is
what the 652 crossings were made of. The width a picture's lines run to is **tried, not estimated** (`REGION_WIDTHS`,
scored by `canvasCost` against `CANVAS_ASPECT` 1.4): a cluster spends lines on its own structure, so `total / width`
undercounts a region's lines badly and a formula tuned on that estimate wrapped 98 boxes into a 4,356px column. Laying
a region out is cheap (~2ms for the whole model, eight widths included) and exact, so `layoutAt` runs the whole pack at
each width and the best finished CANVAS wins. It has to be the canvas, not each region: squaring each region off
individually leaves fewer of them side by side, so `/home` went 3,584px -> 5,624px while every region looked better.
Within a region the clusters STACK — dropping them side by side as the regions drop onto the canvas was measured
(total height 42,084 -> 39,756px, -6%) and rejected, because two clusters side by side run each other's lines through
the other: lines-over-boxes 120 -> 134, crossings 5 -> 8. Regions differ, being far enough apart that few lines run
between them. `/capture` went from a 1,227x5,588 ribbon to 3,417x3,348.
Each region wears a caption (`RegionCaption.svelte` — its component's name over a hairline spanning its width)
and the key explains it. **At rest the picture hides exactly two things** (`stepEdgeVisible`): the anchor's own fan —
the anchor leads to everything *by definition*, `/home`'s 104 ways of saying so were the moiré, so one line into each
region's first box stands in for it — and, as everywhere on the canvas, what points back up the layering. Every other
region stands in for it, landing on the box nearest that region's top-left the anchor actually leads to (the walk's
first member used to stand for the region, but clustering moves a step that fires something BELOW the ones that fire
nothing, so that box could sit lines down inside the region and the line had to reach past everything above it); those
stand-in lines are themselves subject to the stub rule, since on a ten-region screen the ones reaching into a lower
band were 17% of all lines drawn and **79% of every crossing left** — and, as everywhere on the canvas, what points back up the layering. Every other
lead-to draws, a line between two regions included: the empty state's prompt firing the same handler as the header's IS
the picture, and an earlier cut that reserved cross-region lines for selection made a box that leads three places read
as wired to nothing. The two hidings compose well: a shared step fed from below — the toast action every handler calls
@@ -571,6 +597,23 @@ tracked curves (over a tighter in-region gap), same pills, pointer and panel. Re
picture ~3,400px (was 28,452), at-rest lines on `/home` 80 of 190 — the region-local structure plus 11 lines between
regions — with zero boxes that lead somewhere while drawing nothing.
**Stubs — a link too far to follow is said in words, not drawn.** Clustering makes most links local, but not all: a
step reached from two places is drawn under whichever reached it first, so the *other* way in has to cross the picture.
A line is a good drawing of a hop between two boxes a reader takes in at once and a bad one of a hop across two
thousand pixels — on `/capture` the 113 lines drawn at rest crossed each other **652 times** and each ran over ~5 other
boxes' names, so no line could be followed and the boxes could not be read either. So `packStubs` (over the finished
layout, since this is a question about geometry) keeps a link as a line only when it runs down the layering and its two
boxes are within `STUB_SPAN_LINES` (3) lines and `REGION_LINE_MIN` (720px) across; everything else — back edges
included, which drew nothing at all before, **and the screen's own line into a region**, which has no exception —
becomes a `StepStub` at **both** ends: `→ resumeInference` under the box
that leads there, `← CaptureView` under the box it arrives at, rendered by `StepStubs.svelte` in the gap under the box,
capped at three with `+N more`. This is not a hiding: the link is *stated*, which says more than a line vanishing off
the edge of the screen does, and it is the one at-rest cut that does not produce the "box that leads somewhere and
draws nothing" every earlier cut produced (§ the arc above). Selecting the box draws every one of its real lines, as
before, and the stub block steps aside while it is selected. The anchor's fan is never stubbed — it is already one line
per region. Result on `/capture`: 48 lines drawn at rest crossing each other **once** (was 652), lines-over-boxes 553 →
26, with every quiet box still one the screen itself fires directly.
**Decisions — a choice made inside a box, said under it.** A fork the tree can see is written *inside* a box and its
arms *leave* that box: `resolvePostLoginRoute` ends `return (await hasSeenWelcome(id)) ? '/home/' : '/welcome/'`, so two
`navigates` lines leave one store action. Each carried the whole predicate, one of them the other's negation, truncated