Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 102 additions & 83 deletions EngineDesign/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ConfigurationSelector from './components/ConfigurationSelector';
import { emitConfigChanged } from './lib/configBus';
import { useViewState } from './lib/viewState';
import { DesignVersions } from './components/DesignVersions';
import { ErrorBoundary } from './components/ErrorBoundary';
import { ReadOnlyProvider } from '@stardesign-ui';
import { getConfig, getHealth } from './api/client';
import type { EngineConfig } from './api/client';
Expand Down Expand Up @@ -237,117 +238,135 @@ function App() {

{/* Keep all tab panels mounted; hide inactive ones to preserve state */}
<div className={tabPanelClass('forward')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ForwardMode config={config} />
</div>
<ErrorBoundary label="Forward Mode">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ForwardMode config={config} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('timeseries')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<TimeSeriesMode config={config} onConfigLoaded={handleConfigLoaded} />
</div>
<ErrorBoundary label="Time Series">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<TimeSeriesMode config={config} onConfigLoaded={handleConfigLoaded} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('plotter')}>
<CustomPlotter isVisible={activeTab === 'plotter'} />
<ErrorBoundary label="Plotter">
<CustomPlotter isVisible={activeTab === 'plotter'} />
</ErrorBoundary>
</div>

<div className={tabPanelClass('flight')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<FlightSimulation config={config} isVisible={activeTab === 'flight'} onConfigUpdated={handleConfigLoaded} />
</div>
<ErrorBoundary label="Flight Simulation">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<FlightSimulation config={config} isVisible={activeTab === 'flight'} onConfigUpdated={handleConfigLoaded} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('geometry')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ChamberGeometry config={config} />
</div>
<ErrorBoundary label="Geometry">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ChamberGeometry config={config} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('optimizer')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<Optimizer config={config} />
</div>
<ErrorBoundary label="Optimizer">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<Optimizer config={config} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('controller')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ControllerMode config={config} />
</div>
<ErrorBoundary label="Controller">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<ControllerMode config={config} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('demo')}>
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<OptimizerDemo config={config} />
</div>
<ErrorBoundary label="Demo">
<div className="space-y-6">
{!config && (
<div className="p-5 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<h3 className="text-lg font-semibold mb-4 text-[var(--color-text-primary)]">Load Configuration</h3>
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
<OptimizerDemo config={config} />
</div>
</ErrorBoundary>
</div>

<div className={tabPanelClass('config')}>
<div className="space-y-6">
{/* Upload section - compact */}
<div className="p-4 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<div className="flex items-center gap-6">
<div className="flex-1">
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
{config && (
<div className="flex-shrink-0 px-4 py-2 bg-green-500/10 border border-green-500/30 rounded-lg text-green-400 text-sm flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
Config loaded and ready
<ErrorBoundary label="Configuration">
<div className="space-y-6">
{/* Upload section - compact */}
<div className="p-4 rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)]">
<div className="flex items-center gap-6">
<div className="flex-1">
<ConfigUpload onConfigLoaded={handleConfigLoaded} />
</div>
)}
{config && (
<div className="flex-shrink-0 px-4 py-2 bg-green-500/10 border border-green-500/30 rounded-lg text-green-400 text-sm flex items-center gap-2">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
Config loaded and ready
</div>
)}
</div>
</div>
</div>

{/* Editor section - full width */}
<div className="rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)] overflow-hidden" style={{ height: 'calc(100vh - 280px)', minHeight: '500px' }}>
<ConfigEditor config={config} onConfigUpdated={handleConfigLoaded} />
{/* Editor section - full width */}
<div className="rounded-xl bg-[var(--color-bg-secondary)] border border-[var(--color-border)] overflow-hidden" style={{ height: 'calc(100vh - 280px)', minHeight: '500px' }}>
<ConfigEditor config={config} onConfigUpdated={handleConfigLoaded} />
</div>
</div>
</div>
</ErrorBoundary>
</div>
</main>

Expand Down
75 changes: 75 additions & 0 deletions EngineDesign/frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Component, type ErrorInfo, type ReactNode } from 'react';

/**
* Catches render-time exceptions so one bad value cannot blank the whole app.
*
* Without a boundary anywhere in the tree, React unmounts EVERYTHING when a
* render throws -- the user sees a white page with no message, no stack, and no
* way to report what happened. Every render bug then looks identical, which is
* exactly how "press Optimize, page goes blank" got reported with nothing to go
* on. This keeps the failure on screen and legible instead.
*/
interface Props {
children: ReactNode;
/** Shown above the error, e.g. "Flight Simulation". */
label?: string;
}
interface State {
error: Error | null;
info: ErrorInfo | null;
}

export class ErrorBoundary extends Component<Props, State> {
state: State = { error: null, info: null };

static getDerivedStateFromError(error: Error): Partial<State> {
return { error };
}

componentDidCatch(error: Error, info: ErrorInfo) {
// Keep the console record: the boundary stops the crash from propagating,
// so without this the stack would be swallowed entirely.
console.error('[ErrorBoundary]', this.props.label ?? '', error, info.componentStack);
this.setState({ info });
}

private reset = () => this.setState({ error: null, info: null });

render() {
const { error, info } = this.state;
if (!error) return this.props.children;

const detail = [error.stack || String(error), info?.componentStack]
.filter(Boolean)
.join('\n\nComponent stack:');

return (
<div className="m-4 rounded-lg border border-red-500/40 bg-red-500/5 p-4">
<p className="font-medium text-red-300">
{this.props.label ? `${this.props.label} hit an error` : 'Something went wrong'}
</p>
<p className="mt-1 text-sm text-[var(--color-text-secondary)]">
The rest of the app is still running. Copy the detail below when reporting this.
</p>
<p className="mt-2 font-mono text-sm text-red-200">{String(error.message || error)}</p>
<details className="mt-2">
<summary className="cursor-pointer text-xs text-[var(--color-text-secondary)]">
Show stack
</summary>
<pre className="mt-2 max-h-64 overflow-auto whitespace-pre-wrap break-words rounded bg-black/30 p-2 text-xs text-[var(--color-text-secondary)]">
{detail}
</pre>
</details>
<button
type="button"
onClick={this.reset}
className="mt-3 rounded-lg bg-blue-600 px-3 py-1.5 text-sm text-white hover:bg-blue-500"
>
Try again
</button>
</div>
);
}
}

export default ErrorBoundary;
1 change: 1 addition & 0 deletions EngineDesign/frontend/src/lib/gating.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const VIEW_ONLY: Record<string, string> = {
'ConfigEditor.tsx:setSearchQuery': 'filters which sections are shown',
'ConfigEditor.tsx:setIsExpanded': 'expand/collapse a section',
'ConfigUpload.tsx:label': 'the drop zone wrapper, not a control',
'ErrorBoundary.tsx:this.reset': 'clears a caught render error; touches no design state',
'Layer1Optimization.tsx:setShowParameterPlots': 'chart visibility',
'Layer1Optimization.tsx:setShowInjectorPressures': 'chart visibility',
'Layer1Optimization.tsx:setShowSolverInputsEcho': 'diagnostics visibility',
Expand Down
Loading