diff --git a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx index 4e9e20b27..3b76b1fe1 100644 --- a/gateway/internal/adminapi/ui/src/pages/Canvas.tsx +++ b/gateway/internal/adminapi/ui/src/pages/Canvas.tsx @@ -398,7 +398,16 @@ export function Canvas() { const [selectedID, setSelectedID] = useState(null); const rows = matrix.data?.results ?? []; - const canvas = useMemo(() => buildCanvas(rows), [rows]); + // Defer building the canvas until the matrix has loaded. The + // SystemCanvas auto-fits to content on its first non-empty render + // and never re-fits while the canvasRef stays the same, so if we + // fed it the gateway+providers stub while `rows` was still empty, + // the fit would zoom in tight on those 5 nodes — and the + // agent/user nodes would land off-screen when the data arrives. + const canvas = useMemo( + () => (matrix.data ? buildCanvas(rows) : { nodes: [], edges: [] }), + [rows, matrix.data], + ); const selectedNode = selectedID ? canvas.nodes?.find((n) => n.id === selectedID) ?? null @@ -409,7 +418,7 @@ export function Canvas() { // anchors the absolutely-positioned WindowPicker overlay. return (