Round 13: honor login intent, complete rate-limit coverage, localize every reachable error#80
Conversation
…og in' The auth page defaults to the Register tab (a deliberate Round-7 call for fresh visitors arriving from 'Get started' CTAs). But the header CTA labelled 'Log in' and the auth-gate redirects from /dashboard and /chat — surfaces whose users almost certainly already have an account — landed on 'Create your account' too. The nav promised one thing and the page delivered another. - login/page.tsx reads ?mode=login in a post-mount effect (no useSearchParams, avoiding the Suspense/hydration cost on this statically-prerendered page); default stays register. - SiteHeader anonymous CTA, dashboard redirect, and chat redirect now link to /login?mode=login. - pricing: removed a dead ternary whose two branches were identical. - e2e: ui-ux.spec.ts pins both behaviours structurally (display-name field presence) so the tab default can't silently flip either way. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
…m (5/min) Closes the two remaining holes in the per-IP throttling plan from KNOWN_ISSUES 'Ongoing Follow-ups #2'. /api/analyze is the most expensive route in the app — every call burns Workers-AI plus Gemini free-tier quota, and one scripted IP could drain the day's AI budget. /api/promo/redeem is session-gated but allowed a logged-in attacker to enumerate promo codes; /api/voucher/check already throttled the anonymous path, this closes the authenticated one. Limits follow the documented plan (analyze 20/min, promo/redeem 5/min) — far above organic usage, both call sites sit before the route try/catch like every other rateLimit caller. tests/rate-limit.test.ts gains a route-wiring suite that walks all six route sources and asserts rateLimit() + tooManyResponse() are present, so the throttled-route list is a CI-pinned invariant instead of a doc claim. The doc claim had in fact drifted: KNOWN_ISSUES still said login/register were unthrottled two rounds after they were wired. Entry updated. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
…cales)
The login/register form rendered raw English server strings for every
failure: a Thai pilot user registering without a voucher saw 'A voucher
code is required to register during the pilot.' verbatim — violating
the project's own zero-error bar ('every user-facing action returns a
localized, branded error').
- Server: /api/auth/login 401 and /api/auth/register 409 now carry a
machine-readable reason ('invalid_credentials' / 'email_in_use'),
matching the reason field register 400s already had. Additive — the
error string is unchanged for any consumer that reads it.
- Store: auth-store derives a stable errorCode per failure (server
reason preferred, status-class fallback: 401/409/429/5xx/network) and
hardens res.json() so a truncated or non-JSON error body can no
longer surface a SyntaxError as the user-facing message — the
suspected crash class behind KNOWN_ISSUES 2a.
- UI: login page maps known codes to new auth.server_errors.* strings
(th/en/de/da, Shinny voice — Thai with polite particles, German in
du-form); voucher rejection codes reuse the existing
voucher_invalid.* strings so the inline check and the submit error
always say the same thing. Unknown codes fall back to the raw
message.
- Also: pricing.score_breakdown.free_badge was untranslated 'Free' in
th/de/da — now ฟรี / Kostenlos / Gratis.
- Tests: 8 new cases pin deriveErrorCode rules and the store lifecycle
(185 total).
https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
The scan flow's file-rejection errors ('Unsupported format: …', 'File
too large (…MB)', 'Skipped N unsupported/oversized file(s).') were
hardcoded English inside useScanUpload while everything around them
went through t(). A Thai user dropping a 20 MB photo or a PDF got an
untranslated error on the product's primary flow.
New scan.upload_unsupported / upload_too_large / upload_skipped keys in
all four locales, Shinny voice. Single rejected file shows the specific
reason; a partly-rejected batch shows a localized count summary.
https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
The three scan failures a real user can hit surfaced raw English from the server: 403 'You have exhausted your free scans for this month…' (every free-tier user at 10 scans), 503 'Food analysis is temporarily unavailable…' (any cascade exhaustion), and 429 from the new per-IP throttle. useScanAnalysis now maps those statuses to localized Shinny-voice strings (scan.error_quota / error_rate_limited / error_overloaded, th/en/de/da); diagnostic shapes (400 invalid image, 500 with phase info) keep the raw server detail, which is worth more to operators than a generic translated line. The logger still records the raw server message either way. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
|
Warning Review limit reached
More reviews will be available in 25 minutes and 29 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (28)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The 'Recent Scans' card rendered hardcoded fake entries (Pad Thai /
Som Tam / Green Curry, invented scores, 'Today') whenever the server
scan counter was > 0 — fabricated data presented as the user's own
meals, while the real device-local history that the scan flow already
writes (lib/scan-history, with names, scores and thumbnails) sat
unused.
- Renders up to 3 real history entries: thumbnail (or neutral fallback
icon), food name, localized score label, locale-formatted date.
- Empty state now keys on actual history presence, not the server
counter — a user who scanned on another device gets the honest empty
state instead of three invented meals.
- Upgrade CTA subtitle was hardcoded mixed-English ('THB 199/month →
… 8-dimension scores'); now a localized dashboard.upgrade_subtitle
string with the price read from TIER_PRICING (single source with
/pricing).
- Logout icon button: hardcoded title='Logout' → localized
title + aria-label.
https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
Deploying eatinorder with
|
| Latest commit: |
8ed7d26
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7cc6cc43.eatinorder.pages.dev |
| Branch Preview URL: | https://claude-webapp-review-improve.eatinorder.pages.dev |
The non-429 failure branch showed body.error (an English diagnostic string) when present, falling back to the localized generic message only when the body had none — backwards: the localized line is the user copy, the server string is for the console. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
'Meal', 'Menu Scan', 'Drink/Snack' and the '+ N more' suffix were hardcoded English in the history entries the scan flow writes — and the dashboard's recent-scans list (now real data) displays them. New scan.history_* keys in all four locales; labels are resolved at write time since history is device-local and the scan-time locale is the right one. The logger's telemetry label stays English by design. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
…gle-survivor collapse When all but one photo fail to load during collage stitching, the single-survivor shortcut returned validImages[0] — which is the broken image whenever the first photo was the one that failed. Resolve the survivor's index from the load results instead. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
Every run of ci.yml — including on main — has failed since the workflow was introduced, and with no branch protection enforcing the check nobody noticed. The ITERATION_PROCESS §2 table lists GitHub Actions CI as a must-pass gate; in reality it was a permanently red light that proved nothing. Two independent infra bugs: - Frontend job: setup-node pointed cache-dependency-path at frontend/package-lock.json, which doesn't exist — npm workspaces hoists the only lockfile to the repo root. setup-node failed before npm ci even ran (and npm ci inside frontend/ would have failed next for the same reason). Install now runs at the repo root; the check:all step keeps the frontend working directory. - Backend job: requirements.txt pinned pydantic==2.5.0 alongside pydantic-settings>=2.3.0 — every pydantic-settings release in that range requires pydantic>=2.7.0, so pip's resolver correctly refused (ResolutionImpossible). Relaxed to pydantic>=2.7.0,<3, which also satisfies fastapi 0.104.1 and pydantic-extra-types 2.1.0. Verified in a clean venv: resolution succeeds and all 129 backend tests pass. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
The login page absolutely-positions SiteHeader over a vertically centered card; on mobile the card is taller than the viewport, so its top — the brand icon — rendered half-clipped behind the header bar on every load. Top padding clears the header before centering. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
The pt-28 comment landed in expression position between return( and the JSX root, which is a syntax error. Same class of mistake as the pricing edit earlier this round — and it got pushed because the local gate greps for error lines and proceeds when grep SUCCEEDS at finding them. Gate now keyed on the check's exit code. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
- CHANGELOG: Round 13 entry — login intent honoured via ?mode=login, complete per-IP rate-limit coverage with CI-pinned route wiring, localization of every reachable error (auth, scan upload, scan failures, history labels, chat), real dashboard recent-scans instead of mock data, login-card clipping fix, stitcher single-survivor fix, and the headline finding: ci.yml had never passed a single run since Round 11 — fixed (lockfile path + pydantic pin) and now green. - ITERATION_PROCESS §2: records the lesson — a gate is only real once you've seen it green; no branch protection meant a month of red runs went unnoticed. - Version: 2.1.14 (frontend + root kept in sync; root had drifted to 2.1.9 while production reported 2.1.13). https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
The homepage showed anonymous visitors a 7-day streak, 1,250 points and a Practitioner level with nothing marking them as sample numbers — the code comment says 'Gamification Preview' but the rendered UI didn't. Same dishonest-affordance class as the dashboard's mock recent scans. A localized caption now invites the visitor to earn their own. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
…free tier currentTier fell back to 'free' for visitors with no account, so the Starter card showed a dead 'Current Plan' chip to people who have no plan — confusing first-impression copy. Anonymous visitors now get the 'Get Started' CTA into registration. Also: the promo-redeem 'login first' message was a th/en-only hardcoded ternary (de/da users got English) — now a pricing.promo_login_first string in all four locales. https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
Round 13, iteration 1 — review + fix pass
Baseline before changes: 97/97 e2e green against production, 171/171 unit, type-check + i18n clean. This round hunted what the green suites can't see.
1.
/loginopened on "Create your account" even when the user clicked "Log in" (fix)The register-default tab is a deliberate Round-7 call for fresh visitors — but the header CTA labelled Log in and the auth-gate redirects from
/dashboardand/chat(users who almost certainly have an account) landed on the Register form too. The page now honors?mode=loginfrom those three entry points; the default for everyone else is unchanged. Pinned both ways by two new e2e cases inui-ux.spec.ts.2.
/api/analyzeand/api/promo/redeemhad no per-IP rate limit (fix)KNOWN_ISSUES' plan (analyze 20/min, promo/redeem 5/min) was never wired for these two — and the doc entry itself was stale, still claiming login/register were unthrottled two rounds after they were wired. Both routes now throttle; a new
rateLimit route wiringsuite intests/rate-limit.test.tswalks all six route sources so the throttled list is a CI-pinned invariant instead of a doc claim.3. Localize every error a real user can reach (fix, 3 commits)
The project's own zero-error bar says "every user-facing action returns a localized, branded error" — but:
A voucher code is required to register during the pilot.Same for wrong password, email-in-use, 429, 500. The store now derives a stableerrorCode(serverreasonpreferred — login 401 / register 409 now carry one — status-class fallback otherwise) and the login page maps known codes to newauth.server_errors.*strings in th/en/de/da.res.json()on error paths is hardened so a non-JSON body can't surface a SyntaxError as the user-facing message (the suspected crash class behind KNOWN_ISSUES 2a).Unsupported format: …/File too large (…MB)/Skipped N file(s)were hardcoded English on the primary flow → newscan.upload_*keys, all locales.scan.error_quota/error_overloaded/error_rate_limited. Diagnostic shapes (400/500) keep raw detail for operators.pricing.score_breakdown.free_badgewas untranslated "Free" in th/de/da.Validation
npm run check:all: type-check + i18n parity + 185 unit tests (was 171) — greenPer
ITERATION_PROCESS.md §4: merge with Merge, not Squash.https://claude.ai/code/session_01RaziyjCxHgKcqvTqss7bXS
Generated by Claude Code