Files
Atomizer/docs/plans/SAAS_ATOMIZER_ROADMAP.md
Anto01 ea437d360e docs: Major documentation overhaul - restructure folders, update tagline, add Getting Started guide
- Restructure docs/ folder (remove numeric prefixes):
  - 04_USER_GUIDES -> guides/
  - 05_API_REFERENCE -> api/
  - 06_PHYSICS -> physics/
  - 07_DEVELOPMENT -> development/
  - 08_ARCHIVE -> archive/
  - 09_DIAGRAMS -> diagrams/

- Replace tagline 'Talk, don't click' with 'LLM-driven optimization framework' in 9 files

- Create comprehensive docs/GETTING_STARTED.md:
  - Prerequisites and quick setup
  - Project structure overview
  - First study tutorial (Claude or manual)
  - Dashboard usage guide
  - Neural acceleration introduction

- Rewrite docs/00_INDEX.md with correct paths and modern structure

- Archive obsolete files:
  - 01_PROTOCOLS.md -> archive/historical/01_PROTOCOLS_legacy.md
  - 03_GETTING_STARTED.md -> archive/historical/
  - ATOMIZER_PODCAST_BRIEFING.md -> archive/marketing/

- Update timestamps to 2026-01-20 across all key files

- Update .gitignore to exclude docs/generated/

- Version bump: ATOMIZER_CONTEXT v1.8 -> v2.0
2026-01-20 10:03:45 -05:00

864 lines
29 KiB
Markdown

# SaaS-Level Atomizer Roadmap (Revised)
## Executive Summary
This roadmap transforms Atomizer into a **SaaS-grade, LLM-assisted structural optimization platform** with the core innovation being **side-by-side Claude + Canvas** integration where:
1. **Claude talks → Canvas updates in real-time** (user sees nodes appear/change)
2. **User tweaks Canvas → Claude sees changes** (bi-directional sync)
3. **Full Claude Code-level power** through the dashboard chat
4. **Interview-driven study creation** entirely through conversation
**Vision**: An engineer opens Atomizer, describes their optimization goal, watches the canvas build itself, makes quick tweaks, and starts optimization—all through natural conversation with full visual feedback.
---
## The Core Innovation: Unified Claude + Canvas
The power of Atomizer is the **side-by-side experience**:
```
┌───────────────────────────────────────────────────────────────────┐
│ ATOMIZER DASHBOARD │
├────────────────────────────┬──────────────────────────────────────┤
│ CHAT PANEL │ CANVAS │
│ (Atomizer Assistant) │ (SpecRenderer) │
│ │ │
│ "Create a bracket │ [DV: thickness] │
│ optimization with │ │ │
│ mass and stiffness" │ ▼ │
│ │ │ [Model Node] │
│ ▼ │ │ │
│ 🔧 Adding thickness │ ▼ │
│ 🔧 Adding mass ext ◄───┼──►[Ext: mass]──>[Obj: min mass] │
│ 🔧 Adding stiffness ◄───┼──►[Ext: disp]──>[Obj: min disp] │
│ ✓ Study configured! │ │
│ │ (nodes appear in real-time) │
│ User clicks a node ───────┼──► Claude sees the edit │
│ │ │
└────────────────────────────┴──────────────────────────────────────┘
```
---
## Current State vs Target
### What We Have
| Component | Status | Notes |
|-----------|--------|-------|
| Power Mode WebSocket | ✅ Implemented | `/ws/power` endpoint with write tools |
| Write Tools | ✅ Implemented | add_design_variable, add_extractor, etc. |
| spec_modified Events | ✅ Implemented | Frontend receives notifications |
| Canvas Auto-reload | ✅ Implemented | Triggers on spec_modified |
| Streaming Responses | ❌ Missing | Currently waits for full response |
| Canvas State → Claude | ❌ Missing | Claude doesn't see current canvas |
| Interview Engine | ❌ Missing | No guided creation |
| Unified WebSocket | ❌ Missing | Separate connections for chat/spec |
### Target Architecture
```
┌─────────────────────┐
│ Unified WebSocket │
│ /api/atomizer/ws │
└─────────┬───────────┘
Bi-directional Sync
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Chat Panel │ │ Canvas │ │ Spec Store │
│ │ │ │ │ │
│ Send messages │ │ User edits → │ │ Single source │
│ Stream text │ │ Notify Claude │ │ of truth │
│ See tool calls │ │ Receive updates │ │ Validates all │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
---
## Phase 1: Unified WebSocket Hub (1-2 weeks)
### Goal: Single connection for chat + canvas + spec sync
### 1.1 Backend: Unified WebSocket Endpoint
```python
# atomizer-dashboard/backend/api/routes/atomizer_ws.py
@router.websocket("/api/atomizer/ws")
async def atomizer_websocket(websocket: WebSocket):
"""
Unified WebSocket for Atomizer Dashboard.
Handles:
- Chat messages with streaming responses
- Spec modifications with real-time canvas updates
- Canvas edit notifications from user
- Study context switching
"""
await websocket.accept()
agent = AtomizerClaudeAgent()
conversation: List[Dict] = []
current_spec: Optional[Dict] = None
try:
while True:
data = await websocket.receive_json()
if data["type"] == "message":
# Chat message - stream response
async for event in agent.chat_stream(
message=data["content"],
conversation=conversation,
canvas_state=current_spec # Claude sees current canvas!
):
await websocket.send_json(event)
# If spec changed, update our local copy
if event.get("type") == "spec_updated":
current_spec = event["spec"]
elif data["type"] == "canvas_edit":
# User made a manual edit - update spec and tell Claude
current_spec = apply_patch(current_spec, data["patch"])
# Next message to Claude will include updated spec
elif data["type"] == "set_study":
# Switch study context
agent.set_study(data["study_id"])
current_spec = agent.load_spec()
await websocket.send_json({
"type": "spec_updated",
"spec": current_spec
})
except WebSocketDisconnect:
pass
```
### 1.2 Enhanced Claude Agent with Streaming
```python
class AtomizerClaudeAgent:
"""Full-power Claude agent with Claude Code-like capabilities"""
async def chat_stream(
self,
message: str,
conversation: List[Dict],
canvas_state: Optional[Dict] = None
) -> AsyncGenerator[Dict, None]:
"""Stream responses with tool calls"""
# Build system prompt with current canvas state
system = self._build_system_prompt()
if canvas_state:
system += self._format_canvas_context(canvas_state)
messages = conversation + [{"role": "user", "content": message}]
# Use streaming API
with self.client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=8192,
system=system,
messages=messages,
tools=self.tools
) as stream:
current_text = ""
for event in stream:
if event.type == "content_block_delta":
if hasattr(event.delta, "text"):
current_text += event.delta.text
yield {"type": "text", "content": event.delta.text, "done": False}
# Get final message for tool calls
response = stream.get_final_message()
# Process tool calls
for block in response.content:
if block.type == "tool_use":
yield {"type": "tool_start", "tool": block.name, "input": block.input}
result = await self._execute_tool(block.name, block.input)
yield {"type": "tool_result", "tool": block.name, "result": result["preview"]}
if result.get("spec_changed"):
yield {"type": "spec_updated", "spec": self.spec_store.get()}
yield {"type": "done"}
def _format_canvas_context(self, spec: Dict) -> str:
"""Format current canvas state for Claude's context"""
lines = ["\n## Current Canvas State\n"]
if spec.get("design_variables"):
lines.append(f"**Design Variables ({len(spec['design_variables'])}):**")
for dv in spec["design_variables"]:
lines.append(f" - {dv['name']}: [{dv['bounds']['min']}, {dv['bounds']['max']}]")
if spec.get("extractors"):
lines.append(f"\n**Extractors ({len(spec['extractors'])}):**")
for ext in spec["extractors"]:
lines.append(f" - {ext['name']} ({ext['type']})")
if spec.get("objectives"):
lines.append(f"\n**Objectives ({len(spec['objectives'])}):**")
for obj in spec["objectives"]:
lines.append(f" - {obj['name']} ({obj['direction']})")
if spec.get("constraints"):
lines.append(f"\n**Constraints ({len(spec['constraints'])}):**")
for con in spec["constraints"]:
lines.append(f" - {con['name']} {con['operator']} {con['threshold']}")
lines.append("\nThe user can see this canvas. When you modify it, they see changes in real-time.")
return "\n".join(lines)
```
### 1.3 Frontend: Unified Hook
```typescript
// hooks/useAtomizerSocket.ts
export function useAtomizerSocket(studyId?: string) {
const [spec, setSpec] = useState<AtomizerSpec | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [isThinking, setIsThinking] = useState(false);
const [streamingText, setStreamingText] = useState("");
const [activeTool, setActiveTool] = useState<string | null>(null);
const ws = useRef<WebSocket | null>(null);
useEffect(() => {
const url = `ws://localhost:8001/api/atomizer/ws`;
ws.current = new WebSocket(url);
ws.current.onopen = () => {
if (studyId) {
ws.current?.send(JSON.stringify({ type: "set_study", study_id: studyId }));
}
};
ws.current.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case "text":
setStreamingText(prev => prev + data.content);
break;
case "tool_start":
setActiveTool(data.tool);
break;
case "tool_result":
setActiveTool(null);
break;
case "spec_updated":
setSpec(data.spec); // Canvas updates automatically!
break;
case "done":
// Finalize message
setMessages(prev => [...prev, {
role: "assistant",
content: streamingText
}]);
setStreamingText("");
setIsThinking(false);
break;
}
};
return () => ws.current?.close();
}, [studyId]);
const sendMessage = useCallback((content: string) => {
setIsThinking(true);
setMessages(prev => [...prev, { role: "user", content }]);
ws.current?.send(JSON.stringify({ type: "message", content }));
}, []);
const notifyCanvasEdit = useCallback((path: string, value: any) => {
ws.current?.send(JSON.stringify({
type: "canvas_edit",
patch: { path, value }
}));
}, []);
return {
spec,
messages,
streamingText,
isThinking,
activeTool,
sendMessage,
notifyCanvasEdit
};
}
```
---
## Phase 2: Full Tool Set (1-2 weeks)
### Goal: Claude Code-level power through dashboard
### 2.1 Tool Categories
```python
ATOMIZER_TOOLS = {
# === SPEC MODIFICATION (Already Implemented) ===
"add_design_variable": "Add a design variable to the optimization",
"add_extractor": "Add a physics extractor (mass, stress, displacement, custom)",
"add_objective": "Add an optimization objective",
"add_constraint": "Add a constraint",
"update_spec_field": "Update any field in the spec by JSON path",
"remove_node": "Remove a node from the spec",
# === READ/QUERY ===
"read_study_config": "Read the full atomizer_spec.json",
"query_trials": "Query optimization trial data",
"list_studies": "List all available studies",
"get_optimization_status": "Check if optimization is running",
# === STUDY MANAGEMENT (New) ===
"create_study": "Create a new study directory with atomizer_spec.json",
"clone_study": "Clone an existing study as a starting point",
"validate_spec": "Validate the current spec for errors",
# === NX INTEGRATION (New) ===
"introspect_model": "Analyze NX model for expressions and features",
"suggest_design_vars": "AI-suggest design variables from model",
"list_model_expressions": "List all expressions in the NX model",
# === OPTIMIZATION CONTROL (New) ===
"start_optimization": "Start the optimization run",
"stop_optimization": "Stop a running optimization",
# === INTERVIEW (New) ===
"start_interview": "Begin guided study creation interview",
"get_interview_progress": "Get current interview state",
}
```
### 2.2 Create Study Tool
```python
async def _tool_create_study(self, params: Dict) -> Dict:
"""Create a new study with atomizer_spec.json"""
study_name = params["name"]
study_dir = STUDIES_DIR / study_name
# Create directory structure
study_dir.mkdir(parents=True, exist_ok=True)
(study_dir / "1_setup").mkdir(exist_ok=True)
(study_dir / "2_iterations").mkdir(exist_ok=True)
(study_dir / "3_results").mkdir(exist_ok=True)
# Create initial spec
spec = {
"meta": {
"version": "2.0",
"study_name": study_name,
"created_at": datetime.now().isoformat(),
"created_by": "claude_agent"
},
"model": {
"sim": {"path": params.get("model_path", ""), "solver": "nastran"}
},
"design_variables": [],
"extractors": [],
"objectives": [],
"constraints": [],
"optimization": {
"algorithm": {"type": "TPE"},
"budget": {"max_trials": 100}
},
"canvas": {"edges": [], "layout_version": "2.0"}
}
# Save spec
spec_path = study_dir / "atomizer_spec.json"
with open(spec_path, "w") as f:
json.dump(spec, f, indent=2)
# Update agent's study context
self.study_id = study_name
self.study_dir = study_dir
return {
"preview": f"✓ Created study '{study_name}' at {study_dir}",
"spec_changed": True,
"study_id": study_name
}
```
### 2.3 NX Introspection Tool
```python
async def _tool_introspect_model(self, params: Dict) -> Dict:
"""Analyze NX model for design variable candidates"""
model_path = params.get("model_path") or self._find_model_path()
if not model_path or not Path(model_path).exists():
return {"preview": "✗ Model file not found", "spec_changed": False}
# Use NX session to get expressions
expressions = await self._get_nx_expressions(model_path)
# Classify expressions as potential DVs
candidates = []
for expr in expressions:
score = self._score_dv_candidate(expr)
if score > 0.5:
candidates.append({
"name": expr["name"],
"value": expr["value"],
"formula": expr.get("formula", ""),
"score": score,
"suggested_bounds": self._suggest_bounds(expr)
})
# Sort by score
candidates.sort(key=lambda x: x["score"], reverse=True)
return {
"preview": f"Found {len(expressions)} expressions, {len(candidates)} are DV candidates",
"expressions": expressions,
"candidates": candidates[:10], # Top 10
"spec_changed": False
}
def _score_dv_candidate(self, expr: Dict) -> float:
"""Score expression as design variable candidate"""
score = 0.0
name = expr["name"].lower()
# Geometric parameters score high
if any(kw in name for kw in ["thickness", "width", "height", "radius", "diameter", "depth"]):
score += 0.4
# Numeric with reasonable value
if isinstance(expr["value"], (int, float)) and expr["value"] > 0:
score += 0.2
# Not a formula (pure number)
if not expr.get("formula") or expr["formula"] == str(expr["value"]):
score += 0.2
# Common design parameter names
if any(kw in name for kw in ["rib", "web", "flange", "support", "angle"]):
score += 0.2
return min(score, 1.0)
```
---
## Phase 3: Interview Engine (1-2 weeks)
### Goal: Guided study creation through conversation
### 3.1 Interview Engine
```python
class InterviewEngine:
"""Guided study creation through conversation"""
PHASES = [
("welcome", "What kind of optimization do you want to set up?"),
("model", "What's the path to your NX simulation file (.sim)?"),
("objectives", "What do you want to optimize? (e.g., minimize mass, minimize displacement)"),
("design_vars", "Which parameters should vary? I can suggest some from your model."),
("constraints", "Any constraints to respect? (e.g., max stress ≤ 200 MPa)"),
("method", "I recommend {method} for this. Sound good?"),
("review", "Here's your configuration. Ready to create the study?"),
]
def __init__(self):
self.phase_index = 0
self.collected = {}
self.spec_builder = SpecBuilder()
def get_current_question(self) -> str:
phase, question = self.PHASES[self.phase_index]
# Dynamic question customization
if phase == "method":
method = self._recommend_method()
question = question.format(method=method)
elif phase == "design_vars" and self.collected.get("model_expressions"):
candidates = self.collected["model_expressions"][:5]
question += f"\n\nI found these candidates: {', '.join(c['name'] for c in candidates)}"
return question
def process_answer(self, answer: str) -> Dict:
"""Process user's answer and advance interview"""
phase, _ = self.PHASES[self.phase_index]
# Extract structured data based on phase
extracted = self._extract_for_phase(phase, answer)
self.collected[phase] = extracted
# Build spec incrementally
spec_changes = self.spec_builder.apply(phase, extracted)
# Advance
self.phase_index += 1
complete = self.phase_index >= len(self.PHASES)
return {
"phase_completed": phase,
"extracted": extracted,
"spec_changes": spec_changes,
"next_question": None if complete else self.get_current_question(),
"complete": complete,
"spec": self.spec_builder.get_spec() if complete else None
}
def _extract_for_phase(self, phase: str, answer: str) -> Dict:
"""Extract structured data from natural language answer"""
if phase == "model":
# Extract file path
return {"path": self._extract_path(answer)}
elif phase == "objectives":
# Extract objectives
objectives = []
if "mass" in answer.lower() or "weight" in answer.lower():
direction = "minimize" if "minimize" in answer.lower() or "reduce" in answer.lower() else "minimize"
objectives.append({"name": "mass", "direction": direction})
if "displacement" in answer.lower() or "stiff" in answer.lower():
objectives.append({"name": "max_displacement", "direction": "minimize"})
if "stress" in answer.lower():
objectives.append({"name": "max_stress", "direction": "minimize"})
if "wfe" in answer.lower() or "wavefront" in answer.lower():
objectives.append({"name": "wfe", "direction": "minimize"})
return {"objectives": objectives}
elif phase == "constraints":
# Extract constraints
constraints = []
import re
# Pattern: "stress < 200 MPa" or "max stress <= 200"
stress_match = re.search(r'stress[^0-9]*([<>=]+)\s*(\d+)', answer.lower())
if stress_match:
constraints.append({
"name": "max_stress",
"operator": stress_match.group(1),
"threshold": float(stress_match.group(2))
})
return {"constraints": constraints}
return {"raw": answer}
def _recommend_method(self) -> str:
"""Recommend optimization method based on collected info"""
objectives = self.collected.get("objectives", {}).get("objectives", [])
if len(objectives) > 1:
return "NSGA-II (multi-objective)"
return "TPE (Bayesian optimization)"
```
### 3.2 Interview Tool Integration
```python
async def _tool_start_interview(self, params: Dict) -> Dict:
"""Start guided study creation"""
self.interview = InterviewEngine()
question = self.interview.get_current_question()
return {
"preview": f"Starting interview.\n\n{question}",
"interview_started": True,
"spec_changed": False
}
async def _tool_interview_answer(self, params: Dict) -> Dict:
"""Process interview answer"""
if not self.interview:
return {"preview": "No interview in progress", "spec_changed": False}
result = self.interview.process_answer(params["answer"])
response = f"Got it: {result['phase_completed']}\n\n"
if result["spec_changes"]:
response += "Updated configuration:\n"
for change in result["spec_changes"]:
response += f"{change}\n"
if result["next_question"]:
response += f"\n{result['next_question']}"
elif result["complete"]:
response += "\n✓ Interview complete! Creating study..."
# Auto-create the study
self.spec_store.set(result["spec"])
return {
"preview": response,
"spec_changed": result["complete"],
"complete": result["complete"]
}
```
---
## Phase 4: Visual Polish (1 week)
### Goal: Beautiful, responsive canvas updates
### 4.1 Tool Call Visualization
```typescript
// components/chat/ToolCallIndicator.tsx
function ToolCallIndicator({ tool, status }: { tool: string; status: 'running' | 'complete' }) {
const icons: Record<string, JSX.Element> = {
add_design_variable: <Variable className="w-4 h-4" />,
add_extractor: <Cpu className="w-4 h-4" />,
add_objective: <Target className="w-4 h-4" />,
add_constraint: <Lock className="w-4 h-4" />,
create_study: <FolderPlus className="w-4 h-4" />,
introspect_model: <Search className="w-4 h-4" />,
};
return (
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg ${
status === 'running'
? 'bg-amber-500/10 text-amber-400 border border-amber-500/20'
: 'bg-green-500/10 text-green-400 border border-green-500/20'
}`}>
{status === 'running' ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Check className="w-4 h-4" />
)}
{icons[tool] || <Wrench className="w-4 h-4" />}
<span className="text-sm font-medium">
{formatToolName(tool)}
</span>
</div>
);
}
```
### 4.2 Canvas Node Animation
```typescript
// components/canvas/AnimatedNode.tsx
function AnimatedNode({ data, isNew, isHighlighted }) {
return (
<motion.div
initial={isNew ? { scale: 0, opacity: 0 } : false}
animate={{
scale: 1,
opacity: 1,
boxShadow: isHighlighted
? '0 0 0 3px rgba(245, 158, 11, 0.5)'
: 'none'
}}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
className="node-container"
>
{/* Node content */}
</motion.div>
);
}
```
### 4.3 Connection Line Animation
```typescript
// Draw animated line when Claude adds an edge
function AnimatedEdge({ source, target, isNew }) {
return (
<motion.path
d={getBezierPath({ source, target })}
initial={isNew ? { pathLength: 0, opacity: 0 } : false}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
stroke="currentColor"
strokeWidth={2}
fill="none"
/>
);
}
```
---
## User Experience Flows
### Flow 1: Quick Creation (Experienced User)
```
User: "Create bracket optimization, minimize mass, thickness 2-10mm, max stress 200 MPa"
Claude: [Parses complete intent]
🔧 Creating study "bracket_optimization"
🔧 Adding design variable: thickness [2mm - 10mm]
🔧 Adding extractor: mass
🔧 Adding extractor: max_stress
🔧 Adding objective: minimize mass
🔧 Adding constraint: stress ≤ 200 MPa
✓ Created! Canvas shows your setup. Click any node to adjust.
[Canvas animates: DV appears → Model → Extractors → Objectives/Constraints]
[Total time: ~5 seconds]
```
### Flow 2: Guided Interview (New User)
```
User: "Help me set up an optimization"
Claude: What kind of optimization do you want to set up?
(e.g., bracket stiffness, mirror WFE, beam stress)
User: "Mirror surface quality optimization"
Claude: Got it - mirror optimization!
What's the path to your NX simulation file?
(You can drag & drop or paste the path)
User: "studies/M1_Mirror/model.sim"
Claude: 🔧 Loading model...
Found your model with 15 expressions. These look like good design variables:
- rib_thickness (8mm)
- mirror_depth (50mm)
- support_angle (45°)
What are you trying to optimize?
[Canvas: Model node appears]
User: "Minimize wavefront error and mass"
Claude: 🔧 Adding Zernike WFE extractor
🔧 Adding mass extractor
🔧 Adding objectives (multi-objective)
Multi-objective optimization! I recommend NSGA-II.
Which parameters should vary?
[Canvas: Extractors and objectives appear, connected to model]
User: "Use rib_thickness and mirror_depth, 50% range on each"
Claude: 🔧 Adding rib_thickness [4mm - 12mm]
🔧 Adding mirror_depth [25mm - 75mm]
Any constraints?
[Canvas: DVs appear, connected to model]
User: "Keep stress under 150 MPa"
Claude: 🔧 Adding stress extractor
🔧 Adding constraint: stress ≤ 150 MPa
✓ Configuration complete!
Your setup:
- 2 design variables
- 3 extractors (WFE, mass, stress)
- 2 objectives (multi-objective)
- 1 constraint
Ready to run? Say "start" or adjust the canvas first.
[Canvas: Full optimization flow visible]
```
### Flow 3: User Edits, Claude Adapts
```
[User clicks rib_thickness node, changes max from 12mm to 20mm]
User: "Is this a good range?"
Claude: I see you expanded rib_thickness to [4mm - 20mm].
That's a wide range! Considerations:
- Thicker ribs = more mass, more stiffness
- 20mm might exceed typical manufacturing limits
- Wider range = more trials to explore
If you're exploring, this is fine. For production,
I'd suggest [4mm - 15mm].
Want me to adjust, or keep your change?
```
---
## Implementation Priority
### Week 1-2: Unified WebSocket
- [ ] Create `/api/atomizer/ws` endpoint
- [ ] Implement streaming in `AtomizerClaudeAgent`
- [ ] Create `useAtomizerSocket` hook
- [ ] Wire canvas to receive spec updates
- [ ] Add canvas edit notifications
### Week 3-4: Tools & Interview
- [ ] Add `create_study` tool
- [ ] Add `introspect_model` tool
- [ ] Implement `InterviewEngine`
- [ ] Add interview tools
- [ ] Test guided creation flow
### Week 5: Polish
- [ ] Tool call indicators in chat
- [ ] Node appear/highlight animations
- [ ] Edge draw animations
- [ ] Error recovery & reconnection
- [ ] Performance optimization
---
## Success Metrics
| Metric | Target |
|--------|--------|
| Study creation time (experienced) | < 30 seconds |
| Study creation time (interview) | < 3 minutes |
| Canvas update latency | < 200ms |
| User edit → Claude context | < 100ms |
| Interview completion rate | > 90% |
---
## Key Files to Modify
### Backend
- `atomizer-dashboard/backend/api/routes/atomizer_ws.py` (new)
- `atomizer-dashboard/backend/api/services/claude_agent.py` (enhance)
- `atomizer-dashboard/backend/api/services/interview_engine.py` (new)
### Frontend
- `atomizer-dashboard/frontend/src/hooks/useAtomizerSocket.ts` (new)
- `atomizer-dashboard/frontend/src/pages/CanvasView.tsx` (update)
- `atomizer-dashboard/frontend/src/components/canvas/SpecRenderer.tsx` (update)
- `atomizer-dashboard/frontend/src/components/chat/ToolCallIndicator.tsx` (new)
---
*This architecture makes Atomizer uniquely powerful: natural language + visual feedback + full control, all in one seamless experience.*