Tree Data Model & Algorithms
August 22, 2026 · View on GitHub
This document details how conversation versions and turns are represented, branched, and visualized in dsh-plugin-message-edit.
1. Dual-Level Representation
There are two distinct levels of data representation in the system:
-
Storage Level (Session DAG):
- DSH enforces session-level isolation. Each branch is a distinct DSH session record with
parentSessionandseedLength. - The host maintains durable
message-tree/versionmarkers detailing which turn was edited/retried and what changed.
- DSH enforces session-level isolation. Each branch is a distinct DSH session record with
-
Presentation Level (Turn-Level Branching Tree):
- A user thinks of conversation branching at the message/turn level, not the session container level.
buildTurnTreeprojects the session versions into individual turn nodes.
Session DAG (Storage):
Session A (Original) ──[edit turn 1]──> Session B (Fork)
Turn Tree (Visualization):
[Root Conversation]
/ \
[A: Turn 1 (1/2)] [B: Turn 1 (2/2) - Edited]
| |
[A: Turn 2] [B: Turn 2]
2. Core Algorithms
2.1 Turn Tree Construction (buildTurnTree)
Location: lib/tree-logic.js, plugin.client.js
Transforms versions into an array of turn nodes:
- Root Conversation Node (
${rootSessionId}#root): Represents the origin anchor of the conversation. - Root Session Turns:
- Turn 1 hangs off
${rootSessionId}#root. - Turn () hangs off
${rootSessionId}#t${k-1}.
- Turn 1 hangs off
- Forked Session Turns:
- For a session branched at
targetTurn = T:- If : Turn 1 hangs off
${rootSessionId}#root(sibling of the original Turn 1). - If : Turn hangs off
${parentSessionId}#t${T-1}(sibling of parent's Turn ). - Subsequent turns hang off the previous turn in the same session (
${sessionId}#t${k-1}).
- If : Turn 1 hangs off
- For a session branched at
- Safety Fallback: Any node whose computed
parentIddoes not exist in the graph is automatically attached to${rootSessionId}#root, preventing disconnected subtrees.
2.2 Sibling Fan-Out (attachParentId)
Location: lib/tree-logic.js
When a user edits Turn 1 repeatedly (e.g. Turn 1 Edit 1 Edit 2 while viewing Edit 1):
- Without fan-out, edits form a chain: .
attachParentIdtraverses up versions of the same turn and stops at the first session that is not an edit of that turn ().- Result: Both Edit 1 and Edit 2 hang off as sibling branches.
2.3 Ghost Ancestor Recovery (ancestorChainFromLog & collectFamily)
Location: lib/tree-logic.js
If an intermediate session in a family is deleted by the user in DSH:
- The deleted session's own event log is gone.
- However, its descendant sessions inherited its prefix log (including the
message-tree/versionmarker describing the deleted parent). ancestorChainFromLoginspects the surviving descendant's seed events to reconstruct deleted ancestors as ghost nodes (deleted: true).collectFamilyensures the family graph remains fully connected even when intermediate nodes are deleted.
2.4 Active Path Calculation
Location: lib/tree-logic.js
To highlight only the active branch path without highlighting superseded sibling branches:
- Locate the latest turn node in
currentSessionId. - Walk upwards following
parentIdpointers until reaching${rootSessionId}#root. - Mark only nodes on this walk with
onCurrentPath = true.
2.5 Bubble Version Ring (ringFor)
Location: lib/tree-logic.js
Calculates the ‹ n/m › counter under a message at turn while viewing sessionId:
- Walks parent links to find the common fork point for that turn.
- Filters out deleted/ghost sessions (renumbering over surviving versions).
- Returns
{ alternatives, index }. If fewer than 2 alternatives exist, returnsnull(counter is hidden).
3. Graph Layout & Springs
Location: plugin.client.js
- Tidy Tree Layout (
layoutTurnTree):- Leaf nodes take successive horizontal slots (
cursor * SLOT_X, whereSLOT_X = 206px). - Parent nodes center horizontally over their children (
(min_x + max_x) / 2). - Depths scale vertically (
depth * SLOT_Y, whereSLOT_Y = 132px).
- Leaf nodes take successive horizontal slots (
- Spring Physics (
springs.current):- Cards smoothly animate to their target coordinates using critically-damped spring equations ().
- New cards spawn at their parent's coordinates and spring outward.
- Edges are rendered as cubic SVG bezier curves connecting parent card bottoms to child card tops.