# Canvas Deep Fix Investigation **Date**: January 16, 2026 **Status**: ✅ IMPLEMENTATION COMPLETE --- ## Executive Summary Four critical issues have been identified that are blocking Canvas functionality: | # | Issue | Root Cause | Severity | |---|-------|------------|----------| | 1 | Claude Chat Not Working | `asyncio.create_subprocess_exec` fails on Windows | CRITICAL | | 2 | Expressions Can't Connect to Model | ModelNode has `inputs={0}` - no input handle | CRITICAL | | 3 | File Browser Only Shows Studies | Web API can't access OS file system | HIGH | | 4 | Introspection is Fake | Only reads config files, not actual NX models | HIGH | --- ## Issue 1: Claude Chat NotImplementedError ### Root Cause ```python # session_manager.py line 138 process = await asyncio.create_subprocess_exec(...) ``` On Windows, `asyncio.create_subprocess_exec` raises `NotImplementedError` because Windows doesn't support the ProactorEventLoop subprocess methods the same way Unix does. ### Evidence ``` Traceback: File "session_manager.py", line 138, in create_session process = await asyncio.create_subprocess_exec( File "asyncio\subprocess.py", line 218, in create_subprocess_exec File "asyncio\base_events.py", line 498, in _make_subprocess_transport raise NotImplementedError NotImplementedError ``` ### Solution Replace async subprocess with synchronous subprocess + ThreadPoolExecutor: ```python import subprocess from concurrent.futures import ThreadPoolExecutor executor = ThreadPoolExecutor(max_workers=4) async def create_session(...): # Instead of asyncio.create_subprocess_exec loop = asyncio.get_event_loop() process = await loop.run_in_executor( executor, lambda: subprocess.Popen( ["claude", "--print", ...], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=str(ATOMIZER_ROOT), ) ) ``` **OR** - Simpler approach: Skip session-based subprocess entirely, use HTTP streaming for chat: The `/api/claude/chat/stream` endpoint already works (it uses `claude_cli_agent.py` which runs Claude one-shot). The WebSocket session approach is over-engineered for the use case. ### Fix Strategy 1. Make `create_session` return a "virtual" session (no subprocess) 2. Route all messages through HTTP streaming endpoint 3. Keep conversation history in `ConversationStore` database 4. WebSocket just wraps the HTTP streaming calls --- ## Issue 2: Expressions Can't Connect to Model ### Root Cause **ModelNode.tsx:** ```tsx ``` `inputs={0}` means Model has **NO input handle** - nothing can connect TO it! **DesignVarNode.tsx:** ```tsx ``` Uses defaults (`inputs=1, outputs=1`) but DesignVar should have: - `inputs=0` (it's a source node) - `outputs=1` (connects to Model) ### Visual Problem ``` Current (WRONG): DesignVar ←─ Model ──→ Solver ↑ ↓ (has input) (has output only) Should be: DesignVar ──→ Model ──→ Solver ↓ ↑↓ (output) (input & output) ``` ### Fix Required **ModelNode.tsx** - Add input handle: ```tsx ``` **DesignVarNode.tsx** - Remove input handle: ```tsx ``` **SurrogateNode.tsx** - Should be terminal (no output): ```tsx ``` --- ## Issue 3: File Browser Only Shows Studies Folder ### Root Cause The current `FileBrowser.tsx` uses fetch to `/api/files/list` which only lists files within the `studies/` directory. The user wants: 1. Native OS file picker to select files from ANYWHERE 2. Import selected files into the study folder 3. Copy all related files (.prt, .sim, .fem, _i.prt, etc.) ### Web Browser Limitation Browsers can't access the local file system directly for security. Options: **Option A: File System Access API** (Chrome/Edge only) ```typescript const handle = await window.showOpenFilePicker({ types: [{ description: 'NX Files', accept: { '*/*': ['.sim', '.prt', '.fem'] } }] }); const file = await handle.getFile(); // Upload to backend ``` **Option B: Traditional File Input** ```tsx ``` Then upload to backend which saves to study folder. **Option C: Backend Path Input + Validation** User enters full Windows path (e.g., `C:\NX_Models\bracket.prt`), backend validates and copies. ### Recommended Solution Combine B + C: 1. File input for direct upload (drag & drop) 2. Path input for network drives/existing paths 3. Backend endpoint to copy/import files --- ## Issue 4: Introspection is Fake ### Root Cause Current `nx_introspection.py` does NOT actually read NX files. It only: - Reads `optimization_config.json` for existing design variables - Infers expressions based on folder names ("mirror" → suggest mirror expressions) - Guesses solver type from file names ### What Real Introspection Needs **For .prt files (NX Part):** - Use NX Open API to read expressions - Get expression names, values, units, formulas - Requires NX to be installed and licensed **For .sim files (Simulation):** - Parse XML-like structure or use NX Open - Get solver type, boundary conditions, loads - Identify linked .fem and .prt files **For .fem files (FEM):** - Get mesh statistics (nodes, elements) - Material properties - Element types used **For .op2 files (Results):** - Use PyNastran to read binary results - Extract displacement, stress, frequency data - Get node/element IDs for specific extractions ### Implementation Approach **Phase 1: File Discovery (no NX needed)** ```python def discover_related_files(sim_path: Path) -> List[Dict]: """Find all related files by naming convention""" # model_sim1.sim → model.prt, model_fem1.fem, model_fem1_i.prt ``` **Phase 2: Config-based Expression Discovery** ```python def discover_expressions_from_config(study_dir: Path) -> List[Dict]: """Read optimization_config.json for design variables""" ``` **Phase 3: NX Open Integration (requires NX)** ```python def introspect_with_nx_open(prt_path: Path) -> Dict: """Use NX Open API to read actual expressions""" # This requires NX to be running # Use the existing nx_journals/ infrastructure ``` **Phase 4: OP2 Result Analysis (PyNastran)** ```python def analyze_op2_results(op2_path: Path) -> Dict: """Read OP2 file to discover available result types""" from pyNastran.op2.op2 import OP2 op2 = OP2() op2.read_op2(str(op2_path)) # Return available subcases, result types, etc. ``` --- ## Implementation Plan ### Phase 1: Fix Claude Chat (CRITICAL - 30 min) 1. Modify `create_session` to not spawn subprocess 2. Keep session metadata in database only 3. Route all messages through HTTP streaming 4. WebSocket wraps HTTP calls ### Phase 2: Fix Node Handles (CRITICAL - 15 min) 1. Update `ModelNode.tsx`: `inputs={1}` 2. Update `DesignVarNode.tsx`: `inputs={0}, outputs={1}` 3. Update `SurrogateNode.tsx`: `outputs={0}` 4. Test connections work correctly ### Phase 3: Native File Import (HIGH - 45 min) 1. Add file upload input to FileBrowser 2. Create backend `/api/files/upload` endpoint 3. Add path input with validation 4. Create `/api/files/import` for path-based import 5. Copy all related files to study folder ### Phase 4: Real Introspection Service (HIGH - 2 hours) 1. File discovery by naming convention 2. OP2 analysis with PyNastran 3. NX Open integration (optional, requires NX running) 4. Return comprehensive file metadata ### Phase 5: Integration Testing (30 min) 1. Test complete workflow: Select model → Introspect → Add Design Vars → Connect → Execute 2. Fix any remaining issues --- ## Files to Modify ### Backend - `session_manager.py` - Fix Windows subprocess issue - `files.py` - Add upload/import endpoints - `nx_introspection.py` - Real introspection logic ### Frontend - `ModelNode.tsx` - Add input handle - `DesignVarNode.tsx` - Remove input, keep output - `SurrogateNode.tsx` - Remove output - `FileBrowser.tsx` - Add file upload, path input - `IntrospectionPanel.tsx` - Display real introspection data --- ## Estimated Total Time: 4-5 hours --- ## Implementation Summary (Completed) ### Phase 1: Claude Chat Windows Fix ✅ **File**: `atomizer-dashboard/backend/api/services/session_manager.py` - Replaced `asyncio.create_subprocess_exec` with `subprocess.Popen` - Used `ThreadPoolExecutor` with `run_in_executor()` for async compatibility - Made sessions stateless (no persistent subprocess) - Each message handled via one-shot CLI call with 5-minute timeout ### Phase 2: Node Handles ✅ **Files**: - `ModelNode.tsx`: Changed `inputs={0}` to `inputs={1}` (now accepts connections) - `DesignVarNode.tsx`: Added `inputs={0} outputs={1}` (source node) ### Phase 3: Native File Import ✅ **Files**: - `files.py`: Added `/validate-path`, `/import-from-path`, `/upload` endpoints - `FileBrowser.tsx`: Complete rewrite with 3 tabs: - Browse Studies (existing) - Import Path (paste Windows path, validate, import related files) - Upload Files (drag & drop) ### Phase 4: Real NX Introspection ✅ **File**: `atomizer-dashboard/backend/api/services/nx_introspection.py` - Added PyNastran OP2 parsing (displacements, eigenvectors, stress) - BDF/DAT file analysis (mass, grid count, element counts, solver type) - Study database queries for expression discovery - Related file discovery by naming convention - Result file discovery with trial folder detection