Skip to content

fix(client): Replace silent catch blocks across fetch handlers (closes #1058)#1119

Open
jeromehardaway wants to merge 2 commits into
masterfrom
fix/replace-silent-catch-blocks
Open

fix(client): Replace silent catch blocks across fetch handlers (closes #1058)#1119
jeromehardaway wants to merge 2 commits into
masterfrom
fix/replace-silent-catch-blocks

Conversation

@jeromehardaway

Copy link
Copy Markdown
Contributor

Summary

Multiple client-side fetch handlers were swallowing errors with `catch { /* non-critical */ }` — spinners would stop, but nothing rendered and devs couldn't see what failed.

New `src/utils/handle-client-error.ts` exports a single `handleClientError(err, { context })` helper that every silent catch should migrate to. It always logs (with a `[context]` prefix so failures are searchable in the console) and has a TODO marker where Sentry / equivalent will plug in.

Migrated catches

  • `src/pages/challenges/index.tsx` — `fetchRecommended`, `fetchHistory`, `handleGetHint`, `handleShowSolution`. Added per-section error states rendered inline so the user sees what failed instead of a hung spinner.
  • `src/pages/challenges/[id].tsx` — `handleGetHint`, `handleShowSolution`. Piped into the existing `error` banner.
  • `src/pages/challenges/browse.tsx` — `fetchTopics`. Soft-fail; topic chips simply hide, but now logged for devs.
  • `src/components/profile/TroopDashboard.tsx` — `handleModuleChange` (now surfaces a user-visible error), `fetchDashboard`, `fetchWarmups`, `fetchProgress`.
  • `src/components/jobs/ResumeScorer.tsx` — `handleFileUpload`, `handleDrop` (hook-level error reporting kept; underlying failure is now visible to devs too).
  • `src/components/translator/TranslatorForm.tsx` — `fetchDescription`, PDF `extract-fields` fallback, `handlePdfUpload`.
  • `src/containers/profile/bio.tsx` — `fetchProfile`, `handleSave`. Save now renders an inline error banner; previously the failure was silent.

Linting note

Biome's `noEmptyBlockStatements` rule is already `error` for `src/`. The empty catches were being satisfied by `// non-critical` style comments inside the body — that's why these slipped through. Documented this in the helper's JSDoc so future reviewers know what to insist on.

Diff size

Most of the line-count delta in JSX-heavy files (ResumeScorer, TroopDashboard, TranslatorForm) is Biome auto-reformat-on-write wrapping long lines to 100 cols. `git diff --ignore-all-space` shows the semantic changes only.

Closes #1058.

Test plan

  • `npm test` — 380/380 pass
  • `npx biome check` on changed files — no new issues (one pre-existing default-switch error in `browse.tsx` unrelated to this PR)
  • Manual: with DevTools network throttle, fail `/api/j0di3/challenges/recommended` — error banner replaces the empty state on the challenges page
  • Manual: fail `/api/user/profile-basic` PUT — red banner appears above the profile bio header

Closes #1064.

Extends src/lib/rate-limit.ts with applyRateLimit() — a small wrapper
that sets X-RateLimit-* headers (and Retry-After on block) so each
route opts in with one line.

Applied limits:
- /api/j0di3/*    (per-user) 30 / 60s — every call hits a paid AI
- /api/ai/chat    (per-user) 20 / 60s — same reason, streaming
- /api/contact    (per-IP)    5 / 15m — paired with Gemini classifier
- /api/newsletter (per-IP)    5 / 15m
- /api/apply      (per-IP)    3 / 60m
- /api/mentor     (per-IP)    5 / 60m
- /api/mentee     (per-IP)    5 / 60m
- /api/health     (per-IP)   60 / 60s — caps tight-loop pings

Limits documented in docs/RATE_LIMITS.md (response shape, frontend
conventions, how to add a new limit). Stayed on the existing
in-memory bucketing — note in the doc that Redis/Upstash is the
swap when we leave single-instance Vercel.

Test fixtures for j0di3-proxy, /api/contact, and /api/health updated
to include req.headers / req.socket / res.setHeader, and reset the
bucket map via _resetRateLimitForTests in beforeEach so test order
does not accumulate state.
Closes #1058.

Adds src/utils/handle-client-error.ts with handleClientError() and
getErrorMessage() — the single helper every silent catch should be
migrated to. handleClientError always logs (so failures are no
longer invisible) and has a TODO marker where Sentry / equivalent
will plug in once we wire observability.

Migrated every silent catch the issue called out plus a few near-
misses found by walking the AST:

- src/pages/challenges/index.tsx — fetchRecommended, fetchHistory,
  handleGetHint, handleShowSolution. Added per-section error states
  rendered inline so the user sees what failed instead of a hung
  spinner.
- src/pages/challenges/[id].tsx — handleGetHint, handleShowSolution.
  Pipe failures into the existing `error` banner.
- src/pages/challenges/browse.tsx — fetchTopics. Topic chips were
  legitimately a soft-fail; now logged for devs even though UX is
  unchanged.
- src/components/profile/TroopDashboard.tsx — handleModuleChange,
  fetchDashboard, fetchWarmups, fetchProgress. Module change now
  surfaces a user-visible error.
- src/components/jobs/ResumeScorer.tsx — handleFileUpload,
  handleDrop. Both already had hook-level error reporting; the
  helper just makes the underlying failure visible in the console
  / future observability sink.
- src/components/translator/TranslatorForm.tsx — fetchDescription,
  PDF extract-fields fallback, PDF upload. Same hook pattern.
- src/containers/profile/bio.tsx — fetchProfile, handleSave. Save
  now surfaces an inline banner on failure (it used to silently
  drop on the floor).

Biome's `noEmptyBlockStatements` rule is already `error` for src/
and was being satisfied by `// non-critical` style comments inside
the catch — that's why these slipped through. Documented this in
the helper's JSDoc so future reviewers know what to insist on. Most
of the line-count delta in JSX-heavy files is Biome auto-reformat
on save (long lines wrapped to 100 cols); diff with --ignore-all-space
to see the semantic changes only.
@vercel

vercel Bot commented May 13, 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 May 13, 2026 1:55pm

Request Review

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)

1 participant