diff --git a/docs/plans/2026-02-28-cross-reference-loopback-design.md b/docs/plans/2026-02-28-cross-reference-loopback-design.md new file mode 100644 index 00000000..98e6be32 --- /dev/null +++ b/docs/plans/2026-02-28-cross-reference-loopback-design.md @@ -0,0 +1,80 @@ +# Cross-Reference / Loop-Back Support — Design + +**Goal:** Allow tree nodes to reference any other node in the tree (not just direct children), enabling loop-back patterns like "remediate → re-verify from earlier checkpoint." + +**Architecture:** Ghost references on existing tree structure. No schema change, no migration. A cross-reference is any `next_node_id` that points outside the current node's `children` array. The canvas renders these as dashed SVG overlay arrows. Navigation already supports this. + +**Approach chosen:** Approach 1 — "Ghost references" (keep tree structure, add visual cross-ref edges) + +--- + +## 1. Data Model — No Changes + +The `TreeStructure` type and database stay as-is. The distinction is semantic: + +- **Local link:** `next_node_id` → direct child → normal tree edge +- **Cross-reference:** `next_node_id` → node elsewhere in tree → dashed overlay arrow + +No new fields, no new node types, no migration. + +## 2. Validation Changes + +### Backend (`ai_tree_validator.py`) + +- Relax decision option validation: `option.next_node_id` can reference any node in the tree (not just children). Check existence only, same as action nodes. + +### Frontend circular reference detector (`treeEditorStore.ts`) + +- Change loop detection from **error** to **warning**. Loops are now intentional. Warning text: "This path loops back to [node title]." + +### Frontend orphan detection + +- Keep as-is. Orphaned nodes still flagged as warnings. + +## 3. Canvas Rendering — Cross-Reference Edges + +- **SVG overlay** layer on top of the canvas (absolute positioned) +- **Dashed line** with **arrowhead** pointing at target node +- **Purple/primary color** to distinguish from normal gray tree connectors +- Small label on the arrow (option label or "loops back") +- After dagre layout, scan all nodes for `next_node_id` values not matching a direct child +- Look up source/target positions from layout, draw curved SVG bezier path +- Target node gets a subtle badge/indicator for inbound cross-references +- Hovering the badge highlights source nodes + +## 4. Editor UX — Creating Cross-References + +### A. Node picker dropdown (in node form) + +- Action nodes and decision option rows get "Link to existing node" dropdown +- Lists all nodes by title/question, grouped by type +- Selecting sets `next_node_id`; orphaned answer stubs cleaned up +- "Clear link" option to remove + +### B. Canvas drag-to-link + +- Small output port (dot) at bottom of each node +- Drag from port starts a dashed line following cursor +- Drop on any node creates cross-reference +- Drop on empty space cancels +- Existing answer stubs cleaned up if replaced + +### Visual feedback + +- Node form: "Linked to: [node title]" with navigate + remove actions +- Canvas: dashed arrow (Section 3) + +## 5. AI Flow Assist — Prompt Changes + +- Update system prompt STRUCTURAL RULES: "Action nodes can set `next_node_id` to any node in the tree, including ancestors, for loop-backs." +- Add SSH loop example to schema context +- No changes to generation or progressive validation + +## 6. Navigation — No Changes + +`findNode` already searches the full tree. `handleSelectOption` and `handleContinue` follow `next_node_id` without hierarchy checks. Session `pathTaken` will contain repeated IDs for loops — this is correct behavior. + +## 7. Testing + +- Backend: extend validator tests for cross-references +- Frontend: `npm run build` after each piece, manual testing of editor + navigation loops