docs(01-02): summarize PWA-04 safe-area utility gap closure

Closes the orphaned PWA-04 requirement flagged by 01-VERIFICATION.md.
Single edit to app/styles/brand.css adds @utility pt-safe and
@utility pb-safe; production build (load-bearing for Tailwind 4
@utility syntax) compiled successfully and emitted both classes
into the production CSS bundle.
This commit is contained in:
lorentz 2026-05-03 13:57:40 -04:00
parent dff0264115
commit a293a4f662

View file

@ -0,0 +1,174 @@
---
phase: 01-pwa-scaffolding
plan: 02
subsystem: pwa-scaffolding
gap_closure: true
tags: [css, tailwind4, mobile, pwa, safe-area]
requirements_satisfied: [PWA-04]
roadmap_criteria_satisfied: ["Phase 1 SC #3 — safe-area utility available"]
dependency_graph:
requires: []
provides:
- "@utility pt-safe (padding-top: env(safe-area-inset-top))"
- "@utility pb-safe (padding-bottom: env(safe-area-inset-bottom))"
affects:
- "Phase 2 (mobile shell) — sticky header (SHELL-05) and fixed bottom nav (SHELL-06) consume these utilities"
tech_stack:
added: []
patterns:
- "Tailwind 4 @utility blocks (already in use across brand.css)"
- "CSS env(safe-area-inset-*) — browser-native, falls back to 0"
key_files:
created: []
modified:
- app/styles/brand.css
decisions:
- "Named utilities (pt-safe / pb-safe) over arbitrary values (pt-[env(safe-area-inset-top)]) — single source of truth, clearer JSX, easy future tweak if iOS rules change"
- "brand.css over globals.css — co-located with all other named project utilities (num, metric-label, surface-brand, tagline, etc.); already imported by globals.css line 125"
- "Top + bottom only (no pl-safe / pr-safe) — manifest pins orientation to portrait; left/right insets only matter in landscape on notched devices; speculative until a consumer asks"
- "Plain env() (not max(env(), 0px)) — env() already returns 0 on devices without insets; max() wrapper is a no-op"
metrics:
duration: "~5 min"
completed: 2026-05-03
tasks_completed: 1
files_modified: 1
commits: 1
---
# Phase 01 Plan 02: PWA-04 Safe-Area Utility Gap Closure Summary
**One-liner:** Adds shared `pt-safe` / `pb-safe` Tailwind 4 `@utility` blocks to `app/styles/brand.css`, closing the orphaned PWA-04 requirement so Phase 2's sticky header and fixed bottom nav can opt into iOS notch / Android home-indicator padding via `env(safe-area-inset-*)`.
## Requirements Satisfied
- **PWA-04** — Header and bottom tab bar respect `env(safe-area-inset-top/bottom)` (Tailwind arbitrary values or shared utility class). **Closed** by shipping `@utility pt-safe` and `@utility pb-safe` in `app/styles/brand.css`. This restores the orphaned-requirement state flagged by `01-VERIFICATION.md` (where 01-01 had declared `requirements: [PWA-01, PWA-02, PWA-03]` only and silently deferred PWA-04 to Phase 2).
- **ROADMAP Phase 1 Success Criterion #3** — "Shared safe-area utility class available" — satisfied by the same two `@utility` blocks.
## What Changed
### Files Modified
- `app/styles/brand.css` — appended one section comment block + two `@utility` definitions between the existing `@utility tagline` (ends line 140) and the `/* === Wolf-mark watermark === */` section header (now line 165). Net: **+23 lines, 0 deletions.**
### Exact Diff (additive only)
```css
/* === Safe-area insets =================================================
*
* Opt-in padding helpers for sticky top / fixed bottom bars on devices
* with notches, dynamic islands, or gesture home indicators. Pair with
* the viewport-fit=cover viewport meta (set in app/layout.tsx) — without
* that, env(safe-area-inset-*) resolves to 0 and these utilities are
* no-ops, which is the desired fallback on non-PWA / non-mobile contexts.
*
* Usage:
* <header class="sticky top-0 pt-safe ..."> // header clears notch
* <nav class="fixed bottom-0 pb-safe ..."> // bottom bar clears home bar
*
* Closes PWA-04 (REQUIREMENTS.md) and ROADMAP Phase 1 SC #3.
* ==================================================================== */
@utility pt-safe {
padding-top: env(safe-area-inset-top);
}
@utility pb-safe {
padding-bottom: env(safe-area-inset-bottom);
}
```
### What Was NOT Changed
- `app/globals.css` — untouched. The existing `@import "./styles/brand.css";` on line 125 already pulls the new utilities into the global stylesheet.
- All pre-existing `@utility` blocks in `brand.css` (`num`, `num-lg`, `num-xl`, `metric-label`, `surface-brand`, `surface-brand-ink`, `rule-brand`, `text-chrome`, `border-chrome`, `tagline`, `has-mark-watermark`) and the `.mark-watermark` plain rule — preserved verbatim.
- The `:root` / `.dark` Wulf brand token sections — preserved verbatim.
- No `tailwind.config.*` was created (Tailwind 4 + project convention forbids it).
- No `next-pwa`, no service worker, no new dependencies introduced.
## Why These Choices
### `brand.css`, not `globals.css`
All named project utilities (`num`, `metric-label`, `surface-brand`, `tagline`, etc.) already live in `brand.css`. Co-locating safe-area utilities there means Phase 2 has one file to scan when looking for project helpers. `globals.css` is reserved for Tailwind imports, `@theme inline` token mapping, and `:root` / `.dark` variable definitions — adding utility classes there would muddy that separation. Plus `brand.css` is already imported by `globals.css` (line 125), so no new wiring is required.
### Named utilities, not arbitrary values
Phase 2 will use these classes in 2+ places (sticky header, bottom nav, drawer footer, possibly modals). A named utility is a single source of truth — if iOS rules ever change (e.g., `max(env(safe-area-inset-top), 0.5rem)` becomes desirable), it's a one-line edit to `brand.css` instead of multi-file find-and-replace. `pt-safe` / `pb-safe` also reads more clearly in JSX class lists than `pt-[env(safe-area-inset-top)]`. ROADMAP Phase 1 SC #3 explicitly accepts "shared utility class" as one valid form — picking that form removes ambiguity for Phase 2.
### Top + bottom only
The manifest pins orientation to `portrait` (per `01-01-SUMMARY.md`). Left/right safe-area insets (`safe-area-inset-left`, `safe-area-inset-right`) only matter in landscape on notched devices, which the app does not enter. Adding `pl-safe` / `pr-safe` now would be speculative; Phase 2 (or any future phase) can add them in 30 seconds if a real consumer appears.
### Plain `env(safe-area-inset-*)`, not `max(env(...), 0px)`
The CSS `env()` value already returns `0` when no inset is reported by the browser — wrapping it in `max(..., 0)` is a no-op and adds noise. Wrap it later if a real device misbehaves.
## Verification Results
All checks from the plan's `<verification>` section ran successfully:
| Check | Command | Result |
|-------|---------|--------|
| `pt-safe` utility present | `grep -E '@utility pt-safe' app/styles/brand.css` | match (1 line) |
| `pb-safe` utility present | `grep -E '@utility pb-safe' app/styles/brand.css` | match (1 line) |
| Top inset declaration correct | `grep -E 'padding-top:\s*env\(safe-area-inset-top\)' app/styles/brand.css` | match (1 line) |
| Bottom inset declaration correct | `grep -E 'padding-bottom:\s*env\(safe-area-inset-bottom\)' app/styles/brand.css` | match (1 line) |
| `tagline` utility unchanged | `grep -E '@utility tagline' app/styles/brand.css` | match |
| `num` utility unchanged | `grep -E '@utility num \{' app/styles/brand.css` | match |
| Wulf brand tokens intact | `grep -E '\-\-wulf-blue:' app/styles/brand.css` | match |
| `.mark-watermark` intact | `grep -E '\.mark-watermark \{' app/styles/brand.css` | match |
| globals.css import wiring intact | `grep -E '@import "\./styles/brand\.css";' app/globals.css` | match |
| No `pl-safe` / `pr-safe` (out of scope) | `grep -E '@utility (pl-safe\|pr-safe)' app/styles/brand.css` | no match |
| No `tailwind.config.*` created | `test ! -f tailwind.config.{ts,js,mjs}` | exit 0 |
| No service worker shipped | `test ! -f public/{sw,service-worker}.js` | exit 0 |
| `next-pwa` not added | `! grep '"next-pwa"' package.json` | not found |
| Type check baseline preserved | `npx tsc --noEmit --pretty` | exit 0 (clean) |
| **Production build (load-bearing — Tailwind 4 fails on malformed `@utility` syntax)** | `npm run build` | **`✓ Compiled successfully in 20.3s`** |
### Compiled CSS Confirmation
Inspected the production CSS chunk emitted by the build:
```
$ grep -oE '\.pt-safe[^,{]*\{[^}]*\}|\.pb-safe[^,{]*\{[^}]*\}' .next/static/chunks/3c3ee60b60fe53db.css
.pt-safe{padding-top:env(safe-area-inset-top)}
.pb-safe{padding-bottom:env(safe-area-inset-bottom)}
```
Both utilities compiled cleanly into the production bundle and are ready for Phase 2 to consume.
### Notes on Build-Time Runtime Errors (Pre-Existing, Out of Scope)
`npm run build` also surfaces unrelated runtime errors during static page generation: the auto-starting workers (sync-scheduler, analyzer worker, RMM worker) attempt to initialize against Postgres and Better Auth at import time, but no `.env` is present in this worktree, so they log SASL connection errors and a `BETTER_AUTH_SECRET` warning. **These are pre-existing and unrelated to this plan** — they appeared identically in the 01-01 build environment. The compile step (the load-bearing gate for this plan, which validates Tailwind 4 `@utility` syntax) reported `✓ Compiled successfully` and emitted the new classes into the CSS bundle. Logged to deferred-items if needed; not in scope for PWA-04.
## Pointer to Phase 2
The new utilities are ready for `app/mobile/layout.tsx`:
- **Sticky header** (SHELL-05): `<header class="sticky top-0 pt-safe ...">` — clears the iPhone notch / Dynamic Island and Android status bar.
- **Fixed bottom nav** (SHELL-06): `<nav class="fixed bottom-0 pb-safe ...">` — clears the iOS home indicator and Android gesture bar.
No Phase 2 work is required to wire these in — they're already part of the global Tailwind class space the moment Phase 2's components mount.
## Deviations from Plan
None — plan executed exactly as written. Single-task plan, single edit, single commit.
## Threat Surface Scan
No new threat surface. CSS utilities are public client-side styles compiled into the (already-public) Tailwind CSS bundle. `env(safe-area-inset-*)` is a browser-native CSS environment variable resolved entirely client-side from the device viewport — no JavaScript, no user input, no data flow, no auth surface, no new endpoint. STRIDE assessment from the plan stands: only boundary is "browser ↔ static CSS bundle" (information-disclosure → accept; same risk profile as every other Tailwind class). ASVS-L1 baseline preserved.
## Commits
| Task | Commit | Files |
|------|--------|-------|
| 1: Append `pt-safe` / `pb-safe` `@utility` blocks | `dff0264` | `app/styles/brand.css` |
## Self-Check: PASSED
- File modified exists and contains both new utilities — confirmed via grep.
- Commit `dff0264` exists in current branch — confirmed via `git log`.
- Compiled CSS bundle in `.next/static/chunks/` contains `.pt-safe` and `.pb-safe` rules — confirmed via grep.
- `npm run build` exited successfully (`✓ Compiled successfully in 20.3s`).
- `npx tsc --noEmit --pretty` exited 0.
- PWA-04 explicitly claimed in this SUMMARY's `requirements_satisfied` frontmatter and "Requirements Satisfied" section — orphaned-requirement trail closed.