fix(graph): address review — reachFrom direct edges + node a11y
- reachFrom now returns the direct outbound edges PLUS the recursive $graphLookup reaches, de-duped by edge id, with maxDepth to bound cycles. It was dropping edges straight off the start node. [review P2] - GraphView SVG nodes are keyboard-accessible: role/tabIndex/aria-label + Enter/Space handler alongside onClick. [review P3] Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,7 @@ export async function reachFrom(podId: string, startNodeId: string): Promise<Rea
|
||||
const db = await getDb();
|
||||
const rows = await db
|
||||
.collection<GraphEdgeDoc>('graph_edges')
|
||||
.aggregate<{ reaches: GraphEdgeDoc[] }>([
|
||||
.aggregate<GraphEdgeDoc & { reaches: GraphEdgeDoc[] }>([
|
||||
{ $match: { podId, source: startNodeId } },
|
||||
{
|
||||
$graphLookup: {
|
||||
@@ -80,9 +80,25 @@ export async function reachFrom(podId: string, startNodeId: string): Promise<Rea
|
||||
connectToField: 'source',
|
||||
as: 'reaches',
|
||||
restrictSearchWithMatch: { podId },
|
||||
maxDepth: 6,
|
||||
},
|
||||
},
|
||||
])
|
||||
.toArray();
|
||||
return { start: startNodeId, reaches: rows.flatMap((r) => r.reaches) };
|
||||
|
||||
// Include the direct outbound edges (the $match rows) plus everything reachable
|
||||
// from them, de-duped by edge id. Without the direct rows, edges straight off
|
||||
// the start node go missing unless a cycle happens to re-discover them.
|
||||
const seen = new Set<string>();
|
||||
const reaches: GraphEdgeDoc[] = [];
|
||||
for (const row of rows) {
|
||||
const { reaches: recursive, ...direct } = row;
|
||||
for (const edge of [direct as GraphEdgeDoc, ...(recursive ?? [])]) {
|
||||
if (edge?.id && !seen.has(edge.id)) {
|
||||
seen.add(edge.id);
|
||||
reaches.push(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { start: startNodeId, reaches };
|
||||
}
|
||||
|
||||
@@ -245,7 +245,16 @@ export function GraphView({ podId, onClose }: { podId: string; onClose: () => vo
|
||||
<g
|
||||
key={n.id}
|
||||
className={`pm-node ${dimNode(n.id) ? 'pm-dim' : ''}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${n.kind}: ${n.label}`}
|
||||
onClick={() => setSelected((cur) => (cur === n.id ? null : n.id))}
|
||||
onKeyDown={(ev) => {
|
||||
if (ev.key === 'Enter' || ev.key === ' ') {
|
||||
ev.preventDefault();
|
||||
setSelected((cur) => (cur === n.id ? null : n.id));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NodeShape node={n} />
|
||||
<text className="pm-lbl" x={n.x} y={n.y + 33} textAnchor="middle">
|
||||
|
||||
Reference in New Issue
Block a user