Skip to content

feat(web): server-busy toast for rate_limited errors (TASK-2080)#888

Merged
xarmian merged 1 commit into
mainfrom
feat/task-2080-rate-limited-toast
Jul 10, 2026
Merged

feat(web): server-busy toast for rate_limited errors (TASK-2080)#888
xarmian merged 1 commit into
mainfrom
feat/task-2080-rate-limited-toast

Conversation

@xarmian

@xarmian xarmian commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Wires the 429 rate_limited handling from TASK-2026 into a dedicated, user-facing "Server busy" toast. Previously a surfaced 429 fell through to generic error handling; now it shows a distinct, non-alarming notification.

Chokepoint

client.ts already funnels every surfaced 429 through a single throw err in the 429 branch of request(). I added a setRateLimitHandler(handler) seam (mirroring the existing setAccessRevokedHandler pattern) and call notifyRateLimited(err.retryAfterMs) right before that throw. This keeps client.ts free of any UI/Svelte import — the toast wiring lives in the layout + a small helper, exactly like the access-revoked split.

  • +layout.svelte registers the handler once at bootstrap: setRateLimitHandler((ms) => notifyServerBusy(ms)).
  • serverBusyToast.ts owns the message + dedupe, calling the existing toastStore.show(..., 'info', ...).

Dedupe

A busy server 429s a burst of independent in-flight requests at once; the client faithfully fires the seam per request. notifyServerBusy throttles to one toast per 6s window (module-level lastShownAt, seeded to -Infinity so the first call always fires). retryAfterMs names the wait ("try again in 3s") when the server sent a usable Retry-After, else stays vague ("in a moment"). Severity is 'info' (least alarming; transient condition).

Tests

  • serverBusyToast.test.ts (node, mocks toast store): single 429 -> one toast; burst of 10 within window -> one toast; window elapsed -> toast fires again; message formatting.
  • client.test.ts (new describe): a surfaced 429 fires the handler exactly once with the parsed retryAfterMs; a non-429 (500) does not fire it.

All 160 node tests pass; npm run check clean for touched files; npm run build succeeds. (The 2 jsdom suites from TASK-2081 fail to load in this worktree due to a pre-existing shared-node_modules resolution artifact — verified identical with my changes stashed.)

Codex review: CLEAN.

Refs TASK-2080, PLAN-1984.

@xarmian xarmian merged commit 4eb72dc into main Jul 10, 2026
3 of 4 checks passed
@xarmian xarmian deleted the feat/task-2080-rate-limited-toast branch July 10, 2026 00:04
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