Repository navigation
feat(app): recover from a renderer that fails to start - #841
Merged
Merged
Conversation
A render error anywhere in the app, a module that throws while the renderer starts, or a lazily loaded chunk that cannot be fetched used to leave a blank window. - The renderer entry imports the recovery module first. If a later module fails to evaluate, its start-up listener mounts the recovery screen on its own root; once start-up is over, an error boundary above every provider catches render errors. - vite:preloadError (a stale chunk, usually right after an update) gets one automatic reload per window session, guarded in sessionStorage, and the screen after that, so a broken build cannot reload forever. - The screen has a title, a one-line explanation, Reload window and Copy error details. It uses Stave's role color tokens with system-color fallbacks instead of ADS controls or the design provider, follows the saved theme mode and language when the app never started, scrolls in small windows, and is translated in English and Korean, with an English fallback only if the i18n runtime itself failed to load. No logs-folder action exists to offer, so there is no "More options". - Copied details carry the app version, platform, user agent, message, stack, cause chain and component stack, with home-directory account names and credential-shaped values redacted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
astyfx
requested review from
heath-s and
schang124
and removed request for
a team
October 27, 2026 13:53
|
| Commit | Scanned at | New | Resolved | Net |
|---|---|---|---|---|
bc41f92 |
2026-10-27 13:53 UTC | 0 | 0 | 0 |
ecf2d73 |
2026-10-27 14:07 UTC | 0 | 0 | 0 |
5f20b92 < |
2026-10-27 23:57 UTC | 0 | 0 | 0 |
Last scanned: 5f20b92 · 2026-10-27 23:57 UTC
The design-system gate requires product actions to compose the ADS Button. The screen now publishes the ADS-to-Stave role mapping on its own root, so the buttons follow the theme without StaveDesignProvider above them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recovery module imported the canonical ADS Button, which imports Loader, which imports @/i18n. If i18n failed to initialize, the recovery module failed with it before its listeners existed, and the window stayed blank. - The recovery screen uses the ADS headless ButtonRoot with the ADS focus-ring and transition recipes and role-token colors. The canonical Button cannot be used here without pulling i18n in through Loader. - The recovery logic no longer loads src/i18n/locale.ts; it keeps its own locale list, which a test holds equal to APP_LOCALES. - Tests: a static import-graph walk asserts nothing the recovery module loads reaches src/i18n, and a separate bun process makes every i18n module throw at load, then checks that both listeners install, a start-up error records the failure and mounts on #root, and the screen renders with the English fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
astyfx
added a commit
that referenced
this pull request
Oct 28, 2026
Bring in #839, #840, #841, #842, #844 and #846. - Placeholder: one resolver. Main's `resolvePromptPlaceholder` now also takes `approvalRedirect` and returns the decline-and-steer or decline-and-queue keys. The duplicate `resolvePromptInputPlaceholder` is removed, and its tests now go through `resolvePromptPlaceholder`. - Approval keys: main's `useComposerApprovalShortcuts` now returns the Enter/Tab handler the empty editor uses. `use-approval-window-shortcuts.ts` is removed. - The composer's redirect-mode derivation moves into `resolveComposerApprovalRedirectMode`, which keeps `ChatInputComposer.tsx` and `prompt-input.tsx` within main's lowered line limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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
When the renderer fails to start, crashes while rendering, or cannot load a lazily loaded chunk, it now shows a recovery screen instead of a blank window. A stale chunk first gets one automatic reload per session. The recovery path has no dependency on i18n, so it still works when i18n itself fails to initialize.
Changes
Features
StrictModeandStaveDesignProvider. A render error anywhere shows the recovery screen, including during the app shell's first render.src/main.tsxnow importsinstall-renderer-recoveryfirst. If a later module throws while it evaluates, the entry never runs, and the start-up listener mounts the recovery screen on its own root.markRendererStarted()ends this phase once every import has evaluated.vite:preloadErrorhandler. A chunk or CSS file that cannot be fetched (usually a stale build right after an update) gets one automatic reload per window session.sessionStorage, so it survives the reload. A second failure shows the screen instead, so the window cannot reload in a loop.src/i18n. If i18n fails to initialize, the listeners still install and the screen still renders.ButtonRootwith the ADSfocusRing.ringandtransition.colorsrecipes, plus role-token colors at the ADS medium control size. The canonicalButtoncannot be used here because it importsLoader, which imports@/i18nfor its label.StaveDesignProvider. It publishes the ADS-to-Stave role mapping on its own root, and its surfaces use Stave's role tokens with system-color fallbacks.src/i18n/locale.ts. A test keeps it equal toAPP_LOCALES./Users/<user>,/home/<user>,C:\Users\<user>)key=value/JSON pairs, and common API token, JWT and access-key shapesapp:rendererRecovery.*in en and ko.recovery-i18n.tsregisters once the i18n runtime loads. The hard-coded English table (markedi18n-ignore) is used only if that never happens. A test keeps it identical to the en catalog.Test Evidence
Commands run on the final branch head (
5f20b925):bun run lint: pass (0 errors; existing warnings only)bun run typecheck: passbun test ./tests/renderer-recovery.test.tsx: 25 pass, 0 failbun run check:i18n: passbun run check:design-system: passbun run check:style-channel,bun run check:max-lines-ratchet: passbun run build: pass. The entry chunk contains both Vite'svite:preloadErrordispatch and the handler.bun run build:pages: passbun run test:isolated: 8892 pass, 1 fail. The failure is pre-existing and unrelated:tests/claude-sdk-runtime.test.ts, "keeps the generic rate limit wording when the window is unknown".main.bun run build:desktop: passcheck:doc-paths,check:reliability-gates,check:switch-exhaustiveness,check:licenses,check:temporary-migrations: passbun test ./tests/i18n-catalog-check.test.ts ./tests/i18n-hardcoded-strings.test.ts ./tests/i18n-display-change.test.tsx: 25 pass5f20b925: all checks green, includingvalidate.tests/renderer-recovery.test.tsxcovers:src/i18n. The helper istests/fixtures/static-import-graph.ts.bunprocess withtests/fixtures/i18n-init-failure.preload.tsmakes everysrc/i18nmodule throw while loading. It then loads the recovery the waysrc/main.tsxdoes (tests/fixtures/renderer-recovery-i18n-failure.ts) and asserts:errorandvite:preloadErrorlisteners installed#rootRendered check (headless Chrome via
bun run dev, temporary local throws, not committed)Apprender): a blank dark window, nothing to act on.--background: Reload window (primary, focused, focus ring visible) and Copy error details (outline).--primary(darkoklch(0.71 0.15 252), lightoklch(0.54 0.18 260))./Users/<user>/…andtoken=<redacted>in place of the injected path and token, plus the cause chain and component stack.App.tsx(start-up failure): the screen mounted without the app, in the saved language (ko) or theme (dark).globals.cssis injected by the modules that never ran, so the screen used the system-color fallbacks. Text stayed legible.index.html(checked indist/index.html), so the role tokens are present even when no script ran.vite:preloadErrorreloaded the page and set the session mark. The second showed "Stave needs to reload".Notes
PERSISTED_APP_STORE_KEY. A test asserts it matchesAPP_STORE_KEY.🤖 Generated with Claude Code