Carnival Fare — Changelog
Tracked starting v2.1. Earlier versions shipped without a changelog.
Unreleased
Added
- **Bug reporting + error monitoring via Sentry (BFLS-1301).** The floating "Report a bug" button is now Sentry's screenshot-capable widget (it replaces the old feedback button), and the app now automatically captures errors with a privacy-masked session replay so a bug on someone's phone can actually be diagnosed. Replays mask all text and block all media by default, and the guest invite token is scrubbed from everything Sentry sends. Fully no-op when the Sentry DSN isn't configured.
Changed (Ride-only, no Fare changes this round)
- **BFLS-1310 / BFLS-1309 / BFLS-1305 — Ride only this round.** Ride fixed cross-browser password reset (token-hash
/auth/confirmflow), guests seeing a map instead of the venue photo (staged photo→map fallback), and a vote-progress email that could look "behind." Fare has no user-facing changes this round; version pinned for parity. - **BFLS-1294 follow-up — Ride only this round.** Ride fixed expired venue-photo thumbnails on older rides (now re-resolved fresh through a proxy). Fare has no user-facing changes this round; version pinned for parity.
Fixed
- **The "Report a bug" button is always visible again (BFLS-1298).** After the Sentry switch the floating report button vanished on phones ("I don't see the chat icon anymore"). We'd routed it through Sentry's auto-injected widget, which only appears when the DSN is set *and* Sentry's actor button actually injects on the device — neither was reliable.
MobileShellnow renders our ownFeedbackFab**unconditionally** (it posts to/api/feedback→ the Supabasefeedbacktable + Slack, and **forwards each report into Sentry's User Feedback inbox** via a client-sideSentry.captureFeedbackso the report is linked to the user's active session replay + breadcrumbs), and Sentry's own feedback widget is set toautoInject: falseso it can't double up or silently fail. Shared with Ride. - **Screen wake lock retries after a tap (BFLS-1278).** The app-wide
<ScreenWakeLock />only re-tried acquiring the lock onvisibilitychange, so if iOS rejected the initial request right after a page load (document not yet "fully active") and the page then stayed visible, it never recovered. It now also re-acquires on the firstpointerdown/touchend(cheap no-op once a lock is held). **Caveat:** this does not overcome iOS **Low Power Mode** or in-browser Safari's general unreliability with the Wake Lock API — a reliable always-on screen needs the native app's idle-timer flag (tracked under BFLS-1274). Shared with Ride. - **Cancel during "Reading receipt…" now exits to the home screen (BFLS-1282 follow-up).** When the receipt was stuck reading, "Start over" used to re-upload in place — which just looped back to "Reading receipt…" whenever the upload itself kept failing. The stuck-screen "Start over" now discards the receipt and returns to the Fare **home (step 01 — snap a receipt)** via
router.push('/'), so the host gets a clean exit instead of a loop. - **Honest message when receipt OCR hits the free-tier limit (BFLS-1282 follow-up).** The "Couldn't read this receipt" screen used to always blame the photo ("try a sharper shot"). The real cause is often the **Vercel AI Gateway free-tier rate limit** — both Gemini (primary) and Haiku (fallback) return
GatewayRateLimitError. OCR failures are now classified (isRateLimitErrorinreceipt-ocr.ts); the failed screen explains both possibilities and offers **Try again** (re-runs OCR on the same photo) which surfaces the true reason — *"Receipt reading is over its free-tier limit right now. Add AI Gateway credits…"* — and auto-recovers the bill once credits are added. **Try again** vs **Use a different photo** are now distinct actions. The actual fix for the limit is adding paid credits to the AI Gateway.
v2.1.5 — 2026-06-05
Fixed
- **"Start over" actually escapes the stuck "Reading receipt…" screen (BFLS-1282).** The earlier "Cancel and start over" button discarded the receipt but
resetReceiptForReuploadsets the receipt back to statusuploaded— which is the *exact* state the "Reading receipt…" spinner renders — so the host dropped straight back onto the spinner with no OCR running and it looked like nothing happened. The bill flow now forces the view to the **photo re-upload step** after any reset (via a sharedreuploadReceipthelper used by the stuck-screen "Start over", the failed-OCR "Try a different photo", the stepper re-upload, and the confirm-step "Re-take photo"), and clears that override once a new photo uploads so the normal Reading→items flow resumes. Note: this is the escape path; the underlying upload error (a stale Vercel Blob token) is fixed separately in env config.
Changed
- **Screen stays awake across the whole app (BFLS-1278).** The screen no longer dims/sleeps while Fare is open — previously only held during receipt OCR. A new app-wide
<ScreenWakeLock />(in@carnival/ui-shell) is mounted in the root layout and holds anavigator.wakeLocksentinel the entire time the app is foregrounded, re-acquiring onvisibilitychange; backgrounding the app releases it so normal sleep resumes. No-ops where the Wake Lock API is unsupported. Shared with Ride. (The existing per-OCRuseWakeLockis now superseded but left in place — harmless; the API allows multiple sentinels.)
Changed (Ride-only, no Fare changes this round)
- **BFLS-1248 / BFLS-1272 follow-ups — Ride only this round.** Ride hardened its in-app video recorder against the iOS Safari camera-start failure (visible states, 12s timeout, gesture-bound retry, iOS preview fixes) and added Gmail-style swipe-to-Cancel on active home ride rows with a visible red Cancel. Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1295 / BFLS-1296 — Ride only this round.** Ride hid the cost-splitting UI in the create-ride flow (payout-method picker, handle input, and message-preview card on the Guests step; the Payout summary row + Edit link on the Review step). Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1297 — Ride only this round.** Ride's guest voting screen now requires an explicit Yes/No on every proposed time before "Save my choices" enables (no partial submits). Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1294 — Ride only this round.** Ride's venue search now dismisses the mobile keyboard when the search fires. Fare has no user-facing changes this round; version pinned for parity.
v2.1.4 — 2026-06-01
Added
- **Escape the stuck "Reading receipt…" screen (BFLS-1282).** When a receipt upload/OCR fails — e.g. a stale Vercel Blob token returning "this store doesn't exist" — the bill stayed on "Reading receipt…" forever (the 1.5s poll never saw the status flip), with only a Retry that re-ran the same failing step. The loading screen now offers **"Cancel and start over"**, which (after a confirm) discards the stuck receipt via
resetReceiptForReuploadand drops the host back on the upload step — flipping the receipt offuploadedso the poll auto-stops. Note: this makes the failure *escapable*; the underlying blob-token issue is fixed separately in Vercel env config.
Changed
- **Screen stays awake while a receipt is processing (BFLS-1278, Fare side).** Added a
useWakeLock()hook (src/lib/use-wake-lock.ts, mirroring the Ride video-recorder approach) and engaged it inhost-bill-flow.tsxwhilereceipt.status === 'uploaded', so the phone doesn't dim/sleep mid-OCR. Releases when processing ends or the component unmounts, and re-acquires when the tab returns to the foreground. No-ops where the Wake Lock API is unsupported.
Changed (Ride-only, no Fare changes this round)
- **BFLS-1281 — Ride only this round.** Ride now emails the host a lock-time summary (winning time, venue, vote breakdown, who's coming vs not). Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1248 — Ride only this round.** Ride's create-ride video step now makes the in-app recorder (front camera + wake-lock) the primary capture path on mobile, instead of the OS file picker that opened rear-camera-first. Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1271 / BFLS-1272 — Ride only this round.** Ride's host home page now collapses the Recent Activity feed and moves it to the bottom, removes the count-tile scoreboard, and adds a Cancel action (with confirm) to home ride cards. Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1283 — Ride only this round.** Ride's home "Resume" banner now restores a saved create-ride draft in a single tap (previously it dropped the host on a blank form until they tapped a second in-wizard Resume). Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1277 / BFLS-1276 / BFLS-1279 — Ride only this round.** Ride's guest contact picker now pre-checks only the phone (not phone + email) when adding a contact, filters already-added guests out of the Recent/Google lists, and greets the invited guest by first name in the invite SMS. Fare has no user-facing changes this round; version pinned for parity.
v2.1.3 — 2026-05-27
Changed
- **Renamed "Pick" / "Picks" → "Choose" / "Choices" everywhere (BFLS-1263).** User-facing copy across Fare: receipt-confirm step coaching ("Pick your items first." → "Choose your items first.", split-mode toggle "Each picks items" → "Each chooses items", summary "Items you picked" → "Items you chose"), guest bill view ("I'm done picking" → "I'm done choosing", "Items picked (N)" → "Items chosen (N)", "All items picked!" → "All items chosen!", "Tap any item below to change what you picked" → "...what you chose", "You can still adjust what you picked until then" → "...what you chose until then", "Already-picked items get split: tap to share" → "Already-chosen items get split…"), claiming monitor ("All guests finished picking ✓" → "All guests finished choosing ✓", "Everything is picked. Ready to close claiming." → "Everything is chosen. Ready to close claiming.", "Nothing picked yet." → "Nothing chosen yet.", "No one has picked anything yet." → "No one has chosen anything yet."), payment setup step header ("Pick payment method" → "Choose payment method"), Zelle QR upload error ("Pick a QR image to upload." → "Choose a QR image to upload."), add-guests step coaching ("Pick from your Google Contacts…" → "Choose from your Google Contacts…"), Google Contacts picker title ("Pick from Google Contacts" → "Choose from Google Contacts"), contact picker modal ("Pick a contact" → "Choose a contact"), CSV import error ("Pick at least one contact" → "Choose at least one contact"), feedback validation ("Pick a feedback type." → "Choose a feedback type."), password reset confirm ("Pick something at least 8 characters long" → "Choose something at least 8 characters long"), host settings copy ("Each new bill picks from the methods you save here" → "Each new bill chooses from…"), claim API error message ("Only guests can mark picking done" → "Only guests can mark their choices as done"), notifications feed ("picked their times" → "chose their times", "updated their picks" → "updated their choices", "time to pick the winner" → "time to choose the winner", "finished picking items" → "finished choosing items", "All guests finished picking" → "All guests finished choosing"), claiming-open email ("pick what you had at {venue}" → "choose what you had at {venue}", "Open your picking page" → "Open your choosing page", "Your picking page" → "Your choosing page", "Picking lives at a private link tied to you" → "Your choices live at a private link tied to you", "Open my picking page →" → "Open my choosing page →", "{N} picking now" → "{N} choosing now", "Open picking page" → "Open choosing page", plain-text body counterparts updated to match), receipt-video email CTA ("Watch the note & pick yours" / "Pick what you had" → "Watch the note & choose yours" / "Choose what you had"), pay-prompt email preheader ("Picks locked · …" → "Choices locked · …"). Internal symbol names, variable names, comments, and the
picking_done/all_pickednotification event keys /picking_done_atdatabase column were left alone — only end-user-visible strings flipped.
Changed (Ride-only, no Fare changes this round)
- **BFLS-1265 — Ride only this round.** Ride's guest-vote confirmation now produces a real thank-you screen when "Done" is tapped (previously a no-op
window.scrollToleft the button feeling broken), with a small "Change my choices" link to rewind before the deadline. Fare has no user-facing changes this round; version pinned for parity. - **BFLS-1262 — Ride only this round.** Ride's host create-ride Review preview is now split per-element: title, venue, video note, and times each have their own inline Edit link (previously a single "Edit venue" link in the corner sent every edit tap to the venue step). Fare has no user-facing changes this round; version pinned for parity.
- **BFLS-1248 — Ride only this round.** Ride's in-app
<VideoRecorder>now requestsfacingMode: { ideal: 'user' }so iPhones open the selfie camera for the host hello, and holds a Wake Lock sentinel while recording so the screen doesn't auto-sleep mid-record. Fare has no user-facing changes this round.
Fixed
- **"Next" now lands the host at the top of the new step (BFLS-1261).** The host bill flow (
receipt → items → guests → pay → claim) re-renders into a different step's UI wheneverviewOverridechanges or the inferred step advances. Previously there was no scroll handler at the step-change boundary, so the page stayed scrolled to wherever the prior step's Next button was — usually near the bottom — making it look like the tap didn't take.host-bill-flow.tsxnow watches the effectivevisiblestep in auseEffectand issues an instantwindow.scrollTo(0, 0)(plus arequestAnimationFramefollow-up to cover post-commit layout shifts) whenever it flips.
Changed
- **BFLS-1020 — Ride only this round.** Ride relaxed the "Cancel this ride" gate so a host can cancel after the ride locks (previously the cancel surface vanished once
ride.statusflipped tolocked). Hidden once payment is underway (receipt_uploaded/claiming/payment_pending/completed) or the ride is already cancelled. Fare has no user-facing changes this round; version pinned for parity.
Fixed
- **BFLS-1249 — Ride only this round.** Ride fixed two host-side bugs around the response window: the deadline became uneditable after a ride locked (the entire Response window card disappeared from the host detail page), and the Reminder picker's 24h / 6h / 1h preset chips were dead any time the resulting reminder would land in the past — leaving the host with no escape route once the existing reminder went stale. Fare has no user-facing changes this round; version pinned for parity.
Changed
- **BFLS-1252 — Ride only this round.** Ride switched guest voting to a per-option Yes/No model on
/g/[guestToken]. Fare has no user-facing changes; version pinned for parity. - **BFLS-1251 — Ride only this round.** Ride trimmed its guest invite SMS (removed time-options list, dropped the separate video link). Fare has no user-facing changes; version pinned for parity.
- **BFLS-1250 — Ride only this round.** Ride's guest invite page now shows the host's video above the venue card. Fare has no user-facing changes; version pinned for parity.
- **BFLS-1250 follow-up — Ride only this round.** Ride restructured the guest invite (VotingView + LockedView) into a 3-card layout: brand+title → video → venue photo. Fare has no user-facing changes; version pinned for parity.
Added
- **BFLS-1257 — Ride only this round.** Ride ships a branded video viewer page (
/v/p/<rideId>). Fare has no user-facing changes; version pinned for parity.buildVideoViewerUrl()helper lives in@carnival/env, ready for Fare to wire up its own viewer when receipt-video flow ships. - **BFLS-1247 — Ride app only this round.** Ride now ships branded
ride.carnivaldelight.com/v/...URLs in SMS/email instead of raw Vercel Blob hosts (path-prefix rewrite +BLOB_PUBLIC_HOSTenv). Fare has no user-facing changes this round; version pinned for parity. The sameapplyBlobPublicHost()helper is exported from@carnival/envand supports both host-only and host+prefix shapes, so when Fare wires its own branded path for receipt/Zelle-QR blob URLs, the helper is ready.
Fixed
- **Timezone bug in guest bill SMS/email (BFLS-1246).** "Bill confirmed" / "pay now" / "receipt + video" emails used to format venue and sent-at timestamps in Node's UTC mis-labeled as a local time. Now every label renders in the guest's stored IANA tz (captured the first time they open their
/g/[guestToken]link), falling back to the host's tz captured at bill creation, then toAmerica/New_York. - No user-facing changes in Fare this round of BFLS-1246 follow-up; version pinned for parity with Ride.
Added
rides.host_timezone(shared schema) captured at bill creation fromIntl.DateTimeFormat().resolvedOptions().timeZoneand persisted alongside the new ride row.- Public
POST /api/participants/[token]/timezonefor guest TZ capture (mirror of the Ride app endpoint; both apps shareride_participants).
v2.1.0 — 2026-05-24
Added
- Notification audit mirror. When the server-only env var
NOTIFICATION_AUDIT_EMAILis set, every outbound bill-ready, payment-prompt, and bill-confirmed email plus every bill SMS is mirrored to that address so the team can monitor traffic from a single inbox. Failure to mirror never blocks the primary send. - In-app version label in the footer linking to this changelog.
Changed
- Dev
/api/sendtest endpoint now goes through the sharedsendResendEmail()chokepoint so test sends also receive the audit BCC.