From 4169fa7576448b0ab9e97fddd8229f0a0e25e184 Mon Sep 17 00:00:00 2001 From: Devesh36 <142524747+Devesh36@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:32:04 +0530 Subject: [PATCH 1/3] feat: implement SPICE error handling and loading state in schematic viewer --- .../example15-spice-error-test.fixture.tsx | 17 ++ lib/components/LoadingState.tsx | 92 ++++++ lib/components/SchematicViewer.tsx | 11 +- lib/components/SpiceErrorDisplay.tsx | 285 ++++++++++++++++++ lib/components/SpicePlot.tsx | 37 +-- lib/components/SpiceSimulationOverlay.tsx | 5 + lib/hooks/useSpiceSimulation.ts | 7 +- lib/index.ts | 2 + lib/utils/spice-error-utils.ts | 177 +++++++++++ 9 files changed, 605 insertions(+), 28 deletions(-) create mode 100644 examples/example15-spice-error-test.fixture.tsx create mode 100644 lib/components/LoadingState.tsx create mode 100644 lib/components/SpiceErrorDisplay.tsx create mode 100644 lib/utils/spice-error-utils.ts diff --git a/examples/example15-spice-error-test.fixture.tsx b/examples/example15-spice-error-test.fixture.tsx new file mode 100644 index 0000000..e600167 --- /dev/null +++ b/examples/example15-spice-error-test.fixture.tsx @@ -0,0 +1,17 @@ +import { SchematicViewer } from "lib/components/SchematicViewer" +import { renderToCircuitJson } from "lib/dev/render-to-circuit-json" + +export default () => ( + + {/* This circuit should trigger a SPICE error due to missing output specification */} + + + {/* No trace or source - this should cause validation errors */} + , + )} + containerStyle={{ height: "100%" }} + spiceSimulationEnabled={true} + /> +) diff --git a/lib/components/LoadingState.tsx b/lib/components/LoadingState.tsx new file mode 100644 index 0000000..f5f7ca4 --- /dev/null +++ b/lib/components/LoadingState.tsx @@ -0,0 +1,92 @@ +interface LoadingStateProps { + message?: string + subMessage?: string + progress?: number +} + +export const LoadingState: React.FC = ({ + message = "Running simulation...", + subMessage = "Analyzing circuit and computing results", + progress, +}) => { + return ( +
+
+ +
+
+ {message} +
+
{subMessage}
+
+ + {progress !== undefined && ( +
+
+
+
+
+ {Math.round(progress)}% complete +
+
+ )} + + +
+ ) +} diff --git a/lib/components/SchematicViewer.tsx b/lib/components/SchematicViewer.tsx index 51e782a..5b2b172 100644 --- a/lib/components/SchematicViewer.tsx +++ b/lib/components/SchematicViewer.tsx @@ -101,6 +101,7 @@ export const SchematicViewer = ({ ]) const [hasSpiceSimRun, setHasSpiceSimRun] = useState(false) + const [spiceRetryCounter, setSpiceRetryCounter] = useState(0) useEffect(() => { setHasSpiceSimRun(false) @@ -111,7 +112,7 @@ export const SchematicViewer = ({ nodes, isLoading: isSpiceSimLoading, error: spiceSimError, - } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null) + } = useSpiceSimulation(hasSpiceSimRun ? spiceString : null, spiceRetryCounter) const [editModeEnabled, setEditModeEnabled] = useState(defaultEditMode) const [snapToGrid, setSnapToGrid] = useState(true) @@ -339,7 +340,9 @@ export const SchematicViewer = ({ {onSchematicComponentClicked && ( )}
{ + setHasSpiceSimRun(true) + setSpiceRetryCounter((prev) => prev + 1) + }} /> )} {onSchematicComponentClicked && diff --git a/lib/components/SpiceErrorDisplay.tsx b/lib/components/SpiceErrorDisplay.tsx new file mode 100644 index 0000000..8c31c39 --- /dev/null +++ b/lib/components/SpiceErrorDisplay.tsx @@ -0,0 +1,285 @@ +import { useState } from "react" +import { + categorizeSpiceError, + getErrorIcon, + getErrorColor, +} from "../utils/spice-error-utils" + +interface SpiceErrorDisplayProps { + error: string + onRetry?: () => void + onCopyDetails?: () => void + showTechnicalDetails?: boolean +} + +export const SpiceErrorDisplay: React.FC = ({ + error, + onRetry, + onCopyDetails, + showTechnicalDetails = false, +}) => { + const [showFullDetails, setShowFullDetails] = useState(false) + const errorDetails = categorizeSpiceError(error) + + const handleCopyDetails = () => { + const fullErrorText = `Error Type: ${errorDetails.type}\nTitle: ${errorDetails.title}\nMessage: ${errorDetails.userMessage}\nTechnical Details: ${errorDetails.technicalMessage}\nSuggestions:\n${errorDetails.suggestions.map((s) => `- ${s}`).join("\n")}` + + if (navigator.clipboard) { + navigator.clipboard.writeText(fullErrorText) + } else { + // Fallback for older browsers + const textArea = document.createElement("textarea") + textArea.value = fullErrorText + document.body.appendChild(textArea) + textArea.select() + document.execCommand("copy") + document.body.removeChild(textArea) + } + + onCopyDetails?.() + } + + return ( +
+
+ {/* Error Header */} +
+ + {getErrorIcon(errorDetails.type)} + +
+

+ {errorDetails.title} +

+
+ {errorDetails.type} error +
+
+
+ + {/* User-friendly message */} +

+ {errorDetails.userMessage} +

+ + {/* Suggestions */} + {errorDetails.suggestions.length > 0 && ( +
+
+ Suggestions: +
+
    + {errorDetails.suggestions.map((suggestion, index) => ( +
  • + {suggestion} +
  • + ))} +
+
+ )} + + {/* Action buttons */} +
+ {errorDetails.canRetry && onRetry && ( + + )} + + + + {(showTechnicalDetails || + errorDetails.technicalMessage !== error) && ( + + )} +
+ + {/* Technical details (expandable) */} + {showFullDetails && ( +
+ {errorDetails.technicalMessage} +
+ )} +
+
+ ) +} diff --git a/lib/components/SpicePlot.tsx b/lib/components/SpicePlot.tsx index 1873507..854e427 100644 --- a/lib/components/SpicePlot.tsx +++ b/lib/components/SpicePlot.tsx @@ -12,6 +12,8 @@ import { } from "chart.js" import { Line } from "react-chartjs-2" import type { PlotPoint } from "../hooks/useSpiceSimulation" +import { SpiceErrorDisplay } from "./SpiceErrorDisplay" +import { LoadingState } from "./LoadingState" ChartJS.register( CategoryScale, @@ -57,12 +59,14 @@ export const SpicePlot = ({ isLoading, error, hasRun, + onRetry, }: { plotData: PlotPoint[] nodes: string[] isLoading: boolean error: string | null hasRun: boolean + onRetry?: () => void }) => { const yAxisLabel = useMemo(() => { const hasVoltage = nodes.some((n) => n.toLowerCase().startsWith("v(")) @@ -74,19 +78,7 @@ export const SpicePlot = ({ }, [nodes]) if (isLoading) { - return ( -
- Running simulation... -
- ) + return } if (!hasRun) { @@ -107,18 +99,15 @@ export const SpicePlot = ({ if (error) { return ( -
{ + // Optional: show toast notification when copied + console.log("Error details copied to clipboard") }} - > - Error: {error} -
+ showTechnicalDetails={true} + /> ) } diff --git a/lib/components/SpiceSimulationOverlay.tsx b/lib/components/SpiceSimulationOverlay.tsx index d241287..3548642 100644 --- a/lib/components/SpiceSimulationOverlay.tsx +++ b/lib/components/SpiceSimulationOverlay.tsx @@ -19,6 +19,7 @@ interface SpiceSimulationOverlayProps { options: SpiceSimulationOverlayProps["simOptions"], ) => void hasRun: boolean + onRetry?: () => void } export const SpiceSimulationOverlay = ({ @@ -31,6 +32,7 @@ export const SpiceSimulationOverlay = ({ simOptions, onSimOptionsChange, hasRun, + onRetry, }: SpiceSimulationOverlayProps) => { const [startTimeDraft, setStartTimeDraft] = useState( String(simOptions.startTime), @@ -107,6 +109,7 @@ export const SpiceSimulationOverlay = ({ SPICE Simulation