Site review follow-up: security hardening + accessibility/UX pass#22
Open
GreerBK wants to merge 2 commits into
Open
Site review follow-up: security hardening + accessibility/UX pass#22GreerBK wants to merge 2 commits into
GreerBK wants to merge 2 commits into
Conversation
…on, edge caching, security headers
- /api/activities now sends Airtable a fixed query ({Status}='Active') and
applies q/type/intensity/cost/format/days filtering in plain JS. The old
quote-doubling escape was not a documented Airtable escape, so crafted
search terms could break or alter the formula. Search is now also
case-insensitive.
- /api/activity/[id] validates the record-ID shape before building the
upstream URL (blocks encoded ../ traversal), returns 404 for non-Active
records to match the list endpoint, and normalizes upstream errors to a
generic 502.
- All three functions cache at the Cloudflare edge (~5 min), so every
filter combination is served from one cached Airtable payload — keeps
traffic far below Airtable's 5 req/s limit.
- public/_headers adds CSP, X-Frame-Options, nosniff, Referrer-Policy,
Permissions-Policy (geolocation self-only), and HSTS.
- Remove stray public/hello test file; git-ignore .dev.vars.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Accessibility (the audience skews older, with tremor and low vision): - Activity cards are now a real list of articles with a stretched name link — the old button+aria-label hid schedule/cost/distance from screen readers and nested divs inside a button (invalid HTML). - Hero video gets a visible pause/play control (WCAG 2.2.2) and only mounts after window load so the 8 MB file never delays first paint; skipped under data-saver, hidden under reduced motion as before. - Touch-target pass: filter rows 42px, checkboxes 18px, chips 36px, distance ticks 40px, share/print/back 44px; small UI text bumped from 0.75–0.9rem to 0.85–1rem throughout. - Distance slider announces "N miles" (aria-valuetext); scroll-to-top respects prefers-reduced-motion; sidebar no longer swallows mouse-wheel scrolling when it has nothing to scroll. UX: - Keyword search input on the results page (the q parameter existed in the API and state but had no UI). - Results sort A→Z when there's no location to sort by distance. - Share (native sheet or copy-link with live-region confirmation) and Print buttons on activity pages, plus a print stylesheet that turns a detail page into a clean paper handout with URLs spelled out. - Error states get a Try again button; dead placeholder@example.com contact removed until a real address exists. - Home page shows the live activity count; results heading reflects whether filters are applied. Performance & polish: - Fonts self-hosted via @fontsource (no Google Fonts request, simpler CSP). - favicon, meta description, theme-color, Open Graph/Twitter tags, noscript. - Home/search share one cached catalog fetch per session. - Vite 5 → 6.4.3 clears all npm audit findings (dev-server esbuild advisory). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying mnparkinsons with
|
| Latest commit: |
61fc2a3
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b03d25b1.mnparkinsons.pages.dev |
| Branch Preview URL: | https://claude-zealous-hugle-2aaccb.mnparkinsons.pages.dev |
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.
Why
Full site review (accessibility, UX, security) with fixes for everything actionable that turned up. Scores before this PR: Accessibility 8/10 · UX 7.5/10 · Security 6/10 — details below.
Security (the biggest gaps)
filterByFormulawith a quote-doubling escape that Airtable doesn't actually document — a search term with a quote could break or alter the query. The API now sends Airtable one fixed query ({Status}='Active') and does all filtering in plain JavaScript. Bonus: search became case-insensitive ("yoga" now finds "Yoga")./api/activity/<id>previously passed anything into the authenticated Airtable URL; encoded../could steer the request at other Airtable endpoints. IDs must now look like real record IDs, and non-Active records 404 (direct links can no longer surface Pending/Inactive rows).public/_headers: CSP, X-Frame-Options, nosniff, Referrer-Policy, Permissions-Policy, HSTS.npm auditis now clean. Fonts self-hosted (no third-party requests), straypublic/helloremoved.Accessibility (audience skews older, tremor, low vision)
<button aria-label="Name — view details">, which made screen readers skip the schedule, cost, address, and distance entirely. Now a real list of cards with a stretched name-link — everything is read aloud, and the link supports open-in-new-tab/copy-link.UX
qbut no input existed for it.placeholder@example.comcontact from the footer/error text — add a real contact email when one exists (TODO comment marks the spot inApp.jsx).Verified locally
Ran the real stack (
wrangler pages dev+ Airtable): injection attempts return clean 200s, traversal/garbage IDs 404, all headers present, keyword/zip/filter searches return correct live data (39 actives, "55101" → 30 within 50 mi sorted by distance), mobile layout has no overflow, console is clean,npm run buildpasses on Vite 6.Worth considering later
/activity/...instead of#/activity/...) so Google can index individual activities — needs care with existing shared links.serene.mp4(8.2 MB) — even deferred, it's heavy for rural connections.🤖 Generated with Claude Code