feat(ui): where the graph stops — the Flow strip's dynamic-dispatch end cap (CG-51)

A flow that does not reach what it was asked about now ends in a cap instead
of in silence: the dispatch form that ended it, the line, the static key when
the source spells one out, the candidate runtime targets as clickable rows,
and the name-only matches under 0.6 the search refused to follow. A flow that
does reach its destination never shows one.

The verdict is lifted out of `ToolHandler` into
`src/graph/dynamic-boundary-report.ts` and both callers render it —
`codegraph_explore`'s prose and `/api/flow`'s `WireFlowBoundary` — the same
move `named-symbol-flow.ts` made for the path finder, and for the same reason:
a reader holding the strip and the MCP answer must not be told two different
things. The explore prose is unchanged, byte for byte.

When nothing connects at all and a dispatch site explains why, the strip is
that site: one card opened at the line where the static path ends, plus the
cap. When nothing explains it, no stopping point is invented.
This commit is contained in:
Colby McHenry
2026-08-27 04:54:37 -05:00
parent ecd6e1cd15
commit dc7f1e590e
16 changed files with 1666 additions and 160 deletions
+39
View File
@@ -459,11 +459,50 @@ export interface WireFlowHop {
source: WireFlowSource | null;
}
/** One plausible runtime target of a keyed dispatch — a clickable cap row. */
export interface WireBoundaryCandidate {
node: WireNodeRef;
display: string;
named: boolean;
}
/** A dynamic-dispatch site: the form, the key when it is visible, the targets. */
export interface WireBoundarySite {
form: string;
label: string;
snippet: string;
line: number;
key: string | null;
keyIsType: boolean;
moreSites: number;
candidates: WireBoundaryCandidate[];
candidateNote: string | null;
}
export interface WireFlowContinuation {
node: WireNodeRef;
line: number | null;
confidence: number | null;
}
/** Where the graph stops — the strip's end cap (design spec §3.5). */
export interface WireFlowBoundary {
node: WireNodeRef;
sites: WireBoundarySite[];
uncertain: WireList<WireFlowContinuation>;
further: WireList<WireFlowContinuation>;
missed: WireNodeRef[];
}
export interface WireFlow {
id: string;
/** "execute → rowToFileRecord", for the header's flow picker. */
label: string;
hops: WireFlowHop[];
/** Null on a flow that reaches everything it was asked about. */
boundary: WireFlowBoundary | null;
/** One card at the dispatch site, not a path: the answer ran out here. */
partial: boolean;
}
export interface WireFlowAmbiguity {