feat(steps): the order reading is the canvas, not a rail

The first cut drew the code's order as a nested document — a column of boxes,
forks as rows of arm columns. Wrong picture: hard to read, and it threw away
the thing that made the tree legible. The ask was the canvas back, with the
timing fixed: the 200 comes after the token is signed, so it should branch out
of it.

So the order reading is now the SAME canvas, the same boxes, the same pills,
hover and panel — only the graph changes. `ui/src/lib/program-model.ts` walks
the server's block tree carrying a set of tails (the steps a next step would
follow) and emits one edge per "and then": proshop's login draws the anchor,
`User.findOne`, then the fork — `jwt.sign` under one arm with the `200` a row
below it, the `401` under the other. A row down is one more thing that has
already happened; an arm that answers, returns or throws has nothing leaving
it; a helper, a loop, `later` and `together` ride on the line into what they
hold. Rows are settled by relaxation, because a step reached twice can make
the graph cyclic.

A line means "and then" here and "leads to" in the tree, so the key says which.
The fork conditions are drawn at rest rather than only for a selected box —
`placeLabels` takes an `atRest` flag — because on this picture they are the
content, and two ways to one step merge as one condition (`WHEN userExists OR
NOT user`), not as two rendered labels stuck together.

`StepsRail.svelte` and `RailBlock.svelte` are gone; `StepBox.svelte` stays as
the box both readings draw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REFyW9hmNrxhwN5wxRoAkC
This commit is contained in:
Colby McHenry
2026-08-29 14:16:01 -05:00
co-authored by Claude Opus 5
parent 75686502e3
commit 209a07e881
11 changed files with 529 additions and 674 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ The public API surface is `src/index.ts` — the `CodeGraph` class wires all the
- `src/installer/` — see below.
- `src/bin/codegraph.ts` — CLI (commander). Subcommands: `install`, `init`, `uninit`, `index`, `sync`, `status`, `query`, `files`, `context`, `affected`, `serve --mcp`.
- `src/ui/` — terminal UI (shimmer progress, worker).
- `src/ui-server/` — the `codegraph ui` browser viewer's read-only JSON API (`api/`: one module per endpoint — `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`…) and static server; the Svelte viewer itself lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `api/screens.ts` (the app as screens and transitions) and `api/steps.ts` (what happens from a screen, an endpoint or a symbol, as typed steps — screens, handlers, native bridge calls and events, store actions, calls that leave the index) share one fold: everything between two boxes is `via`, and the branch guards along it join into `when` (`graph/branch-guards.ts`, read at request time). `api/program.ts` is the SECOND reading of that same walk (spec §3.13.1): the anchor's body in the code's order — items in source order, a fork wherever two sites are arms of one decision, a helper drawn in place, an arm that answers or leaves ending there. It is pure over the records `steps.ts` keeps while it walks (`ProgramSite`), so the rail and the tree can never hold different steps; what makes the fold possible is that a guard names the DECISION it belongs to (`BranchGuard.branch`), not only its own words. Two helpers sit beside them: `api/route-roots.ts` (where a route's code starts — the handler a resolver named, the page a screen file exports, or the route itself for an inline handler; one rule for every framework) and `api/effects.ts` (the curated table of calls that leave the index — database / response / queue / email / payments / cache / auth / process / network / storage / device / telemetry — matched on the call **as written**, per language family, plus the model / read-write and the response status). `graph/branch-guards.ts` reads, from one cached tree per file, the conditions a site runs under (each with the branching construct it belongs to and how its arm leaves), the loops it is written inside, what it passes, the call as written (the index keeps only the last segment of a deep member chain), the decorators on a definition and the declared types of a class's members — for JS/TS, Swift, Python, Java, Kotlin, C#, Go and C; a language without rules yields nothing, never a wrong label. `steps.ts`'s `crossing()` reads an edge's `tier` / `channel` marker before the languages, so a synthesized cross-tier hop between two TS files draws as a bridge (an endpoint reached over HTTP — a boundary like another screen, entered with `through=1`) or an event (a job, an event, a message arriving); a Next server action is marked at request time from its `'use server'` directive (`api/when.ts`'s `directive`).
- `src/ui-server/` — the `codegraph ui` browser viewer's read-only JSON API (`api/`: one module per endpoint — `node`, `flow`, `map`, `screens`, `steps`, `deadcode`, `trails`…) and static server; the Svelte viewer itself lives in `ui/` (see `docs/design/codegraph-ui-design-spec.md`). `api/screens.ts` (the app as screens and transitions) and `api/steps.ts` (what happens from a screen, an endpoint or a symbol, as typed steps — screens, handlers, native bridge calls and events, store actions, calls that leave the index) share one fold: everything between two boxes is `via`, and the branch guards along it join into `when` (`graph/branch-guards.ts`, read at request time). `api/program.ts` is the SECOND reading of that same walk (spec §3.13.1): the anchor's body as a block tree — items in source order, a fork wherever two sites are arms of one decision, a helper drawn in place, an arm that answers or leaves ending there — which `ui/src/lib/program-model.ts` turns into the canvas's graph of what happens NEXT (a line means "and then", a row down is one more thing already done). It is pure over the records `steps.ts` keeps while it walks (`ProgramSite`), so the rail and the tree can never hold different steps; what makes the fold possible is that a guard names the DECISION it belongs to (`BranchGuard.branch`), not only its own words. Two helpers sit beside them: `api/route-roots.ts` (where a route's code starts — the handler a resolver named, the page a screen file exports, or the route itself for an inline handler; one rule for every framework) and `api/effects.ts` (the curated table of calls that leave the index — database / response / queue / email / payments / cache / auth / process / network / storage / device / telemetry — matched on the call **as written**, per language family, plus the model / read-write and the response status). `graph/branch-guards.ts` reads, from one cached tree per file, the conditions a site runs under (each with the branching construct it belongs to and how its arm leaves), the loops it is written inside, what it passes, the call as written (the index keeps only the last segment of a deep member chain), the decorators on a definition and the declared types of a class's members — for JS/TS, Swift, Python, Java, Kotlin, C#, Go and C; a language without rules yields nothing, never a wrong label. `steps.ts`'s `crossing()` reads an edge's `tier` / `channel` marker before the languages, so a synthesized cross-tier hop between two TS files draws as a bridge (an endpoint reached over HTTP — a boundary like another screen, entered with `through=1`) or an event (a job, an event, a message arriving); a Next server action is marked at request time from its `'use server'` directive (`api/when.ts`'s `directive`).
### NodeKind / EdgeKind