Skip to content

Commit 6a155b9

Browse files
committed
Add error boundary logic
1 parent 1daf323 commit 6a155b9

11 files changed

Lines changed: 193 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,
@@ -354,7 +356,9 @@ export const AiCell: React.FC<AiCellProps> = ({
354356
)}
355357

356358
{/* Outputs Section */}
357-
{hasOutputs && cell.outputVisible && renderOutputs()}
359+
<ErrorBoundary FallbackComponent={OutputsErrorBoundary}>
360+
{cell.outputVisible && <MaybeOutputs />}
361+
</ErrorBoundary>
358362
</CellContainer>
359363
);
360364
};

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,
@@ -211,35 +213,39 @@ export const Cell: React.FC<CellProps> = ({
211213
// Route to specialized cell components
212214
if (cell.cellType === "sql") {
213215
return (
214-
<SqlCell
215-
cell={cell}
216-
onAddCell={onAddCell}
217-
onDeleteCell={onDeleteCell}
218-
onMoveUp={onMoveUp}
219-
onMoveDown={onMoveDown}
220-
onFocusNext={onFocusNext}
221-
onFocusPrevious={onFocusPrevious}
222-
autoFocus={autoFocus}
223-
onFocus={onFocus}
224-
contextSelectionMode={contextSelectionMode}
225-
/>
216+
<ErrorBoundary fallback={<div>Error rendering SQL cell</div>}>
217+
<SqlCell
218+
cell={cell}
219+
onAddCell={onAddCell}
220+
onDeleteCell={onDeleteCell}
221+
onMoveUp={onMoveUp}
222+
onMoveDown={onMoveDown}
223+
onFocusNext={onFocusNext}
224+
onFocusPrevious={onFocusPrevious}
225+
autoFocus={autoFocus}
226+
onFocus={onFocus}
227+
contextSelectionMode={contextSelectionMode}
228+
/>
229+
</ErrorBoundary>
226230
);
227231
}
228232

229233
if (cell.cellType === "ai") {
230234
return (
231-
<AiCell
232-
cell={cell}
233-
onAddCell={onAddCell}
234-
onDeleteCell={onDeleteCell}
235-
onMoveUp={onMoveUp}
236-
onMoveDown={onMoveDown}
237-
onFocusNext={onFocusNext}
238-
onFocusPrevious={onFocusPrevious}
239-
autoFocus={autoFocus}
240-
onFocus={onFocus}
241-
contextSelectionMode={contextSelectionMode}
242-
/>
235+
<ErrorBoundary fallback={<div>Error rendering AI cell</div>}>
236+
<AiCell
237+
cell={cell}
238+
onAddCell={onAddCell}
239+
onDeleteCell={onDeleteCell}
240+
onMoveUp={onMoveUp}
241+
onMoveDown={onMoveDown}
242+
onFocusNext={onFocusNext}
243+
onFocusPrevious={onFocusPrevious}
244+
autoFocus={autoFocus}
245+
onFocus={onFocus}
246+
contextSelectionMode={contextSelectionMode}
247+
/>
248+
</ErrorBoundary>
243249
);
244250
}
245251

@@ -363,15 +369,17 @@ export const Cell: React.FC<CellProps> = ({
363369
{/* Editor Content Area */}
364370
{cell.sourceVisible && (
365371
<div className="cell-content bg-white py-1 pl-4 transition-colors">
366-
<Editor
367-
localSource={localSource}
368-
handleSourceChange={handleSourceChange}
369-
updateSource={updateSource}
370-
handleFocus={handleFocus}
371-
cell={cell}
372-
autoFocus={autoFocus}
373-
keyMap={keyMap}
374-
/>
372+
<ErrorBoundary fallback={<div>Error rendering editor</div>}>
373+
<Editor
374+
localSource={localSource}
375+
handleSourceChange={handleSourceChange}
376+
updateSource={updateSource}
377+
handleFocus={handleFocus}
378+
cell={cell}
379+
autoFocus={autoFocus}
380+
keyMap={keyMap}
381+
/>
382+
</ErrorBoundary>
375383
</div>
376384
)}
377385
</div>
@@ -443,8 +451,9 @@ export const Cell: React.FC<CellProps> = ({
443451
</div>
444452
</div>
445453
)}
446-
447-
{hasOutputs && renderOutputs()}
454+
<ErrorBoundary FallbackComponent={OutputsErrorBoundary}>
455+
{hasOutputs && <MaybeOutputs />}
456+
</ErrorBoundary>
448457
</div>
449458
)}
450459
</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)