fix(frontend): add error boundaries so one render throw doesn't blank the app - #159
Closed
lucastononro wants to merge 7 commits into
Closed
fix(frontend): add error boundaries so one render throw doesn't blank the app#159lucastononro wants to merge 7 commits into
lucastononro wants to merge 7 commits into
Conversation
… the app There was no app/error.tsx, app/global-error.tsx, or any ErrorBoundary in src/ — with untyped SSE payloads and live-rendered agent-authored markdown + self-contained HTML artifacts, one bad payload throwing in render took down the entire SPA with a blank screen. - src/app/error.tsx: route-level boundary (Next.js App Router convention) with a "Try again" / "Reload page" recovery screen. - src/app/global-error.tsx: root-layout boundary with its own <html>/ <body> and inline styles (no dependency on globals.css/Tailwind/app components, since those may be what failed to mount). - src/components/ErrorBoundary.tsx: reusable class-component boundary (no hook equivalent exists for getDerivedStateFromError/componentDidCatch) with an optional custom fallback + reset callback. - Wrapped the workspace canvas (WorkspaceSidebar, keyed by sessionId) and every markdown renderer (chat assistant bubbles, sub-agent result summaries, the report tab, and markdown file previews) so a crash is contained to that panel/bubble instead of the whole app. Closes #102 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
- ErrorBoundary: default export -> named export, per the components/ style guide (default export is reserved for page components). - global-error.tsx: log the caught error via useEffect + console.error (matching error.tsx) so root-layout crashes aren't silent; both are zero-dependency builtins, keeping the boundary dependency-free. - SubAgentCard: hoist the 800-char summary truncation so the error fallback renders the same bounded text as the ReactMarkdown path instead of dumping the full untruncated summary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wp66uSq9saSpRq9Bbmjxj4
Owner
Author
Greptile review follow-up (commit 11a197e)Fixed (3/3 — all findings were valid)
DismissedNone. TestsThe frontend has no test framework (repo convention per Manual test tutorial Setup:
Validation
|
…ptile P1 on #159) FileViewer is memoized and keeps its component instance across file switches, so a markdown render crash left the boundary stuck in its error fallback for every subsequent file. key={filePath} remounts the boundary per file.
lucastononro
added a commit
that referenced
this pull request
Jul 29, 2026
Owner
Author
|
Merged into integration branch |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
app/error.tsx,app/global-error.tsx, or anyErrorBoundaryinsrc/. Given untyped/unvalidated SSE payloads and live-rendered agent-authored markdown + self-contained HTML artifacts, one bad render throw took down the entire SPA with a blank screen.Changes
src/app/error.tsx— route-level boundary (Next.js App Router convention: a file namederror.tsxwraps its route segment, the whole app here since it's directly undersrc/app/). Shows a "Try again" (callsreset()) / "Reload page" recovery screen instead of a blank page.src/app/global-error.tsx— root-layout boundary.error.tsxcan't catch errors thrown by the root layout itself (or providers it mounts, e.g.AppProvider/ToastProvider), onlyglobal-error.tsxcan, and to do so it must render its own<html>/<body>(it replaces the root layout while active). Intentionally dependency-free — inline-styled, noglobals.css/Tailwind/app components, since those may be exactly what failed to mount.src/components/ErrorBoundary.tsx— reusable class-component boundary (getDerivedStateFromError/componentDidCatchhave no hook equivalent in React 18) with an optional customfallbackrender-prop and areset()callback.WorkspaceSidebar, keyed bysessionIdso switching sessions also clears any prior crash state) — protects file viewing, the report tab, metrics, lineage, notebooks, and HTML artifacts as one unit.ChatItemView), sub-agent result summaries (SubAgentCard), the report tab (ReportMarkdown), and markdown file previews (FileViewer).Test plan
Existing (must still work)
npm run buildpasses clean (next buildcompiles + prerenders all routes successfully).New
layout.tsx) — confirmglobal-error.tsxrenders (its own minimal styled page, independent of Tailwind) with working "Try again"/"Reload page" buttons.Closes #102
🤖 Generated with Claude Code
Greptile Summary
This PR adds three layers of error containment —
error.tsx(route-level),global-error.tsx(root-layout-level), and a reusableErrorBoundaryclass component — to prevent a single bad render from blanking the entire SPA. The boundaries are placed precisely where unvalidated SSE payloads and agent-authored content are rendered.ErrorBoundary.tsx: named-export class component withgetDerivedStateFromError/componentDidCatch, an optionalfallbackrender-prop, and a generic recovery panel; wraps the workspace canvas (keyed bysessionId), file markdown (keyed byfilePath), report tab,SubAgentCardsummaries, andChatItemViewbubbles.error.tsx/global-error.tsx: Next.js App Router conventions satisfied — route-level boundary lives within the root layout (Tailwind/lucide available), while the root-layout boundary renders its own<html>/<body>with inline styles only, independent of any provider that might have failed.page.tsx: extractstruncatedSummaryso both the normal markdown path and the error-boundary fallback render the same 800-character-capped text;FileViewer's boundary is keyed byfilePathso switching files always resets stale error state.Confidence Score: 5/5
Safe to merge — adds containment around existing renderers with no logic changes and no new data paths.
All three boundary layers follow Next.js App Router conventions correctly. Previously flagged issues are all addressed in this version of the PR.
Files Needing Attention: No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Render throw] --> B{Where did it occur?} B -->|Inside WorkspaceSidebar| C["ErrorBoundary (key=sessionId)\nWorkspace fallback panel\n+ Try again"] B -->|Inside FileViewer markdown| D["ErrorBoundary (key=filePath)\n'Couldn't render this file'\n+ Try again"] B -->|Inside ReportMarkdown| E["ErrorBoundary\n'Couldn't render this report'\n+ Try again"] B -->|Inside SubAgentCard| F["ErrorBoundary\nfallback: raw truncatedSummary text"] B -->|Inside ChatItemView| G["ErrorBoundary\nfallback: raw item.content text"] B -->|Escapes nested boundaries| H["error.tsx\nRoute-level boundary\nTailwind UI, logs via useEffect"] B -->|Root layout / providers throw| I["global-error.tsx\nRoot-layout boundary\nOwn html/body, inline styles only"] C --> J[Rest of app still works] D --> J E --> J F --> J G --> J H --> K[Full-page recovery screen] I --> L[Minimal inline-styled recovery screen]Reviews (3): Last reviewed commit: "merge: staging-v0.0.5 into fix/102-error..." | Re-trigger Greptile