Skip to content

fix(pages): offer Try again and name the plugin when a page or panel crashes - #3099

Draft
matt2e wants to merge 2 commits into
errors/user-copy-messagesfrom
errors/plugin-crash
Draft

matt2e wants to merge 2 commits into
errors/user-copy-messagesfrom
errors/plugin-crash

Conversation

@matt2e

@matt2e matt2e commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Summary

When a page or panel threw while rendering, its error boundary printed String(error) (for example Error: Fixture page render failure). It also showed the plugin-only Open Settings to disable, delete, or roll back this plugin. on every page, including built-in ones, and offered no way to recover. Both boundaries now render one failure card with Try again, which remounts the content. An external plugin's failure names the plugin and offers Manage plugin. The raw exception text moves under a collapsed Details.

Stacked on #3096: this PR targets errors/user-copy-messages (PR #3096), which adds the shared ErrorDetails disclosure used here. Review and merge #3096 first.

What changed

Case Before After
Built-in page This page couldn’t open / Error: Fixture page render failure / Open Settings to disable, delete, or roll back this plugin., no button This page couldn’t open / Something went wrong showing this page. / If it keeps happening, restart Buzz. + Try again + Details
External plugin page This page couldn’t open / Error: Orbit board render failure: cannot read properties of undefined (reading 'lanes') / Open Settings to disable, delete, or roll back this plugin., no button This page couldn’t open / Orbit had a problem showing it. + Manage plugin, then Try again + Details
Built-in panel This panel couldn’t open / Error: …, no button This panel couldn’t open / Something went wrong showing this panel. / If it keeps happening, restart Buzz. + Try again + Details
External plugin panel (incl. launcher-opened) This panel couldn’t open / Error: Orbit tools render failure: …, no button This panel couldn’t open / Orbit had a problem showing it. + Manage plugin, then Try again + Details
  • Try again clears the failure and remounts the page or panel.
  • Manage plugin opens Settings → Plugins at that plugin. On a two-button card the primary action, Try again, comes last, matching the App page failure cards in fix(errors): plain copy for failed history reads and connections #3096.
  • App.tsx provides the plugin owners above AppShell through PluginOwnersProvider, so pages, the panel callers (useChannelPanels, ConversationHost, PanelCard) and the launcher-opened companion can all name an external plugin.
  • pluginOwners() looks the plugin ID up in the catalog. Bundled plugins, unknown IDs and a catalog that isn't ready yet keep the built-in copy.

Before / After

a built-in page that throws

A built-in page that throws: the Error: text and plugin-only advice give way to Try again, a restart hint and a collapsed Details.

the external Orbit plugin page throws

The external Orbit plugin's page throws: the plugin is named, with Manage plugin first and Try again as the filled, trailing action.

Orbit's launcher-opened panel throws

Orbit's launcher-opened panel throws after mounting: the raw Error: text becomes the plugin's name, Try again, Manage plugin and Details. Older capture (round 2): it shows Try again before Manage plugin. The shipped card is Manage plugin, then Try again, as in the page capture above.

Try again recovering the page. Both builds break the same built-in page. On the before build there is no button, so it stays on the crash card. On the after build, the red pointer clicks Try again and the page remounts. The clip has Before/After headers but no step captions (7.4 s).

crash-try-again.mp4

Checks

  • pnpm typecheck: clean on this branch (on top of fix(errors): plain copy for failed history reads and connections #3096).
  • pnpm lint: exit 0, with 30 infos that predate this change. biome format is clean on the changed files.
  • Tests were not run for this PR (the workstation was under load). The new and changed tests (PageView, PanelView, plugin-owners, App.plugin-owners, tests/browser/navigation-lifecycle.spec.mjs) are included but not run on this snapshot.

🤖 Generated with Claude Code

matt2e and others added 2 commits November 9, 2026 15:28
…the relay's words

Add userCopy(error, context) in features/relay. It classifies a failure
by the typed errors Buzz already produces (ReadError kind and status,
ApiPaused/ApiCapacity, UploadError, PublishRejected/PublishNotSent,
fetch's network TypeErrors, DOMException names) and returns { text,
retry, kind, details }. text is the site's own sentence plus a plain
next step and never carries an exception name, HTTP status, URL or raw
relay or native string; those stay in details. ErrorDetails renders
details under a collapsed "Details" disclosure.

The relay layer keeps failures typed instead of stringified:
- The service snapshot stores a RelayFailure ({ kind, details }) and
  channel windows and thread snapshots keep a WindowFailure. History
  parse failures are ReadError("invalid-response").
- The window store records errorSource "older" only when loadOlder
  failed, so a head or catch-up failure no longer blames older history.
- PublishNotSent carries the relay's wait for a quota hint or a held
  lane, so busy copy can say how long.
- Copy and retry share one set of fetch network messages (including
  WebKit's), and a body cut off after its headers is an unavailable read.
- An answer Buzz can't use is not retried at once. The service asks
  again in the background after 30 s, 1, 2 and every 5 minutes, keeping
  the failure on screen meanwhile.

History reads: ChannelBody, ChannelTimeline, NewMessage, ThreadPanel,
MediaReviewViewer and DraftPreview map the failure with historyFailure():
"Messages didn’t load." with "Try again" for an outage or 5xx; "Buzz is
busy. Try again in a few seconds." (or "in about N minutes/hours") when
paused; "Check for updates" and no Try again for a 4xx or invalid
answer; "You don’t have access to this conversation." for a refusal with
no known reason. The older edge says "Older messages didn’t load.".

Connection failures: Workflows and Notes show the Messages
ConnectionFailed card: "Can’t connect", "Check your connection. We’ll
keep trying." and "Try again".
An answer Buzz can't use reads "Buzz couldn’t connect to this
community." with "Check for updates"; a refusal shows the community
refusal title with no retry. Bestie, Pinned Bestie, Activity, its
preview and Reminders say the same through connectionFailureText and
connectionFailureAction. App's page failure cards put the primary
action last and drop Try again for an unusable connection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
…e plugin behind it

The page and panel error boundaries printed String(error) and, for
every page, the plugin-only "Open Settings to disable, delete, or roll
back this plugin.", with no way to recover. Both now render
ContributionFailure:
- built-in: "This page couldn’t open" (or "This panel couldn’t open"),
  "Something went wrong showing this page." and "If it keeps happening,
  restart Buzz.", with "Try again"
- external plugin: "<Plugin> had a problem showing it.", with "Manage
  plugin", then "Try again" as the trailing primary action

Try again clears the failure and remounts the content. The exception
text moves under a collapsed "Details" (ErrorDetails).

App provides the plugin owners above AppShell through
PluginOwnersProvider, so pages, the panel callers (useChannelPanels,
ConversationHost, PanelCard) and the launcher-opened companion can name
an external plugin. pluginOwners() looks a plugin ID up in the catalog
and, for an external plugin, returns its name and a Settings → Plugins
opener; bundled, unknown and not-ready catalog entries stay built-in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
@matt2e
matt2e force-pushed the errors/user-copy-messages branch 2 times, most recently from 32c31dc to 0e35fbc Compare November 12, 2026 23:50

This branch has not been deployed

No deployments
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.

1 participant