Repository navigation
Conversation
…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>
Profile Settings printed reason.message, so a failed save showed the
server's "internal error", a relay restriction such as "restricted: you
are timed out until 1794600000", or a JSON parser error when a proxy
answered with an HTML page.
communityRequest now checks response.ok before parsing, on both hosts.
A non-transient failure throws CommunityRefused (the status plus the
bounded refusal reason), the relay's OK false becomes PublishRejected,
and an unconfirmed publication is ReadError("invalid-response").
Callers that match on the message keep working.
Profile Settings maps the failure by where the save stopped:
- not sent, 5xx or unknown: "Couldn’t save your profile. Try again."
(Save stays enabled)
- refused: "This community didn’t accept your profile change." plus a
known reason
- not confirmed: "Buzz couldn’t confirm your profile was saved. Save
again to check." (it said "Your profile change is not current. Your
edits are retained; save again to retry.")
The shared refusal reason for "restricted: you are timed out until
<ts>" now reads "You’re rate limited until Nov 13, 8:00 PM." (or
"You’re rate limited." without a readable end) instead of "You’re timed
out until …".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
The shared refusal reason parsed the Unix timestamp out of the relay's "restricted: you are timed out until <ts>" text and formatted it as a date. That meant extracting data from free-form error text. refusalReason now matches the phrase only and returns the fixed copy "You’re timed out in this community." The formatUntil helper and the Date-based test expectations are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…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>
Five journeys still asserted the copy that the previous commit replaced: - thread-window: the older-replies alert said "Older page failed"; it now says "Older replies didn’t load." (ThreadPanel's OlderRepliesFailure). - media-controls, message-overlays, gallery-theme: MediaReviewViewer's unavailable-media button was "Retry"; it is "Try again". The message-overlays title and the gallery comment follow the rename. - navigation-repairs: the keyboard hand-off journey failed the session with an empty JSON object. That is now an answer Buzz can't use, so the card says "Buzz couldn’t connect to this community." and offers Check for updates, not Try again. The journey now fails the broker registration with a recorded 502 instead, a failure the service leaves for the user, so the card still says "Check your connection, then try again." and offers Try again. Every caller registers the community, so every registration fails until the user acts, and the retry is counted from then on. Product copy is unchanged. Signed-off-by: Matt Toohey <contact@matttoohey.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
matt2e
force-pushed
the
errors/user-copy-messages
branch
from
November 9, 2026 16:06
4b2e055 to
32c31dc
Compare
…errors/profile-save # Conflicts: # src/features/relay/user-copy.test.ts # src/features/relay/user-copy.ts
matt2e
force-pushed
the
errors/user-copy-messages
branch
from
November 12, 2026 23:50
32c31dc to
0e35fbc
Compare
This branch has not been deployed
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
Profile Settings printed the failure's own message when a save failed: the server's
internal error, a relay restriction such asrestricted: you are timed out until 1794600000, or a JSON parser error when a proxy answered with an HTML page. Saves now fail with typed errors, and Profile Settings says what happened in plain words.Stacked on #3096: this PR targets
errors/user-copy-messages(PR #3096), which adds the shareduserCopy/refusalReasonhelper used here. Review and merge #3096 first.What changed
Typed save failures (
communities/api.ts,communities/native-api.ts, both hosts)communityRequestchecksresponse.okbefore parsing, so a proxy's HTML error page is no longer a JSON parser error.CommunityRefused(the status plus the bounded refusal reason). The relay'sOK falsebecomesPublishRejected, and an unconfirmed publication isReadError("invalid-response"). Callers that match on the message keep working.Profile Settings copy
internal errorCouldn’t save your profile. Try again.(Save stays enabled)restricted: you are timed out until 1794600000This community didn’t accept your profile change. You’re timed out in this community.This community didn’t accept your profile change.Your profile change is not current. Your edits are retained; save again to retry.Buzz couldn’t confirm your profile was saved. Save again to check.Shared refusal reason (
user-copy.ts):restricted: you are timed out until <ts>now readsYou’re timed out in this community.It no longer parses or shows the timestamp from the relay's text. In #3096 it readsYou’re timed out until <date>.Before / After
Profile save answered 500: the server's
internal errorbecomesCouldn’t save your profile. Try again.Save stays enabled.A 403
restricted: you are timed out until 1794600000: the relay's text becomesThis community didn’t accept your profile change. You’re timed out in this community.The timestamp is not shown.Checks
pnpm typecheck: clean on this branch (on top of fix(errors): plain copy for failed history reads and connections #3096).vitest runonuser-copy.test.ts,ProfileSettings.avatar.test.tsx,communities/api.test.ts,communities/native-api.test.ts: 4 files, 110 tests passed.pnpm lintand the full unit suite were not run for this snapshot.Note
🤖 Generated with Claude Code