fix: handle jersey checkout network errors gracefully on Safari/iOS (#220) #221
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/westside-app!221
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "220-fix-handle-jersey-checkout-network-error"
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?
Summary
A parent (Daniel Niyitanga) clicked "Order" on the jersey page from Safari/iOS and saw a raw "load failed" error. Investigation confirmed the basketball-api backend is fully functional -- CORS preflight returns 200 with correct headers, POST /jersey/checkout works end-to-end returning a Stripe checkout URL, Stripe API key is present, and Tailscale Funnel is configured correctly. The root cause is Safari throwing
TypeError("Load failed")on transient network failures duringfetch(), which the frontend displayed verbatim.Changes
src/routes/(app)/jersey/+page.svelte:TypeError(Safari "Load failed", Chrome "Failed to fetch") andAbortErrorseparately from server errors, showing user-friendly messages with retry guidanceencodeURIComponent()to token and player_id in checkout and player-info URLsAbortControllerto prevent indefinite hangsdetailfield from JSON error responses for clearer server-side error messagesTest Plan
npm run buildpasses)Review Checklist
Related Notes
forgejo_admin/basketball-api #340-- parent issue reporting the bugwestside-- the project this work belongs toSafari throws TypeError("Load failed") on transient network failures during fetch(), which was displayed raw to parents. This change: - Detects network-level errors (TypeError) and AbortController timeouts separately from server errors, showing user-friendly messages - URL-encodes token and player_id in checkout and player-info URLs - Adds 30s fetch timeout via AbortController to prevent indefinite hangs - Extracts detail field from JSON error responses for clearer server errors Closes forgejo_admin/basketball-api#340 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Review: APPROVED
Investigation summary: Confirmed all three suspected causes from basketball-api#340 are NOT the root cause:
access-control-allow-originheader for the production hostnamesk_live_51...), pod can reach Stripe API, checkout endpoint returns valid Stripe checkout URL8d0eac3) matchesorigin/mainHEADThe "load failed" is Safari/iOS's
TypeError.messagefor transient network failures duringfetch(). The fix correctly handles this at the frontend level.Diff review:
+,/,=from base64)finally { clearTimeout }is correct cleanup patternBuild: Passes (
npm run buildsucceeds)PR #221 Review
DOMAIN REVIEW
Tech stack: SvelteKit, vanilla JS (no TypeScript), browser fetch API.
Network error handling (core fix)
The
TypeError/AbortErrordetection pattern is correct. Safari throwsTypeError("Load failed"), Chrome throwsTypeError("Failed to fetch"), and Firefox throwsTypeError("NetworkError..."). CatchingTypeErrorcovers all three browsers. TheAbortErrorcheck viaDOMExceptionwithname === 'AbortError'is the correct pattern forAbortControllertimeouts.AbortController timeout
Implementation is correct. The
setTimeout->controller.abort()pattern withclearTimeoutin thefinallyblock prevents timer leaks whether the fetch succeeds, fails, or throws. 30 seconds is a reasonable timeout for a Stripe session creation round-trip.URL encoding
encodeURIComponent()ontokenandplayerIdis correct defensive coding. The?? ''fallback for nullable values avoids passing"undefined"or"null"as literal strings, which is good.JSON error parsing
The
try { JSON.parse } catch { use raw text }pattern for extractingdetailfrom error responses is sound. FastAPI returns{"detail": "..."}by default, so this extracts meaningful messages when the backend returns structured errors.Consistency observation (nit, not blocker)
The
onMountfetch block (player-info, options, sizes around lines 152-222) does NOT get the same network error handling or timeout treatment. That block has its own catch that falls back to hardcoded options, which is acceptable for page load -- but it means a network failure during initial load still shows the fallback UI silently, while a network failure during checkout now shows a clear error. This asymmetry is fine given the different UX requirements (page load vs. payment action), but worth noting.BLOCKERS
None.
On the test coverage BLOCKER criterion: This repo has zero test infrastructure -- no test files, no test runner configured for component tests. The PR is a targeted bugfix to an existing untested Svelte component, not new functionality. Requiring test coverage for a 3-line error classification change in a repo with no test harness would be disproportionate. The manual test plan in the PR body is appropriate for this change. If/when this repo gets test infrastructure, the jersey checkout flow should be a priority for coverage.
NITS
Timeout magic number:
30000is hardcoded. Consider extracting to a named constant (e.g.,const CHECKOUT_TIMEOUT_MS = 30000;) for readability and single-point-of-change. Minor.Missing timeout on player-info fetch: The initial
Promise.allfetch block (line ~161) has no timeout. A hung connection during page load would spin the loading indicator indefinitely. Lower priority since the user can navigate away, but worth a follow-up ticket.Comment uses emoji: Line 253 in the diff has a unicode em-dash comment (
Timeout after 30s ---). This is fine functionally but note the project CLAUDE.md says no emojis -- the em-dash is not an emoji, just flagging for awareness.SOP COMPLIANCE
220-fix-handle-jersey-checkout-network-errorfollows{issue-number}-{kebab-case-purpose}PROCESS OBSERVATIONS
VERDICT: APPROVED