[Frontend] Stop swallowing errors: ~18 places where a failed request looks identical to success or nothing happened #377
Closed
opened 2026-08-25 20:42:16 +00:00 by claude-bot
·
2 comments
No Branch/Tag specified
main
feat/1328-fill-it-in
feat/1328-fill-frontend
feat/1328-fill-backend
feat/9-foundry-push
fix/9-foundry-live
feat/9-foundry-push-frontend
renovate/celery-5.x
feat/5-foundry-connect
feat/9-foundry-push-backend
feat/9-foundry-push-module
feat/5-foundry-frontend
feat/5-foundry-backend
feat/5-foundry-module
release/v4.8.0-rc1
docs/d341-convert-draft
feat/958-update-to-version
feat/958-convert-sheet
feat/958-pathbuilder-import-frontend
fix/1360-heading-order
feat/958-pathbuilder-import-backend
feat/11-weekly-digest
feat/1341-auto-update
docs/d332-superseded
chore/1345-names-cleanup
feat/1342-mentioned-pages
feat/1328-recipes
fix/1346-pgdg-key
perf/1247-shared-recording-stream
chore/132-gitleaks-allowlist
release/v4.7.1
spike/foundry-module
release/v4.7.1-rc1
docs/decisions-d332-d337
fix/bot-certifi
chore/release-4.7.0
chore/release-4.7.0-rc4
chore/deps-4.7.0
chore/release-4.7.0-rc3
fix/1280-quote-must-say-it
fix/1257-names-row-squeeze
chore/release-4.7.0-rc2
fix/1245-pg-upgrade-count
chore/release-4.7.0-rc1
feat/1257-names-in-prep
feat/1257-be
feat/1257-fe
feat/1257-rating-fixes
design/1328-fill-it-in
feat/1257-name-splits
fix/1326-redis-503
feat/1280-fact-suggestions
feat/1280-be
feat/1280-fe
design/1280-fact-suggestions
feat/1279-infobox
feat/1279-be
feat/1279-fe
feat/1321-shelf-copies
design/1279-infobox
feat/1243-table-find
feat/1257-who-rated
feat/1243-prep-drawer
feat/1243-sheet-pickers
design/1243-browse-in-place
feat/1315-hotwords-default
feat/1242-system-filters
feat/1257-more-name-styles
feat/506-leg3-hotwords
design/1242-system-filters
fix/1302-packs-polish
feat/1241-rules-library
design/1241-rules-library
feat/1245-postgres-18
integ/4.6.2
fix/1263-map-ui-2
fix/1263-map-server
fix/1263-map-ui-1
fix/1261-editor-link-menu
fix/1229-axe-4.14-name-mismatch
renovate/axllent-mailpit-1.x
renovate/eslint-monorepo
renovate/authlib-1.x
chore/release-4.6.0
chore/release-4.6.0-rc2
fix/460-rc2-screens
fix/460-rc2-server
fix/release-prerelease-exit
chore/release-4.6.0-rc1
feat/125-campaign-templates
feat/126-calendar
feat/1181-pictures-and-files
feat/1190-event-people
integ/4.6.0
feat/1191-line-of-the-night
feat/1200-starter-packs
chore/bot-multidict-and-playwright-group
fix/1202-this-week-answered-count
fix/122-124-leftovers
fix/1178-get-routes-that-write
fix/1203-journeys-check-names
design/125-campaign-templates
feat/126-calendar-engine
design/126-in-world-calendar
fix/1189-cited-stamp-in-range
fix/1189-v4-looser-check
fix/1189-richer-beats
fix/1113-export-bind-lists
fix/1154-release-notes-by-file
chore/release-4.5.0
chore/release-4.5.0-rc12
fix/1135-wiki-markup-as-text
fix/demo-usage-cost-in-window
chore/release-4.5.0-rc11
fix/1121-sheet-consistency
fix/1121-sheet-5e-terms-and-card-hp
copy/1122-busiest-pages
fix/1121-relevant-choices
fix/1121-wiki-create-npc-save-sheet-links
fix/1029-menu-stays-on-screen
docs/prototype-transcript-bin-wording
fix/1023-editor-save-row-back-scroll
fix/1020-search-exact-first
fix/1020-table-prep
fix/1020-shell-wordmark-footer-bell
fix/1020-wiki-article-party
fix/872-admin-kept-until-phone-tables
feat/l13-milestone-level-waiting
fix/1000-1001-bell-older-setup-context
fix/999-pronouns-in-prompts
fix/l19-other-class-feats
fix/1010-transcript-window-ordinary-confirm
docs/d254-d269-decisions
fix/702-member-search
fix/680-transcript-bin
fix/993-unidentified-prompts
fix/841-redis-timeouts
fix/990-binned-not-exported
fix/830-export-prep-shelf
fix/deps-audit-2026-10-01
fix/877-title-reads
fix/798-audio-received-at
build/lane11-17-j7
build/lane11-16-levelup
build/lane11-14-seams
build/lane11-14-seed
build/lane11-15-start
build/lane11-13-old-block-out
fix/980-review-r3
build/lane11-12-block
build/lane11-8-packs
fix/demo-seed-canonical-names
build/lane11-11-hot-half
docs/l11-decisions
build/lane11-7-convert
build/lane11-10-editing
fix/npm-audit-brace-expansion
build/lane11-9-sheet-page
fix/bot-pyjwt
build/lane11-6-content
fix/lane11-rank-names
build/lane11-2b-language
build/lane11-4-js-evaluator
build/lane11-3-server
build/lane11-5a-pf2e
build/lane11-5b-5e
feat/design-admin
build/lane11-2-evaluator
build/lane11-1-sheet-pieces
build/lane11-0-drawing
fix/943-quest-objectives-fk
fix/prepsheet-shelf-flake
fix/945-leftovers
build/demo-gm-copy
fix/901-email-landings
build/lane10-8-journeys
build/demo-seed-lanes-8-10
fix/887-gm-own-confirmation
build/lane9-5-j1-admin
fix/918-audience-click
fix/useasync-overtaken
ci/939-critical-path
test/937-backend-suite-speed
chore/claude-orchestration
fix/924-xp-gm-only
fix/session-stale-poll
docs/decisions-d239-d242
fix/930-pypdf
build/lane8-journeys
build/925-quest-editor
fix/894-private-page-pictures
build/lane8-p4-drafts
build/lane8-p7-map
build/lane8-p6-handouts-rules
fix/wiki-fixture-names
build/lane10-d227-test-usage
fix/rsvp-horizon-test-tuesday
fix/media-volume-owner
build/lane8-p3-editor
fix/912-unidentified-rename
build/lane8-p5-party-quests
build/lane8-handout-takeback
build/lane10-pickers
build/lane8-p2-article
build/lane8-6-map
build/lane10-7b-email-pages
build/lane8-5-handouts
build/lane10-7a-test-the-bot-pages
build/lane8-p1-pages
build/lane8-4-quests
build/lane8-2b-feeds
build/lane8-3-parties
build/lane8-2a-links
build/lane10-5-commands
build/lane9-6-seed
build/lane10-4b-reactions
build/lane9-4d-pages
build/lane9-4c-pages
build/lane10-4a-posts
build/lane10-3-email
fix/891-web-fonts
build/lane9-4b-pages
build/lane10-6-test-the-bot
build/lane10-2-notify-out
build/lane8-1-wiki-text
build/lane9-4a-frame
design/lane10-drawings
fix/j6-budget
fix/j5-phone-flake
fix/869-test-leaks
fix/843-record-start-followups
build/lane7-all
build/lane6-all
build/lane9-b2-server
chore/changelog-fragments
docs/decisions-d218
build/lane9-a-owner-overview
fix/rule-card-level-label
fix/834-retention-semantics
fix/o3c-record-without-announcement
docs/decisions-d179
docs/decisions-d131-d170
fix/851-commit-before-response
build/lane6-j3-seed
build/lane9-b1-d67-identity
fix/841-blocking-loop
build/lane9-c-backups-audit
fix/vote-race-and-start-commit
fix/recording-announcement
fix/proposal-read-leak
fix/last-admin-guard
build/lane6-p4c-planning
build/lane6-p4b-margin
build/lane6-p4a-sheet
build/lane5-j4-seed
build/lane6-base
build/lane7-pages-talk
build/lane5-pages
build/lane7-pages-safety
build/lane7-p3-safety
build/lane7-p2-notify
build/lane6-p3-planning
build/lane6-p2-history
build/lane6-p1b-items
build/lane7-p1-threads
build/lane5-p6-faces
build/lane6-p1a-sheet
build/lane5-p5-find
build/lane5-p4a-board
build/lane5-p4b-scenes
build/lane5-p3-live
build/lane5-base
build/lane5-p2-cards
docs/lane5-decisions
build/review-r2-wide
fix/review-r1
build/lane5-p1-scenes
build/demo-seed
build/lane4-journeys-j5
build/lane4-page-review-b
build/lane4-page-review-a
build/lane4-page-read
build/lane4-journeys
build/lane4-page-sess2
build/lane4-page-sess1
build/lane4-notes
build/lane4-discord-on-approval
build/lane4-scheduling
build/lane4-versions
build/760-session-number
build/lane4-decisions
build/797-erase-no-regenerate
build/test-race-audit
build/lane3-completions
build/794-dark-only
build/796-titles-scroll
build/791-retention-anchor
build/a11y-sweep
build/shell-followups
build/retention-preview
build/753-backup-readback
docs/d91-d93
build/lane3-j1
build/ci-harness-paths-selftest
fix/useauth-test-race
build/ci-harness-paths
build/777-ai-settings-read
build/lane3-settings-c
build/lane3-settings-b
build/lane3-j6
build/lane3-sessionform
build/lane3-settings-a
build/lane3-members
build/lane3-thisweek
build/lane3-story
chore/87-align-python-3.12
v4.8.0-rc1
v4.7.1
v4.7.1-rc1
v4.7.0
v4.7.0-rc4
v4.7.0-rc3
v4.7.0-rc2
v4.7.0-rc1
v4.6.2
v4.6.2-rc4
v4.6.2-rc3
v4.6.2-rc2
v4.6.2-rc1
v4.6.1
v4.6.1-rc1
v4.6.0
v4.6.0-rc2
v4.6.0-rc1
v4.5.3
v4.5.3-rc1
v4.5.2
v4.5.2-rc1
v4.5.1
v4.5.1-rc2
v4.5.1-rc1
v4.5.0
v4.5.0-rc12
v4.5.0-rc11
v4.5.0-rc10
v4.5.0-rc9
v4.5.0-rc8
v4.5.0-rc7
v4.5.0-rc6
v4.5.0-rc5
v4.5.0-rc4
v4.5.0-rc3
v4.5.0-rc2
v4.5.0-rc1
v4.4.1
v4.4.0
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.2
v4.1.1
v4.1.0
v4.0.1
v4.0.0
v3.11.4
v3.11.3
v3.11.2
v3.11.1
v3.11.0
v3.10.0
v3.9.1
v3.9.0
v3.8.0
v3.7.0
v3.6.1
v3.6.0
v3.5.0
v3.4.1
v3.4.0
v3.3.0
v3.2.0
v3.1.0
v3.0.0
v2.1.0
v2.0.0
v1.1.0
v1.0.0
v0.12.0
v0.11.0
v0.10.0
v0.9.1
v0.3.3
v0.9.0
v0.8.0
v0.3.2
v0.3.1
v0.3.0
v0.7.0
v0.6.0
v0.5.0
v0.4.0
v0.2.1
v0.2.0
v0.1.1
v0.1.0
Labels
Clear labels
backend
Issue related to the backend
bot
Issue related to the bot
bug
Something is not working
duplicate
This issue or pull request already exists
enhancement
New feature
frontend
Issue related to the frontend
help wanted
Need some help
invalid
Something is wrong
question
More information is needed
recording
Issue related to the recording system
security
Security hardening / vulnerability work
wontfix
This won't be fixed
No labels
backend
bot
bug
duplicate
enhancement
frontend
help wanted
invalid
question
recording
security
wontfix
Milestone
Clear milestone
No items
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/Quest-Board#377
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Impact: HIGH
Found in the August 2026 session lifecycle review (#319).
What the user experiences
Across the app, a long list of user actions and background fetches fail silently: nothing changes, no toast appears, no inline error shows, and the UI simply looks the same as it would if the action had never been attempted or had succeeded. A vote that didn't stick looks like a vote. A shelf card position that didn't save looks saved until the next reload. A live recording that lost its network connection is displayed as "not recording" — actively misleading mid-session, not just uninformative.
Evidence
Each of these swallows an error with no user-visible signal:
webapp/frontend/src/components/VotingGrid.jsx:159-161— a failed voteconsole.errors only; the cell silently reverts.webapp/frontend/src/pages/SessionShelf.jsx:969—updateShelfCard(..., { position })is.catch(() => {}); a drag/resize appears to work and silently doesn't persist.webapp/frontend/src/pages/CampaignDetail.jsx:467-469andwebapp/frontend/src/pages/CampaignStoryline.jsx:160-161—fetchLoreProposals/fetchRelationshipProposalsfailures are swallowed; the proposals block and its badge simply don't render, indistinguishable from "no proposals" (see also the lore-proposal discovery issue in this milestone, which owns the destination/duplication fixes for this same surface).webapp/frontend/src/pages/CampaignPlanning.jsx:848—setPrefetchStatus("error")is stored in state and never rendered anywhere.webapp/frontend/src/components/TitleSuggestions.jsx:49— a suggestion-fetch failure is swallowed; the helper just doesn't appear.webapp/frontend/src/components/ColdOpenPanel.jsx:59— a cold-open fetch failure is swallowed by design; the "Previously on" panel silently doesn't appear.webapp/frontend/src/pages/SessionDetail.jsx:518-532— attendance-proposal fetch failure swallowed as "best-effort".webapp/frontend/src/pages/SessionDetail.jsx:535-553— loot/XP fetch failure swallowed.webapp/frontend/src/pages/RecordingDashboard.jsx:100-102— a failedfetchRecordingLivesets{active:false}, i.e. a network failure is displayed as "not recording" during a live session.webapp/frontend/src/pages/SessionTable.jsx:40-44— poll failures after the initial load are silent by design; a player's table view can freeze on stale reveals with no indication.webapp/frontend/src/pages/WikiArticle.jsx:2007-2016andwebapp/frontend/src/pages/WikiNewEntry.jsx:336-340— "Check for link suggestions" is synchronous; when it returns[]there is literally zero visible change on click, so a user cannot tell whether it ran.webapp/frontend/src/pages/WikiArticle.jsx:1376-1378—fetchGameSystemSchemafailure silently returnsnull, degrading stat labels to raw keys with no explanation of why.Why it matters for a hosted product
Each of these is individually small — a one-to-five-line fix — but collectively they mean a user genuinely cannot trust that the app told them the truth about whether their action worked.
RecordingDashboard.jsx:100-102is the most severe: it turns a transient network blip into an actively false "not recording" status in the middle of a live session, which is exactly when a GM needs the status to be trustworthy.Proposed fix
Apply the inline-error pattern already used correctly elsewhere in the codebase (e.g.
Dashboard.jsx's import success/error box at:358-368) to each site above: a toast, an inline error message, or a visibly distinct state, rather than a silent catch.RecordingDashboard.jsx:100-102specifically must not collapse a network failure into{active:false}— show "connection lost, retrying" instead. This is the audit's P5 plus the additional sites the audit's wiki appendix (§9.4) identified.Acceptance criteria
RecordingDashboard.jsxdistinguishes a network failure from a genuine "not recording" state during a live session.VotingGridandSessionShelfposition-save both surface a retry affordance on failure, not just an error message.CampaignPlanning's prefetch error (:848) is rendered somewhere in the UI.Picking this up now that phases 1–6 of v4.3.0 (#514) are on main, since it touches twelve files across every lane and is cheapest once they have settled. One small reusable inline-error component used at every site; the recording page gets a real state (connection lost, retrying, last known state kept) rather than a message, because a network blip reading as "not recording" mid-session is the worst of the twelve; the two proposal-fetch sites were already fixed by #375 and will be verified rather than redone.
Done in the #377 PR (auto-merging on green); ships with v4.3.0. Frontend only, ten commits.
Every site in the audit now shows a visible signal on failure. Site 3 (
fetchLoreProposals/fetchRelationshipProposalson CampaignDetail and CampaignStoryline) needed nothing: #375 had already fixed both with the shared "Couldn't load suggested wiki updates." state; verified on main, nothing added.One small reusable
InlineError(components/InlineError.jsx: message, optional retry, optional boxed look) carries eleven of them, andCampaignPlanning's private copy, which was the prototype, was folded into it rather than left as a second implementation.The one that got a state machine is the recording page.
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 in Discord.livenow only ever holds something the server actually said, a separateconnectionstate 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; where nothing has ever been read the page says "Unknown" rather than picking the answer that invites a second recording of the same evening. The players' table view got the same indicator for its own 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, because by the time anyone clicks Try again the card has usually been nudged again.
CampaignPlanning's prefetch error now says what it means (name suggestions were not pre-generated; the tools still work, the first model call may be slower). "Check for link suggestions" is computed locally, so its fix is empty-result feedback: "No link suggestions found." A genuine failure to load the game-system schema is reported in the stat block and 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, which is why it looked like a misclick. And
ColdOpenPanelis still mounted only from the GM's shelf, not for players (#388 changes that); the copy is quiet and does not leak the backend's message either way.Tests: 696 passing across 59 files (was 667/57). Every site has a test that the failure renders its signal, every retry has a test that it re-calls the API, and the recording page has tests that a failed poll keeps the last known active state, says how stale the reading is, clears on the next success, and says "Unknown" when never answered.