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.
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +536 -43
- package/package.json +27 -3
- package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
- package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
- package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
- package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
- package/templates/arch-ui/.arch/graph.json +0 -1
- package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
- package/templates/arch-ui/.arch/nodes/domains.json +0 -1
- package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
- package/templates/arch-ui/.arch/nodes/modules.json +0 -1
- package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
- package/templates/arch-ui/app/api/health/route.ts +5 -4
- package/templates/arch-ui/app/api/node-files/route.ts +6 -1
- package/templates/arch-ui/app/api/search/route.ts +0 -1
- package/templates/arch-ui/app/work/page.tsx +94 -64
- package/templates/arch-ui/components/app-shell.tsx +1 -7
- package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
- package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
- package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
- package/templates/arch-ui/components/graph/graph-types.ts +49 -49
- package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
- package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
- package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
- package/templates/arch-ui/components/graph-canvas.tsx +90 -74
- package/templates/arch-ui/components/inspector.tsx +56 -22
- package/templates/arch-ui/components/sidebar.tsx +18 -8
- package/templates/arch-ui/components/topbar.tsx +8 -11
- package/templates/arch-ui/components/work-table.tsx +1 -5
- package/templates/arch-ui/components/workspace-context.tsx +2 -1
- package/templates/arch-ui/lib/graph-dataset.ts +4 -8
- package/templates/arch-ui/lib/graph-schema.ts +1 -4
- package/templates/arch-ui/package.json +0 -1
- 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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
|
|
8
|
+
edges: Edge[],
|
|
9
|
+
hiddenNodeIds: Set<string>,
|
|
10
|
+
flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
|
|
11
11
|
) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
return useCallback(() => {
|
|
13
|
+
const currentViewport = flowInstance?.getViewport();
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
+
dagre.layout(graph);
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
connection: Connection,
|
|
24
|
+
nodes: Node<ArchNodeData>[],
|
|
25
|
+
edges: Edge[],
|
|
26
26
|
) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (!connection.source || !connection.target || connection.source === connection.target) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const allowedTargets = nodeAllowsOutgoing(sourceNode.data.kind);
|
|
38
|
+
if (!allowedTargets.includes(targetNode.data.kind)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const restoreFlow = useCallback(() => {
|
|
26
|
+
if (typeof window === "undefined") return;
|
|
27
|
+
const payload = window.localStorage.getItem(storageKey);
|
|
28
|
+
if (!payload) return;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
}, [
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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) =>
|
|
392
|
+
(edge) => visibleNodeIdSet.has(edge.source) && visibleNodeIdSet.has(edge.target),
|
|
404
393
|
);
|
|
405
394
|
}
|
|
406
|
-
|
|
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) =>
|
|
409
|
+
(edge) => pinnedNodeIds.has(edge.source) && pinnedNodeIds.has(edge.target),
|
|
409
410
|
);
|
|
410
|
-
}
|
|
411
|
-
|
|
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(
|
|
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({
|