How BranchNavigator Switches Between Leaf Contexts in a Pi‑Web Session
BranchNavigator switches leaf contexts through a callback-driven flow where UI selection triggers an RPC navigate_tree command that updates the session's cursor on the backend.
The BranchNavigator component in agegr/pi-web enables users to jump between any branch tip (leaf) within an active Pi‑Web session. Understanding this mechanism requires tracing the data flow from UI interaction through React hooks to the RPC layer that coordinates session state.
The Two-Step Leaf Switching Flow
BranchNavigator does not directly manipulate session state. Instead, it delegates context switching through a decoupled event flow:
- UI captures the selection — Clicking a tree node extracts the target leaf's entry ID
- Hook issues the command —
useAgentSessiondetects the ID change and sends a backend command - Backend updates the cursor — The RPC manager repositions the session and returns fresh context
This separation keeps the navigator component presentational while centralizing session logic in the hook layer.
Step 1: Capturing Leaf Selection in BranchNavigator
The tree rendering logic in components/BranchNavigator.tsx handles user clicks by bubbling the selected leaf's ID upward through the onLeafChange callback prop【BranchNavigator.tsx lines 76-78】.
// components/BranchNavigator.tsx – Tree node click handling
function TreeNodeView({ node, ..., onSelect }: TreeNodeProps) {
...
onClick={() => onSelect(rep.entry.id)} // ← leaf ID extracted from entry
}
...
const handleSelect = useCallback((id: string) => {
onLeafChange(id); // Propagate to parent component
}, [onLeafChange]);
The onLeafChange prop is the sole output contract for this component. It receives a string ID representing any leaf in the branch tree, making the navigator agnostic to what happens after selection.
Step 2: Reacting to Leaf Changes in useAgentSession
The session management hook hooks/useAgentSession.ts wires the leaf change to an actual session transition. It tracks activeLeafId in state and uses a useEffect to trigger the backend command whenever this value changes【useAgentSession.ts lines 1443 and 1455】.
// hooks/useAgentSession.ts – Leaf change side effect
useEffect(() => {
// When the active leaf changes, tell the agent to jump there
sendAgentCommand(sessionId, {
type: "navigate_tree",
targetId: leafId, // leaf ID originating from BranchNavigator
}).catch(() => {});
}, [sessionId, leafId]);
The navigate_tree command type is specific to leaf navigation. The hook's dependency array ensures the command fires precisely when either the session or target leaf changes, preventing redundant calls.
Step 3: Backend Command Processing
The RPC manager at lib/rpc-manager.ts handles the navigate_tree command by:
- Validating the target leaf ID exists within the session's branch tree
- Updating the internal session cursor to reference the new entry
- Returning the complete context state for the selected leaf
This backend transition is what actually switches the conversation context. The UI receives the response and re-renders the chat view with messages and state relative to the new leaf position.
Key Architectural Decisions
The BranchNavigator's leaf switching design reflects several intentional patterns in agegr/pi-web:
| Pattern | Implementation | Benefit |
|---|---|---|
| Callback props | onLeafChange output only |
Component reusability across different session types |
| Effect-driven commands | useEffect + sendAgentCommand |
Automatic synchronization without manual trigger |
| Command pattern | navigate_tree RPC type |
Extensible backend protocol for navigation operations |
| Optimistic UI | Immediate state update, error catch silent | Responsive feel without blocking on network |
Summary
- BranchNavigator emits leaf selections via the
onLeafChangecallback prop incomponents/BranchNavigator.tsx - useAgentSession listens for
activeLeafIdchanges and sendstype: "navigate_tree"commands viasendAgentCommandinhooks/useAgentSession.ts - lib/rpc-manager.ts processes the command, updates the session cursor, and returns context that drives the UI re-render
- The decoupled architecture separates presentation (tree UI) from session logic (hook) from state mutation (backend)
Frequently Asked Questions
What is a "leaf" in Pi‑Web's BranchNavigator?
A leaf is the terminal node at the tip of a branch in Pi‑Web's conversation tree. Each leaf represents a distinct point in the dialogue where the user can continue the conversation from that specific context. The BranchNavigator visualizes the entire tree structure but only emits leaf IDs for selectable endpoints.
Why does BranchNavigator use a callback instead of directly calling sendAgentCommand?
The callback pattern keeps components/BranchNavigator.tsx presentational and framework-agnostic. By accepting onLeafChange as a prop, the same component can work with different session implementations—React hooks, direct API calls, or even test mocks—without internal code changes. This separation of concerns follows the container/presenter pattern common in React applications.
What happens if the navigate_tree command fails?
The useEffect in hooks/useAgentSession.ts includes a .catch(() => {}) handler that silently suppresses errors【line 1455】. This optimistic approach assumes the command succeeds while allowing the UI to remain responsive. In production, you may want to add error handling to revert the activeLeafId state or display a notification to the user.
Can BranchNavigator switch to non-leaf nodes?
Based on the implementation, onSelect specifically passes rep.entry.id from leaf representations. The navigate_tree command and the backend cursor update are designed for leaf destinations. Internal branch nodes serve structural purposes in the tree UI but do not appear to be valid targets for session context switching in the current agegr/pi-web architecture.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →