work-pane¶
The node popup — a reference view of intent; the live work surface moved to the session interface.
raw source¶
The spec and the terminal are one act split in two — the spec is the intent, the terminal is where you
change it in place. Originally they shared one pop-out work pane, two columns (spec left for reading,
terminal right for the work), so intent and the surface that changes it sat side by side in a fixed-size
panel that never grows to xterm's measured width.
expanded spec¶
The node popup is the i surface: a fixed pop-out (min(900px,90vw) × min(600px,84vh)) with tabs, opened
over the board and dismissed with Esc. It is reference-only (NodeView.jsx) — no work pane, no
embedded terminal. The intent half is the spec doc — an information board. A stat bar carries the
node's at-a-glance signals, the same the tile speaks: derived status, version, the aggregate yatsu
score (yatsu-score-badge), and the drift count when a governed file outran the spec
(source-of-truth) — so score and drift live in the popup now, not only on the tile. Below it the governed
files, then the body as a living current-state document (the two
labelled parts — raw source / expanded spec — when authored that way, else the flat body). Neither part is
an agent-authored current state — what's-done is read from the derived status, never narrated, because
agents hallucinate completion. The proof and
evolution of that intent live in the history tab.
An issues tab lists the forge work bound to this node — open and closed alike, with both counts on the
tab face (the board's badge/card show only the open ones; see dashboard-issues); the data already rides
the board fold (node.issues), so the tab is a no-fetch group, silent when empty. An edit tab makes a
node's in-flight change reviewable from the board: it exists only while the node has a pending overlay,
and when it does it leads (first tab, editing-session count on its face), so a node mid-change — a
freshly-added ghost most of all, otherwise near-empty on spec/history — opens with its change front-and-
centre. It lazily fetches the unified diff of the node's spec.md in the editing worktree vs the fork point
(/api/edit), rendered with the history tab's diff view and memoised the same way — re-opening shows the
last diff at once, not a reload — but revalidated each open, since a pending change is live.
panesFor(node) is the single source of which tabs exist and their order — both the tab bar and App's
keyboard pane-nav read it, so number/Tab keys never cycle to a tab that isn't there. panesFor also
registers an eval pane (a fourth reference face), but that pane's component and data contract — it
rides node.evals, the board fold — belong to yatsu-eval-tab, just as the issues tab's content is
dashboard-issues'; this node owns the popup shell and the spec/history/issues/edit panes, so the eval pane's
reframe into a verdict-over-evidence timeline is that node's evolution, never work-pane drift. The history tab is
the one merged version log: the latest version sits expanded with its proof, older ones start collapsed and
reveal one at a time on the down gesture once you've finished the open one — scrolling past its end, or
a j/↓ keypress when there is nothing left to scroll (a short history with no scrollbar, or the bottom of
a long one). Tying reveal to the gesture, not to scroll movement alone, is what keeps a sub-page history from
dead-ending with older versions forever hidden (a header click also toggles by hand). A version's proof is
the spec.md line diff it introduced, fetched lazily on expand — every version, memoised by hash (the
latest no longer shipped precomputed); a version with no recorded change says so plainly. That scaffold — scroll container,
latest-expanded reveal, click-toggle, and the per-row header-over-evidence shape — is data-agnostic and
shared: the eval pane (yatsu-eval-tab) rides the same component (version rows + diff here, reading rows
+ screenshot there).
The "change it in place" surface — the live terminal — relocated to the session that does the changing
(Enter; see session-console and term-input), keyed to a session rather than pinned to a node. The
panel sizes to itself, never to xterm's measured width (each pane scrolls its own content, no stray
horizontal scrollbar) — but that sizing lives in styles.css, the dashboard's shared stylesheet governed by
node-graph; this node owns only the
popup component, so a style change elsewhere is never drift here. So the original "one act split in two"
intent stands, but the union is dissolved: intent in the popup, the changing surface with the session.