Skip to content

fix(startup): show a recoverable screen when Buzz fails to open or crashes - #3085

Merged
matt2e merged 3 commits into
mainfrom
errors-3-da12-startup-fallback
Nov 9, 2026
Merged

matt2e merged 3 commits into
mainfrom
errors-3-da12-startup-fallback

Conversation

@matt2e

@matt2e matt2e commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Part 2 of 3 from the errors-3 work (DA-12). When Buzz cannot finish opening, the window today stays on the looping "Opening Buzz" mark with nothing a person can do; when the running app hits a React error no boundary catches, the window goes blank. Screen readers hear only "Opening Buzz". This PR adds a recoverable fallback that needs nothing from the app bundle: Buzz couldn’t open for startup failures, Buzz stopped working for a crash after launch, each with Reload, Restart Buzz on the desktop, and the error behind a collapsed Details. A start that takes more than 20 seconds says This is taking longer than usual. and offers Reload while still opening by itself if it finishes.

Changes

  • 1c6351d09 fix(startup): DA-12 show "Buzz couldn’t open" when startup fails
  • dd9ee5339 fix(startup): DA-12 say "Buzz stopped working" when a running app crashes

Mechanisms:

  • Bundle-independent DOM fallback in public/launch-init.js: builds the page with plain DOM and the tokens startup.css already loads, removes the launch, hides and inerts #root and #buzz-toast-root, reveals a still-hidden first-welcome Mac window, and focuses Reload. Later failures append to Details.
  • error / unhandledrejection listeners in launch-init.js (capture phase, so a failed <script> load counts) that fail startup until React has the app, then leave other errors to the running app.
  • Startup try/catch in src/main.tsx around createServices() and the first render, reporting through showStartupFailure() in src/app/launch.ts; startupHandedToReact() tells launch-init.js once React has the app.
  • onUncaughtError on createRoot: an error no boundary caught reaches the same fallback with the component stack in Details.
  • 20 s watchdog: adds "This is taking longer than usual." [Reload] to the launch status region; the launch still leaves on its own if it ends.
  • Post-launch "Buzz stopped working": once React had the app and the launch is gone, the fallback's title and sentence say the app stopped, not that it couldn't open.
  • Test infra for these journeys: a startupFailureFixture build option (tests/browser/build.mjs) that throws where window.__buzzFixtureStartupFailure says (services, render, running), and expectedStartupFailure in tests/browser/fixture.mjs so a journey's injected failure passes the console/page-error gate.
  • docs/buzz-help.md describes the behaviour.

Evidence

Headless Chromium 1280×800, light theme, launchAnimation: true, driven by tests/browser/fixture.mjs. Before is the merge base 40e6ccb597 with only this branch's test infra applied; After is this branch.

1. Main bundle fails to load (desktop), at 15 s

Before: the opening mark still loops at 15 s. After: Buzz couldn’t open, with Reload, Restart Buzz and a collapsed Details.

2. The same, with Details expanded

Before: nothing to expand. After: Details shows which script failed to load.

3. createServices() throws (storage unavailable), at 15 s

Before: the mark loops; the rejection is unhandled. After: Buzz couldn’t open, with the error in Details.

4. App throws during its first render

Before: React unmounts the tree and the mark loops over an empty root. After: Buzz couldn’t open through onUncaughtError.

5. Watchdog: the bundle is held, at 20 s

Before: the mark loops with no hint. After: the launch says This is taking longer than usual. and offers Reload.

6. Crash after launch (the app's content throws on the next navigation)

Before: an empty window. After: Buzz stopped working, with Reload and Details.

7. Browser build (no Tauri stub): Reload only

Before: the mark loops. After: Buzz couldn’t open with Reload only, since only the desktop can restart itself.

8. Screen-reader view (Playwright aria snapshot)

Before: the only accessible node is status Opening Buzz. After: main Buzz couldn’t open with an alert, two buttons and a Details group.

Text of the same snapshots, from page.locator("body").ariaSnapshot() with the bundle failing to load (desktop stub):

Before (the same in every scenario above, except the crash after launch, where the body has no accessible nodes at all):

- status "Opening Buzz":
  - img "Buzz"

After:

- main "Buzz couldn’t open":
  - alert:
    - heading "Buzz couldn’t open" [level=1]
    - paragraph: Something went wrong while starting. Your messages and settings are safe.
  - button "Reload"
  - button "Restart Buzz"
  - group: Details

After, slow launch at 20 s (the launch is a status region, so the added text is read when it appears):

- status "Opening Buzz":
  - img "Buzz"
  - paragraph: This is taking longer than usual.
  - button "Reload"

After, crash after launch:

- main "Buzz stopped working":
  - alert:
    - heading "Buzz stopped working" [level=1]
    - paragraph: Something went wrong. Your messages and settings are safe.
  - button "Reload"
  - group: Details

9. Reload with the bundle restored

Before: restoring the bundle changes nothing; the window has no way to reload. After: clicking Reload opens Buzz in the primary channel.

Videos

Same scripted steps in both builds: open with the bundle failing, wait 20 s (sped up 8× with a caption saying so), restore the bundle, then click Reload where there is one. Captions are in a band under the content.

Before

before.mp4

After

after.mp4

Side by side

side-by-side.mp4

Testing

Run in the PR worktree at dd9ee5339:

bin/pnpm exec biome format .                      # Checked 3235 files. No fixes applied.
bin/pnpm exec biome lint --error-on-warnings .    # Checked 3336 files. Found 30 infos (pre-existing), exit 0
bin/node scripts/design-system/check-icons.mjs    # ok
bin/pnpm typecheck                                # tsc --noEmit: ok
bin/pnpm design:typecheck                         # tsc -p tsconfig.design.json: ok
bin/pnpm exec vitest run src/app/launch.test.ts src/app/device-settings.test.ts \
  src/app/App.profile.test.tsx src/app/IdentitySettings.test.tsx src/app/entity-navigation.test.tsx
                                                  # 5 files, 117 tests passed
bin/pnpm exec playwright test --config tests/browser/playwright.config.mjs \
  --project=chromium --project=webkit --no-deps \
  tests/browser/startup-failure.spec.mjs tests/browser/startup.spec.mjs
                                                  # 34 passed (2.3m): 6 startup-failure + 11 startup journeys per engine

Not run here: just scan, the native build, and a human test in the desktop app (Restart Buzz is exercised only through a __TAURI_INTERNALS__ stub).

Notes

  • src/main.tsx is a FOUNDATION file. It was changed on explicit human instruction: startup is wrapped in try/catch, createRoot gets onUncaughtError, and startupHandedToReact() runs after the first render. Please give it the stricter review.
  • The plan and before/after notes are in Staged (project "Errors 3").
  • PR 1 of 3 (DA-9, Huddles) and PR 3 of 3 (DA-23, plugin update checks) are independent of this one.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review November 9, 2026 14:56
@matt2e
matt2e requested a review from a team as a code owner November 9, 2026 14:56

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord automated source review via Wes’s account, with independent Rocket, Mantis and Groot coverage.

One P3 test-gate issue: the expected bundle-404 filter also exempts unrelated console errors from the main chunk after Reload (inline). No source-demonstrated production defect found in the reviewed startup, crash, or native recovery paths.

Optional follow-ups: cover automatic completion while the slow-start Reload button owns focus, since removing the overlay can leave focus on the body; isolate the window listeners installed by each launchInit() unit-test invocation; add focused coverage for unhandled rejection, first-welcome reveal, and rejected restart. These are improvement suggestions, not additional blocking findings.

Reviewed head dd9ee533962a79175a2d3e9af71f1039ee658090, target base 3a1eb5601e997811009d01601786df8af9393a7f, feature merge-base 40e6ccb597cf9d8b47f9692de8285458cc304017. Source-only: no tests, builds, or apps executed by reviewers. One-time hosted check snapshot reports CI required passing; Windows native validation skipped. Native restart/reveal and screen-reader behavior remain unexercised. Inspected the description and all nine screenshots; the three videos were not inspected.

Comment thread tests/browser/fixture.mjs Resolved
matt2e and others added 3 commits November 10, 2026 02:37
If the app bundle failed to load, createServices() threw, or App threw
before marking the launch ready, the window stayed on the looping "Opening
Buzz" mark with #root empty and inert, and screen readers heard only
"Opening Buzz". An uncaught render error after launch left an empty
window. Nothing caught any of these.

- launch-init.js, which needs nothing from the bundle, now renders the
  fallback in plain DOM: "Buzz couldn’t open" / "Something went wrong
  while starting. Your messages and settings are safe." [Reload], plus
  [Restart Buzz] on the desktop through the native restart, and the error
  in a collapsed Details. It removes the launch, hides the app roots,
  shows a still-hidden first-welcome Mac window, and focuses Reload.
- It installs global error (including failed script loads) and
  unhandledrejection listeners that fail startup until React has the app,
  then leave other errors to the running app.
- main.tsx (FOUNDATION, edited on the user's explicit instruction) wraps
  startup in try/catch and passes createRoot an onUncaughtError, both of
  which show the fallback through src/app/launch.ts, and tells
  launch-init.js once React has the app.
- A watchdog adds "This is taking longer than usual." [Reload] to the
  launch after 20 seconds; the launch still leaves by itself if it ends.
- Style both with design tokens in startup.css, which now also loads the
  typography roles it uses.
- Browser journeys in both engines cover a bundle that doesn't load,
  Restart Buzz, a throwing createServices() and App (via a fixture build),
  and the slow launch under a controlled clock. Unit tests cover the
  fallback, its listeners and the watchdog.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: mtoohey <[email protected]>
(cherry picked from commit bfd8001fb84967696325c1460ccb9e2cdab20f52)
Signed-off-by: Matt Toohey <[email protected]>
…shes

React reports an error no boundary caught through the root's
onUncaughtError, which shows launch-init.js's fallback page. That page
always said "Buzz couldn’t open … while starting", even when the user
had been using Buzz for hours.

Once React has the app and the launch screen is gone, the page now
says "Buzz stopped working" / "Something went wrong. Your messages and
settings are safe.", with the same Reload, Restart Buzz and Details.
Startup failures keep "Buzz couldn’t open".

The startup-failure fixture build gains a "running" seam in the app's
content, which throws on the next navigation, so a browser journey can
crash the app after it opened.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: mtoohey <[email protected]>
(cherry picked from commit 76eb5aca896c3ab27668fe16e176808574592066)
Signed-off-by: Matt Toohey <[email protected]>
The bundle-load journeys declared `expectedStartupFailure: "/assets/index-"`,
and the fixture excused any console error whose text or source location
contained that string. After Reload the recovered app runs from that same
chunk, so an unrelated error it logged, or any Error whose stack named the
chunk, passed the no-errors gate.

Split the option. `expectedStartupFailure` keeps matching an injected
exception's message. New `expectedStartupLoadFailure` names a resource that
fails to load: the fixture records each failed response to a URL containing
it, and excuses only that response's own "Failed to load resource" error, once,
by exact URL and status, as the injected 403/404 matchers already do. A
control journey that crashed the app after Reload now fails the gate with
the chunk's error; the six startup journeys pass in Chromium and WebKit.

Signed-off-by: Matt Toohey <[email protected]>
Co-Authored-By: Claude Fable 5.1 <[email protected]>
@matt2e
matt2e force-pushed the errors-3-da12-startup-fallback branch from dd9ee53 to a4672ad Compare November 9, 2026 15:43

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Kyle reviewed this PR and the Nexus review-tracker notes and approved with no comments.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@matt2e
matt2e merged commit a16d778 into main Nov 9, 2026
64 checks passed
@matt2e
matt2e deleted the errors-3-da12-startup-fallback branch November 9, 2026 21:42
wpfleger96 added a commit that referenced this pull request Nov 9, 2026
…restricted-communities

* origin/main:
  ci: give WebKit shards headroom and keep fixture servers' idle connections open (#3074)
  fix(messages): offer Retry as soon as a send fails (#3080)
  fix(agents): link Settings from key failures and quote the provider (#3081)
  fix(identity): link Builderlab from Identity sync where its page holds the next step (#3082)
  fix(startup): show a recoverable screen when Buzz fails to open or crashes (#3085)
  fix(attention): ask before deleting a watch (#3092)
  fix(agents): remove the owner-access read tools (#3123)
  fix(channels): keep the side pane mounted under the image viewer (#3111)
  fix(solo): ask before Escape or Close drops a new workspace's typed fields (#3114)

# Conflicts:
#	docs/configuration.md
#	vite.config.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants