DocumentsLobeHub
Branching Conversation Navigation
Branching Conversation Navigation
Type
Topic
Status
Published
Created
Jul 7, 2026
Updated
Jul 7, 2026

Branching Conversation Navigation#

The conversation-flow package maintains messages as a tree (each message has a parentId), but the UI renders a single linear list. Branching navigation is the mechanism by which one active path is selected from that tree and projected into a flat, ordered sequence.

Two classes collaborate to do this:

  • BranchResolver — pure logic for picking which child is the "active branch" at any fork.
  • FlatListBuilder — recursive tree traversal that consumes BranchResolver to produce the flat rendering list.

A parallel output, ContextTreeBuilder, uses BranchResolver to produce a semantic context tree (with explicit BranchNode entries) used for navigation UI rather than rendering.

Both builders are wired together by the Transformer class in transformation/index.ts and invoked from the top-level parse.ts entry point.


The activeBranchIndex Metadata Field#

metadata.activeBranchIndex is the primary signal stored on a parent message to declare which of its children is the currently visible branch. It is a zero-based integer index into the ordered list of child IDs.

BranchResolver applies a three-priority fallback strategy when resolving the active branch :

PriorityStrategy
1Read message.metadata.activeBranchIndex; use if in-bounds
2Infer: pick the first child that itself has descendants
3Default to the first child (childIds[0])

A special optimistic-update case: when activeBranchIndex === childIds.length, the branch is being created but doesn't exist yet — BranchResolver returns undefined and the caller omits branch navigation UI .

BranchResolver exposes two method variants with identical logic:


How FlatListBuilder Selects the Active Path#

FlatListBuilder.flatten() starts from the root (messages with no parentId) and calls buildFlatListRecursive(), which walks the tree depth-first. At each fork it calls BranchResolver to pick one child and recurses only into that branch.

Two branching cases are handled:

User message with multiple assistant children : BranchResolver.getActiveBranchIdFromMetadata selects the active assistant. The selected message is emitted with a branch annotation { count, activeBranchIndex } attached, so the UI can render branch-switching controls.

Assistant message with multiple user children : Same pattern — BranchResolver picks the active user reply, which is emitted with branch metadata.

In both cases, tool-message children are excluded from branch counting because they are inline data of their parent assistant, not sibling branches .


How ContextTreeBuilder Produces a BranchNode#

ContextTreeBuilder.createBranchNode() calls BranchResolver.getActiveBranchId, then:

  1. Records the resolved activeBranchIndex on the BranchNode.
  2. Recursively builds all branches as full subtrees (not just the active one) — all branches exist in the context tree; only activeBranchIndex flags which is currently selected.

This gives navigation UI a complete picture of every available path while still marking one as active.


Key Files#

FileRole
BranchResolver.tsBranch selection logic (three-priority strategy + optimistic updates)
FlatListBuilder.tsFlat rendering list; consumes BranchResolver at branching points
ContextTreeBuilder.tsSemantic context tree; emits BranchNode with full branch subtrees
types/contextTree.tsBranchNode type definition (activeBranchIndex, branches)
transformation/index.ts Wires all builders together via Transformer
parse.ts Top-level entry point; calls transformAll + flatten