Skip to content

fix(ui): Surface fetch errors instead of swallowing them#1168

Merged
jeromehardaway merged 5 commits into
masterfrom
fix/1058-silent-catch-blocks
Jun 22, 2026
Merged

fix(ui): Surface fetch errors instead of swallowing them#1168
jeromehardaway merged 5 commits into
masterfrom
fix/1058-silent-catch-blocks

Conversation

@jeromehardaway

@jeromehardaway jeromehardaway commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Problem

Client-side fetch/submit handlers across the app caught errors silently (catch { /* non-critical */ }). When a request failed, users saw hung spinners (profile bio loaded forever), blank pages (lesson detail), misleading empty states ("No challenges attempted yet" on a 500), or buttons that simply did nothing (Get Hint, Reveal Solution, coach, module change, profile save).

Solution

  • Added one small helper, handleClientError(err, context) in src/utils/handle-client-error.ts: logs with context via console.error and returns a user-safe message string.
  • Every fixed site sets a component error state from the helper and renders the message with a retry affordance (a Retry button for load paths; the original action button doubles as retry for action paths). Non-ok responses in these paths now throw and surface too, instead of being silently ignored.
  • No toast library exists in package.json, so per-component error state was used (option 2 from the issue). No dependencies added.

Fixed sites:

File Site Failure before
src/pages/challenges/index.tsx fetchRecommended, fetchHistory misleading empty states; now per-panel error + Retry
src/pages/challenges/index.tsx handleGetHint, handleShowSolution button did nothing; now shared error banner
src/pages/challenges/[id].tsx handleGetHint, handleCoach, handleShowSolution button did nothing; now existing error banner
src/containers/profile/bio.tsx fetchProfile, handleSave infinite spinner / silent save failure; now error + Retry, inline save error
src/components/profile/TroopDashboard.tsx handleModuleChange select silently reverted; now inline error under the select
src/pages/lessons/lesson/[id].tsx lesson fetch blank page; now error + Retry
src/pages/community/index.tsx candidates fetch misleading empty sections; now error + Retry

Skipped (deliberate non-critical suppressions, one line each):

  • src/utils/safe-storage.ts (4 sites) — storage availability/cleanup guards, silent by design.
  • src/lib/translator-analytics.ts — analytics must never break the app.
  • src/components/translator/TranslatorForm.tsx (3) — auto-fill conveniences degrade to manual entry; PDF upload errors are surfaced via the usePdfUpload hook's own error state.
  • src/components/jobs/ResumeScorer.tsx — same: usePdfUpload tracks and renders the error.
  • src/components/profile/TroopDashboard.tsx fetchWarmups/fetchProgress/fetchXp/fetchStreak/fetchToday — decorative tiles/trays that hide gracefully; the primary dashboard fetch already sets an error.
  • src/components/profile/ProfileSettings.tsx — optional placements/MOS panels hide; the destructive actions already set a message state.
  • src/components/ai-assistant/AITeachingAssistant.tsx sendFeedback — optimistic thumbs-up/down, non-critical.
  • src/pages/challenges/browse.tsx fetchTopics — topic chips stay empty; the catalog fetch already surfaces errors.
  • src/pages/lessons/index.tsx recall fetch — optional recall tray hides; the module grid is static.
  • src/pages/admin/j0di3/index.tsx stats fetch — admin convenience tiles hide; nav links are static.
  • src/pages/profile/[id].tsx, src/pages/p/[callsign].tsx — server-side getServerSideProps fallbacks (avatar fallback / notFound), not client-side.

Biome rule: suspicious/noEmptyBlockStatements is already enabled at error in biome.json (with test/scripts overrides). It permits comment-only blocks by design, which is why these catch { // non-critical } blocks passed lint — no config change needed.

Note: staged files were normalized by the pre-commit biome check --write hook, which accounts for the formatting-only hunks in __tests__/pages/challenges/index.test.tsx.

Verification

  • npm run typecheck — no new errors (7 pre-existing errors in untouched __tests__/{lib,pages/api}/j0di3/* files exist on master)
  • npm run lint — changed files contribute 0 errors; repo baseline unchanged (25 pre-existing errors on master)
  • npm test — 390 passed (42 files), including 3 new challenges-page tests (rejecting fetch shows error + Retry recovers; non-ok history surfaces; hint failure surfaces) and 3 helper unit tests
  • npm run build — succeeds (exit 0)

Review updates

  • src/pages/community/index.tsx: the if (res.ok) guards still skipped state on a 4xx/5xx, leaving empty sections despite being listed as fixed (caught in review). Hardened to throw on any non-OK response so the existing error + Retry path surfaces it.
  • Swept the rest of src/ for the same pattern (if (res.ok) with no else, .catch(() => …)): no remaining genuine silent failures. The other ~25 .catch/res.ok sites are the documented best-effort suppressions already listed under "Skipped" (optional enrichment tiles, prefetch/warmup, safe-storage, telemetry, navigator.share cancellation).

Closes #1058

Client-side fetch and submit handlers caught errors silently, leaving
users with hung spinners, blank pages, or misleading empty states.

Add handleClientError(err, context) in src/utils/ that logs with
context and returns a user-safe message, and use it at every fixed
site. Components now set an error state and render the message with a
retry affordance (retry button or the original action button).

Fixed sites:
- challenges/index: recommended + history fetches (per-panel error and
  retry), hint and solution handlers (shared error banner)
- challenges/[id]: hint, coach, and solution handlers
- containers/profile/bio: profile load (was an infinite spinner) and
  profile save
- components/profile/TroopDashboard: module-change PATCH
- lessons/lesson/[id]: lesson load (was a blank page), with retry
- community/index: candidates load, with retry

Non-ok responses in these paths now throw and surface too, instead of
being ignored. Deliberate non-critical suppressions (safe-storage,
analytics, optional decorative panels, hooks that track their own
errors) are left alone.
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vets-who-code-app Ready Ready Preview, Comment Jun 22, 2026 2:12am

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves client-side UX and debuggability by surfacing fetch/submit failures (instead of silently swallowing them), using a shared handleClientError helper that logs context and returns a renderable message.

Changes:

  • Added handleClientError(error, context) utility and unit tests.
  • Updated multiple pages/components to throw on non-OK responses, set error state, and render inline error + Retry affordances.
  • Expanded challenges page tests to cover “reject/non-ok surfaces error” and “Retry recovers” flows.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/utils/handle-client-error.ts New helper for contextual logging + returning a message for UI error state.
src/utils/__tests__/handle-client-error.test.ts Unit tests validating helper logging and message selection.
src/pages/lessons/lesson/[id].tsx Adds load error state + Retry for lesson fetch (throws on non-OK).
src/pages/community/index.tsx Adds load error state + Retry for candidates fetch (but currently only catches network errors).
src/pages/challenges/index.tsx Surfaces errors for recommended/history fetches and hint/solution actions; adds Retry UI for panels.
src/pages/challenges/[id].tsx Surfaces errors for hint/coach/solution actions using existing error banner.
src/containers/profile/bio.tsx Surfaces profile load/save errors with Retry (load) and inline save error.
src/components/profile/TroopDashboard.tsx Surfaces module update failures inline under the module select.
__tests__/pages/challenges/index.test.tsx Adds coverage for surfaced fetch errors and Retry recovery behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pages/community/index.tsx Outdated
@jeromehardaway jeromehardaway self-assigned this Jun 22, 2026
…ch-blocks

# Conflicts:
#	src/components/profile/RepositoryShowcase.tsx
@jeromehardaway

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

I checked this branch locally and there are no remaining merge conflicts to resolve: master is already merged into fix/1058-silent-catch-blocks at f103c49, and git merge origin/master reports "Already up to date." If GitHub is still showing a conflict, please refresh/recheck the PR mergeability and I can take another pass if anything new appears.

@jeromehardaway jeromehardaway merged commit 7109fe7 into master Jun 22, 2026
6 checks passed
@jeromehardaway jeromehardaway deleted the fix/1058-silent-catch-blocks branch June 22, 2026 02:16
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.

Replace silent catch blocks across fetch handlers (users see hung spinners)

3 participants