Accessibility fixes + Performance (P2/P3) + main.ts cleanup - #36
Merged
alexpricedev merged 8 commits intoJul 14, 2026
Merged
Conversation
Two issues flagged by the Lighthouse / Core Web Vitals accessibility audit: - Heading order: the "The problem" / "The approach" section labels jumped from the hero <h1> straight to <h3>, skipping <h2>. Promoted them to <h2> so the heading tree descends sequentially (h1 → h2 → h3). The .section-label class controls all visual styling, so appearance is unchanged. - Footer contrast: footer text and links used --color-text-quaternary (#5c5d60) on the #0a0a0b background — ~3.0:1, below the WCAG AA 4.5:1 threshold for 13px text. Bumped to --color-text-tertiary (#8a8b8e), ~5.8:1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The .section-label titles ("The problem" / "The approach") used
--color-text-quaternary (#5c5d60) on the #0a0a0b background — ~3.0:1, below
the AA 4.5:1 threshold for 11px uppercase text. Moved to
--color-text-tertiary (#8a8b8e, ~5.8:1), matching the footer fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rather than redirect individual call sites, bump --color-text-quaternary from #5c5d60 (~3.0:1 on the #0a0a0b background — fails WCAG AA) to #78797c (~4.5:1 — passes AA) so every muted-text usage clears the threshold: badges, table meta, login/admin helper text, the etymology label, footer, and the section-label titles. Reverts the earlier footer and section-label overrides back to --color-text-quaternary now that the token itself passes, keeping the muted tier a single consistent token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resource hints: preconnect (+ dns-prefetch fallback) to esm.sh and unpkg in the Layout head, so the TLS handshake for the Preact importmap and Lottie overlaps HTML parsing instead of blocking the first cross-origin fetch. Only in Layout — BaseLayout loads neither origin. Static-file caching: extract serveFile into utils/static-files.ts and have it set a revalidatable Cache-Control (max-age=3600 + stale-while-revalidate/stale-if-error), a weak ETag from size+mtime, and Last-Modified. Conditional requests whose validator still matches get an empty 304. Covers public/ assets (favicons, og-image, manifest, cube.json) and the un-hashed dev bundle; fingerprinted bundles keep the immutable path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- build:client now passes --minify (main.js 4.6KB -> 2.5KB before compression; dev:client stays unminified for debugging). - scrollbar-gutter: stable on <html> so pages that grow past one viewport don't shift horizontally when the classic scrollbar appears. - body min-height uses 100dvh with a 100vh fallback, so mobile layouts track the actually-visible viewport instead of a retracted-toolbar height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main.ts is now a pure bootstrap file (env + migrations + seed + assets, then Bun.serve). The catch-all request handler — trailing-slash canonicalisation, health check, static/public file serving — moves to utils/fallback.ts, which also makes it unit-testable (importing main.ts starts the server, so it never could be). Adds fallback.test.ts covering redirect, health, static serving, and 404s. No behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "What's included" feature cards and "Capture your backpressure" rows aren't links, so their hover background/border change was misleading. Remove those :hover rules and the transitions that only served them. The spec section already neutralised the row hover with an override, now redundant, so drop that too. Also updates the Testing card copy from "270+ tests" to "300+ tests". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Performance is now in place, so it joins Foundations/SEO/Accessibility/Security/ Agent-readiness in the "Built to a public standard" list. Removes the "Performance, privacy, resilience, and internationalisation are next." note and its now-unused .spec-note styles; the CTA picks up the top margin the note used to provide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's in here
Four independent, low-risk changes — grouped below so you can review them one at a time.
1. Accessibility
Heading order. On the homepage, the "The problem" / "The approach" section labels jumped from the hero
<h1>straight to<h3>, skipping<h2>— which Lighthouse flags as a broken heading outline. Promoted them to<h2>. The.section-labelclass owns all the visual styling, so nothing looks different; only the semantic level moved. The outline now reads cleanly: h1 → h2 → h3.Footer contrast. The footer text and links sat at roughly 3:1 against the dark background — under the WCAG AA minimum of 4.5:1 for small text. Nudged the
--color-text-quaternarytoken lighter so it clears AA (~4.5:1). Because it's a token change, every use of that colour gets the fix, and the hover state is untouched.2. Performance — resource hints + caching
Preconnect to the CDNs we use. The page pulls Preact (via the importmap) from
esm.shand Lottie fromunpkg.com. We nowpreconnectto both (with adns-prefetchfallback) so the browser opens those connections while it's still parsing the HTML, instead of paying the full DNS + TLS cost only when it hits the first cross-origin request. These hints live inLayoutonly —BaseLayoutdoesn't load either origin, so it doesn't get them.Real caching for static files. Files served from
public/(favicons, the OG image, the web manifest, the Lottiecube.json) previously went out with no cache headers at all.serveFilenow sends:Cache-Controlthat lets the browser reuse the file but re-check when it's stale (max-age=3600+stale-while-revalidate+stale-if-errorfor resilience),ETag(from the file's size + modified time) andLast-Modified,Fingerprinted bundles (
main.js/main.css) keep their existing "cache forever" behaviour — this only changes the un-fingerprinted assets that actually need revalidation.3. Performance — build + CSS polish
build:clientnow runs with--minify, takingmain.jsfrom 4.6 KB → 2.5 KB. The dev build stays unminified so it's still debuggable.scrollbar-gutter: stableon<html>— pages that grow past one screen no longer shift sideways when the scrollbar appears.100dvhwith a100vhfallback on the body — on mobile the layout now tracks the actually visible viewport instead of being sized for a browser toolbar that's scrolled away.4. Cleanup:
main.tsis now just bootstrapThe catch-all request handler (
handleFallback— trailing-slash redirects, the health check, static file serving) moves out ofmain.tsinto its ownutils/fallback.ts.main.tsis now ~30 lines that read top-to-bottom as "set up the app, then start the server." As a bonus,handleFallbackis finally unit-testable — it couldn't be before, because importingmain.tsstarts the server.How it was verified
bun run check(lint + typecheck): cleanstatic-files.test.tsandfallback.test.tscurlagainst the dev server: preconnect hints present; static files returnETag/Cache-Control/Last-Modified, and a repeat request comes back as 304 / 0 bytes;/health,/, the/stack/ → /stackredirect, and 404s all behave as before/browserender check: hero animates, layout is intact (no shift fromscrollbar-gutter), and there are no new console errors — only the pre-existing cosmetic Permissions-Policy warningsIntentionally left for later (with reasons)
🤖 Generated with Claude Code