wulf-pulse/.planning/phases/01-pwa-scaffolding/01-02-PLAN.md
lorentz 7b644e475f docs(planning): sync STATE/ROADMAP + capture untracked phase plans before master merge
- STATE/ROADMAP/config updated to reflect Phase 09.1 execution
- 09-01 plan refreshed (gap-closure detail)
- 09-02..09-05 plans updated during execution
- Add untracked 09-06 plan + 01-01/01-02 PWA scaffolding plans (orphaned from earlier sessions)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:30:51 -04:00

20 KiB

phase plan type wave depends_on gap_closure files_modified autonomous requirements must_haves
01-pwa-scaffolding 02 execute 1
true
app/styles/brand.css
true
PWA-04
truths artifacts key_links
A shared @utility named pt-safe is defined in app/styles/brand.css that applies padding-top: env(safe-area-inset-top)
A shared @utility named pb-safe is defined in app/styles/brand.css that applies padding-bottom: env(safe-area-inset-bottom)
Phase 2's sticky header can opt into safe-area-inset-top padding by adding the pt-safe class
Phase 2's fixed bottom nav can opt into safe-area-inset-bottom padding by adding the pb-safe class
The Tailwind 4 build accepts the new @utility blocks (no CSS syntax errors, npm run build succeeds)
path provides contains
app/styles/brand.css Two new @utility blocks (pt-safe, pb-safe) sitting alongside the existing num/metric/surface/rule/chrome/tagline utilities @utility pt-safe
from to via pattern
app/styles/brand.css (@utility pt-safe) rendered CSS class .pt-safe Tailwind 4 @utility block — Tailwind compiles @utility name { ... } into a class .name { ... } @utility pt-safe
from to via pattern
app/styles/brand.css (@utility pb-safe) rendered CSS class .pb-safe Tailwind 4 @utility block @utility pb-safe
from to via pattern
app/globals.css app/styles/brand.css @import './styles/brand.css' on line 125 (already wired — no change required) @import "./styles/brand.css"
Close the PWA-04 gap from Phase 01 verification by adding shared safe-area `@utility` blocks to `app/styles/brand.css`. Phase 2's sticky header and fixed bottom nav need to opt into `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)` padding so content paints correctly under the iOS home indicator and Android gesture bar when `viewport-fit=cover` is in effect (already shipped by 01-01).

Purpose: PWA-04 — make a safe-area utility available so any sticky top/bottom bar can opt in. ROADMAP Phase 1 SC #3 requires the utility to be available in Phase 1; Phase 2's contract (SHELL-05, SHELL-06) only mandates consumption. This plan restores the broken phase boundary identified by 01-VERIFICATION.md.

Output: app/styles/brand.css updated with two new @utility blocks (pt-safe, pb-safe) appended to the existing utility section. No other files touched. Verifiable by grep -E '@utility (pt-safe|pb-safe)' app/styles/brand.css and npm run build.

Why app/styles/brand.css (not app/globals.css):

  • All named project utilities (num, num-lg, num-xl, metric-label, surface-brand, surface-brand-ink, rule-brand, text-chrome, border-chrome, tagline, has-mark-watermark) already live there.
  • brand.css is already imported into globals.css (line 125) — no extra wiring needed.
  • Keeps utilities co-located so Phase 2 has a single 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.

Note on traceability: this plan claims PWA-04 in its requirements frontmatter, restoring the orphaned-requirement state flagged by 01-VERIFICATION.md. The executor's SUMMARY (01-02-SUMMARY.md) should explicitly call out that PWA-04 is now satisfied, closing the requirements traceability table.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @.planning/phases/01-pwa-scaffolding/01-VERIFICATION.md @.planning/phases/01-pwa-scaffolding/01-01-SUMMARY.md @docs/superpowers/specs/2026-05-03-mobile-shell-design.md @CLAUDE.md @app/styles/brand.css @app/globals.css

Tailwind 4 @utility syntax (already in use in this project):

  • This project is Tailwind 4 with NO tailwind.config.* file. Custom utilities are declared inline in CSS using the @utility at-rule.
  • Pattern: @utility name { /* CSS declarations */ } — Tailwind compiles this to a class .name { ... } that participates in the variant system (hover:name, md:name, etc.).
  • See existing examples in app/styles/brand.css lines 70-140 (e.g., @utility num { ... }, @utility metric-label { ... }, @utility surface-brand { ... }).
  • Each @utility block holds plain CSS property declarations. No @apply is required for simple padding-* cases.

env() CSS environment variables for safe areas:

  • env(safe-area-inset-top) — top safe-area inset (e.g., iPhone notch / Dynamic Island area).
  • env(safe-area-inset-bottom) — bottom safe-area inset (e.g., iPhone home indicator area).
  • Browser-side CSS feature; no JavaScript involvement. Falls back to 0 on browsers/devices without safe-area insets.
  • Requires <meta name="viewport" content="...viewport-fit=cover"> to take non-zero values. This is already shipped by 01-01 (viewportFit: "cover" in app/layout.tsx).

Existing app/styles/brand.css structure (line numbers from current file):

  • Lines 1-21: file header / brand documentation comment.
  • Lines 23-52: :root overrides (--wulf-blue, etc.) for the light theme.
  • Lines 54-62: .dark overrides.
  • Lines 64-68: /* === Utility classes === */ section header comment.
  • Lines 70-140: existing @utility blocks — num, num-lg, num-xl, metric-label, surface-brand, surface-brand-ink, rule-brand, text-chrome, border-chrome, tagline.
  • Lines 142-147: /* === Wolf-mark watermark === */ section header comment.
  • Lines 149-152: @utility has-mark-watermark.
  • Lines 154-170: .mark-watermark plain rule + .dark .mark-watermark override.
  • Insertion point for new utilities: after the tagline utility (line 140) and before the watermark section header (line 142). This keeps utilities grouped before the watermark block, which has its own thematic header.

app/globals.css import wiring (already in place — DO NOT change):

  • Line 125: @import "./styles/brand.css"; — pulls brand.css into the global stylesheet at the end. Anything added to brand.css is automatically available app-wide. No additional wiring needed.

Spec wording (docs/superpowers/specs/2026-05-03-mobile-shell-design.md §5/§6):

  • The mobile shell's sticky top header must respect env(safe-area-inset-top).
  • The fixed bottom nav must respect env(safe-area-inset-bottom) (often combined with the bottom-nav height).
  • The spec accepts either a named utility or Tailwind 4 arbitrary values (pt-[env(safe-area-inset-top)]).

Why ship a named utility (not arbitrary values):

  • Phase 2 will use these classes in 2+ places (header, bottom nav, drawer footer, possibly modals). A named utility is one source of truth — if the iOS rules ever change (e.g., add max(env(safe-area-inset-top), 0.5rem)), it's a one-line edit instead of a multi-file find-and-replace.
  • pt-safe / pb-safe reads more clearly in JSX class lists than pt-[env(safe-area-inset-top)].
  • ROADMAP Phase 1 SC #3 explicitly mentions "shared utility class" as one acceptable form — picking that form removes ambiguity for Phase 2.

Verification commands the executor will use:

  • grep -E '@utility pt-safe' app/styles/brand.css
  • grep -E '@utility pb-safe' app/styles/brand.css
  • grep -E 'env\(safe-area-inset-top\)' app/styles/brand.css
  • grep -E 'env\(safe-area-inset-bottom\)' app/styles/brand.css
  • npm run build (CSS @utility blocks must parse — broken syntax fails the Tailwind compile step in Next.js)
  • npx tsc --noEmit --pretty (sanity check; CSS doesn't affect TS but pre-existing baseline must hold)
Task 1: Append pt-safe and pb-safe @utility blocks to app/styles/brand.css app/styles/brand.css - app/styles/brand.css (the entire file — confirm line numbers above match current state; the exact insertion point is between the existing `tagline` utility and the watermark section header) - app/globals.css lines 1-5 and 125 (confirm `brand.css` is still imported; no change needed) - .planning/phases/01-pwa-scaffolding/01-VERIFICATION.md (the gap source — frontmatter `gaps[0].missing`) - .planning/REQUIREMENTS.md line 16 (PWA-04 wording) Edit `app/styles/brand.css`. Append two new `@utility` blocks **after** the existing `@utility tagline { ... }` block (which ends around line 140) and **before** the `/* === Wolf-mark watermark === */` section header comment (around line 142). Do NOT touch any other part of the file.

Insert exactly this block (including the leading section comment and the two @utility definitions):

/* === 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);
}

Notes on the choices (so a reviewer doesn't have to ask):

  • Two utilities only (pt-safe, pb-safe). The spec only requires top + bottom safe-area handling — that's what notches and home indicators occupy. Left/right insets (safe-area-inset-left, safe-area-inset-right) only matter for landscape mode on devices with notches, and the manifest pins orientation to portrait (per 01-01-SUMMARY.md). Adding pl-safe / pr-safe now would be speculative; Phase 2 can add them in 30 seconds if a real consumer appears.
  • No additive variants like pt-safe-4. ROADMAP Phase 1 SC #3 only requires the base utility be available. Combinations like "safe-area inset PLUS 1rem" can be expressed at consume time with pt-safe pt-4 or arbitrary values. Don't pre-build helpers that nothing yet calls.
  • Plain env(safe-area-inset-top) (not max(env(safe-area-inset-top), 0px)). The CSS env value already returns 0 when no inset is reported — wrapping it in max(..., 0) is a no-op and adds noise. Wrap it later if a real device misbehaves.
  • Insertion location: alongside the other @utility blocks in brand.css. Already justified in <objective>: keeps named utilities co-located, requires no new import wiring, fits the project's "Wulf design helpers" theme (these are layout primitives that go with the rest of the brand kit).

Do NOT:

  • Edit app/globals.css — the existing @import "./styles/brand.css" (line 125) is already correct.
  • Add a new file — these are utilities, not a new module.
  • Add pl-safe, pr-safe, or stacked variants — out of scope for PWA-04.
  • Modify any existing @utility block (num, metric-label, tagline, etc.) — they are unrelated.
  • Modify the :root / .dark token sections at the top of brand.css — these are CSS custom properties, not utilities.
  • Touch the .mark-watermark plain rule or the watermark section header — they live below the insertion point and are unrelated.
  • Add a tailwind.config.* file — this is a Tailwind 4 project with no config file, by design (CLAUDE.md Conventions to follow).

After editing, the file should be ~12-13 lines longer than before (1 comment block + 2 @utility definitions = ~13 lines including blank lines). The diff should show only additions, no deletions. grep -q '@utility pt-safe' app/styles/brand.css && grep -q '@utility pb-safe' app/styles/brand.css && grep -q 'padding-top: env(safe-area-inset-top)' app/styles/brand.css && grep -q 'padding-bottom: env(safe-area-inset-bottom)' app/styles/brand.css && npx tsc --noEmit --pretty && npm run build <acceptance_criteria> - grep -E '@utility pt-safe' app/styles/brand.css matches exactly one line. - grep -E '@utility pb-safe' app/styles/brand.css matches exactly one line. - grep -E 'padding-top:\s*env\(safe-area-inset-top\)' app/styles/brand.css matches exactly one line. - grep -E 'padding-bottom:\s*env\(safe-area-inset-bottom\)' app/styles/brand.css matches exactly one line. - The existing @utility tagline block is still present and unchanged: grep -E '@utility tagline' app/styles/brand.css matches. - The existing @utility num block is still present: grep -E '@utility num \{' app/styles/brand.css matches. - The existing .mark-watermark rule is still present: grep -E '\.mark-watermark \{' app/styles/brand.css matches. - The :root --wulf-blue token is still present: grep -E '\-\-wulf-blue:' app/styles/brand.css matches. - No tailwind.config file was created: test ! -f tailwind.config.ts && test ! -f tailwind.config.js && test ! -f tailwind.config.mjs. - app/globals.css is unchanged from baseline: grep -E '@import "\./styles/brand\.css";' app/globals.css matches (the existing import is intact). - No pl-safe / pr-safe utilities were added (out of scope): ! grep -E '@utility (pl-safe|pr-safe)' app/styles/brand.css. - No service worker file shipped: test ! -f public/sw.js && test ! -f public/service-worker.js. - next-pwa is not in dependencies: ! grep '"next-pwa"' package.json. - Type check passes: npx tsc --noEmit --pretty exits 0 (CSS changes don't affect TS, but the pre-existing baseline is preserved). - Production build succeeds: npm run build exits 0. This is the load-bearing gate — Tailwind 4 will fail the build if the @utility syntax is malformed, so a green build confirms the new utilities compile and are emitted into the production CSS bundle. </acceptance_criteria> app/styles/brand.css contains two new @utility blocks — pt-safe (sets padding-top: env(safe-area-inset-top)) and pb-safe (sets padding-bottom: env(safe-area-inset-bottom)) — placed between the existing @utility tagline block and the /* === Wolf-mark watermark === */ section header. The block is preceded by a section comment that documents the intent, usage, and the PWA-04 / SC #3 requirement IDs being closed. No other file is modified. npm run build and npx tsc --noEmit --pretty both exit 0. PWA-04 is satisfied; the orphaned-requirement state from 01-VERIFICATION.md is closed.

<threat_model>

Trust Boundaries

Boundary Description
Browser ↔ static CSS bundle Public client read of compiled Tailwind CSS containing .pt-safe / .pb-safe rules. No auth, no input, no data flow.

STRIDE Threat Register

No trust boundaries crossed. CSS utilities are public client-side styles; env(safe-area-inset-*) is a CSS environment variable resolved by the browser based on the device viewport. There is no user input, no data flow, no auth surface, no new endpoint. ASVS-L1 baseline preserved — no new attack surface introduced beyond the (already-public) Tailwind CSS bundle.

Threat ID Category Component Disposition Mitigation Plan
T-01.02-01 Information Disclosure Compiled CSS bundle (.pt-safe, .pb-safe class rules) accept CSS class rules are world-readable by design — same risk profile as every other Tailwind utility class. Contains zero secrets, zero user data, zero endpoints.
</threat_model>
With dev server NOT required (these checks are static-file and build-time):
# New utilities exist in brand.css
grep -E '@utility pt-safe' app/styles/brand.css
grep -E '@utility pb-safe' app/styles/brand.css

# They reference the correct CSS env() variables
grep -E 'padding-top:\s*env\(safe-area-inset-top\)' app/styles/brand.css
grep -E 'padding-bottom:\s*env\(safe-area-inset-bottom\)' app/styles/brand.css

# Existing utilities and brand tokens are untouched
grep -E '@utility tagline' app/styles/brand.css
grep -E '@utility num \{' app/styles/brand.css
grep -E '\-\-wulf-blue:' app/styles/brand.css

# globals.css import wiring is unchanged
grep -E '@import "\./styles/brand\.css";' app/globals.css

# Out-of-scope items NOT added
! grep -E '@utility (pl-safe|pr-safe)' app/styles/brand.css
test ! -f tailwind.config.ts && test ! -f tailwind.config.js && test ! -f tailwind.config.mjs
test ! -f public/sw.js
! grep '"next-pwa"' package.json

# Type check (sanity, baseline preserved)
npx tsc --noEmit --pretty

# Production build (load-bearing — Tailwind 4 fails the build on malformed @utility syntax)
npm run build

With dev server running on port 3100, an OPTIONAL spot check (not required for the automated gate; useful for human verification on a real iPhone):

# Confirm the compiled .pt-safe / .pb-safe classes are present in the served CSS bundle
curl -s http://localhost:3100/ | grep -oE '/_next/static/css/[^"]+\.css' | head -1 | xargs -I {} curl -s "http://localhost:3100{}" | grep -E '\.pt-safe|\.pb-safe'

<success_criteria>

  • app/styles/brand.css defines @utility pt-safe { padding-top: env(safe-area-inset-top); } (PWA-04, SC #3).
  • app/styles/brand.css defines @utility pb-safe { padding-bottom: env(safe-area-inset-bottom); } (PWA-04, SC #3).
  • The new utilities are placed in the existing utilities section of brand.css (between @utility tagline and the watermark section), preceded by a section comment that documents intent and references PWA-04.
  • All pre-existing @utility blocks (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 remain unchanged.
  • app/globals.css is unchanged (the existing @import "./styles/brand.css" on line 125 already pulls the new utilities into the global stylesheet).
  • npm run build exits 0 — confirms Tailwind 4 accepts the new @utility syntax and compiles .pt-safe / .pb-safe into the production CSS bundle.
  • npx tsc --noEmit --pretty exits 0 (baseline preserved; CSS edits don't affect TS).
  • No tailwind.config.* file is created (Tailwind 4 + CLAUDE.md convention).
  • No service worker, no next-pwa, no new dependencies introduced.
  • PWA-04 is satisfied; the orphaned-requirement state from 01-VERIFICATION.md is closed (SUMMARY explicitly claims PWA-04). </success_criteria>
After completion, create `.planning/phases/01-pwa-scaffolding/01-02-SUMMARY.md` documenting: - File modified (`app/styles/brand.css`) with the exact diff (one section comment block + two `@utility` definitions appended). - The two utility names (`pt-safe`, `pb-safe`) and the CSS they emit (`padding-top: env(safe-area-inset-top)`, `padding-bottom: env(safe-area-inset-bottom)`). - Why `brand.css` was chosen over `globals.css` (utilities co-located with other named project utilities; already imported into globals; no extra wiring required). - Why only `pt-safe` / `pb-safe` (not `pl-safe` / `pr-safe` / additive variants) — orientation is portrait-locked, spec only mandates top/bottom; left/right and additive variants are speculative until a consumer asks. - Verification results (the grep commands and `npm run build` exit code from ``). - **Explicit Requirements Satisfied entry: `PWA-04`** — call this out by name so the orphaned-requirement state from `01-VERIFICATION.md` is visibly closed in the SUMMARY trail. This makes the next verification pass green on the requirements coverage table. - Pointer to Phase 2: the new utilities are ready for `app/mobile/layout.tsx` (sticky header → `pt-safe`, fixed bottom nav → `pb-safe`). - Threat surface scan: confirm "no new threat surface" (CSS utility addition only, no auth/input/data flow).