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:

  1. Storage Level (Session DAG):

    • DSH enforces session-level isolation. Each branch is a distinct DSH session record with parentSession and seedLength.
    • The host maintains durable message-tree/version markers detailing which turn was edited/retried and what changed.
  2. Presentation Level (Turn-Level Branching Tree):

    • A user thinks of conversation branching at the message/turn level, not the session container level.
    • buildTurnTree projects 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:

  1. Root Conversation Node (${rootSessionId}#root): Represents the origin anchor of the conversation.
  2. Root Session Turns:
    • Turn 1 hangs off ${rootSessionId}#root.
    • Turn kk (k>1k > 1) hangs off ${rootSessionId}#t${k-1}.
  3. Forked Session Turns:
    • For a session branched at targetTurn = T:
      • If T=1T = 1: Turn 1 hangs off ${rootSessionId}#root (sibling of the original Turn 1).
      • If T>1T > 1: Turn TT hangs off ${parentSessionId}#t${T-1} (sibling of parent's Turn TT).
      • Subsequent turns T+1,T+2,T+1, T+2, \dots hang off the previous turn in the same session (${sessionId}#t${k-1}).
  4. Safety Fallback: Any node whose computed parentId does 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 \rightarrow Edit 1 \rightarrow Edit 2 while viewing Edit 1):

  • Without fan-out, edits form a chain: ABCA \rightarrow B \rightarrow C.
  • attachParentId traverses up versions of the same turn and stops at the first session that is not an edit of that turn (AA).
  • Result: Both Edit 1 and Edit 2 hang off AA 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/version marker describing the deleted parent).
  • ancestorChainFromLog inspects the surviving descendant's seed events to reconstruct deleted ancestors as ghost nodes (deleted: true).
  • collectFamily ensures 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:

  1. Locate the latest turn node in currentSessionId.
  2. Walk upwards following parentId pointers until reaching ${rootSessionId}#root.
  3. 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, returns null (counter is hidden).

3. Graph Layout & Springs

Location: plugin.client.js

  • Tidy Tree Layout (layoutTurnTree):
    • Leaf nodes take successive horizontal slots (cursor * SLOT_X, where SLOT_X = 206px).
    • Parent nodes center horizontally over their children ((min_x + max_x) / 2).
    • Depths scale vertically (depth * SLOT_Y, where SLOT_Y = 132px).
  • Spring Physics (springs.current):
    • Cards smoothly animate to their target coordinates using critically-damped spring equations (k=190,c=24k = 190, c = 24).
    • 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.