diff --git a/atomizer-dashboard/frontend/src/components/canvas/panels/IntrospectionPanel.tsx b/atomizer-dashboard/frontend/src/components/canvas/panels/IntrospectionPanel.tsx
index 633d5e23..087f7b33 100644
--- a/atomizer-dashboard/frontend/src/components/canvas/panels/IntrospectionPanel.tsx
+++ b/atomizer-dashboard/frontend/src/components/canvas/panels/IntrospectionPanel.tsx
@@ -15,6 +15,10 @@ import {
FlaskConical,
SlidersHorizontal,
AlertTriangle,
+ Scale,
+ Link,
+ Box,
+ Settings2,
} from 'lucide-react';
import { useCanvasStore } from '../../../hooks/useCanvasStore';
@@ -316,6 +320,180 @@ export function IntrospectionPanel({ filePath, studyId, onClose }: Introspection
)}
+ {/* Mass Properties Section */}
+ {result.mass_properties && (
+
+
toggleSection('mass')}
+ className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
+ >
+
+
+ Mass Properties
+
+ {expandedSections.has('mass') ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSections.has('mass') && (
+
+ {result.mass_properties.mass_kg !== undefined && (
+
+ Mass
+
+ {(result.mass_properties.mass_kg as number).toFixed(4)} kg
+
+
+ )}
+ {result.mass_properties.volume_mm3 !== undefined && (result.mass_properties.volume_mm3 as number) > 0 && (
+
+ Volume
+
+ {((result.mass_properties.volume_mm3 as number) / 1e9).toFixed(6)} m³
+
+
+ )}
+ {result.mass_properties.surface_area_mm2 !== undefined && (result.mass_properties.surface_area_mm2 as number) > 0 && (
+
+ Surface Area
+
+ {((result.mass_properties.surface_area_mm2 as number) / 1e6).toFixed(4)} m²
+
+
+ )}
+ {Array.isArray(result.mass_properties.center_of_gravity_mm) && (
+
+ CoG (mm)
+
+ [{(result.mass_properties.center_of_gravity_mm as number[]).map((v: number) => v.toFixed(1)).join(', ')}]
+
+
+ )}
+ {typeof result.mass_properties.num_bodies === 'number' && (
+
+ Bodies
+ {result.mass_properties.num_bodies}
+
+ )}
+
+ )}
+
+ )}
+
+ {/* Linked Parts / File Dependencies Section */}
+ {result.linked_parts && (
+
+
toggleSection('linked')}
+ className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
+ >
+
+
+
+ Linked Parts ({(result.linked_parts.loaded_parts as Array<{name: string}>)?.length || 0})
+
+
+ {expandedSections.has('linked') ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSections.has('linked') && (
+
+ {((result.linked_parts.loaded_parts as Array<{name: string, path: string, leaf_name: string}>) || []).map((part) => (
+
+
{part.name}
+
+ {part.leaf_name || part.path.split(/[/\\]/).pop()}
+
+
+ ))}
+ {((result.linked_parts.loaded_parts as Array
) || []).length === 0 && (
+ No linked parts found
+ )}
+
+ )}
+
+ )}
+
+ {/* Bodies Section */}
+ {result.bodies && (result.bodies.counts as {total: number})?.total > 0 && (
+
+
toggleSection('bodies')}
+ className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
+ >
+
+
+
+ Bodies ({(result.bodies.counts as {total: number})?.total || 0})
+
+
+ {expandedSections.has('bodies') ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSections.has('bodies') && (
+
+
+ Solid Bodies
+ {(result.bodies.counts as {solid: number})?.solid || 0}
+
+
+ Sheet Bodies
+ {(result.bodies.counts as {sheet: number})?.sheet || 0}
+
+
+ )}
+
+ )}
+
+ {/* Units Section */}
+ {result.units && (
+
+
toggleSection('units')}
+ className="w-full flex items-center justify-between px-3 py-2 bg-dark-800 hover:bg-dark-750 transition-colors"
+ >
+
+
+
+ Units ({(result.units as {system?: string})?.system || 'Unknown'})
+
+
+ {expandedSections.has('units') ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSections.has('units') && (
+
+ {(result.units as {base_units?: Record
})?.base_units &&
+ Object.entries((result.units as {base_units: Record}).base_units).map(([key, value]) => (
+
+ {key}
+ {value}
+
+ ))
+ }
+
+ )}
+
+ )}
+
{/* Extractors Section - only show if available */}
{(result.extractors_available?.length ?? 0) > 0 && (
diff --git a/atomizer-dashboard/frontend/src/pages/CanvasView.tsx b/atomizer-dashboard/frontend/src/pages/CanvasView.tsx
index b5784eb0..7189322c 100644
--- a/atomizer-dashboard/frontend/src/pages/CanvasView.tsx
+++ b/atomizer-dashboard/frontend/src/pages/CanvasView.tsx
@@ -296,17 +296,34 @@ export function CanvasView() {
{/* Action Buttons */}
- {/* Save Button - only show when there's a study and changes */}
- {activeStudyId && (
+ {/* Save Button - always show in spec mode with study, grayed when no changes */}
+ {useSpecMode && spec && (
+
+ {isSaving ? 'Saving...' : 'Save'}
+
+ )}
+
+ {/* Legacy Save Button */}
+ {!useSpecMode && activeStudyId && (
+
{isSaving ? 'Saving...' : 'Save'}
@@ -314,7 +331,7 @@ export function CanvasView() {
)}
{/* Reload Button */}
- {activeStudyId && (
+ {(useSpecMode ? spec : activeStudyId) && (