Files
touchbase/docs/progress/2026-05-02-pwa-and-polish.md
2026-05-02 14:05:30 -04:00

123 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-05-02 — PWA Shell + Polish (Phases D + E)
> Companion to `BuildLog.md`. Predecessor: `2026-05-02-booking-lifecycle.md`. Closes Phases D and E of the post-payments UX plan — UX-completeness done.
## Milestone
Two small phases bundled because both came in well under estimate. The app is now installable as a PWA (Add to Home Screen on iOS / Android, manifest + service worker + icons), has friendly 404 / error pages instead of Next's default boxes, and the slot grid + admin tables stop crushing on narrow viewports.
**This closes the UX-completeness pivot.** Backend is feature-complete for v1 minus payments + reminders. Next workstream is **5d Stripe**.
## What landed
### Phase D — PWA shell
| Path | Role |
|---|---|
| `public/icon.svg` | Simple TB monogram on dark rounded square — works as favicon and as PWA icon at any size |
| `public/icon-mask.svg` | Maskable variant — full-bleed background, content in safe area for Android adaptive icons |
| `src/app/manifest.ts` | Next.js typed Metadata Manifest. name + short_name + display=standalone + theme color + 2 icons (any + maskable) |
| `public/sw.js` | Hand-rolled service worker. Network-first for HTML, stale-while-revalidate for static assets, **never** caches API/auth/admin/account/therapist/book paths. Versioned cache name (`tb-html-v1`, `tb-static-v1`). |
| `src/app/layout.tsx` | Added `manifest`, `appleWebApp`, `icons` to metadata. Added `viewport.themeColor` (light + dark + viewport-fit=cover for iPhone notch). Inline SW registration script gated to `NODE_ENV === "production"` (avoids stale-chunk weirdness in dev). |
### Phase E — Polish
| Path | Role |
|---|---|
| `src/app/not-found.tsx` | Friendly 404 — required because `notFound()` is called in several admin/account pages |
| `src/app/error.tsx` | Global error boundary client component with "Try again" + "Go home" buttons. Error digest displayed for debugging. |
| Slot grids in `/book` and `/admin/bookings/new` | Default to `grid-cols-3` instead of crushing to 1 col on mobile. Promotes to 4/6 cols at sm/md. |
| Admin tables (5 files) | `overflow-hidden``overflow-x-auto` so they scroll on narrow viewports instead of clipping |
## What's verified
- `pnpm test`**79/79 green**
- `pnpm lint` — clean
- `pnpm exec tsc --noEmit` — clean
- Live PWA smoke (curl on dev server):
- `GET /sw.js` → 200, `application/javascript`, 3.3 KB
- `GET /manifest.webmanifest` → 200, `application/manifest+json`, well-formed JSON with TouchBase + display=standalone + theme color + 2 icon refs
- `GET /icon.svg` → 200, `image/svg+xml`, 361 B
- `GET /icon-mask.svg` → 200, 437 B
- HTML head includes: `<meta viewport>` (with `viewport-fit=cover`), 2× `<meta theme-color>` (light + dark), `<link rel="manifest">`, `<meta mobile-web-app-capable>`, `<meta apple-mobile-web-app-title>`, `<meta apple-mobile-web-app-status-bar-style>`, `<link rel="apple-touch-icon">`
- 404 smoke: `GET /no-such-page` → renders the new not-found page with "404" heading + "We couldn't find that page" + "Back to home" link
## Decisions ratified
| Decision | Resolution |
|---|---|
| PWA library | **None** — hand-rolled `public/sw.js` (~80 LOC). Reason: zero dependency, full visibility into caching behavior, easy to reason about. `next-pwa` / `@serwist/next` are reasonable upgrades when we have specific needs (offline queueing, push notifications, etc.). |
| Icon format | **SVG** (single file, scaled by browser). Reason: ships in 360 bytes, looks crisp at any size, no asset pipeline. iOS Safari supports SVG `apple-touch-icon` from iOS 17+; on older iOS the icon falls back to the bookmark default — acceptable for v1. We can add a 180×180 PNG later if needed. |
| SW caching policy | Network-first for HTML; stale-while-revalidate for static; **never** cache `/api/`, `/admin/`, `/account/`, `/therapist/`, `/book*`. Reason: any cached page in those routes could show stale slot availability or stale booking state. Safer to require fresh data. |
| Versioned cache name | `tb-html-v1`, `tb-static-v1`. Bump on deploys that should invalidate. Reason: simple manual control vs. fingerprint-based invalidation. |
| SW registration timing | `window.addEventListener('load', ...)` — runs after the page is interactive so it doesn't compete with first paint. Production-only via inline script gated on `NODE_ENV`. |
| `mobile-web-app-capable` instead of `apple-mobile-web-app-capable` | Next.js's `appleWebApp.capable: true` emits the modern unprefixed form, which is the spec-current name. Both Apple and Android browsers respect it. |
| `dynamic = "force-dynamic"` everywhere | Already the default in our pages. SW reinforces this — stale HTML is OK on cold reload but never on subsequent navigation. |
| Error page is a Client Component | Required by Next.js's error boundary spec. Server logs already capture the underlying error; client-side `console.error` is a backup for browser inspection. |
| 404 + error page styling | Match the rest of the app — minimal, Tailwind utility classes, matches dark mode automatically. |
| Mobile slot grid breakpoint | `grid-cols-3 sm:grid-cols-4 md:grid-cols-6`. 3-across on phone is comfortable thumb territory; 4 on tablet; 6 on desktop. |
| Admin table overflow | `overflow-x-auto` rather than `overflow-hidden`. Tradeoff: rounded corners look slightly worse when content overflows, but the alternative (clip) is functionally broken on phone. |
## Gotchas hit
### `apple-mobile-web-app-capable` missing
Initial smoke didn't find this meta tag. Turned out Next.js (modern versions) emits the unprefixed `mobile-web-app-capable` instead — which is the current spec form. Both work. No fix needed.
### Lint flagged `<a>` instead of `<Link>` in error page
Started with `<a href="/">` to avoid importing `<Link>` for one element. ESLint rule (`@next/next/no-html-link-for-pages`) caught it. Switched to `<Link>`.
### `'event' is defined but never used` in SW
Pure JS file linted alongside the rest. The unused param in the install handler triggered the unused-vars rule. Removed the param.
## Open questions
1. Customer-visible brand name (still pending)
2. Currency
3. Stripe account ownership — **needed for next phase**
4. **NEW**: should the SW also cache the home page for an "offline" experience? Currently network-first means it works offline ONLY if previously visited. Acceptable for v1.
5. **NEW**: PNG fallback for `apple-touch-icon` for iOS <17? Defer until someone reports the bookmark icon is missing.
## Roadmap status
UX-completeness:
- A — Admin CRUD: done 2026-05-02
- B — Therapist self-serve: done 2026-05-02
- C — Booking lifecycle: done 2026-05-02
- **D — PWA shell: done 2026-05-02 (this session, part 1)**
- **E — Polish: done 2026-05-02 (this session, part 2)**
**UX is done.** Backend roadmap continues:
- 5d — Stripe deposit flow + webhook
- 5e — Email reminders (pg-boss scheduled jobs)
## Recommended next step
**5d — Stripe deposit flow.** The biggest remaining backend chunk. Sequence per `BuildLog.md` notes:
1. Stripe SDK + env config (test keys — user provides)
2. Render Stripe Elements on `/book/confirm` (this is the **first place we need a real Client Component for interactive UI** — past Client Components have been minimal)
3. `createPaymentIntent` for the deposit at `createHold` time; pass `client_secret` to the page
4. Webhook handler at `/api/stripe/webhook` (verifies signature, updates `Payment` row, transitions Booking from HOLD to CONFIRMED on `payment_intent.succeeded`)
5. Hold expiry job via pg-boss cancels HOLDs whose deposit didn't capture in time
~35 days. After 5d, **5e reminders** is small — pg-boss schedule + reminder template + send job (~23 days).
Before starting 5d we'll need:
- Stripe test secret key + publishable key
- Webhook signing secret (from Stripe CLI for local dev)
## How to resume
```bash
cd /Users/noise/Documents/code/touchbase
docker-compose up -d postgres mailpit
pnpm db:seed
pnpm tsx scripts/book-on-behalf.ts alex@example.com "60-minute Swedish" 2026-05-05T10:00
pnpm dev
# Try the customer flow on a phone (or DevTools mobile preview):
# http://<your-IP>:3000 → tap a service → date → pick time → sign in → confirm
# Add to Home Screen — PWA icon + standalone display
# Try the admin/therapist flows on a phone — tables now scroll horizontally
# Hit a bad URL like /xyzzy → see the 404 page
```