Skip to content

Commit e967891

Browse files
authored
Add error boundary logic (#210)
Add error boundary logic
2 parents 6068ba7 + a2898c4 commit e967891

11 files changed

Lines changed: 192 additions & 123 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"lucide-react": "^0.514.0",
7575
"react": "19.0.0",
7676
"react-dom": "19.0.0",
77+
"react-error-boundary": "^6.0.0",
7778
"react-markdown": "^10.1.0",
7879
"react-syntax-highlighter": "^15.6.1",
7980
"strip-ansi": "^7.1.0",

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Root.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { getCurrentNotebookId, getStoreId } from "./util/store-id.js";
2121
import { useStore } from "@livestore/react";
2222
import { queryDb } from "@livestore/livestore";
2323
import { getCurrentAuthToken, isAuthStateValid } from "./auth/google-auth.js";
24+
import { ErrorBoundary } from "react-error-boundary";
2425

2526
const NotebookApp: React.FC = () => {
2627
// In the simplified architecture, we always show the current notebook
@@ -168,11 +169,13 @@ const NotebookApp: React.FC = () => {
168169
</div>
169170
)}
170171
{/* Main Content */}
171-
<NotebookViewer
172-
notebookId={currentNotebookId}
173-
debugMode={debugMode}
174-
onDebugToggle={setDebugMode}
175-
/>
172+
<ErrorBoundary fallback={<div>Error loading notebook</div>}>
173+
<NotebookViewer
174+
notebookId={currentNotebookId}
175+
debugMode={debugMode}
176+
onDebugToggle={setDebugMode}
177+
/>
178+
</ErrorBoundary>
176179
</div>
177180
);
178181
};

src/components/notebook/AiCell.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { PlayButton } from "./shared/PlayButton.js";
1414
import { AiCellTypeSelector } from "./shared/AiCellTypeSelector.js";
1515
import { Button } from "@/components/ui/button";
1616
import { ChevronUp, ChevronDown } from "lucide-react";
17+
import { ErrorBoundary } from "react-error-boundary";
18+
import { OutputsErrorBoundary } from "./shared/OutputsErrorBoundary.js";
1719

1820
interface AiCellProps {
1921
cell: typeof tables.cells.Type;
@@ -56,7 +58,7 @@ export const AiCell: React.FC<AiCellProps> = ({
5658
});
5759

5860
// Use shared outputs hook with AI-specific configuration
59-
const { outputs, hasOutputs, renderOutputs } = useCellOutputs({
61+
const { outputs, hasOutputs, MaybeOutputs } = useCellOutputs({
6062
cellId: cell.id,
6163
groupConsecutiveStreams: false,
6264
enableErrorOutput: true,
@@ -368,7 +370,9 @@ export const AiCell: React.FC<AiCellProps> = ({
368370
)}
369371

370372
{/* Outputs Section */}
371-
{hasOutputs && cell.outputVisible && renderOutputs()}
373+
<ErrorBoundary FallbackComponent={OutputsErrorBoundary}>
374+
{cell.outputVisible && <MaybeOutputs />}
375+
</ErrorBoundary>
372376
</CellContainer>
373377
);
374378
};

src/components/notebook/Cell.tsx

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { PlayButton } from "./shared/PlayButton.js";
2121
import { CellTypeSelector } from "./shared/CellTypeSelector.js";
2222
import { CodeToolbar } from "./toolbars/CodeToolbar.js";
2323
import { MarkdownToolbar } from "./toolbars/MarkdownToolbar.js";
24+
import { ErrorBoundary } from "react-error-boundary";
25+
import { OutputsErrorBoundary } from "./shared/OutputsErrorBoundary.js";
2426

2527
type CellType = typeof tables.cells.Type;
2628

@@ -59,7 +61,7 @@ export const Cell: React.FC<CellProps> = ({
5961
});
6062

6163
// Use shared outputs hook with code-specific configuration
62-
const { outputs, hasOutputs, renderOutputs } = useCellOutputs({
64+
const { outputs, hasOutputs, MaybeOutputs } = useCellOutputs({
6365
cellId: cell.id,
6466
groupConsecutiveStreams: true,
6567
enableErrorOutput: true,
@@ -223,35 +225,39 @@ export const Cell: React.FC<CellProps> = ({
223225
// Route to specialized cell components
224226
if (cell.cellType === "sql") {
225227
return (
226-
<SqlCell
227-
cell={cell}
228-
onAddCell={onAddCell}
229-
onDeleteCell={onDeleteCell}
230-
onMoveUp={onMoveUp}
231-
onMoveDown={onMoveDown}
232-
onFocusNext={onFocusNext}
233-
onFocusPrevious={onFocusPrevious}
234-
autoFocus={autoFocus}
235-
onFocus={onFocus}
236-
contextSelectionMode={contextSelectionMode}
237-
/>
228+
<ErrorBoundary fallback={<div>Error rendering SQL cell</div>}>
229+
<SqlCell
230+
cell={cell}
231+
onAddCell={onAddCell}
232+
onDeleteCell={onDeleteCell}
233+
onMoveUp={onMoveUp}
234+
onMoveDown={onMoveDown}
235+
onFocusNext={onFocusNext}
236+
onFocusPrevious={onFocusPrevious}
237+
autoFocus={autoFocus}
238+
onFocus={onFocus}
239+
contextSelectionMode={contextSelectionMode}
240+
/>
241+
</ErrorBoundary>
238242
);
239243
}
240244

241245
if (cell.cellType === "ai") {
242246
return (
243-
<AiCell
244-
cell={cell}
245-
onAddCell={onAddCell}
246-
onDeleteCell={onDeleteCell}
247-
onMoveUp={onMoveUp}
248-
onMoveDown={onMoveDown}
249-
onFocusNext={onFocusNext}
250-
onFocusPrevious={onFocusPrevious}
251-
autoFocus={autoFocus}
252-
onFocus={onFocus}
253-
contextSelectionMode={contextSelectionMode}
254-
/>
247+
<ErrorBoundary fallback={<div>Error rendering AI cell</div>}>
248+
<AiCell
249+
cell={cell}
250+
onAddCell={onAddCell}
251+
onDeleteCell={onDeleteCell}
252+
onMoveUp={onMoveUp}
253+
onMoveDown={onMoveDown}
254+
onFocusNext={onFocusNext}
255+
onFocusPrevious={onFocusPrevious}
256+
autoFocus={autoFocus}
257+
onFocus={onFocus}
258+
contextSelectionMode={contextSelectionMode}
259+
/>
260+
</ErrorBoundary>
255261
);
256262
}
257263

@@ -377,15 +383,17 @@ export const Cell: React.FC<CellProps> = ({
377383
{/* Editor Content Area */}
378384
{cell.sourceVisible && (
379385
<div className="cell-content bg-white py-1 pl-4 transition-colors">
380-
<Editor
381-
localSource={localSource}
382-
handleSourceChange={handleSourceChange}
383-
updateSource={updateSource}
384-
handleFocus={handleFocus}
385-
cell={cell}
386-
autoFocus={autoFocus}
387-
keyMap={keyMap}
388-
/>
386+
<ErrorBoundary fallback={<div>Error rendering editor</div>}>
387+
<Editor
388+
localSource={localSource}
389+
handleSourceChange={handleSourceChange}
390+
updateSource={updateSource}
391+
handleFocus={handleFocus}
392+
cell={cell}
393+
autoFocus={autoFocus}
394+
keyMap={keyMap}
395+
/>
396+
</ErrorBoundary>
389397
</div>
390398
)}
391399
</div>
@@ -457,8 +465,9 @@ export const Cell: React.FC<CellProps> = ({
457465
</div>
458466
</div>
459467
)}
460-
461-
{hasOutputs && renderOutputs()}
468+
<ErrorBoundary FallbackComponent={OutputsErrorBoundary}>
469+
{hasOutputs && <MaybeOutputs />}
470+
</ErrorBoundary>
462471
</div>
463472
)}
464473
</CellContainer>

src/components/notebook/Editor.tsx

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import { Maximize2, Minimize2 } from "lucide-react";
66
import { useState } from "react";
77
import { Button } from "../ui/button";
88
import { CodeMirrorEditor } from "./codemirror/CodeMirrorEditor";
9+
import { ErrorBoundary } from "react-error-boundary";
10+
11+
const ErrorFallback = () => {
12+
return <div>Error rendering editor</div>;
13+
};
914

1015
export function Editor({
1116
localSource,
@@ -29,18 +34,20 @@ export function Editor({
2934
if (!isMaximized) {
3035
return (
3136
<div className={cn("relative min-h-[1.5rem]")}>
32-
<CodeMirrorEditor
33-
className="text-base sm:text-sm"
34-
language={languageFromCellType(cell.cellType)}
35-
placeholder={placeholderFromCellType(cell.cellType)}
36-
value={localSource}
37-
onValueChange={handleSourceChange}
38-
autoFocus={autoFocus}
39-
onFocus={handleFocus}
40-
keyMap={keyMap}
41-
onBlur={updateSource}
42-
enableLineWrapping={cell.cellType === "markdown"}
43-
/>
37+
<ErrorBoundary FallbackComponent={ErrorFallback}>
38+
<CodeMirrorEditor
39+
className="text-base sm:text-sm"
40+
language={languageFromCellType(cell.cellType)}
41+
placeholder={placeholderFromCellType(cell.cellType)}
42+
value={localSource}
43+
onValueChange={handleSourceChange}
44+
autoFocus={autoFocus}
45+
onFocus={handleFocus}
46+
keyMap={keyMap}
47+
onBlur={updateSource}
48+
enableLineWrapping={cell.cellType === "markdown"}
49+
/>
50+
</ErrorBoundary>
4451
<MaxMinButton
4552
className="absolute top-1 right-1 sm:hidden"
4653
isMaximized={isMaximized}
@@ -55,13 +62,15 @@ export function Editor({
5562
<Dialog.Root defaultOpen={true} onOpenChange={setIsMaximized}>
5663
<div className={cn("relative min-h-[1.5rem]")}>
5764
{/* Duplicate editor for dialog to prevent layout shift */}
58-
<CodeMirrorEditor
59-
className="text-base sm:text-sm"
60-
language={languageFromCellType(cell.cellType)}
61-
placeholder={placeholderFromCellType(cell.cellType)}
62-
value={localSource}
63-
enableLineWrapping={cell.cellType === "markdown"}
64-
/>
65+
<ErrorBoundary FallbackComponent={ErrorFallback}>
66+
<CodeMirrorEditor
67+
className="text-base sm:text-sm"
68+
language={languageFromCellType(cell.cellType)}
69+
placeholder={placeholderFromCellType(cell.cellType)}
70+
value={localSource}
71+
enableLineWrapping={cell.cellType === "markdown"}
72+
/>
73+
</ErrorBoundary>
6574
<MaxMinButton
6675
className="absolute top-1 right-1 sm:hidden"
6776
isMaximized={isMaximized}
@@ -82,18 +91,20 @@ export function Editor({
8291
onEscapeKeyDown={() => setIsMaximized(false)}
8392
>
8493
<Dialog.Title className="sr-only">Editor</Dialog.Title>
85-
<CodeMirrorEditor
86-
className="relative text-base sm:text-sm"
87-
maxHeight="100svh"
88-
language={languageFromCellType(cell.cellType)}
89-
placeholder={placeholderFromCellType(cell.cellType)}
90-
value={localSource}
91-
onValueChange={handleSourceChange}
92-
autoFocus={true}
93-
onFocus={handleFocus}
94-
onBlur={updateSource}
95-
enableLineWrapping={cell.cellType === "markdown"}
96-
/>
94+
<ErrorBoundary FallbackComponent={ErrorFallback}>
95+
<CodeMirrorEditor
96+
className="relative text-base sm:text-sm"
97+
maxHeight="100svh"
98+
language={languageFromCellType(cell.cellType)}
99+
placeholder={placeholderFromCellType(cell.cellType)}
100+
value={localSource}
101+
onValueChange={handleSourceChange}
102+
autoFocus={true}
103+
onFocus={handleFocus}
104+
onBlur={updateSource}
105+
enableLineWrapping={cell.cellType === "markdown"}
106+
/>
107+
</ErrorBoundary>
97108
<MaxMinButton
98109
className="top-1 right-1"
99110
isMaximized={isMaximized}

0 commit comments

Comments
 (0)