Skip to content

Accessibility fixes + Performance (P2/P3) + main.ts cleanup - #36

Merged
alexpricedev merged 8 commits into
mainfrom
alexpricedev/a11y-heading-order-footer-contrast
Jul 14, 2026
Merged

Accessibility fixes + Performance (P2/P3) + main.ts cleanup#36
alexpricedev merged 8 commits into
mainfrom
alexpricedev/a11y-heading-order-footer-contrast

Conversation

@alexpricedev

@alexpricedev alexpricedev commented Jul 14, 2026

Copy link
Copy Markdown
Owner

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-label class 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-quaternary token 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.sh and Lottie from unpkg.com. We now preconnect to both (with a dns-prefetch fallback) 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 in Layout only — BaseLayout doesn'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 Lottie cube.json) previously went out with no cache headers at all. serveFile now sends:

  • a sensible Cache-Control that lets the browser reuse the file but re-check when it's stale (max-age=3600 + stale-while-revalidate + stale-if-error for resilience),
  • a lightweight ETag (from the file's size + modified time) and Last-Modified,
  • and honours conditional requests — if the browser already has the current version, it gets an empty 304 Not Modified instead of the whole file again.

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

  • Minified client JS. build:client now runs with --minify, taking main.js from 4.6 KB → 2.5 KB. The dev build stays unminified so it's still debuggable.
  • scrollbar-gutter: stable on <html> — pages that grow past one screen no longer shift sideways when the scrollbar appears.
  • 100dvh with a 100vh fallback 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.ts is now just bootstrap

The catch-all request handler (handleFallback — trailing-slash redirects, the health check, static file serving) moves out of main.ts into its own utils/fallback.ts. main.ts is now ~30 lines that read top-to-bottom as "set up the app, then start the server." As a bonus, handleFallback is finally unit-testable — it couldn't be before, because importing main.ts starts the server.


How it was verified

  • bun run check (lint + typecheck): clean
  • Full test suite: 311 pass / 0 fail — includes new static-files.test.ts and fallback.test.ts
  • Live curl against the dev server: preconnect hints present; static files return ETag / Cache-Control / Last-Modified, and a repeat request comes back as 304 / 0 bytes; /health, /, the /stack/ → /stack redirect, and 404s all behave as before
  • /browse render check: hero animates, layout is intact (no shift from scrollbar-gutter), and there are no new console errors — only the pre-existing cosmetic Permissions-Policy warnings

Intentionally left for later (with reasons)

  • A caching policy for HTML pages — there's no single safe value: public marketing pages want to be cacheable, but authenticated admin/account pages must not be. Doing it right needs per-route awareness of auth, so it's its own task.
  • Critical-CSS inlining — to do this without hurting caching you need a build-time extraction tool plus real browser measurement; a half-version would be worse than none.
  • Self-hosting Preact + Lottie — a bigger change (bundling + tightening the CSP to drop the CDN allow-list). Preconnect is the quick win now; self-hosting is the stronger follow-up.

🤖 Generated with Claude Code

alexpricedev and others added 5 commits July 14, 2026 12:26
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>
@alexpricedev alexpricedev changed the title Fix accessibility: heading order + footer contrast (Core Web Vitals) Accessibility fixes + Performance audit P2/P3 (resource hints, caching, minify, CSS) Jul 14, 2026
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>
@alexpricedev alexpricedev changed the title Accessibility fixes + Performance audit P2/P3 (resource hints, caching, minify, CSS) Accessibility fixes + Performance (P2/P3) + main.ts cleanup Jul 14, 2026
alexpricedev and others added 2 commits July 14, 2026 12:57
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>
@alexpricedev
alexpricedev merged commit 7140db9 into main Jul 14, 2026
3 checks passed
@alexpricedev
alexpricedev deleted the alexpricedev/a11y-heading-order-footer-contrast branch July 14, 2026 12:00
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.

1 participant