Skip to content

React UI Production Upgrade #31

Description

@zaebee

🎯 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:

  1. Migrate the React code into our main monorepo under a /ui directory.
  2. Upgrade the application from Create React App (CRA) to Vite for speed, strict building, and modern ESM compilation.
  3. 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.
  4. 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

  • Test 1 (Vite Build redirection): Running npm run build inside /ui correctly outputs static files into src/cgis/api/static/.
  • Test 2 (Idempotent Merge): Double-clicking the same node multiple times must not result in duplicate nodes or edges on the React Flow canvas (guaranteed by Set filtering).
  • Test 3 (FQN Display): Node labels must be derived cleanly (e.g., displaying IngestionPipeline.run instead of the raw FQN src.cgis.pipeline.IngestionPipeline.run to keep nodes visually small and readable).

Metadata

Metadata

Assignees

Labels

could-haveNice for AI agents and UX

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions