create-project-arch 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +536 -43
  4. package/package.json +27 -3
  5. package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
  6. package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
  7. package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
  8. package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
  9. package/templates/arch-ui/.arch/graph.json +0 -1
  10. package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
  11. package/templates/arch-ui/.arch/nodes/domains.json +0 -1
  12. package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
  13. package/templates/arch-ui/.arch/nodes/modules.json +0 -1
  14. package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
  15. package/templates/arch-ui/app/api/health/route.ts +5 -4
  16. package/templates/arch-ui/app/api/node-files/route.ts +6 -1
  17. package/templates/arch-ui/app/api/search/route.ts +0 -1
  18. package/templates/arch-ui/app/work/page.tsx +94 -64
  19. package/templates/arch-ui/components/app-shell.tsx +1 -7
  20. package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
  21. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
  22. package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
  23. package/templates/arch-ui/components/graph/graph-types.ts +49 -49
  24. package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
  25. package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
  26. package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
  27. package/templates/arch-ui/components/graph-canvas.tsx +90 -74
  28. package/templates/arch-ui/components/inspector.tsx +56 -22
  29. package/templates/arch-ui/components/sidebar.tsx +18 -8
  30. package/templates/arch-ui/components/topbar.tsx +8 -11
  31. package/templates/arch-ui/components/work-table.tsx +1 -5
  32. package/templates/arch-ui/components/workspace-context.tsx +2 -1
  33. package/templates/arch-ui/lib/graph-dataset.ts +4 -8
  34. package/templates/arch-ui/lib/graph-schema.ts +1 -4
  35. package/templates/arch-ui/package.json +0 -1
  36. package/templates/arch-ui/tsconfig.json +3 -11
@@ -4,62 +4,62 @@ import type { Edge, Node, ReactFlowInstance } from "reactflow";
4
4
  import { ArchNodeData, NODE_HEIGHT, NODE_WIDTH } from "./graph-types";
5
5
 
6
6
  export function useAutoLayout(
7
- setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
8
- edges: Edge[],
9
- hiddenNodeIds: Set<string>,
10
- flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
7
+ setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
8
+ edges: Edge[],
9
+ hiddenNodeIds: Set<string>,
10
+ flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
11
11
  ) {
12
- return useCallback(() => {
13
- const currentViewport = flowInstance?.getViewport();
12
+ return useCallback(() => {
13
+ const currentViewport = flowInstance?.getViewport();
14
14
 
15
- setNodes((currentNodes) => {
16
- const graph = new dagre.graphlib.Graph();
17
- graph.setDefaultEdgeLabel(() => ({}));
18
- graph.setGraph({
19
- rankdir: "LR",
20
- nodesep: 40,
21
- ranksep: 100,
22
- marginx: 20,
23
- marginy: 20,
24
- });
15
+ setNodes((currentNodes) => {
16
+ const graph = new dagre.graphlib.Graph();
17
+ graph.setDefaultEdgeLabel(() => ({}));
18
+ graph.setGraph({
19
+ rankdir: "LR",
20
+ nodesep: 40,
21
+ ranksep: 100,
22
+ marginx: 20,
23
+ marginy: 20,
24
+ });
25
25
 
26
- const visibleNodeSet = new Set(
27
- currentNodes.filter((node) => !hiddenNodeIds.has(node.id)).map((node) => node.id),
28
- );
29
- currentNodes
30
- .filter((node) => visibleNodeSet.has(node.id))
31
- .forEach((node) => {
32
- graph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
33
- });
26
+ const visibleNodeSet = new Set(
27
+ currentNodes.filter((node) => !hiddenNodeIds.has(node.id)).map((node) => node.id),
28
+ );
29
+ currentNodes
30
+ .filter((node) => visibleNodeSet.has(node.id))
31
+ .forEach((node) => {
32
+ graph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
33
+ });
34
34
 
35
- edges
36
- .filter((edge) => visibleNodeSet.has(edge.source) && visibleNodeSet.has(edge.target))
37
- .forEach((edge) => {
38
- graph.setEdge(edge.source, edge.target);
39
- });
35
+ edges
36
+ .filter((edge) => visibleNodeSet.has(edge.source) && visibleNodeSet.has(edge.target))
37
+ .forEach((edge) => {
38
+ graph.setEdge(edge.source, edge.target);
39
+ });
40
40
 
41
- dagre.layout(graph);
41
+ dagre.layout(graph);
42
42
 
43
- return currentNodes.map((node) => {
44
- if (!visibleNodeSet.has(node.id)) {
45
- return node;
46
- }
47
- const position = graph.node(node.id) as { x: number; y: number } | undefined;
48
- if (!position) {
49
- return node;
50
- }
51
- return {
52
- ...node,
53
- position: {
54
- x: position.x - NODE_WIDTH / 2,
55
- y: position.y - NODE_HEIGHT / 2,
56
- },
57
- };
58
- });
59
- });
60
- if (flowInstance && currentViewport) {
61
- // Preserve user zoom/pan while applying new node positions.
62
- flowInstance.setViewport(currentViewport, { duration: 0 });
43
+ return currentNodes.map((node) => {
44
+ if (!visibleNodeSet.has(node.id)) {
45
+ return node;
46
+ }
47
+ const position = graph.node(node.id) as { x: number; y: number } | undefined;
48
+ if (!position) {
49
+ return node;
63
50
  }
64
- }, [edges, flowInstance, hiddenNodeIds, setNodes]);
51
+ return {
52
+ ...node,
53
+ position: {
54
+ x: position.x - NODE_WIDTH / 2,
55
+ y: position.y - NODE_HEIGHT / 2,
56
+ },
57
+ };
58
+ });
59
+ });
60
+ if (flowInstance && currentViewport) {
61
+ // Preserve user zoom/pan while applying new node positions.
62
+ flowInstance.setViewport(currentViewport, { duration: 0 });
63
+ }
64
+ }, [edges, flowInstance, hiddenNodeIds, setNodes]);
65
65
  }
@@ -2,61 +2,61 @@ import { addEdge, Connection, Edge, getOutgoers, Node } from "reactflow";
2
2
  import type { ArchNodeData, GraphKind } from "./graph-types";
3
3
 
4
4
  function nodeAllowsOutgoing(kind: GraphKind): GraphKind[] {
5
- switch (kind) {
6
- case "decision":
7
- return ["domain"];
8
- case "phase":
9
- return ["milestone"];
10
- case "milestone":
11
- return ["task"];
12
- case "task":
13
- return ["file", "decision"];
14
- case "domain":
15
- return ["file"];
16
- case "file":
17
- default:
18
- return [];
19
- }
5
+ switch (kind) {
6
+ case "decision":
7
+ return ["domain"];
8
+ case "phase":
9
+ return ["milestone"];
10
+ case "milestone":
11
+ return ["task"];
12
+ case "task":
13
+ return ["file", "decision"];
14
+ case "domain":
15
+ return ["file"];
16
+ case "file":
17
+ default:
18
+ return [];
19
+ }
20
20
  }
21
21
 
22
22
  export function isValidArchitectureConnection(
23
- connection: Connection,
24
- nodes: Node<ArchNodeData>[],
25
- edges: Edge[],
23
+ connection: Connection,
24
+ nodes: Node<ArchNodeData>[],
25
+ edges: Edge[],
26
26
  ) {
27
- if (!connection.source || !connection.target || connection.source === connection.target) {
28
- return false;
29
- }
27
+ if (!connection.source || !connection.target || connection.source === connection.target) {
28
+ return false;
29
+ }
30
30
 
31
- const sourceNode = nodes.find((node) => node.id === connection.source);
32
- const targetNode = nodes.find((node) => node.id === connection.target);
33
- if (!sourceNode || !targetNode) {
34
- return false;
35
- }
31
+ const sourceNode = nodes.find((node) => node.id === connection.source);
32
+ const targetNode = nodes.find((node) => node.id === connection.target);
33
+ if (!sourceNode || !targetNode) {
34
+ return false;
35
+ }
36
36
 
37
- const allowedTargets = nodeAllowsOutgoing(sourceNode.data.kind);
38
- if (!allowedTargets.includes(targetNode.data.kind)) {
39
- return false;
40
- }
37
+ const allowedTargets = nodeAllowsOutgoing(sourceNode.data.kind);
38
+ if (!allowedTargets.includes(targetNode.data.kind)) {
39
+ return false;
40
+ }
41
41
 
42
- const nextEdges = addEdge({ ...connection, type: "smoothstep" }, edges);
43
- const hasCycle = (node: Node<ArchNodeData>, visited = new Set<string>()): boolean => {
44
- if (visited.has(node.id)) {
45
- return false;
46
- }
47
- visited.add(node.id);
42
+ const nextEdges = addEdge({ ...connection, type: "smoothstep" }, edges);
43
+ const hasCycle = (node: Node<ArchNodeData>, visited = new Set<string>()): boolean => {
44
+ if (visited.has(node.id)) {
45
+ return false;
46
+ }
47
+ visited.add(node.id);
48
48
 
49
- const outgoers = getOutgoers(node, nodes, nextEdges);
50
- for (const outgoer of outgoers) {
51
- if (outgoer.id === connection.source) {
52
- return true;
53
- }
54
- if (hasCycle(outgoer as Node<ArchNodeData>, visited)) {
55
- return true;
56
- }
57
- }
58
- return false;
59
- };
49
+ const outgoers = getOutgoers(node, nodes, nextEdges);
50
+ for (const outgoer of outgoers) {
51
+ if (outgoer.id === connection.source) {
52
+ return true;
53
+ }
54
+ if (hasCycle(outgoer as Node<ArchNodeData>, visited)) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ };
60
60
 
61
- return !hasCycle(targetNode);
61
+ return !hasCycle(targetNode);
62
62
  }
@@ -3,46 +3,46 @@ import type { Edge, Node, ReactFlowInstance } from "reactflow";
3
3
  import { ArchNodeData } from "./graph-types";
4
4
 
5
5
  export function useFlowPersistence(
6
- setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
7
- setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
8
- setHiddenNodeIds: React.Dispatch<React.SetStateAction<Set<string>>>,
9
- flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
10
- storageKey: string,
6
+ setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
7
+ setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
8
+ setHiddenNodeIds: React.Dispatch<React.SetStateAction<Set<string>>>,
9
+ flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
10
+ storageKey: string,
11
11
  ) {
12
- const saveFlow = useCallback(() => {
13
- if (typeof window === "undefined" || !flowInstance) return;
14
- const flow = flowInstance.toObject();
15
- window.localStorage.setItem(
16
- storageKey,
17
- JSON.stringify({
18
- nodes: flow.nodes,
19
- edges: flow.edges,
20
- viewport: flow.viewport,
21
- }),
22
- );
23
- }, [flowInstance, storageKey]);
12
+ const saveFlow = useCallback(() => {
13
+ if (typeof window === "undefined" || !flowInstance) return;
14
+ const flow = flowInstance.toObject();
15
+ window.localStorage.setItem(
16
+ storageKey,
17
+ JSON.stringify({
18
+ nodes: flow.nodes,
19
+ edges: flow.edges,
20
+ viewport: flow.viewport,
21
+ }),
22
+ );
23
+ }, [flowInstance, storageKey]);
24
24
 
25
- const restoreFlow = useCallback(() => {
26
- if (typeof window === "undefined") return;
27
- const payload = window.localStorage.getItem(storageKey);
28
- if (!payload) return;
25
+ const restoreFlow = useCallback(() => {
26
+ if (typeof window === "undefined") return;
27
+ const payload = window.localStorage.getItem(storageKey);
28
+ if (!payload) return;
29
29
 
30
- try {
31
- const parsed = JSON.parse(payload) as {
32
- nodes: Node<ArchNodeData>[];
33
- edges: Edge[];
34
- viewport?: { x: number; y: number; zoom: number };
35
- };
36
- setNodes(parsed.nodes);
37
- setEdges(parsed.edges);
38
- if (parsed.viewport && flowInstance) {
39
- flowInstance.setViewport(parsed.viewport);
40
- }
41
- setHiddenNodeIds(new Set());
42
- } catch {
43
- // Ignore invalid state.
44
- }
45
- }, [flowInstance, setEdges, setNodes, setHiddenNodeIds, storageKey]);
30
+ try {
31
+ const parsed = JSON.parse(payload) as {
32
+ nodes: Node<ArchNodeData>[];
33
+ edges: Edge[];
34
+ viewport?: { x: number; y: number; zoom: number };
35
+ };
36
+ setNodes(parsed.nodes);
37
+ setEdges(parsed.edges);
38
+ if (parsed.viewport && flowInstance) {
39
+ flowInstance.setViewport(parsed.viewport);
40
+ }
41
+ setHiddenNodeIds(new Set());
42
+ } catch {
43
+ // Ignore invalid state.
44
+ }
45
+ }, [flowInstance, setEdges, setNodes, setHiddenNodeIds, storageKey]);
46
46
 
47
- return { saveFlow, restoreFlow };
47
+ return { saveFlow, restoreFlow };
48
48
  }
@@ -85,17 +85,16 @@ export function GraphCanvas({
85
85
  );
86
86
  const storageKey = `arch:graph:view:${viewMode}:v1`;
87
87
 
88
- const initialGraph = useMemo(
89
- () => buildGraphFromDataset(data, viewMode),
90
- [data, viewMode],
91
- );
88
+ const initialGraph = useMemo(() => buildGraphFromDataset(data, viewMode), [data, viewMode]);
92
89
 
93
90
  const [nodes, setNodes, onNodesChange] = useNodesState<ArchNodeData>(initialGraph.nodes);
94
91
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialGraph.edges);
95
92
 
96
93
  useEffect(() => {
97
94
  setNodes((currentNodes) => {
98
- const currentPositions = new Map(currentNodes.map((node) => [node.id, node.position] as const));
95
+ const currentPositions = new Map(
96
+ currentNodes.map((node) => [node.id, node.position] as const),
97
+ );
99
98
  return initialGraph.nodes.map((node) => {
100
99
  const remembered = rememberedPositions.current.get(node.id);
101
100
  const current = currentPositions.get(node.id);
@@ -174,10 +173,7 @@ export function GraphCanvas({
174
173
  if (hiddenNodeIds.has(node.id)) return false;
175
174
  if (!viewKinds[viewMode].includes(node.data.kind)) return false;
176
175
  const typeLabel = (node.data.canonicalType ?? "").toLowerCase();
177
- if (
178
- typeLabel.includes("arch_folder") &&
179
- !enabledFilters.includes("domains")
180
- ) {
176
+ if (typeLabel.includes("arch_folder") && !enabledFilters.includes("domains")) {
181
177
  return false;
182
178
  }
183
179
  if (
@@ -192,10 +188,7 @@ export function GraphCanvas({
192
188
  if (typeLabel.includes("roadmap_") && !enabledFilters.includes("tasks")) {
193
189
  return false;
194
190
  }
195
- if (
196
- typeLabel.includes("project_folder") &&
197
- !enabledFilters.includes("modules")
198
- ) {
191
+ if (typeLabel.includes("project_folder") && !enabledFilters.includes("modules")) {
199
192
  return false;
200
193
  }
201
194
  if (
@@ -345,71 +338,92 @@ export function GraphCanvas({
345
338
  if (!inScope) return false;
346
339
  return matchesSearch(node);
347
340
  });
348
- }, [edges, enabledFilters, hideCompletedTasks, hiddenNodeIds, neighborhoodIds, nodes, pinnedNodeIds, searchQuery, selectedNodeId, showExternalDependencies, viewMode]);
349
- const visibleEdges = useMemo(
350
- () => {
351
- const allowedEdgeTypes = new Set(enabledEdgeFilters);
352
- const allowedAuthorities = new Set(enabledAuthorityFilters);
353
- const baseEdges = edges.filter(
354
- (edge) =>
355
- !hiddenNodeIds.has(edge.source) &&
356
- !hiddenNodeIds.has(edge.target) &&
357
- allowedEdgeTypes.has((edge.data?.edgeType as GraphEdgeFilter | undefined) ?? "dependency") &&
358
- allowedAuthorities.has(
359
- (edge.data?.authority as GraphEdgeAuthority | undefined) ?? "authoritative",
360
- ),
361
- );
362
- const precedence = new Map<string, number>([
363
- ["authoritative", 3],
364
- ["manual", 2],
365
- ["inferred", 1],
366
- ]);
367
- const dedupedByRelation = new Map<string, Edge>();
368
- baseEdges.forEach((edge) => {
369
- const key = `${edge.source}|${edge.target}|${edge.data?.edgeType ?? "dependency"}`;
370
- const incomingScore = precedence.get(edge.data?.authority ?? "authoritative") ?? 0;
371
- const existing = dedupedByRelation.get(key);
372
- if (!existing) {
373
- dedupedByRelation.set(key, edge);
374
- return;
375
- }
376
- const existingScore = precedence.get(existing.data?.authority ?? "authoritative") ?? 0;
377
- if (incomingScore > existingScore) {
378
- dedupedByRelation.set(key, edge);
379
- }
380
- });
381
- const resolvedEdges = [...dedupedByRelation.values()];
382
- if (!selectedNodeId) {
383
- if (viewMode === "project" && searchQuery.trim().length === 0) {
384
- const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
385
- return resolvedEdges.filter(
386
- (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
387
- );
388
- }
389
- if (viewMode === "architecture-map" && searchQuery.trim().length === 0) {
390
- const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
391
- return resolvedEdges.filter(
392
- (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
393
- );
394
- }
395
- if (viewMode === "tasks" && searchQuery.trim().length === 0) {
396
- const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
397
- return resolvedEdges.filter(
398
- (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
399
- );
400
- }
401
- if (pinnedNodeIds.size === 0) return [];
341
+ }, [
342
+ edges,
343
+ enabledFilters,
344
+ hideCompletedTasks,
345
+ hiddenNodeIds,
346
+ neighborhoodIds,
347
+ nodes,
348
+ pinnedNodeIds,
349
+ searchQuery,
350
+ selectedNodeId,
351
+ showExternalDependencies,
352
+ viewMode,
353
+ ]);
354
+ const visibleEdges = useMemo(() => {
355
+ const allowedEdgeTypes = new Set(enabledEdgeFilters);
356
+ const allowedAuthorities = new Set(enabledAuthorityFilters);
357
+ const baseEdges = edges.filter(
358
+ (edge) =>
359
+ !hiddenNodeIds.has(edge.source) &&
360
+ !hiddenNodeIds.has(edge.target) &&
361
+ allowedEdgeTypes.has(
362
+ (edge.data?.edgeType as GraphEdgeFilter | undefined) ?? "dependency",
363
+ ) &&
364
+ allowedAuthorities.has(
365
+ (edge.data?.authority as GraphEdgeAuthority | undefined) ?? "authoritative",
366
+ ),
367
+ );
368
+ const precedence = new Map<string, number>([
369
+ ["authoritative", 3],
370
+ ["manual", 2],
371
+ ["inferred", 1],
372
+ ]);
373
+ const dedupedByRelation = new Map<string, Edge>();
374
+ baseEdges.forEach((edge) => {
375
+ const key = `${edge.source}|${edge.target}|${edge.data?.edgeType ?? "dependency"}`;
376
+ const incomingScore = precedence.get(edge.data?.authority ?? "authoritative") ?? 0;
377
+ const existing = dedupedByRelation.get(key);
378
+ if (!existing) {
379
+ dedupedByRelation.set(key, edge);
380
+ return;
381
+ }
382
+ const existingScore = precedence.get(existing.data?.authority ?? "authoritative") ?? 0;
383
+ if (incomingScore > existingScore) {
384
+ dedupedByRelation.set(key, edge);
385
+ }
386
+ });
387
+ const resolvedEdges = [...dedupedByRelation.values()];
388
+ if (!selectedNodeId) {
389
+ if (viewMode === "project" && searchQuery.trim().length === 0) {
390
+ const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
402
391
  return resolvedEdges.filter(
403
- (edge) => pinnedNodeIds.has(edge.source) && pinnedNodeIds.has(edge.target),
392
+ (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
404
393
  );
405
394
  }
406
- const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
395
+ if (viewMode === "architecture-map" && searchQuery.trim().length === 0) {
396
+ const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
397
+ return resolvedEdges.filter(
398
+ (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
399
+ );
400
+ }
401
+ if (viewMode === "tasks" && searchQuery.trim().length === 0) {
402
+ const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
403
+ return resolvedEdges.filter(
404
+ (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
405
+ );
406
+ }
407
+ if (pinnedNodeIds.size === 0) return [];
407
408
  return resolvedEdges.filter(
408
- (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
409
+ (edge) => pinnedNodeIds.has(edge.source) && pinnedNodeIds.has(edge.target),
409
410
  );
410
- },
411
- [edges, enabledAuthorityFilters, enabledEdgeFilters, hiddenNodeIds, pinnedNodeIds, searchQuery, selectedNodeId, viewMode, visibleNodes],
412
- );
411
+ }
412
+ const visibleNodeIdSet = new Set(visibleNodes.map((node) => node.id));
413
+ return resolvedEdges.filter(
414
+ (edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
415
+ );
416
+ }, [
417
+ edges,
418
+ enabledAuthorityFilters,
419
+ enabledEdgeFilters,
420
+ hiddenNodeIds,
421
+ pinnedNodeIds,
422
+ searchQuery,
423
+ selectedNodeId,
424
+ viewMode,
425
+ visibleNodes,
426
+ ]);
413
427
  const renderedEdges = useMemo(
414
428
  () =>
415
429
  visibleEdges.map((edge) => ({
@@ -526,7 +540,9 @@ export function GraphCanvas({
526
540
 
527
541
  function selectNodeForInspector(node: Node<ArchNodeData>) {
528
542
  const parsed = parseNodeId(node.id);
529
- const hasVisibleLinks = visibleEdges.some((edge) => edge.source === node.id || edge.target === node.id);
543
+ const hasVisibleLinks = visibleEdges.some(
544
+ (edge) => edge.source === node.id || edge.target === node.id,
545
+ );
530
546
  const metadata = [...node.data.metadata];
531
547
  if (viewMode === "architecture-map" && !hasVisibleLinks) {
532
548
  metadata.push({