chore: Project cleanup and Canvas UX improvements (Phase 7-9)

## Cleanup (v0.5.0)
- Delete 102+ orphaned MCP session temp files
- Remove build artifacts (htmlcov, dist, __pycache__)
- Archive superseded plan docs (RALPH_LOOP V2/V3, CANVAS V3, etc.)
- Move debug/analysis scripts from tests/ to tools/analysis/
- Archive redundant NX journals to archive/nx_journals/
- Archive monolithic PROTOCOL.md to docs/archive/
- Update .gitignore with missing patterns
- Clean old study files (optimization_log_old.txt, run_optimization_old.py)

## Canvas UX (Phases 7-9)
- Phase 7: Resizable panels with localStorage persistence
  - Left sidebar: 200-400px, Right panel: 280-600px
  - New useResizablePanel hook and ResizeHandle component
- Phase 8: Enable all palette items
  - All 8 node types now draggable
  - Singleton logic for model/solver/algorithm/surrogate
- Phase 9: Solver configuration
  - Add SolverEngine type (nxnastran, mscnastran, python, etc.)
  - Add NastranSolutionType (SOL101-SOL200)
  - Engine/solution dropdowns in config panel
  - Python script path support

## Documentation
- Update CHANGELOG.md with recent versions
- Update docs/00_INDEX.md
- Create examples/README.md
- Add docs/plans/CANVAS_UX_IMPROVEMENTS.md
This commit is contained in:
2026-01-24 15:17:34 -05:00
parent 2cb8dccc3a
commit a3f18dc377
38 changed files with 1172 additions and 2570 deletions

View File

@@ -49,16 +49,23 @@ import { validateSpec, canRunOptimization } from '../../lib/validation/specValid
// Drag-Drop Helpers
// ============================================================================
/** Addable node types via drag-drop */
const ADDABLE_NODE_TYPES = ['designVar', 'extractor', 'objective', 'constraint'] as const;
import { SINGLETON_TYPES } from './palette/NodePalette';
/** All node types that can be added via drag-drop */
const ADDABLE_NODE_TYPES = ['model', 'solver', 'designVar', 'extractor', 'objective', 'constraint', 'algorithm', 'surrogate'] as const;
type AddableNodeType = typeof ADDABLE_NODE_TYPES[number];
function isAddableNodeType(type: string): type is AddableNodeType {
return ADDABLE_NODE_TYPES.includes(type as AddableNodeType);
}
/** Check if a node type is a singleton (only one allowed) */
function isSingletonType(type: string): boolean {
return SINGLETON_TYPES.includes(type as typeof SINGLETON_TYPES[number]);
}
/** Maps canvas NodeType to spec API type */
function mapNodeTypeToSpecType(type: AddableNodeType): 'designVar' | 'extractor' | 'objective' | 'constraint' {
function mapNodeTypeToSpecType(type: AddableNodeType): 'designVar' | 'extractor' | 'objective' | 'constraint' | 'model' | 'solver' | 'algorithm' | 'surrogate' {
return type;
}
@@ -67,6 +74,22 @@ function getDefaultNodeData(type: AddableNodeType, position: { x: number; y: num
const timestamp = Date.now();
switch (type) {
case 'model':
return {
name: 'Model',
sim: {
path: '',
solver: 'nastran',
},
canvas_position: position,
};
case 'solver':
return {
name: 'Solver',
engine: 'nxnastran',
solution_type: 'SOL101',
canvas_position: position,
};
case 'designVar':
return {
name: `variable_${timestamp}`,
@@ -130,6 +153,23 @@ function getDefaultNodeData(type: AddableNodeType, position: { x: number; y: num
enabled: true,
canvas_position: position,
};
case 'algorithm':
return {
name: 'Algorithm',
type: 'TPE',
budget: {
max_trials: 100,
},
canvas_position: position,
};
case 'surrogate':
return {
name: 'Surrogate',
enabled: false,
model_type: 'MLP',
min_trials: 20,
canvas_position: position,
};
}
}
@@ -602,6 +642,18 @@ function SpecRendererInner({
return;
}
// Check if this is a singleton type that already exists
if (isSingletonType(type)) {
const existingNode = localNodes.find(n => n.type === type);
if (existingNode) {
// Select the existing node instead of creating a duplicate
selectNode(existingNode.id);
// Show a toast notification would be nice here
console.log(`${type} already exists - selected existing node`);
return;
}
}
// Convert screen position to flow position
const position = reactFlowInstance.current.screenToFlowPosition({
x: event.clientX,
@@ -612,8 +664,19 @@ function SpecRendererInner({
const nodeData = getDefaultNodeData(type, position);
const specType = mapNodeTypeToSpecType(type);
// For structural types (model, solver, algorithm, surrogate), these are
// part of the spec structure rather than array items. Handle differently.
const structuralTypes = ['model', 'solver', 'algorithm', 'surrogate'];
if (structuralTypes.includes(type)) {
// These nodes are derived from spec structure - they shouldn't be "added"
// They already exist if the spec has that section configured
console.log(`${type} is a structural node - configure via spec directly`);
setError(`${type} nodes are configured via the spec. Use the config panel to edit.`);
return;
}
try {
const nodeId = await addNode(specType, nodeData);
const nodeId = await addNode(specType as 'designVar' | 'extractor' | 'objective' | 'constraint', nodeData);
// Select the newly created node
selectNode(nodeId);
} catch (err) {
@@ -621,7 +684,7 @@ function SpecRendererInner({
setError(err instanceof Error ? err.message : 'Failed to add node');
}
},
[editable, addNode, selectNode, setError]
[editable, addNode, selectNode, setError, localNodes]
);
// Loading state