()
| 135 | } |
| 136 | |
| 137 | export default function CodeGraphExplorer() { |
| 138 | // Canvas is the landing view: it self-populates with the default slice, |
| 139 | // so tab entry shows the graph immediately (Overview stays one click away). |
| 140 | const [view, setView] = useState<"overview" | "canvas">("canvas"); |
| 141 | const [overview, setOverview] = useState<GraphOverview | null>(null); |
| 142 | const [error, setError] = useState(""); |
| 143 | const [loading, setLoading] = useState(false); |
| 144 | |
| 145 | // Accumulated canvas graph (progressive exploration). |
| 146 | const [graphNodes, setGraphNodes] = useState<Map<string, GraphNode>>(new Map()); |
| 147 | const [graphEdges, setGraphEdges] = useState<Map<string, GraphEdge>>(new Map()); |
| 148 | const [focusId, setFocusId] = useState<string | null>(null); |
| 149 | const [fitSignal, setFitSignal] = useState(0); |
| 150 | const [history, setHistory] = useState<Array<{ id: string; name: string }>>([]); |
| 151 | |
| 152 | // Filters. |
| 153 | const [kindFilters, setKindFilters] = useState<Set<string>>(new Set()); |
| 154 | const [langFilters, setLangFilters] = useState<Set<string>>(new Set()); |
| 155 | const [dirScope, setDirScope] = useState(""); |
| 156 | |
| 157 | // Path-finding mode. |
| 158 | const [pathMode, setPathMode] = useState(false); |
| 159 | const [pathFrom, setPathFrom] = useState<GraphNode | null>(null); |
| 160 | const [pathTo, setPathTo] = useState<GraphNode | null>(null); |
| 161 | const [pathResult, setPathResult] = useState<GraphPathResponse | null>(null); |
| 162 | |
| 163 | // True while the canvas shows the untouched seedless default view; the |
| 164 | // first search-to-focus replaces it instead of merging into it. |
| 165 | const defaultViewRef = useRef(false); |
| 166 | // Invalidates an in-flight default load once the user takes over the canvas. |
| 167 | const defaultSeq = useRef(makeSequence()).current; |
| 168 | |
| 169 | const { |
| 170 | selected, |
| 171 | neighbors, |
| 172 | setSelected, |
| 173 | setNeighbors, |
| 174 | inspect, |
| 175 | } = useGraphInspection({ |
| 176 | loadNode: api.node, |
| 177 | loadNeighbors: (id: string) => api.neighbors(id, { limit: 60 }), |
| 178 | onError: setError, |
| 179 | }); |
| 180 | |
| 181 | useEffect(() => { |
| 182 | api.overview().then(setOverview).catch((err) => setError(String(err))); |
| 183 | }, []); |
| 184 | |
| 185 | const mergeGraph = useCallback((nodes: GraphNode[], edges: GraphEdge[]) => { |
| 186 | setGraphNodes((prev) => mergeNodesInto(prev, nodes)); |
| 187 | setGraphEdges((prev) => mergeEdgesInto(prev, edges)); |
| 188 | }, []); |
| 189 | |
| 190 | /** Loads the seedless default slice (top-connected hubs + their edges). */ |
| 191 | const loadDefaultView = useCallback(async () => { |
| 192 | const ticket = defaultSeq.next(); |
| 193 | setLoading(true); |
| 194 | setError(""); |
nothing calls this directly
no test coverage detected