Dash session-tree navigation

Dash session-tree navigation

Dash Rich panes expose the complete persisted Pi conversation tree without flattening sibling branches into one false transcript. The active transcript continues to follow only Pi's authoritative current leaf; opening or filtering the tree is read-only until the controller explicitly chooses an action.

Projection and bounds

The browser requests stock Pi get_tree over its existing authenticated Rich channel. It validates the nested response before rendering:

The resulting model retains parent/children IDs, depth, branch points, labels, entry type/role/timestamp, user text eligible for edit-resubmit, and two separate facts: onActivePath and activeLeaf. A depth-first row array is only a virtualization index; it does not change conversation semantics.

Filters search bounded label/type/role/snippet text and can select labeled rows, branch points, entry types, or recent timestamps. Matching rows retain their ancestors so branch context stays visible. The 10,000-row acceptance fixture is prepared under 250 ms and the React viewport renders only visible rows.

Browser interaction

The tree is a keyboard-accessible virtual tree/treeitem surface:

Rows publish aria-level, selected state, expanded/child state, and exact active leaf state. Side-by-side comparison finds the common ancestor and renders only the two divergent paths. Search, labels, timestamps, branch counts, preview selection, and comparison are read-only and available to observers.

The pane can hand the same logical session to the canonical TUI presentation at any time. Shadow TUI remains the fidelity path for extension components and terminal interaction; Rich tree state never becomes a second JSONL writer.

Mutations and controller authority

Mutation buttons require the current Rich-channel controller and exact host/session/generation identity:

Fork, clone, and navigate use fresh idempotency keys per explicit click. A cancelled action leaves the tree unchanged. Successful actions reload get_tree; append/fork/switch events mark an open tree stale until refreshed. Disconnected or timed-out remote mutations are indeterminate and are never blindly replayed.

Framed in-place navigation extension

Pi's stock 31-command RPC union has get_tree, fork, and clone, but does not expose AgentSession.navigateTree(). Pi Daemon therefore keeps raw pi-rpc.v1 exact and adds one capability-gated frame only to pi-daemon-rpc.v1:

{
  "kind": "tree_navigate",
  "correlationId": "tree-navigation-01",
  "request": {
    "entryId": "entry-user-01",
    "summarize": true,
    "customInstructions": "Summarize abandoned work.",
    "label": "abandoned-review"
  }
}

The response is a private, non-replayed tree_navigate_result carrying either a bounded result or typed error. The attachment verifies controller role and in-flight capacity before calling the transport-neutral controller method. The method validates entry/instruction/label bounds, runs through the daemon-wide turn scheduler (summary generation can call a model), and returns only cancel/abort state, bounded editor text, and summary entry ID. The generation is already fixed by the authenticated attachment.

Neutral service capabilities advertise optional resources.treeNavigation. Remote backends add the Dashboard navigate_tree operation only when that bit is present, so an older compatible daemon continues to offer read-only tree, fork, and clone without receiving an unsupported frame. Browser controls follow the same negotiated operation list.

The frame schema and frozen examples are published in session-api.schema.json, rpc.tree-navigate.frame.json, and rpc.tree-navigate-result.frame.json.