Steps to reproduce
- Open one application's graph.
- Navigate to a second application with a different topology and open its graph.
Observed behavior
The second graph is laid out against state left behind by the first. Node positions can differ from the positions the same application produces when it is the first graph rendered in the session.
The cause is a single module-level Dagre graph in packages/rad-components/src/components/appgraph/AppGraph.tsx:
const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
export function getLayoutedElements(nodes, edges, options) {
g.setGraph({ rankdir: options.direction });
edges.forEach(edge => g.setEdge(edge.source, edge.target));
nodes.forEach(node => g.setNode(node.id, node as NodeLabel));
Dagre.layout(g);
// ...
}
g is created once per module, never reset, and only ever added to. Nodes and edges from every previously laid-out graph remain in it for the lifetime of the page. setNode and setEdge overwrite matching keys but never remove stale ones, so the layout being solved is the union of every graph rendered so far, not the graph that was passed in.
Desired behavior
getLayoutedElements should construct a fresh Dagre graph per call, or clear the existing one, so that layout is a pure function of its arguments. Laying out the same graph twice, in any order relative to other graphs, should produce identical positions.
Workaround
A full page reload before viewing each graph.
Additional context
Pinned by GU-08 in packages/rad-components/src/__test__/graphInvariants.test.ts, added in nicolejms#1, tagged KNOWN-DEFECT.
One note for whoever writes the regression test after fixing this. The leaked state lives in a module-level binding, so the first layout performed in a test file pollutes every later one and there is no clean baseline left to compare against. A naive "lay out A then B, compare against B alone" test passes while the defect is present — it is a false negative, because the "B alone" measurement is itself already polluted. GU-08 uses jest.isolateModules to obtain a genuinely fresh module per sequence, which is what makes the difference observable.
Found while building the graph invariant suite ahead of the graph rearchitecture, tracked in docs/design/2026-09-dashboard-plugin-test-plan.md.
Steps to reproduce
Observed behavior
The second graph is laid out against state left behind by the first. Node positions can differ from the positions the same application produces when it is the first graph rendered in the session.
The cause is a single module-level Dagre graph in
packages/rad-components/src/components/appgraph/AppGraph.tsx:gis created once per module, never reset, and only ever added to. Nodes and edges from every previously laid-out graph remain in it for the lifetime of the page.setNodeandsetEdgeoverwrite matching keys but never remove stale ones, so the layout being solved is the union of every graph rendered so far, not the graph that was passed in.Desired behavior
getLayoutedElementsshould construct a fresh Dagre graph per call, or clear the existing one, so that layout is a pure function of its arguments. Laying out the same graph twice, in any order relative to other graphs, should produce identical positions.Workaround
A full page reload before viewing each graph.
Additional context
Pinned by
GU-08inpackages/rad-components/src/__test__/graphInvariants.test.ts, added in nicolejms#1, taggedKNOWN-DEFECT.One note for whoever writes the regression test after fixing this. The leaked state lives in a module-level binding, so the first layout performed in a test file pollutes every later one and there is no clean baseline left to compare against. A naive "lay out A then B, compare against B alone" test passes while the defect is present — it is a false negative, because the "B alone" measurement is itself already polluted.
GU-08usesjest.isolateModulesto obtain a genuinely fresh module per sequence, which is what makes the difference observable.Found while building the graph invariant suite ahead of the graph rearchitecture, tracked in
docs/design/2026-09-dashboard-plugin-test-plan.md.