Repository navigation
Frontend: isolate every extension contribution and the app root with an error boundary #880
Copy link
Copy link
Closed
Feature
Copy link
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend (React/TypeScript)Frontend (React/TypeScript)
Milestone
Description
Goal
A render error in one extension contribution (settings section, form section, server settings tab, session header item, composer action, manage view, document action) no longer blanks the whole window: only that contribution disappears, the rest of the app stays usable, and a render error outside the slots shows a recovery screen with a reload button instead of an empty window.
Context
- The only error boundary in the frontend is
ExtensionBoundaryinapps/smart-ssh-community/frontend/src/components/FirstRunNoticeScreen.tsx:35-60(issue Isolate first-run notice extensions with an error boundary #159), used only for first-run notice extensions (:142-149). - All other slot contributions from
src/extensions/registry.ts(lines 265-509) render unprotected:- settings sections:
components/SettingsScreen.tsx:105-110and the "Data and backup" list at:284 - server/group form sections, server settings tabs, manage views:
extensions/FormSlots.tsx:22,52,63,67,81(manage views rendered fromcomponents/Sidebar.tsx:91) - session header items:
extensions/SessionHeaderItems.tsx:14 - composer actions:
components/ChatPanel.tsx:411; document actions:components/ChatPanel.tsx:2913
- settings sections:
- A throwing contribution unmounts the whole React tree; the desktop window stays empty until restart.
src/main.tsx:56-62renders<StartupGate><App/></StartupGate>without a boundary.
Requirements
- Move the boundary to
src/components/ui/ErrorBoundary.tsxand export it fromcomponents/ui/index.ts:- props:
id: string,fallback?: ReactNode(default: render nothing),onError?: (id, error, info) => void,children componentDidCatchalways logs once viaconsole.errorwith the id (no error payload in the UI)- optional
resetKeys?: unknown[]: when a value changes, the boundary tries to render again (so a contribution that failed for one server can render for the next one)
- props:
FirstRunNoticeScreenuses the new component; its behaviour from Isolate first-run notice extensions with an error boundary #159 stays unchanged.- Every slot renderer listed under Context wraps each contribution individually (keyed by the contribution id) in
ErrorBoundary. Where a contribution is a button (composer action, document action), the fallback is "not rendered". - Root boundary:
main.tsxwraps the tree insideStrictModein anErrorBoundarywhose fallback is a full-window recovery screen:- title "Something went wrong", text "Smart SSH hit an unexpected error in the interface. Reload to continue. If it happens again, create a diagnostics bundle under Settings → Diagnostics after reloading.", button "Reload" (
window.location.reload()) - uses the existing UI building blocks (
Button, colour tokens), works in the narrow layout, keyboard-reachable, passes the a11y lint - texts as new i18n keys in all locales under
src/locales/*(locale parity test stays green; translate, do not copy English)
- title "Something went wrong", text "Smart SSH hit an unexpected error in the interface. Reload to continue. If it happens again, create a diagnostics bundle under Settings → Diagnostics after reloading.", button "Reload" (
- Spec 0045 (frontend extension points) states: each contribution is isolated; a failing one is not rendered and is logged; a render error outside the slots shows the recovery screen.
Acceptance Criteria
-
components/ui/ErrorBoundary.tsxexists with the props above; unit test: a throwing child renders the fallback,onErroris called once with the id,console.erroris called; changingresetKeysrenders the child again. - Test: a registered settings section that throws during render is not shown, every other section (built-in and registered) is still rendered and interactive, and
console.errorwas called. - Same kind of test for one form section, one session header item, one composer action and one document action.
- Test: a component that throws inside
<App/>outside any slot shows the recovery screen with a "Reload" button that callswindow.location.reload(mocked). -
FirstRunNoticeScreentests from Isolate first-run notice extensions with an error boundary #159 pass unchanged. - New texts exist in every locale; locale parity and placeholder tests pass.
- Spec 0045 updated as described.
- Relevant automated tests pass (
npm run lint,npm test,tsc -b).
Technical Notes
- Files:
src/components/ui/ErrorBoundary.tsx(new),src/components/ui/index.ts,src/components/FirstRunNoticeScreen.tsx,src/components/SettingsScreen.tsx,src/extensions/FormSlots.tsx,src/extensions/SessionHeaderItems.tsx,src/components/ChatPanel.tsx(only the two render sites),src/components/Sidebar.tsxor wherever manage views render,src/main.tsx,src/locales/*/common.json,docs/specs/0045-registry-cleanup-document-actions.md. ChatPanel.tsxis very large (> 3,000 lines): change only the two.maprender sites, read it with offsets. If the document card or the composer has already moved out ofChatPanel.tsx(Chat: move ChatItemView, the action card parts, QuickRuleButton, DocumentCard and AssistantMessageView into components/chat/ #832, Chat: extract useChatComposer, useVoiceComposer and the Composer component #842), wrap the contributions where they render now.- React error boundaries do not catch errors in event handlers or async code; that is expected and out of scope.
- No security-relevant code path is touched.
Spec impact: update 0045
Open questions
- none
Related Work
- Isolate first-run notice extensions with an error boundary #159 (first-run notice extensions isolated with an error boundary)
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend (React/TypeScript)Frontend (React/TypeScript)
Type
Fields
Priority
P2 - Normal
Activity
4 remaining items
feat(frontend): isolate extension contributions and app root with err…
smart-ssh-coder commentedon Oct 27, 2026
Implementation ready for review. PR #987. Added ErrorBoundary, wrapped every extension slot contribution individually, added a root recovery screen with i18n in all locales, updated Spec 0045. Lint, tsc and vitest pass locally and CI is green. The root-screen test covers the boundary plus RootErrorScreen composition, not main.tsx itself.
smart-ssh-reviewer commentedon Oct 27, 2026
Review: 1. The root recovery-screen criterion (test with a throwing component inside ) is not covered: RootErrorScreen.test.tsx rebuilds the boundary composition by hand and never exercises main.tsx, so removing the boundary from main.tsx keeps it green. Extract the root wiring into an exported component used by main.tsx and test that component with a throwing child.
fix(frontend): extract AppRoot so the root boundary wiring is tested (#…
smart-ssh-coder commentedon Oct 27, 2026
Implementation ready for review. PR #987. Extracted the root boundary wiring into an exported AppRoot component used by main.tsx; RootErrorScreen.test.tsx now renders AppRoot with a throwing child. tsc, lint and the vitest file pass locally. wait-ci reported no checks on PR #987 after 5 minutes, so CI status is unverified.
smart-ssh-reviewer commentedon Oct 27, 2026
Review passed – moving on to merge. Previous finding fixed: root boundary wiring is now an exported AppRoot used by main.tsx and tested with a throwing child (red without the boundary, green with). Local vitest/tsc/lint green. Note: no CI checks were reported for the branch.
smart-ssh-coordinator commentedon Oct 27, 2026
Merge not possible, back to coding: PR #987 has conflicts with develop – please resolve them.
feat(frontend): isolate extension contributions and app root with err…
fix(frontend): extract AppRoot so the root boundary wiring is tested (#…
smart-ssh-coder commentedon Oct 27, 2026
Implementation ready for review. PR #987. Rebased PR #987 onto develop (resolved conflicts in ChatPanel.tsx and FirstRunNoticeScreen.tsx), force-pushed. tsc, lint and component/extension vitest pass locally; CI green on all jobs.
smart-ssh-reviewer commentedon Oct 27, 2026
Review passed – moving on to merge. Re-approved after rebase onto develop: diff unchanged in substance, all slot contributions and the root still isolated, CI green, local vitest/tsc green.
feat(frontend): isolate extension contributions and app root with err…
smart-ssh-coordinator commentedon Oct 27, 2026
Merged and branch deleted: PR #987 (squash) in develop