fix(webapp): stop swallowing errors, and keep the recording page honest when the connection drops (#377) #529
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/377-silent-errors"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #377. The last cross-cutting item of the v4.3.0 build order (#514), run after every lane settled. Frontend only, ten commits.
Every site in the audit now shows a visible signal on failure. Site 3 (the proposal fetches on
CampaignDetailandCampaignStoryline) needed nothing; #375 had already fixed both with a shared "Couldn't load suggested wiki updates." state, verified on main.One small reusable
InlineError(components/InlineError.jsx: message, optional retry, optional boxed look) carries eleven of them;CampaignPlanning's private copy, which was the prototype, is folded into it.The recording page gets a state machine.
fetchRecordingLiverejecting used to set{active: false}, which is the same value the server sends when nothing is recording, so a dropped connection mid-session redrew the page as "No active recording", idle, with a Start button, while the bot was still capturing.livenow only ever holds something the server actually said, a separateconnectionstate (connecting | ok | lost) says whether we are still hearing from it, and a failed read raises "Connection lost, retrying… Showing the last reading, from Xs ago." over the last known state instead of replacing it. Any successful read or SSE event clears it; a page that has never had an answer says "Unknown" rather than picking the value that invites a second recording of the same evening. The SSEonerrordoes not itself mark the connection lost (it re-reads first), so a stream that merely cycles does not flicker a banner. The players' table view gets the same indicator for its polling.Retry affordances: the vote grid re-sends the exact vote that failed; the shelf's position save re-reads the card's current position, since by the time anyone clicks Try again the card has usually moved again.
CampaignPlanning's prefetch error is rendered with what it actually means (name suggestions were not pre-generated; the tools still work). Title suggestions, the cold-open panel, and the session page's attendance-proposal and loot reads each show a quiet inline line with retry. "Check for link suggestions" now says "No link suggestions found" (or that the rest were dismissed); the suggestions are computed locally, so this was empty-result feedback rather than an error. A real failure to load the game-system schema says so in the stat block, distinguished from a 404 that legitimately means "this system has no schema".Two notes where the audit and the code differed: the vote grid never "silently reverted" a cell (it is not optimistic, so a rejected vote produced no DOM change at all), and
ColdOpenPanelis still mounted only from the GM's shelf.vitest 696 passed (59 files, +29), eslint clean apart from the pre-existing
CampaignDetailwarning,vite buildgreen. No backend change, no migration.🤖 Generated with Claude Code
Twelve places in the app treated a failed request as nothing at all: a console.error, a `.catch(() => {})`, a panel that simply never appeared. The fix at each of them is the same shape — a short red line beside the control that failed, plus a button to try again where repeating the action is the obvious next move — so it is one component rather than twelve near-identical divs. Both looks it offers were already in the tree: plain red text for a failure inside a panel (the wiki lane's #395 errors) and `boxed` for one that owns the top of a page. It renders nothing for a null message, so callers hand it their error state without guarding first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Dropping a card after a drag or a resize wrote its geometry back with `.catch(() => {})`. The card is moved locally before the request goes out, so a failed save looked exactly like a successful one — the board was right, and stayed right, until the next reload replayed the server's copy and put every card back where it had been. A GM arranging a board between scenes had no way to know the arrangement was being thrown away. The save now reports a failure above the board, names the card, and offers to send it again. The retry re-reads that card's position off the live board rather than replaying the one that failed, because by the time anyone clicks it the card has usually been nudged again. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>The live panel had two states where the world has three. A failed `fetchRecordingLive` — the fallback the page falls back to every time the event stream drops — set `{ active: false }`, which is the same value the server sends when nothing is being recorded. So a GM whose wifi blinked during a session watched the page redraw itself as "No active recording", "Idle", no speakers, and a Start Recording button, while the bot went on capturing in Discord. The obvious response to that screen starts a second recording of the same evening. `live` now only ever holds something the server actually said, and a new `connection` state says whether we are still hearing from it. A failed read leaves the last known state exactly where it was and raises "Connection lost, retrying…" over it, with how long ago that state was true, so a two-second blip reads differently from a page that has been frozen for ten minutes. Any successful read or stream event clears it. Where nothing has ever been read the page says "Unknown" rather than picking the answer that is dangerous to be wrong about. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>