Skip to content

Fix the 11 bugs from today's QA pass (H1, M1–M5, L1–L5) - #184

Merged
Programcode merged 13 commits into
mainfrom
qa/2026-10-22-bug-fixes
Oct 22, 2026
Merged

Programcode merged 13 commits into
mainfrom
qa/2026-10-22-bug-fixes

Conversation

@Programcode

Copy link
Copy Markdown
Owner

Fixes the 11 bugs from this morning's QA pass (H1, M1–M5, L1–L5). One PR, one commit per bug or pair, plus docs, verify recipes and two small follow-ups the browser checks caught. Frontend only: no migrations, no Edge Function changes, nothing to deploy beyond the normal Vercel build.

Use the staging preview to check these (staging sign-in with an email code works; the trial is 5 AI requests a day).

Bugs

H1: a revoked session looked signed in, and saves showed "saved".

  • What changed: the Supabase client watches its own responses. When a signed-in request is refused (401 from REST or Functions, 401/403 from Auth's /user), it forces one token refresh after that request finishes. If Auth refuses the refresh too, the session is dead: the browser signs out locally and /login says "Your session expired. Sign in again." A network error, 5xx or 429 keeps the session (that is Stop treating an Auth outage as a successful local save #171's outage case).
  • The false "Flowchart saved" toast: with this PR the revoked session is signed out as soon as the first refused request comes back, before any tool can save. A save that races it now fails openly, because Stop treating an Auth outage as a successful local save #171 (merged) made writes go to the server or throw.
  • Check: sign in, then in DevTools set debate-auth-staging → access_token to any invalid JWT, refresh_token to x, expires_at to now + 3000. Reload /app. You land on /login with the message, once. A second reload doesn't show it again.

M1: Crossfire started its clock when the first AI turn was refused, and couldn't be ended.

  • What changed: if the first AI turn is refused for the daily limit (or a Pro model), the round goes back to setup with your resolution kept and no clock, and the same limit dialog Ask uses opens. A round that already has an exchange stays, and End works. Ending a round by hand before any exchange now just returns to setup, with no "Complete at least one exchange" error.
  • Check: use up today's 5 requests, then Crossfire → "Let the AI question me" → Start. Dialog opens, setup stays, no clock.

M2: Sparring let an out-of-quota user start a 38-minute round.

  • What changed: setup compares today's remaining requests with what the round needs (shown as "N AI requests") and shows a clear amber warning before you start, such as "You have no AI requests left today, so the opponent can't answer." An opponent or judge turn refused for the limit opens the shared dialog.
  • Why it warns rather than blocks: the browser can't see global rate_limits rows (server-only by RLS), so its count is an estimate. The server's 402 still decides.
  • Check: with no requests left, open Sparring. The warning shows under the request count.

M3: research saved the same page to the Library again and again.

  • What changed: auto-saved sources are matched per debate by a normalized URL (host lower-cased without www., no scheme, #fragment, tracking parameters or trailing slash). A page already in the debate's Library gets its research metadata refreshed instead of a new row; your title, folder and on/off choice stay. Batches from tools that finish at the same time now run one after another, so each sees what the last saved.
  • Not covered: two browser tabs saving the same page at the same instant. Only a unique index would stop that, and it needs a migration.
  • Check: in one debate run Generate, Brainstorm, Factoids and Attack on the same motion, then open Library. Each page appears once.

M4: a wrong email code was called "expired", and letters were sent.

  • What changed: the message is "That code is wrong or has expired. Check it or send a new one." (Supabase returns otp_expired for both.) The field keeps digits only, a pasted "123 456" becomes "123456", and Verify stays disabled until there are exactly 6 digits.
  • Note: main already refused to submit anything but six digits, so QA's "12ab was sent" likely came from a different input. Either way the field now strips non-digits as you type.
  • Check: /login → request a code → type 12ab (it shows 12, Verify disabled) → type 000000 → Verify → the new message.

M5: mobile Library cut titles to a few characters, and row controls were tiny.

  • What changed: below the sm breakpoint a title wraps to two lines, and the type badge (the row icon already shows the type) hides to make room. On touch screens the delete button is 44×44, and the on/off switch gets a 44 px-tall tap area, the same way the Checkbox does.
  • Check: /app/library on a phone with a few long-titled sources.

L1: Settings told everyone "Signed in with Google".

  • What changed: the description keeps only the API-key sentence. Under your email, Settings shows how the account signs in, from what Auth recorded: "Sign-in: Google", "email code", "Google or email code", or "demo code". This is the account's sign-in methods, not only this session's.
  • Check: sign in with an email code → Settings → Account shows "Sign-in: email code".

L2: the limit messages disagreed, and remaining requests were never shown.

  • What changed: Crossfire and Sparring use the same dialog as Ask (one shared component), and their inline errors use the dialog's own words instead of the server's English. Settings shows "Today: 3 of 5 AI requests used. They refresh at midnight UTC (HH:MM your time)." The count leaves out refused calls and jev_* helper rows, reads your own rate_limits override, and is hidden whenever any read fails.
  • Check: Settings → plan card shows today's usage.

L3: flowchart screen-reader text stayed English, and edges were internal ids.

  • What changed: edges are announced by node titles, e.g. "From Thesis to Argument 1" (ES "De … a …", HU "Kapcsolat: … → …"). This is computed for the canvas only and never saved. React Flow 11 has no ariaLabelConfig (that arrived in v12), so a small component puts the translated keyboard help into the two description elements React Flow renders.
  • Not covered: React Flow's "Moved selected node…" live message stays English until a React Flow 12 upgrade.
  • Check: switch to Español → Flowchart → inspect an edge's aria-label.

L4: unlabeled controls and one title for every page.

  • What changed: the Attack "Detect Fallacies" and "Analyze Weighing" switches are labelled, and their descriptions are linked. The Ask box has a label ("Message to your debate coach"). Each /app page has its own title, e.g. "Generate · DebateLab".
  • Check: Attack → More options → the switches have names. The tab title changes per page.

L5: private routes were indexable with a generic title.

  • What changed: /login, /app/* and /share/* (including the "no longer available" share page) set robots: noindex, nofollow and a page title while on screen. That matches the X-Robots-Tag header vercel.json already sends for crawlers that don't run JS. /expocit/sources is untouched, as asked.
  • Check: on /login, document.querySelector('meta[name=robots]').content is noindex, nofollow, and the landing page is back to index, follow after you leave.

What I ran (local, at the final head)

  • npm run verify: docs:check, lint, typecheck, unit tests (51 files), collaboration (30), test:edge (361), build, test:sw, and the desktop @smoke browser set (30/30). All passed. This is a local result, not CI.
  • New focused tests: H1 (src/test/auth/session-expiry.test.ts: which responses count, revoked vs outage vs rate limit vs 5xx, one check at a time, the check runs after the request), M4 (src/test/auth/login-otp.test.tsx), M3 (src/lib/ai/sources.test.ts: URL variants, two tools at once → one insert and one update, other debates unaffected), M1 (src/test/crossfire/crossfire-daily-limit.test.tsx).
  • Real app (verify-debatelab, fake provider, local Supabase), desktop-chromium and mobile-chromium:
    • New maintained recipes: auth-session-expired (QA's H1 steps), cross-daily-limit + spar-daily-limit (the real ai function's 402), cross-end-early. All pass.
    • Existing recipes auth, practice, library, case-generation, ai-tools pass.
    • account: 8 of 10 pass. timer-run fails on both viewports, but that comes from main: Keep timer shortcuts away from buttons and open menus #172 made timer shortcuts ignore a focused button, and the recipe presses r while the Pause button has focus. This PR doesn't touch the timer.
    • One-off checks (not committed): M5 at 393 px with a coarse pointer (two-line title; delete 44×44; a tap 7 px above the switch flips it), L1 "Sign-in: demo code", L2 usage line, L3 in Spanish, L4 names and titles, L5 robots on /login, /app, /share/. All pass, twice.
  • Test infrastructure: the stub-session helper (e2e/helpers/auth.ts) now answers the token refresh with the same stub session. Without that, H1 correctly signs out the bogus stub session, and the render-only smoke tests would also have sent refresh requests to production Auth.

Not verified: staging or production (I ran nothing there), real model output (fake provider), Google sign-in.

Overlap with other PRs

The branch is rebased on current main, which already includes #171, #173, #158, #170, #172, #174, #175, #169, #159, #160 and #163. Nothing here redoes them; H1 builds on #171, and the dialog is #173's version.

Against the PRs still open, checked with git merge-tree:

Follow-ups (not in this PR)

🤖 Generated with Claude Code

Programcode and others added 12 commits October 22, 2026 12:50
H1: when Supabase refuses a signed-in request (401 from REST or Functions,
401/403 from Auth's user lookup), force a token refresh outside the request.
If Auth refuses that too, sign this browser out locally and show
"Your session expired. Sign in again." on /login. Outages, 5xx and rate
limits keep the session.

M4: the code field keeps digits only, Verify waits for six digits, and a
refused code says it may be wrong or expired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tings (M1, M2, L2)

M1: when Crossfire's first AI turn is refused for the daily limit, the round
goes back to setup (no clock) and the daily-limit dialog Ask uses opens.
A round with exchanges keeps its state and can still be ended. Ending a
round with no exchange by hand no longer shows an error.

M2: Sparring setup warns when today's remaining requests are fewer than the
round needs, and an opponent or judge turn refused for the limit opens the
same dialog.

L2: one shared dialog component for every 402 in these tools, a localized
inline message instead of the server's English text, and Settings shows
requests used today and when they refresh. The client count now leaves out
refused calls and jev_* helper rows, reads the account's own rate_limits
override, and is null (never blocking) when it cannot be read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto-saved sources are matched per debate by a normalized URL (host
lower-cased without www, no scheme, fragment, tracking parameters or trailing
slash). A page already in the debate's Library gets its research metadata
refreshed instead of a second row; the student's title, folder and on/off
choice stay. Batches from tools that finish together run one after another so
each sees the rows the last one saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Below the sm breakpoint a source title takes two lines instead of one
truncated line, and the type badge (already shown by the row icon) is hidden
to give it room. On touch screens the delete button is 44px and the whole
44px square around the on/off switch toggles it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings no longer hard-codes "Signed in with Google". Under the email it
shows how the account signs in, from what Auth recorded: Google, email code,
both, or demo code. The description keeps only the API key sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edges are announced as "From Thesis to Argument 1" (computed for the canvas
only, never saved) instead of "Edge from thesis to c1". React Flow 11 has
no prop for its keyboard help text, so a small component puts the EN/ES/HU
help into the two description elements it renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/login, /app/* and /share/* (including a share link that no longer works) get
a page title from the existing route labels, such as "Generate · DebateLab",
and robots "noindex, nofollow" while on screen, matching the X-Robots-Tag
vercel.json already sends. /expocit/sources is left as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the Ask label in replays

architecture.md describes the revoked-session check (H1), one Library row per
page per debate (M3), and what the remaining-requests estimate can and cannot
know (M1, M2, L2). The Ask message box's accessible name is now its label
(L4), so the two recorded agentic replays target that name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… requests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M5: on touch screens the Library on/off switch gets its 44px tap area from
a pseudo-element, the same way the Checkbox does. Wrapping it in a label did
not forward taps reliably.

L4: page titles end in "DebateLab" (the shell's title) rather than the
longer brand name from site.json, as the QA report asked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
getUserFacingAIError now turns a daily-limit or trial refusal into the limit
dialog's own text, so Crossfire and Sparring stop printing the server's
English message. Sparring's change matches open PR #178 line for line, so
that file merges cleanly; #178's fuller helper in error-display.ts should
replace this one when it lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 13:14
@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown

Deployment failed for project debate-navigator with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit

@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 11 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 6260da2 and 01f6f3e.

📒 Files selected for processing: 35
  • .claude/skills/verify-debatelab/features/auth-demo-login.md
  • .claude/skills/verify-debatelab/features/practice.md
  • .claude/skills/verify-debatelab/recipes/auth.spec.ts
  • .claude/skills/verify-debatelab/recipes/practice.spec.ts
  • agentic/replay/4b19b5a96c8088da6cfdc7035b87036fd9cc0ac6dbdd9fc8d128176a57f60dfd.json
  • agentic/replay/d636e78bd57e1346e9ad878bdd394cbf70ccb746bdcfcc35af4c0a7d2cf6795e.json
  • docs/architecture.md
  • e2e/helpers/auth.ts
  • src/App.tsx
  • src/components/features/ask/AskChat.tsx
  • src/components/features/attack/AttackForm.tsx
  • src/components/features/sparring/SparringSetup.tsx
  • src/components/features/subscription/SubscriptionErrorModals.tsx
  • src/components/features/subscription/index.ts
  • src/hooks/use-subscription.ts
  • src/lib/ai/error-display.ts
  • src/lib/ai/sources.test.ts
  • src/lib/ai/sources.ts
  • src/lib/seo/use-page-head.ts
  • src/lib/supabase/client.ts
  • src/lib/supabase/session-expiry.ts
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/hu.json
  • src/pages/CrossfirePage.tsx
  • src/pages/FlowchartPage.tsx
  • src/pages/LibraryPage.tsx
  • src/pages/LoginPage.tsx
  • src/pages/SettingsPage.tsx
  • src/pages/SparringPage.tsx
  • src/test/auth/login-otp.test.tsx
  • src/test/auth/session-expiry.test.ts
  • src/test/crossfire/crossfire-daily-limit.test.tsx
  • src/test/pages/CrossfireVoice.test.tsx
  • src/test/pages/SparringVoice.test.tsx
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Oct 22, 2026

Copy link
Copy Markdown

Deployment failed for project debatelab-staging with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/adam-gaborjanis-projects?upgradeToPro=build-rate-limit

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.

2 participants