Layout specification
The right-side Viewport minimap: expected appearance for each scenario. This is the implementation reference.
The rendered result must match it. Node position = (lane column + tier indentation, depth row).
◇ ROOT Session root (diamond)
● user User message (circle)
▲ llm AI reply (triangle)
■ code Tool/function (square)
◉ merge Merge node (solid circle with a hole)
⚒N Execution subtree badge(aggregated by default, scenario 11)
×N Loop folding
↗ Cross-session spawn
Status = stroke(running dashed/error red/cancelled gray, scenario 12)
Frontend module boundaries:
DAG TypeScript is organized into data projection (passes/), geometry (layout/),
drawing (render/), interaction and camera (interaction/). The root directory retains only public
entry points, types, rendering gates, and the core pipeline. Separate the tooltip’s pure content model from its DOM lifecycle,
graph preparation from SVG commit in the pipeline, and node classification from ownership calculation in threads. Splitting files must not change public entry points, DOM
classes, SVG structure, test-visible state, or camera behavior. Each file should generally stay within 500 lines.
Phase one: establish the branch-head data contract before changing graphics:
SessionStore.list_branches() returns only true leaves of the conversation predecessor graph.
Conversation nodes include user, llm, and top-level Programs whose caller is empty or ROOT. A node with any
valid conversation successor is an intermediate node and must not be returned again as a branch tip. Internal Program
execution children, runtime/attach rows, and context rows do not extend the conversation, so they do not remove their owner’s
leaf status. Multiple Programs sharing one predecessor without links between them are parallel leaves and must each be returned.
Phase-one production file:openprogram/store/session/session_store.py.
Public-entry RED:SessionStore.list_branches() returns only the final Program
in a sequential top-level Program chain, returns every leaf among parallel Program siblings, and creates no extra tip for an internal execution child.
Explicit exclusions:lanes, coordinates, edges, colors, badges, camera, expand/collapse, and all frontend rendering changes. Graph-structure
design starts only after the underlying contract passes focused/affected/full gates, independent specification review, independent quality review, and real-session acceptance on the default instance.
Implementation status:Phase one implements a shared conversation-node predicate with identical semantics for successors and tip candidates.
Sequential top-level Programs retain only the final leaf; parallel siblings remain separate. Runtime/context rows,
internal execution children, and merged heads cannot be reintroduced by fallback. Public-entry RED/GREEN, direct callers,
documentation gates, independent specification review, and independent quality review have passed. Frontend graphics code is outside this phase.
Phase two A: first repair the topology projection used by graph layout:
Top-level Programs that point to the same ROOT must not be treated as retry branches replacing one another.
Top-level Programs without an explicit retry_of remain on the ROOT main lane in chronological order. Only
Programs with a persisted retry_of create a new fork lane, aligned with the run they replace.
The public entry point build_session_graph() verifies this rule, which must keep real-session lanes
continuous without empty horizontal columns caused by ordinary sequential runs.
Explicit exclusions for this step:node shapes, colors, badges, camera, expand/collapse, and CSS. Visual implementation follows topology GREEN.
Implementation evidence:build_session_graph() now projects ROOT Programs without retry_of
onto the same lane. The Retry entry point persists the source run ID as
metadata.retry_of; explicit retries retain a separate lane aligned with the source run.
Both specification re-review and quality re-review passed.
Color = branch identity; line style = connection type (independent dimensions):
Color always follows the branch—each lane has one color. A line uses the color of the branch it belongs to or points to.
Never use color to distinguish connection types (that would conflict with “color = branch”). Connection types use only line style (dash pattern / width):
━━ Same-branch connection: solid (parent → child, normal conversation/child call)
┄┄ Retry fork (manual user retry/rewrite): dashed 5 4
┄·┄ Model-initiated branch (spawn_branch): dash-dot 4 2 1 2
···· Inter-branch communication (send_to_branch): dotted 1 5 ——hidden by default; shown on hover (persistent display becomes cluttered with many communications)
═━ Merge input (merge): thick solid 2.4px
┈┈ Attach return (child-branch result embedded back): long dash 4 4
All line styles use the corresponding branch’s lane color. The same line style has different colors on different branches; different connection types on one branch share its color and differ by dash pattern.
Three global layout rules:
① Square grid: horizontal spacing = vertical spacing (COL_W = ROW_H). Every column and row uses identical spacing. A child is positioned strictly down and right of its parent—one column right and one row down (45° diagonal, distance √2 × grid spacing).
② Strict grid alignment: every node lies on a grid intersection. Nodes in a row share exactly the same y; nodes in a column share exactly the same x. Half-cell offsets are prohibited.
Compact in both dimensions: remove an empty column by shifting columns to its right leftward (see ③); remove an empty row by shifting lower rows upward (collapse a node → its row becomes empty → all lower rows shift upward together). Retain no empty rows or columns.
③ Place branches adjacent by actual column occupancy without overlap: a branch occupies columns from its starting column to its subtree’s deepest cell. The next branch starts at the previous branch’s actual rightmost occupied column + 1, entirely to its right, without overlap.
Collapsing shifts it left: collapsing child calls in the preceding branch reduces that branch’s occupied columns, so the next branch’s starting column shifts left. For example, expanded lane0 occupies columns 0–3 and lane1 starts at column 4; after collapsing tools, lane0 occupies only 0–2 and lane1 shifts left to column 3. Empty columns are never retained.