Skip to content

fix: review pass — docs, a11y, shortcut handler, and e2e#3

Merged
imnotannamaria merged 19 commits into
mainfrom
fix/review-pass-1
Jul 7, 2026
Merged

fix: review pass — docs, a11y, shortcut handler, and e2e#3
imnotannamaria merged 19 commits into
mainfrom
fix/review-pass-1

Conversation

@imnotannamaria

@imnotannamaria imnotannamaria commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Consolidates fixes from a full review pass across the monorepo.

Docs

  • Rewrote installation flow: SQL migrations and all files inlined for copy-paste (no external links)
  • Server-side file viewer for registry files users copy

Shortcut / handler

  • Match actual flat payload from the iOS Shortcut
  • Harden /shortcut route; rename handler to wristkit-sync
  • Fix: copy shortcut file before dev server starts so the e2e test passes in CI

Components

  • today-activity-card: fix timezone, hydration, lastSync semantics
  • Drop partial state copy, trim card content

A11y

  • Restore keyboard focus rings and add landmarks
  • Fix skip link (use top instead of transform)

DX / infra

  • Security headers + MDX error boundary
  • Dedupe migration, rename schema to wristkit-schema
  • Drop CLI registry endpoint, fix velite-data warning

imnotannamaria and others added 16 commits May 23, 2026 14:58
Now that the CLI is going away, no consumer needs the JSON registry.
Remove the /r/[name] route, the build.ts that emitted those JSON
files, and the pretest/prebuild scripts that ran it. Drop the
matching e2e tests.

Replace the CLI-themed CTAs on the marketing page with copy-paste
language (browse the components / read the docs), point the
registry-items cards at their docs pages, and rewrite the install
checklist as four manual steps with the new /api/wristkit-sync
path.

Sidestep the recurring webpack warning "docs is not exported from
velite-data" by reading .velite/docs.json directly through a small
lib/docs.ts shim. Drop the velite-data path alias and stale d.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move handlers/healthkit-handler to handlers/wristkit-sync-handler
and update the install path to app/api/wristkit-sync/route.ts so
the public surface matches the brand.

Tighten the template every wristkit user installs:

- Constant-time API key compare via crypto.timingSafeEqual, with
  length check up front.
- Per-IP rate limit (30 requests / 5 min) in an in-memory bucket,
  with a comment pointing at @vercel/firewall or @upstash/ratelimit
  for production-grade traffic.
- Content-Type check (415) and Content-Length cap at 256 KB (413).
- Try/catch around the insert returning a safe 500 instead of
  letting Postgres errors bubble out.
- value: finite, 0..1_000_000; unit max 16; source max 64; strict
  schemas so unknown keys 400 instead of silently dropping.

createDb now memoizes the postgres client per process so warm
function instances reuse the connection. Tests cover the new 415,
429, and strict-schema branches (now 17 tests total).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
loadTodayActivity accepts { tz, goals } so the day boundary and the
displayed time match the user's locale instead of the server's
(usually UTC). Goals default to 600/30/8000 and are overridable.

queries.ts now uses the IANA timezone to compute startOfToday, and
treats null vs 0 correctly (kcal[0]?.value != null) so a real 0-step
day no longer flips to "partial". lastSync is taken from recordedAt
instead of ingestedAt so a backfill of yesterday's data still reads
as stale.

TodayData now carries lastSyncIso, lastSyncLabel and hoursSinceSync
pre-computed on the server, eliminating the hydration mismatch from
Date.now()/getHours() running on both sides. The Stale card no
longer needs a separate lastSync prop.

states.tsx swaps the Apple-style red/green/blue for the entrepta
violet (#7c6bff) / emerald (#10b981) / amber (#f59e0b), so the
component a user copies looks like the demo they saw on the site.

Tests updated to match the new TodayData shape; 17 still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a headers() block to next.config.mjs covering CSP (default-src
self plus the inline+eval Next needs for hydration), X-Frame-Options
DENY, X-Content-Type-Options nosniff, Referrer-Policy, HSTS, and a
Permissions-Policy that closes camera, mic, geolocation and the
interest-cohort tracker.

The /shortcut route now reads from apps/web/public/wristkit-sync.shortcut
(populated by the prebuild copy step) instead of climbing two levels
out of the deployment package. The previous path silently 404s on
Vercel because files outside apps/web are not bundled into the
serverless function.

Wrap MdxContent in an error boundary so a broken doc no longer
blanks out the whole docs page. Boundary logs the stack and shows a
small inline alert with the error message.

DB pooling is already covered by the lazy singleton added in the
handler hardening commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add schemas/0002_dedupe.sql with a UNIQUE index on
(user_id, metric, recorded_at) NULLS NOT DISTINCT, so a re-run of
the iOS Shortcut against the same day cannot insert duplicate rows.
The migration includes a commented-out cleanup CTE for tables that
already drift.

Mirror the unique index in lib/schema.ts via uniqueIndex so the
drizzle schema stays in sync.

Rename the registry meta from healthkit-schema to wristkit-schema
to match the brand, and list both SQL files in the install plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
lib/registry-files.ts reads the actual source files from
packages/registry at request/build time and exposes typed bundles
for the today-activity-card component, the sync handler, and the
two SQL migrations. The path is normalized and the read is asserted
to stay inside the monorepo.

components/docs/registry-file-bundle.tsx renders those files in
entrepta Tabs with a CodeBlock per file, showing the destination
path and a copy button. Section heading and intro line are passed
in by the docs page.

apps/web/app/docs/[[...slug]]/page.tsx wires the bundles in for
/docs/components/today-activity-card and /docs/installation, after
the MDX content. Other slugs render unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop every CLI reference. The new story is: copy the files shown on
this site into your project, set two env vars, run the SQL on
Supabase, install the Shortcut.

Index, installation, shortcut setup, FAQ, today-activity-card,
concepts/registry, concepts/component-states and concepts/data-model
are all rewritten:

- /api/healthkit is now /api/wristkit-sync everywhere
- installation flow points at the file bundles rendered after the MDX
- TodayActivityCard docs show every state as a live demo plus the
  new tz / goals options
- component-states snippet now declares the data object before
  branching on it (the old snippet referenced an undefined variable)
- data-model documents the new uniqueIndex
- registry concept page explains why the CLI is gone
- FAQ swaps "update the component" guidance from a CLI command to
  diffing the copy on the site

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the global 'outline: none !important' override with a
':focus:not(:focus-visible)' selector so mouse clicks stay quiet
but keyboard focus still gets the global ring on inputs, buttons,
menu items, options, tabs and cmdk items. Components that ship
their own focus-visible styling (Button, code-block copy, etc.)
already override the ring with their own indicator.

Add a SkipLink (CSS-only, slides in on focus) and wire it into the
marketing page and the docs layout. The marketing top bar becomes a
<header>; <main> gets id="main-content" with tabIndex=-1 so the
skip target is reachable. The docs sidebar is wrapped in a
<nav aria-label="Documentation"> and its <main> mirrors the same
pattern with id="docs-content".

Other a11y polish:

- MDX h1 is remapped to h2 to avoid two H1s on doc pages, since the
  page header already renders the title as h1.
- MDX links now have underline (not color-only) for users who can't
  perceive color.
- TodayActivityCardLoading announces itself with role=status and an
  aria-label.
- Badge solid-error: white-on-rose contrast was ~3.4:1; now uses
  bg-canvas (dark) which clears AA at small text.
- code-block adds aria-label on <pre> and a polite aria-live region
  that announces "Copied to clipboard".
- External links in TopNavLink and SiteFooter get aria-hidden on
  the visual arrow and an sr-only "(opens in new tab)" notice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two SQL files share the same dest ("Supabase SQL Editor") so React
warned about duplicate keys and the second tab would silently take
over the first. Switch the key + Tabs value to the source path,
which is always unique on disk, and label each tab with the source
filename. The CodeBlock still shows the dest as the filename label.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tailwind v4's preflight clobbers the inline transform on .skip-link
so the "skip to content" pill was visible on every page load.
Hide it the same way most a11y libraries do: position fixed at
top: -100px and animate top back to 8px on focus. Same UX, no
transform involved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The iOS Shortcut posts { steps, moveKcal, exerciseMin } — not the
samples array we had documented. Update the validator, handler, tests,
docs, and hero preview to the real shape, and point the download URL at
the canonical iCloud Shortcut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Strip the made-up marketing copy from the demo (.tsx suffix, Ln/Col,
"strength day", retry button) and drop the partial state — it can't
exist with the flat Shortcut payload. The error card is now generic
so it doesn't leak which kind of error occurred (auth, db, anything).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Step 3 of the install guide references "SQL migrations" but the bundle
rendered after "Route handler", so readers scanning top-to-bottom hit
the wrong one first and assumed the SQL was missing. Swap the order
and name each bundle explicitly in the prose.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Readers reported scrolling past the bundle at the bottom and assuming
the SQL was missing. Drop the SQL bundle, paste the two migrations
(plus the optional dedupe CTE) inline next to the instructions, and
link to /docs/concepts/data-model for the design rationale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Embed all lib, handler and component code directly in the page so the
  user never navigates away to copy files
- Remove the RegistryFileBundle that was appending the route handler a
  second time at the bottom of the page
- Replace the direct connection URL example with the Transaction pooler
  format and explain why the direct URL fails on Vercel (ENOTFOUND)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add optional `transform` to `RegistryFileSpec` so registry file content
  can be rewritten at load time without touching the source files.
- Use it on load.ts to replace the monorepo-internal `./queries` shim with
  `@/lib/wristkit/queries`, the path users actually have in their projects.
- Define `RegistryFile` as a plain struct (no `transform` field) so the
  return value serialises cleanly when passed to Client Components.
- Fix the path-traversal guard: append `path.sep` to ROOT before `startsWith`
  so a sibling directory whose name shares the ROOT prefix can't bypass it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
wristkit-web Ready Ready Preview, Comment Jul 7, 2026 11:02pm

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /shortcut route reads from public/ at runtime, but only prebuild
was copying the file there. Adding predev ensures the file exists when
playwright starts the server with `pnpm dev`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnotannamaria imnotannamaria changed the title Fix/review pass 1 fix: review pass — docs, a11y, shortcut handler, and e2e Jul 7, 2026
Remove all CLI references. The install flow is now manual: copy files
from the docs, run SQL in Supabase, download the iOS Shortcut.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnotannamaria
imnotannamaria merged commit e894e9b into main Jul 7, 2026
4 checks passed
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