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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user