# Canvas V3 - Comprehensive Fix & Enhancement Plan **Created**: January 16, 2026 **Status**: Planning **Priority**: High --- ## Problem Analysis ### Critical Bugs (Must Fix) | Issue | Impact | Root Cause (Likely) | |-------|--------|---------------------| | **Atomizer Assistant broken** | High - core feature unusable | WebSocket connection or chat hook error | | **Cannot delete connections** | High - workflow editing blocked | Missing edge selection/delete handler | | **Drag & drop positioning wrong** | Medium - UX frustration | Position calculation not accounting for scroll/zoom | ### Data Loading Issues | Issue | Impact | Root Cause (Likely) | |-------|--------|---------------------| | **Auto-connect missing** | High - manual work required | `loadFromConfig` creates nodes but not edges | | **Missing elements (OPD extractor)** | High - incomplete workflows | Incomplete parsing of `optimization_config.json` | | **Constraints not shown** | High - incomplete workflows | Constraints not parsed from config | | **Algorithm not pre-selected** | Medium - extra clicks | Algorithm node not created from config | ### UI/UX Issues | Issue | Impact | Root Cause (Likely) | |-------|--------|---------------------| | **Interface too small** | Medium - wasted screen space | Fixed dimensions, not responsive | | **Insufficient contrast** | Medium - accessibility | White text on light blue background | | **Font size too small** | Low - readability | Hardcoded small font sizes | ### Feature Requests | Feature | Value | Complexity | |---------|-------|------------| | **Auto-complete with Claude** | High - smart assistance | Medium | | **Templates/guidelines** | Medium - onboarding | Low | | **Canvas ↔ Assistant integration** | High - conversational control | High | --- ## Implementation Strategy ### Phased Approach ``` Phase 1: Critical Fixes (2 hours) ↓ Phase 2: Data Loading (3 hours) ↓ Phase 3: UI/UX Polish (2 hours) ↓ Phase 4: Claude Integration (3 hours) ↓ Phase 5: Testing & Commit (1 hour) ``` --- ## Phase 1: Critical Bug Fixes ### 1.1 Fix Atomizer Assistant Error **Investigation Steps:** 1. Check `ChatPanel.tsx` for error handling 2. Check `useCanvasChat.ts` hook for connection issues 3. Verify WebSocket endpoint `/api/chat/` is working 4. Check if `useChat.ts` base hook has errors **Likely Fix:** - Add error boundary around chat component - Add null checks for WebSocket connection - Provide fallback UI when chat unavailable ### 1.2 Enable Connection Deletion **Current State:** Edges can't be selected or deleted **Implementation:** ```tsx // In AtomizerCanvas.tsx selectEdge(edge.id)} /> ``` **Store Update:** ```typescript // In useCanvasStore.ts deleteEdge: (edgeId: string) => { set((state) => ({ edges: state.edges.filter((e) => e.id !== edgeId), })); }, ``` ### 1.3 Fix Drag & Drop Positioning **Problem:** New nodes appear at wrong position (not where dropped) **Fix in AtomizerCanvas.tsx:** ```typescript const onDrop = useCallback((event: DragEvent) => { event.preventDefault(); if (!reactFlowInstance.current || !reactFlowWrapper.current) return; const type = event.dataTransfer.getData('application/reactflow') as NodeType; if (!type) return; // Get correct position accounting for viewport transform const bounds = reactFlowWrapper.current.getBoundingClientRect(); const position = reactFlowInstance.current.screenToFlowPosition({ x: event.clientX - bounds.left, y: event.clientY - bounds.top, }); addNode(type, position); }, [addNode]); ``` --- ## Phase 2: Data Loading Improvements ### 2.1 Enhanced `loadFromConfig` Function **Goal:** When loading a study, create ALL nodes AND edges automatically. **Current Problems:** - Nodes created but not connected - Some extractors/constraints missing - Algorithm not created **New Implementation Strategy:** ```typescript loadFromConfig: (config: OptimizationConfig) => { const nodes: Node[] = []; const edges: Edge[] = []; // Layout constants const COLS = { model: 50, dvar: 50, solver: 250, extractor: 450, obj: 650, algo: 850 }; const ROW_HEIGHT = 100; const START_Y = 100; // Track IDs for connections const nodeIds = { model: '', solver: '', dvars: [] as string[], extractors: {} as Record, // extractor_id -> node_id objectives: [] as string[], constraints: [] as string[], algorithm: '', }; // 1. Create Model Node if (config.nx_model) { const id = `model_${Date.now()}`; nodeIds.model = id; nodes.push({ id, type: 'model', position: { x: COLS.model, y: START_Y }, data: { type: 'model', label: 'Model', configured: true, filePath: config.nx_model.sim_path || config.nx_model.prt_path, fileType: config.nx_model.sim_path ? 'sim' : 'prt', }, }); } // 2. Create Solver Node if (config.solver) { const id = `solver_${Date.now()}`; nodeIds.solver = id; nodes.push({ id, type: 'solver', position: { x: COLS.solver, y: START_Y }, data: { type: 'solver', label: 'Solver', configured: true, solverType: `SOL${config.solver.solution_type}`, }, }); // Connect Model → Solver if (nodeIds.model) { edges.push({ id: `e_model_solver`, source: nodeIds.model, target: nodeIds.solver, animated: true, }); } } // 3. Create Design Variables (connected to Model) config.design_variables?.forEach((dv, i) => { const id = `dvar_${i}_${Date.now()}`; nodeIds.dvars.push(id); nodes.push({ id, type: 'designVar', position: { x: COLS.dvar, y: START_Y + 150 + i * ROW_HEIGHT }, data: { type: 'designVar', label: dv.name, configured: true, expressionName: dv.nx_expression || dv.name, minValue: dv.lower_bound, maxValue: dv.upper_bound, unit: dv.unit, }, }); // Connect DVar → Model (or keep disconnected, they're inputs) }); // 4. Create Extractors from objectives AND constraints const allExtractors = new Set(); config.objectives?.forEach(obj => allExtractors.add(obj.extractor_id)); config.constraints?.forEach(con => { if (con.extractor_id) allExtractors.add(con.extractor_id); }); let extractorRow = 0; allExtractors.forEach((extractorId) => { const id = `extractor_${extractorId}_${Date.now()}`; nodeIds.extractors[extractorId] = id; // Find extractor config const objWithExt = config.objectives?.find(o => o.extractor_id === extractorId); const conWithExt = config.constraints?.find(c => c.extractor_id === extractorId); nodes.push({ id, type: 'extractor', position: { x: COLS.extractor, y: START_Y + extractorRow * ROW_HEIGHT }, data: { type: 'extractor', label: extractorId, configured: true, extractorId: extractorId, extractorName: objWithExt?.name || conWithExt?.name || extractorId, }, }); extractorRow++; // Connect Solver → Extractor if (nodeIds.solver) { edges.push({ id: `e_solver_${extractorId}`, source: nodeIds.solver, target: id, }); } }); // 5. Create Objectives (connected to Extractors) config.objectives?.forEach((obj, i) => { const id = `obj_${i}_${Date.now()}`; nodeIds.objectives.push(id); nodes.push({ id, type: 'objective', position: { x: COLS.obj, y: START_Y + i * ROW_HEIGHT }, data: { type: 'objective', label: obj.name, configured: true, name: obj.name, direction: obj.direction, weight: obj.weight, }, }); // Connect Extractor → Objective const extractorNodeId = nodeIds.extractors[obj.extractor_id]; if (extractorNodeId) { edges.push({ id: `e_ext_obj_${i}`, source: extractorNodeId, target: id, }); } }); // 6. Create Constraints (connected to Extractors) config.constraints?.forEach((con, i) => { const id = `con_${i}_${Date.now()}`; nodeIds.constraints.push(id); const objY = START_Y + (config.objectives?.length || 0) * ROW_HEIGHT; nodes.push({ id, type: 'constraint', position: { x: COLS.obj, y: objY + i * ROW_HEIGHT }, data: { type: 'constraint', label: con.name, configured: true, name: con.name, operator: con.type === 'upper' ? '<=' : con.type === 'lower' ? '>=' : '==', value: con.upper_bound ?? con.lower_bound ?? con.target, }, }); // Connect Extractor → Constraint if (con.extractor_id) { const extractorNodeId = nodeIds.extractors[con.extractor_id]; if (extractorNodeId) { edges.push({ id: `e_ext_con_${i}`, source: extractorNodeId, target: id, }); } } }); // 7. Create Algorithm Node if (config.optimization) { const id = `algo_${Date.now()}`; nodeIds.algorithm = id; nodes.push({ id, type: 'algorithm', position: { x: COLS.algo, y: START_Y }, data: { type: 'algorithm', label: 'Algorithm', configured: true, method: config.optimization.sampler || 'TPE', maxTrials: config.optimization.n_trials || 100, }, }); // Connect Objectives → Algorithm nodeIds.objectives.forEach((objId, i) => { edges.push({ id: `e_obj_algo_${i}`, source: objId, target: id, }); }); } // 8. Create Surrogate Node (if enabled) if (config.surrogate?.enabled) { const id = `surrogate_${Date.now()}`; nodes.push({ id, type: 'surrogate', position: { x: COLS.algo, y: START_Y + 150 }, data: { type: 'surrogate', label: 'Surrogate', configured: true, enabled: true, modelType: config.surrogate.type || 'MLP', minTrials: config.surrogate.min_trials || 20, }, }); } // Apply to store set({ nodes, edges, selectedNode: null, validation: { valid: false, errors: [], warnings: [] }, }); }; ``` ### 2.2 Parse Full Config Structure **Ensure we handle:** - `nx_model.sim_path` / `prt_path` / `fem_path` - `solver.solution_type` - `design_variables[]` with all fields - `objectives[]` with `extractor_id`, `name`, `direction`, `weight` - `constraints[]` with `extractor_id`, `type`, `upper_bound`, `lower_bound` - `optimization.sampler`, `n_trials` - `surrogate.enabled`, `type`, `min_trials` - `post_processing[]` (for future) --- ## Phase 3: UI/UX Polish ### 3.1 Responsive Full-Screen Canvas **CanvasView.tsx:** ```tsx export function CanvasView() { return (
{/* Minimal header */}
Canvas Builder
{/* Canvas fills remaining space */}
); } ``` ### 3.2 Fix Contrast Issues **Problem:** White text on light blue is hard to read **Solution:** Use darker backgrounds or different text colors ```css /* Node backgrounds */ .bg-dark-850 /* #0A1525 - dark enough for white text */ /* Avoid light blue backgrounds with white text */ /* If using blue, use dark blue (#1e3a5f) or switch to light text */ /* Specific fixes */ .node-header { background: #0F1E32; /* dark-800 */ color: #FFFFFF; } .node-content { background: #0A1525; /* dark-850 */ color: #E2E8F0; /* light gray */ } /* Badge/pill text */ .badge-primary { background: rgba(0, 212, 230, 0.15); /* primary with low opacity */ color: #00D4E6; /* primary-400 */ border: 1px solid rgba(0, 212, 230, 0.3); } ``` ### 3.3 Increase Font Sizes **Current vs New:** | Element | Current | New | |---------|---------|-----| | Node label | 12px | 14px | | Node detail | 10px | 12px | | Palette item | 12px | 14px | | Panel headers | 14px | 16px | | Config labels | 10px | 12px | --- ## Phase 4: Claude Integration ### 4.1 Fix Chat Panel Connection **Error Handling:** ```tsx function ChatPanel({ onClose }: ChatPanelProps) { const { messages, isConnected, isThinking, error } = useCanvasChat(); if (error) { return (

{error}

); } // ... rest of component } ``` ### 4.2 Auto-Complete with Claude **Concept:** A button that sends current canvas state to Claude and asks for suggestions. **UI:** - Button: "Complete with Claude" next to Validate - Opens chat panel with Claude's analysis - Claude suggests: missing nodes, connections, configuration improvements **Implementation:** ```typescript // In useCanvasChat.ts const autoCompleteWithClaude = useCallback(async () => { const intent = toIntent(); const message = `Analyze this Canvas workflow and suggest what's missing or could be improved: \`\`\`json ${JSON.stringify(intent, null, 2)} \`\`\` Please: 1. Identify any missing required components 2. Suggest extractors that should be added based on the objectives 3. Recommend connections that should be made 4. Propose any configuration improvements Be specific and actionable.`; await sendMessage(message); }, [toIntent, sendMessage]); ``` ### 4.3 Canvas ↔ Assistant Integration (Future) **Vision:** Claude can modify the canvas through conversation. **Commands:** - "Add a displacement extractor" - "Connect the mass extractor to objective 1" - "Set the algorithm to CMA-ES with 200 trials" - "Load the bracket_v3 study" **Implementation Approach:** 1. Define canvas manipulation actions as Claude tools 2. Parse Claude responses for action intents 3. Execute actions via store methods **MCP Tools (New):** - `canvas_add_node` - Add a node of specified type - `canvas_remove_node` - Remove a node by ID - `canvas_connect` - Connect two nodes - `canvas_disconnect` - Remove a connection - `canvas_configure` - Update node configuration - `canvas_load_study` - Load a study into canvas --- ## Phase 5: Testing & Validation ### Test Cases | Test | Steps | Expected | |------|-------|----------| | Load study with connections | Import → Select study → Load | All nodes + edges appear | | Delete connection | Click edge → Press Delete | Edge removed | | Drag & drop position | Drag node to specific spot | Node appears at drop location | | Chat panel opens | Click chat icon | Panel opens without error | | Full screen canvas | Open /canvas | Canvas fills window | | Contrast readable | View all nodes | All text legible | ### Build Verification ```bash cd atomizer-dashboard/frontend npm run build # Must pass without errors ``` --- ## Files to Modify | File | Changes | |------|---------| | `useCanvasStore.ts` | Enhanced `loadFromConfig`, `deleteEdge` | | `AtomizerCanvas.tsx` | Edge deletion, drag/drop fix, responsive | | `CanvasView.tsx` | Full-screen layout | | `ChatPanel.tsx` | Error handling, reconnect | | `useCanvasChat.ts` | Auto-complete function, error state | | `BaseNode.tsx` | Font sizes, contrast | | `NodePalette.tsx` | Font sizes | | `NodeConfigPanel.tsx` | Font sizes, contrast | --- ## Summary **Total Effort:** ~11 hours across 5 phases **Priority Order:** 1. Fix Atomizer Assistant (blocking) 2. Fix connection deletion (blocking editing) 3. Fix data loading (core functionality) 4. UI/UX polish (user experience) 5. Claude integration (enhancement) **Success Criteria:** - [ ] All bugs from user report fixed - [ ] Loading a study shows ALL elements with connections - [ ] Canvas is responsive and readable - [ ] Chat panel works without errors - [ ] Build passes without errors --- *Plan created for Ralph Loop autonomous execution.*