🎯 Feature Design: Embedded Production-Grade React Flow UI
1. Context & Goal
We have a working prototype of a React debugger in graph-debugger. However, to make CGIS a professional, self-contained developer tool, we must:
- Migrate the React code into our main monorepo under a
/ui directory.
- Upgrade the application from Create React App (CRA) to Vite for speed, strict building, and modern ESM compilation.
- Configure the build step to output static files directly into our Python backend folder (
src/cgis/api/static/) so we can serve the UI via cgis ui command.
- Transition the graph rendering to a modular, cycle-safe, and interactive React Flow (@xyflow/react) implementation supporting progressive disclosure.
2. Monorepo Integration & Build Pipeline
We will place the React app inside /ui at the root. The build command in ui/package.json must be configured to output to the Python package's static directory.
// ui/package.json
{
"name": "cgis-ui",
"private": true,
"version": "0.1.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"@xyflow/react": "^12.0.0", // Modern React Flow
"lucide-react": "^0.300.0"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
// ui/vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
build: {
// Compile directly into Python's FastAPI static directory
outDir: path.resolve(__dirname, '../src/cgis/api/static'),
emptyOutDir: true,
}
});
3. UI Algorithmic Blueprint: Progressive Disclosure
The core of our production UI is progressive disclosure (dynamic subgraph expanding).
When the user clicks a node, the React Flow state must fetch only its neighbors from /api/graph/explore and merge them into the canvas.
// Pseudo-code for ui/src/App.js
import React, { useState, useCallback } from 'react';
import { ReactFlow, useNodesState, useEdgesState } from '@xyflow/react';
export default function App() {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const expandNode = useCallback(async (nodeId) => {
// Fetch only the direct 1-depth ego-graph of the clicked node
const response = await fetch(`/api/graph/explore?node_id=${nodeId}&depth=1`);
const data = await response.json();
setNodes((prevNodes) => {
// Merge unique new nodes into current state
const existingIds = new Set(prevNodes.map(n => n.id));
const newNodes = data.nodes.filter(n => !existingIds.has(n.id));
// Highlight the focused node
const updatedPrevNodes = prevNodes.map(n =>
n.id === nodeId ? { ...n, className: 'focused-node' } : n
);
return [...updatedPrevNodes, ...newNodes];
});
setEdges((prevEdges) => {
// Merge unique new edges
const existingIds = new Set(prevEdges.map(e => e.id));
const newEdges = data.edges.filter(e => !existingIds.has(e.id));
return [...prevEdges, ...newEdges];
});
}, [setNodes, setEdges]);
const onNodeDoubleClick = (event, node) => {
expandNode(node.id);
};
return (
<div style={{ width: '100vw', height: '100vh' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeDoubleClick={onNodeDoubleClick}
fitView
/>
</div>
);
}
4. Layout Strategy (Solving Clutter)
To prevent nodes from spawning on top of each other, we must integrate a lightweight layout helper. We can use @reactflow/dagre or a custom lightweight 2D grid/force layout in ui/src/layout.js to automatically calculate positions when new nodes are appended.
5. TDD Acceptance Criteria
🎯 Feature Design: Embedded Production-Grade React Flow UI
1. Context & Goal
We have a working prototype of a React debugger in
graph-debugger. However, to make CGIS a professional, self-contained developer tool, we must:/uidirectory.src/cgis/api/static/) so we can serve the UI viacgis uicommand.2. Monorepo Integration & Build Pipeline
We will place the React app inside
/uiat the root. The build command inui/package.jsonmust be configured to output to the Python package's static directory.3. UI Algorithmic Blueprint: Progressive Disclosure
The core of our production UI is progressive disclosure (dynamic subgraph expanding).
When the user clicks a node, the React Flow state must fetch only its neighbors from
/api/graph/exploreand merge them into the canvas.4. Layout Strategy (Solving Clutter)
To prevent nodes from spawning on top of each other, we must integrate a lightweight layout helper. We can use
@reactflow/dagreor a custom lightweight 2D grid/force layout inui/src/layout.jsto automatically calculate positions when new nodes are appended.5. TDD Acceptance Criteria
npm run buildinside/uicorrectly outputs static files intosrc/cgis/api/static/.Setfiltering).IngestionPipeline.runinstead of the raw FQNsrc.cgis.pipeline.IngestionPipeline.runto keep nodes visually small and readable).