Skip to content

board-lean

The board payload is a lean summary — per-node detail that the graph overview never shows is dropped from every fetch and reconstructed or lazy-loaded where it's actually viewed.

raw source

/api/board shipped every node's full detail — the spec body, its parsed parts, and its whole evals history — on every single fetch. Measured at ~1.2 MB for ~117 nodes, and ~88% of it is detail the graph overview never renders (a tile shows a title, a desc, a status, a yatsu score — not prose). The board is a hot, frequently-fetched surface; it should carry the summary the overview needs and nothing else. Detail belongs where a node is actually opened, not on the wire every time.

expanded spec

The board is the summary, not the archive: per-node payload that only a distinct surface consumes is excluded, and that surface reconstructs or lazy-fetches it. The hot path is trimmed without changing a pixel of the overview, one field at a time so each cut is verifiable.

body and parts are both dropped (~56% of the payload together). parts is pure redundancy — it is parseParts(body) (three-part-body), and neither rides the board now. Detail reaches its two viewers off the hot poll: the detail view fetches {body, parts} from /api/specs/:id/content on open; the search palette (spec-search) fetches the body corpus from /api/specs/lite on open and ranks nodes over full prose — body is load-bearing for search, so the corpus keeps ranking whole. Both endpoints are filesystem-only (no git); loadSpecs//api/specs still expose body+parts verbatim, three-part-body's contract untouched. Where a payload is in flight the detail view shows a loading spinner (not an empty pane), so a slow /content read shows as loading, not a bodyless node; a failed fetch resolves to an empty body, never a spinner that never stops. No minimum display time — the body lands the instant it arrives; a manufactured spinner would spend the user's time on a decoration. The graph overview never rendered these fields, so it does not change at all.

Freshness is preserved, not traded for the saving. The detail cache is keyed by (id, version) — the board carries the live version, so a new version refetches and the prose can never lag the version badge above it (a non-OK response is shown but never cached). The search corpus revalidates on every palette open, seeded instantly from the last one; the open overlay is keyed by node id so switching never flashes one node's prose under another's header.

evals is cut the same way (it had grown to ~70% of the payload): the board carries only the latest reading per scenario — what every overview surface (badge, stats, search) reduces to anyway, so they consume it unchanged — and the eval tab lazy-loads the full timeline from /api/specs/:id/evals when opened, cache keyed by the summary's newest reading so a fresh filing refetches. A failed timeline fetch falls back to the board's summary readings — truthful, just shallow. Measured: the dogfood board halved again (~576KB → ~270KB).

The scenarios declarations are the third cut. Each declared scenario rides the board slim{name, tags}, the fields every overview surface joins state onto — while its prose (description / expected) and per-scenario code join the lite corpus: a yatsu node's /api/specs/lite row carries its scenarios whole, so the one corpus fetch ranks scenario prose as it ranks node bodies. The shared fetch (corpus.js) revalidates at most once per mount, when prose is first needed — the palette per open, the always-mounted focus-panel on its first scenario-bearing focus, whose clamped expected preview and tracked-files line join from it (rows render name/state/tags instantly; prose fills in). The eval tab's blind-spot rows take expected+code from the /evals fetch it already makes, falling back to the slim board set — shallow, never wrong. Measured: the fold 73KB → 9KB, the dogfood frame ~304KB → ~240KB (~-21%).

This node holds the lean-payload contract those cuts extend, beside the freshness-side board-stream and the change-side board-delta. Still duplicated: each summary reading's expected inside evals — the annotator lane's follow-up, since latestPerScenario is a filter, never a projection.