Repository navigation
Fix phone layout, contrast, focus and touch targets (T10: VS-05, AX-02, AX-04, AX-07, VS-01, VS-03, VS-09, AX-09) - #214
Conversation
VS-05: the Current Debate grids use minmax(0, 1fr) below md and the motion heading can shrink, so long titles truncate instead of widening the page. AX-02: a skip link is the first Tab stop on the landing, app and public shells. AX-04/AX-09: pills, the active nav tint, Timer speech chips and the use-case subtitles reach 4.5:1 in light and dark. AX-07: switch, checkbox, input, tabs and small buttons reach 44px on coarse pointers and 24px elsewhere; flowchart edges get a zoom-aware hit stroke and the React Flow credit a real target. VS-01/VS-03/VS-09: the landing nav collapses below lg, the logo has a transparent surround, the locked Reasoning field stays readable, and the language menu closes on a choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lighthouse flagged label-content-name-mismatch: the buttons read "Load" but were
named "Open {title}". The title now follows the visible word in a screen-reader-only
span, and the unused openItemAria strings are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The skip link makes main focusable just for its jump, so a click inside main still leaves focus on body. - Debate context rows keep the checkbox's touch area inside their own row. - The free tools pages get the skip link too. - Flowchart passes React Flow a stable onMove. - The new e2e tests match names as strings and open the menu whenever it shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isVisible() does not wait, so on a phone it could run before the navbar rendered and skip opening the menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ℹ️ Recent review info
📝 Walkthrough
Note 🎁 Summarized by CodeRabbit Free
Comment |
Batch T10 from the 22 Oct 2026 QA report: phone layout, contrast, focus and touch targets. Most of the fixes live in shared primitives (switch, checkbox, input, tabs, button
sm), so they apply on every page, not only the ones the report measured. No migrations, no Edge Function changes, nothing to deploy beyond the frontend.Issues
VS-05 (Medium), /app/debate scrolls sideways on phones. Reproduced on main: with a long motion and attached sources, the page was 640–680 px wider than a 360/375 px screen in EN, ES and HU (0 at 768 px). The cause was the side and source grids. With no explicit column below
md, the implicit track grew to the min-content width of the truncated titles. They now usegrid-cols-1(minmax(0, 1fr)). The cards, the PageHeaderh1(so a long unbroken word wraps) and the item row getmin-w-0. Badges and the Load button can't be squeezed, and card headers wrap. NowscrollWidthequals the viewport at 360/375/768 px in all three languages, including with a 70-character unbroken motion. The new e2e test fails on main (Load button's right edge at 991 px) and passes here.AX-02 (Low), first Tab stop on / had no visible ring. On main the first stop was the logo, and it already had a ring from the global
:focus-visiblerule (d64d94c). There was no skip link at all. A newSkipLinkis now the first Tab stop on the landing page, the app shell, the public page shell and the free tools pages. It is hidden until focused, then shows as a 44 px chip with a 2 pxringoutline, primary on background, about 5:1 in light and more in dark. It moves focus to<main id="main-content">without adding a hash to the URL, and makesmainfocusable only for that jump. Newcommon.skipToContentstring in en/es/hu.AX-04 (Low), landing pills fail contrast. Confirmed on main: axe reports 7 nodes at 1.74:1, the What's New and Pricing pills (
text-muted-foregroundonbg-secondary/60, where--secondaryis dark slate in light mode). Changing--secondaryitself would also change everyvariant="secondary"button, so the pills use the--mutedtoken pair instead: 4.90:1 light, 5.70:1 dark. The use-case subtitles (text-primary/80, 3.35:1, hidden from axe by the scroll reveal) now usetext-primary: 4.69 light, 6.73 dark.VS-01 (Nit), logo ring and 768 px nav. The ring wasn't clipped on main, but it was square and sat on the wordmark's last letter. The link now has
rounded-lg pr-1. At 768 px the four section links and the CTA no longer fit beside the wordmark, so the landing nav collapses into the menu belowlg(1024 px) instead ofmd.VS-03 (Nit), white logo square in dark mode and a dim "Reasoning: Low". C3 (#199) shipped favicon sizes and the share image, but no transparent or dark logo.
public/favicon-72.pngis now the mark cut out oficon-512.pngwith a transparent surround (only white connected to the image edge is removed, so the inner white strokes stay). The filename is unchanged on purpose:public/sw.js(T11) precaches it andtest:swasserts the offline logo'ssrc. Because of that, returning visitors keep the old image for up to 30 days (max-age=2592000) or until the service-worker cache version changes. The tiny "DebateLab" lettering inside the old tile is gone; the wordmark next to it already says it. The locked Reasoning select now keeps its text atopacity-100intext-muted-foregroundonbg-muted/50: 5.38:1 light, 6.22:1 dark. It still reads as locked.VS-09 (Nit), language menu stayed open. The popover is now controlled and closes after a choice in either list. Detection and routing are untouched. New e2e test (fails on main: both radiogroups still open).
AX-07 (Low), touch targets. Most of these are invisible hit areas on
pointer: coarsein the primitives, so the desktop look doesn't change.::aftermakes the 44×24 track a 44×44 target.::aftermakes it 24 px everywhere and 44 px on touch (was 36 px on touch, 16 px on desktop).min-h-11on touch (was 40 px). Button already hadmin-h-11, so input and button rows stay aligned.min-h-11on touch and the list grows to fit. The login Email/Demo code tabs go from 32 to 44 px without touchingLoginPage.tsx.size="sm":min-w-11on touch. The flowchart's icon-only Load/Save/Auto layout/Export buttons were 42 px wide./expocit/sourcesreference links are now at least 24 px tall, and 44 px on touch.--flow-zoomon every move, and CSS sets the stroke tocalc(24px / zoom), or 44 px on touch. Edge labels get 24 px pills. The React Flow attribution stays, as a 24/44 px target.AX-09 (contrast and target-size parts).
bg-primary/10+text-primary): 4.45:1 → 4.78:1 with a 5% tint (AppLayout and CurrentDebateMenu). This is the "nav span" Lighthouse flagged on every app page.bg-blue-500was 3.45:1 in light and 3.92:1 in dark. Each speech colour is two shades darker with white text, so every chip is at least 4.8:1 and the relative variation between speeches stays.Also fixed (found while measuring): Lighthouse flagged
label-content-name-mismatchon /app/debate, because the "Load" buttons were named "Open {title}". The name is now "Load {title}" (visible word plus a screen-reader-only title, so each row stays distinct), and the now-unuseddebate.openItemAriastrings are removed.Measurements
Lighthouse accessibility (snapshot of the live page, 375 px, coarse pointer, seeded stub session for app pages). Main → this branch:
/color-contrast:7/expocit/sources/login/app/app/debatecolor-contrast:1,label-content-name-mismatch:2/app/generatecolor-contrast:1/app/attackcolor-contrast:1/app/sparringcolor-contrast:1/app/crossfirecolor-contrast:1/app/flowchartcolor-contrast:1/app/timercolor-contrast:1/app/settingsaxe (
color-contrast,target-size,heading-order, landmarks, names) and a coarse-pointer target census (< 44 px on touch, < 24 px on desktop), EN. Main → this branch://expocit/sources/login/app/app/debate/app/generate/app/attack/app/sparring/app/crossfire/app/flowchart/app/timer/app/settingsIn this census the
/loginrows ran with the signed-in stub, so they redirected to/app. The logged-out login page is covered by the Lighthouse rows above. Its Email/Demo code tabs measure 32 → 44 px and the email field 40 → 44 px on touch, with desktop unchanged at 32/40 px."rf" counts are React Flow edges and nodes, whose SVG boxes can't show the stroke-based hit area. That area was measured directly: 44 px on a phone and 24 px on desktop, after zoom.
Screenshots: 192 full-page shots (12 pages × 360/375/768/1280 px × light/dark × EN/HU), all with
scrollWidthequal to the viewport. Selected before/after pairs are on thepr-assets/t10-layout-a11ybranch (not for merge):Verification
GitHub Actions minutes are used up, so CI will not run on this PR. Everything below is a local pass, not a CI pass.
Full
npm run verifyat the PR heada93855b4(rebased on45afb251, #213): exit 0.docs:check,lint(eslint + knip + repo rules),typecheck: cleannpm test: 80 files, 581/581 passedtest:collaboration: 5 files, 30/30 passedtest:edge: 457 passed, 0 failedbuild: prerender and sitemap ok;check-site: 48 HTML files, sitemap and llms.txt resolve, no problemstest:sw:{"upgradeActivated":true,"firstVisitedRouteOffline":true,"unvisitedRouteShowsRecovery":true,"intentPrefetchedRouteOffline":true,"offlineLogo":true,"apiCacheEntries":0}@smoke): 47/47 passedOne earlier verify at this same head failed once in
test:sw: the offline reload of/appdidn't show its heading within 15 s. It then passed 3/3 on its own and again in this full run. The VPS was carrying two extra dev servers of mine at the time.Beyond
verify:e2e/quality-backlog.spec.ts): the 360 px scrollWidth check in EN/ES/HU and the language menu closing on select. Run on desktop-chromium and mobile-chromium with--repeat-each=3 --retries=0: 24/24 passed. Both fail on main's code: the Load button's right edge lands at 991 px, and both language radiogroups stay open.quality-backlog,home-navigationandpublic-pageson both projects: 64/64 passed.npm run test:e2e(desktop + mobile, 284 tests): every failure was either the language test before its wait fix (fixed above) or one that also fails on untouchedorigin/mainwhen I ran the same specs there. Those arenavigation-performance.spec.ts:344"subscription reads stay cached across sibling tool routes" (an extrasubscriptionsread, both projects),search-quality.spec.ts:7"authenticated Factoids shows the consulted-source quality panel" (both projects), andhome-navigation.spec.ts:90onboarding delay (flaky on both). These are not T10's; they look like fallout from the billing (T4) and Factoids changes.agentic/*.e2e.ts, which this branch's nav breakpoint and new first Tab stop could touch), the verify-debatelab stack (no backend change, and the Supabase lock is shared), staging, and a real phone.case-generationpicks Magyar and then presses Escape. With the menu now closing on select, that Escape lands on the trigger and does nothing.Review
One fresh-context round, as a T3 child on
claudeAgent_cpawithclaude-opus-5-5, readinggit diff origin/main...HEAD. The fixes below came after that round. The batch rule asks for a new round only after Medium or higher findings, so I didn't run a second one.Round 1: no Medium or higher findings. Lows and Nits, and what I did with each:
tabIndex={-1}on<main>made a click on plain text focusmain. Fixed: the skip link addstabindexonly for its jump and removes it on blur. Checked live: Tab → Enter focusesmain, the next Tab goes into the content, and a click leaves focus onbody.onMovewas a new function each render, so React Flow re-registered its zoom listener. Fixed withuseCallback./tools/*had no skip link. Fixed inToolPageLayout.hover:bg-primary/90. Left: it predates this branch and contrast stays fine.Left open / needs a real device
::afterhit areas and the flowchart edge stroke were measured in emulated Chromium (coarse pointer, touch). Tapping a switch's label-side edge and a flowchart edge on a real phone is worth one manual check.🤖 Generated with Claude Code