diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
new file mode 100644
index 0000000..24c188d
--- /dev/null
+++ b/.planning/ROADMAP.md
@@ -0,0 +1,153 @@
+# Roadmap: Pulse Mobile Shell Redesign
+
+## Overview
+
+Eight phases mirror the deliberate build order in the source spec
+(`docs/superpowers/specs/2026-05-03-mobile-shell-design.md` §8). Each phase
+ships independently to `master` — no big-bang merge. Phase 1 lays PWA
+metadata and safe-area utilities. Phase 2 rebuilds `app/mobile/layout.tsx`
+with the new header, 5-cell bottom nav, and More drawer (deleting
+`/mobile/nav` in the same change). Once the shell lands, Phases 3–7 are
+independent restyles/new pages and may be executed in parallel; Phase 8
+follows Phase 7 because the user profile is reached from the Engagement
+overview. All work happens in place under `/mobile/*` — no `/mobile-v2`,
+no parallel routes.
+
+## Phases
+
+**Phase Numbering:**
+- Integer phases (1, 2, 3): Planned milestone work
+- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
+
+Decimal phases appear between their surrounding integers in numeric order.
+
+- [ ] **Phase 1: PWA Scaffolding** — Manifest, viewport meta, and safe-area utilities so the shell installs and paints under the home indicator
+- [ ] **Phase 2: Mobile Shell + More Drawer** — New `app/mobile/layout.tsx` (header + 5-cell bottom nav) and Sheet drawer that replaces `/mobile/nav`
+- [ ] **Phase 3: Dashboard Restyle** — 2×2 KPI grid, Needs Attention strip, worker/backup status row (no charts)
+- [ ] **Phase 4: Tickets Restyle** — Collapsible URL-synced filters, priority-bar rows, cursor-based infinite scroll, detail header reskin
+- [ ] **Phase 5: Finance Restyle** — Adopt new Card + typography scale, swap wide tables for stacked lists
+- [ ] **Phase 6: Analyzer Feed (NEW)** — `/mobile/analyzer` read-only stream + `/api/mobile/analyzer/feed`
+- [ ] **Phase 7: Engagement Overview (NEW)** — `/mobile/engagement` phone-first overview reachable from the More drawer
+- [ ] **Phase 8: Engagement User Profile (NEW)** — `/mobile/engagement/[userId]` real-page profile that replaces the desktop modal pattern
+
+## Phase Details
+
+### Phase 1: PWA Scaffolding
+**Goal**: A manager who taps "Add to Home Screen" gets a standalone Pulse icon that opens to the mobile shell with content respecting the device safe areas.
+**Depends on**: Nothing (first phase)
+**Requirements**: PWA-01, PWA-02, PWA-03, PWA-04
+**Success Criteria** (what must be TRUE):
+ 1. Visiting `/manifest.json` returns valid JSON with `name: "Pulse"`, `display: "standalone"`, `start_url: "/mobile"`, and theme/background colors matching the app shells
+ 2. The root `app/layout.tsx` references the manifest via `` and the viewport meta includes `viewport-fit=cover`
+ 3. A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)` padding
+ 4. Installing Pulse to a phone home screen launches a chromeless app pointed at `/mobile` (no service worker, no offline)
+**Plans**: 2 plans
+- [x] 01-01-PLAN.md — Web App Manifest + viewport-fit=cover (PWA-01, PWA-02, PWA-03)
+- [x] 01-02-PLAN.md — Safe-area `pt-safe` / `pb-safe` @utility blocks in brand.css (PWA-04, gap closure)
+**UI hint**: no
+
+### Phase 2: Mobile Shell + More Drawer
+**Goal**: Every `/mobile/*` page renders inside a new layout — sticky header (Wulf mark + Bell placeholder + avatar), scrollable content, and a 5-cell bottom nav whose fifth control opens a Sheet drawer that fully replaces `/mobile/nav`.
+**Depends on**: Phase 1
+**Requirements**: SHELL-01, SHELL-02, SHELL-03, SHELL-04, SHELL-05, SHELL-06, NAV-01, NAV-02, NAV-03, DRAWER-01, DRAWER-02, DRAWER-03, DRAWER-04, DRAWER-05, DRAWER-06
+**Success Criteria** (what must be TRUE):
+ 1. On any `/mobile/*` route the user sees a sticky header with the Wulf wordmark linking to `/mobile/dashboard`, a Bell icon button (keyboard-focusable, no menu), and a compact avatar — no page title in the header
+ 2. A fixed bottom bar exposes four primary tabs (Dashboard, Tickets, Finance, Analyzer) plus a More cell; tapping a tab routes to its page and the active tab uses `text-primary` based on `pathname.startsWith(href)`
+ 3. Tapping More (or the header avatar) opens a single Sheet drawer with three sections — Mobile sections (Engagement), Full site (Quotes, Configuration Items, Backup Status, Ticket Digest, Admin/Sync — each with an `ExternalLink` hint), and Account (current user read-only + Sign out)
+ 4. Tapping Sign out in the drawer signs the user out and lands them on `/auth/sign-in`
+ 5. `app/mobile/nav/page.tsx` no longer exists; visiting `/mobile/nav` does not render the old standalone nav page
+ 6. Page content scrolls under the sticky header and is not hidden behind the bottom nav (bottom padding accounts for nav height + safe-area inset)
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 3: Dashboard Restyle
+**Goal**: A manager opening `/mobile/dashboard` sees the state of the business at a glance — four KPIs, items needing attention, and a worker/backup status row — with no charts.
+**Depends on**: Phase 2
+**Requirements**: DASH-01, DASH-02, DASH-03, DASH-04
+**Success Criteria** (what must be TRUE):
+ 1. Dashboard renders a 2×2 grid of four primary KPI cards drawn from desktop hero stats (no 1×4 row, no charts)
+ 2. Below the grid, a "Needs Attention" horizontally-scrollable strip surfaces overdue tickets, failed backups, and stalled workflows; tapping a card opens its detail view
+ 3. A compact status row shows analyzer worker, RMM worker, and backup-success-rate; tapping any element opens the corresponding desktop admin page
+ 4. The page contains no recharts/chart components on phone widths
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 4: Tickets Restyle
+**Goal**: A manager triages tickets on a phone with a collapsible filter bar that deep-links via URL, priority-coloured rows, and infinite scroll — and the detail page header matches the new shell.
+**Depends on**: Phase 2
+**Requirements**: TICK-01, TICK-02, TICK-03, TICK-04, TICK-05, TICK-06, TICK-07
+**Success Criteria** (what must be TRUE):
+ 1. The Tickets page opens with the filter strip collapsed; expanding it reveals status, priority, queue, and an assigned-to-me toggle, and changing any filter updates the URL query string (deep link works on reload)
+ 2. Each list row has a left-edge stripe matching priority (Critical/High/Medium/Low → red/orange/amber/slate) and shows ticket #, title, company, age, and assignee
+ 3. Single-tapping a row navigates to `/mobile/tickets/[id]`
+ 4. Scrolling to the bottom of the list automatically loads the next ~25 rows (no Next button); a "Load more" fallback button is also visible/focusable for accessibility
+ 5. The detail page header uses the new shell styling (Wulf mark, breadcrumb back) while the body remains largely unchanged
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 5: Finance Restyle
+**Goal**: A manager reading AR / invoice / payment status on a phone sees properly spaced cards and stacked lists instead of squished wide tables — same data, new shell.
+**Depends on**: Phase 2
+**Requirements**: FIN-01, FIN-02
+**Success Criteria** (what must be TRUE):
+ 1. `/mobile/finance` adopts the new Card and typography scale — no horizontal overflow, spacing legible on small phones
+ 2. Sections that previously rendered wide tables on phone widths now render as stacked lists (no new sections, no new data sources)
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 6: Analyzer Feed (NEW)
+**Goal**: A manager taps the Analyzer tab and skims a most-recent-first stream of AI ticket analyses, opening any one to a phone-friendly summary view that links out to desktop for full details.
+**Depends on**: Phase 2
+**Requirements**: ANL-01, ANL-02, ANL-03, ANL-04, ANL-05, ANL-06
+**Success Criteria** (what must be TRUE):
+ 1. Tapping the Analyzer tab in the bottom nav lands on `/mobile/analyzer` and shows a most-recent-first list of AI ticket analyses
+ 2. Each row shows ticket #, title, the analyzer's one-line summary, a confidence badge, and a stage indicator (Triage → Analyze → Deep Review)
+ 3. Tapping a row opens a mobile summary view rendering Summary, Next Step, and Next Step Rationale, with a "View full analysis" link out to the desktop analyzer page
+ 4. The mobile feed never exposes editing, re-run, or prompt-tuning controls (read-only by design)
+ 5. The list reads from `analyzer_analyses` via `/api/mobile/analyzer/feed` (or a reused list endpoint that already returns the right shape)
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 7: Engagement Overview (NEW)
+**Goal**: A manager reaches Engagement from the More drawer and sees a phone-first overview — period chips, stacked summary cards, a sortable per-employee list, and one compact sparkline.
+**Depends on**: Phase 2
+**Requirements**: ENG-01, ENG-02, ENG-03, ENG-04, ENG-05, ENG-09
+**Success Criteria** (what must be TRUE):
+ 1. The Mobile sections row in the More drawer links to `/mobile/engagement`; the Analyzer is on the bottom bar but Engagement is not
+ 2. The overview page shows a period selector (today / 7d / 30d) sticky just below the H1, with active period clearly indicated
+ 3. Summary cards (active users, total Graph hours, total Autotask hours, hours-per-active-user) render single-column stacked — no 4-up grid on phone widths
+ 4. The per-employee list renders as stacked rows (avatar/initials, name, role, hours bar) with a search input and a sort control above (sort by hours, name, utilization)
+ 5. A single compact "hours trend" sparkline renders at the top of the list, scoped to the selected period — no multi-series chart
+**Plans**: TBD
+**UI hint**: yes
+
+### Phase 8: Engagement User Profile (NEW)
+**Goal**: From the Engagement overview, a manager taps an employee row and arrives at a real, shareable profile page — single-column phone-first — and the device back gesture returns them to the overview.
+**Depends on**: Phase 7
+**Requirements**: ENG-06, ENG-07, ENG-08
+**Success Criteria** (what must be TRUE):
+ 1. Tapping a row in the per-employee list navigates to `/mobile/engagement/[userId]` (segment form, shareable URL)
+ 2. The profile is a real page (not a modal) — the device/browser back gesture returns to the overview at the same scroll position
+ 3. The profile renders single-column: identity header → period selector → key metrics (compact) → activity breakdown list → recent items, sourced from the existing engagement profile data endpoints (no new data)
+**Plans**: TBD
+**UI hint**: yes
+
+## Progress
+
+**Execution Order:**
+Phases execute in numeric order. Phase 2 unblocks Phases 3–7 (any order, parallelizable). Phase 8 follows Phase 7.
+
+| Phase | Plans Complete | Status | Completed |
+|-------|----------------|--------|-----------|
+| 1. PWA Scaffolding | 1/2 | Executing | - |
+| 2. Mobile Shell + More Drawer | 0/TBD | Not started | - |
+| 3. Dashboard Restyle | 0/TBD | Not started | - |
+| 4. Tickets Restyle | 0/TBD | Not started | - |
+| 5. Finance Restyle | 0/TBD | Not started | - |
+| 6. Analyzer Feed | 0/TBD | Not started | - |
+| 7. Engagement Overview | 0/TBD | Not started | - |
+| 8. Engagement User Profile | 0/TBD | Not started | - |
+
+---
+*Roadmap created: 2026-05-03*
+*Source spec: `docs/superpowers/specs/2026-05-03-mobile-shell-design.md`*
diff --git a/.planning/STATE.md b/.planning/STATE.md
index f4a7146..ef53676 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -1,3 +1,19 @@
+---
+gsd_state_version: 1.0
+milestone: v1.0
+milestone_name: milestone
+status: executing
+stopped_at: Roadmap created — ready to plan Phase 1
+last_updated: "2026-05-03T18:05:22.540Z"
+last_activity: 2026-05-03
+progress:
+ total_phases: 8
+ completed_phases: 1
+ total_plans: 2
+ completed_plans: 2
+ percent: 100
+---
+
# Project State
## Project Reference
@@ -5,21 +21,22 @@
See: .planning/PROJECT.md (updated 2026-05-03)
**Core value:** A manager can open Pulse on their phone and, in under 30 seconds, see the state of the business and triage tickets — without ever needing to switch to desktop for read-only awareness.
-**Current focus:** Phase 1 — PWA Scaffolding
+**Current focus:** Phase 01 — pwa-scaffolding
## Current Position
-Phase: 1 of 8 (PWA Scaffolding)
-Plan: 0 of TBD in current phase
-Status: Ready to plan
-Last activity: 2026-05-03 — Roadmap created (8 phases, 47/47 v1 requirements mapped)
+Phase: 2
+Plan: Not started
+Status: Ready to execute
+Last activity: 2026-05-03
Progress: [░░░░░░░░░░] 0%
## Performance Metrics
**Velocity:**
-- Total plans completed: 0
+
+- Total plans completed: 2
- Average duration: —
- Total execution time: 0.0 hours
@@ -27,9 +44,10 @@ Progress: [░░░░░░░░░░] 0%
| Phase | Plans | Total | Avg/Plan |
|-------|-------|-------|----------|
-| - | - | - | - |
+| 01 | 2 | - | - |
**Recent Trend:**
+
- Last 5 plans: —
- Trend: —
diff --git a/.planning/phases/01-pwa-scaffolding/01-VERIFICATION.md b/.planning/phases/01-pwa-scaffolding/01-VERIFICATION.md
new file mode 100644
index 0000000..00cf48e
--- /dev/null
+++ b/.planning/phases/01-pwa-scaffolding/01-VERIFICATION.md
@@ -0,0 +1,152 @@
+---
+phase: 01-pwa-scaffolding
+verified: 2026-05-03T18:05:00Z
+status: human_needed
+score: 4/4 must-haves verified
+re_verification:
+ previous_status: gaps_found
+ previous_score: 3/4
+ gaps_closed:
+ - "A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into env(safe-area-inset-top) / env(safe-area-inset-bottom) padding"
+ gaps_remaining: []
+ regressions: []
+human_verification:
+ - test: "Install Pulse on a real iPhone and confirm Add-to-Home-Screen launches a chromeless app at /mobile"
+ expected: "Tapping the installed icon opens Pulse with no Safari chrome (no URL bar, no tabs); landing route is /mobile; status bar tints match theme_color #0075AD on iOS, background flashes #FFFFFF then renders the mobile shell"
+ why_human: "Real PWA install behavior (chromeless launch, system UI tint, splash background) cannot be verified programmatically — Chrome DevTools 'Add to Home Screen' simulation is approximate but only a real device exercises Safari's manifest pickup, status bar colorization, and splash rendering. Required by ROADMAP Phase 1 SC #4."
+ - test: "Install Pulse on a real Android device (Chrome) and confirm the install banner appears, the icon shows on the home screen, and tapping it lands on /mobile in standalone mode"
+ expected: "Chrome shows an install prompt (or 'Add to Home Screen' from menu); the installed icon uses one of the configured PNGs; launching opens a standalone window with no browser chrome; URL bar is hidden; navigating beyond /mobile (scope='/') stays in-app"
+ why_human: "Same as above — requires a real device (Chromium PWA install heuristics depend on visit count, manifest validation, and platform). Required by ROADMAP Phase 1 SC #4 and PWA-01 wording."
+ - test: "Confirm `viewport-fit=cover`, ``, and the new `.pt-safe` / `.pb-safe` rules render in a real browser when the dev server is running"
+ expected: "View source on http://localhost:3100/ and confirm `` is present, plus ``. Inspect the live CSS bundle and confirm `.pt-safe { padding-top: env(safe-area-inset-top) }` and `.pb-safe { padding-bottom: env(safe-area-inset-bottom) }` are emitted (already confirmed in the .next build artifact during this re-verification — repeat against the live dev server)."
+ why_human: "Dev server was not running during this verification pass; the static checks (file contents, type-check, production build artifact inspection) prove the metadata API and Tailwind 4 @utility blocks compile correctly, but a smoke test against the running app proves the runtime serializes as expected. Optional/routine — can be done by anyone with `npm run dev` access."
+---
+
+# Phase 01: PWA Scaffolding Verification Report (Re-verification)
+
+**Phase Goal:** A manager who taps "Add to Home Screen" gets a standalone Pulse icon that opens to the mobile shell with content respecting the device safe areas.
+**Verified:** 2026-05-03T18:05:00Z (re-verification after PWA-04 gap closure)
+**Status:** human_needed (all programmatic checks pass; awaiting real-device install verification)
+**Re-verification:** Yes — after gap closure (plan 01-02 closed PWA-04 by adding `pt-safe` / `pb-safe` `@utility` blocks to `app/styles/brand.css`)
+
+## Re-verification Summary
+
+| Item | Previous (initial verification) | Current (after 01-02) |
+|------|-------------------------------|----------------------|
+| Status | `gaps_found` | `human_needed` |
+| Score | 3/4 | **4/4** |
+| Truth #3 (safe-area utility) | ✗ FAILED — no utility, no arbitrary-value usage anywhere in repo | ✓ VERIFIED — `@utility pt-safe` and `@utility pb-safe` defined in `app/styles/brand.css`, compiled into production CSS bundle |
+| PWA-04 traceability | ORPHANED — no plan claimed it | SATISFIED — `01-02-PLAN.md` declares `requirements: [PWA-04]`; `01-02-SUMMARY.md` lists it under "Requirements Satisfied" |
+| Regressions introduced by 01-02 | n/a | None — Truths 1, 2, 4 unchanged; manifest, layout, icons all intact |
+
+The only outstanding item is the human verification block (real-device install tests + optional live dev-server smoke test). These are not gaps — they are the same items that were routed to humans in the initial verification, plus a small addition asking the human to confirm the new `.pt-safe` / `.pb-safe` rules render at runtime (already confirmed in the production CSS bundle artifact, but a live dev-server check costs nothing).
+
+## Goal Achievement
+
+The phase now delivers the goal in full at the artifact level:
+- **Manifest** — correct fields, correct icons, correct start_url
+- **Viewport** — `viewport-fit=cover` exported via Next.js 16 Viewport API
+- **Safe-area utility** — `pt-safe` / `pb-safe` `@utility` blocks in `brand.css`, compiled to `.pt-safe { padding-top: env(safe-area-inset-top) }` / `.pb-safe { padding-bottom: env(safe-area-inset-bottom) }` in the production CSS bundle, ready for Phase 2's sticky header (SHELL-05) and fixed bottom nav (SHELL-06) to consume
+
+The remaining "human_needed" status is purely about real-device install behavior (which cannot be programmatically verified) — not about missing code.
+
+### Observable Truths
+
+| # | Truth | Status | Evidence |
+| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 1 | Visiting `/manifest.json` returns valid JSON with `name: "Pulse"`, `display: "standalone"`, `start_url: "/mobile"`, and theme/background colors matching the app shells | ✓ VERIFIED | `public/manifest.json` exists; jq validation passes (`name=Pulse`, `short_name=Pulse`, `display=standalone`, `start_url=/mobile`, `theme_color=#0075AD`, `background_color=#FFFFFF`); 3 icons present and the referenced PNG files all exist on disk |
+| 2 | The root `app/layout.tsx` references the manifest via `` and the viewport meta includes `viewport-fit=cover` | ✓ VERIFIED | `app/layout.tsx:31` `manifest: "/manifest.json"` in metadata (Next 16 emits the link tag); `app/layout.tsx:42-50` exports `viewport: Viewport` with `viewportFit: "cover"`, `width: device-width`, `initialScale: 1`, paired light/dark themeColor; `tsc --noEmit` exits 0 |
+| 3 | A safe-area utility (Tailwind arbitrary values or shared class) is available so any sticky top/bottom bar can opt into `env(safe-area-inset-top)` / `env(safe-area-inset-bottom)` padding | ✓ VERIFIED (was ✗ FAILED) | `app/styles/brand.css:157-163` defines `@utility pt-safe { padding-top: env(safe-area-inset-top); }` and `@utility pb-safe { padding-bottom: env(safe-area-inset-bottom); }`; brand.css is imported by `app/globals.css:125` (unchanged); production CSS bundle (`.next/static/chunks/3c3ee60b60fe53db.css`) contains the compiled rules `.pt-safe{padding-top:env(safe-area-inset-top)}` and `.pb-safe{padding-bottom:env(safe-area-inset-bottom)}`; `npm run build` exits 0 |
+| 4 | Installing Pulse to a phone home screen launches a chromeless app pointed at `/mobile` (no service worker, no offline) | ? UNCERTAIN | Manifest fields are correct for this outcome (`display=standalone`, `start_url=/mobile`, no `serviceworker` field, no `next-pwa` dep, no `public/sw.js`). Real install behavior must be verified on a physical device — see Human Verification section. (Unchanged from initial verification.) |
+
+**Score:** **4/4** truths verified (1 routed to human verification for real-device confirmation)
+
+### Required Artifacts
+
+| Artifact | Expected | Status | Details |
+| ------------------- | ------------------------------------------------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `public/manifest.json` | Web App Manifest with required fields and ≥1 icon | ✓ VERIFIED | Exists (31 lines), valid JSON, all required fields present with spec-mandated values, 3 icons referencing real assets in `/public` |
+| `app/layout.tsx` | Root layout exporting metadata.manifest and viewport with viewportFit:"cover" | ✓ VERIFIED | Both exports present; `Metadata` and `Viewport` named imports; RootLayout body unchanged; type check passes |
+| `app/styles/brand.css` (NEW for re-verification) | Two new `@utility` blocks (`pt-safe`, `pb-safe`) sitting alongside existing utilities | ✓ VERIFIED | Lines 157-163: both `@utility` blocks present with correct `env(safe-area-inset-top/bottom)` declarations; placed between existing `@utility tagline` (line 132) and `/* === Wolf-mark watermark === */` section header (line 165); existing utilities (`num`, `metric-label`, `surface-brand`, `tagline`, `has-mark-watermark`, `.mark-watermark`) and brand tokens (`--wulf-blue`, etc.) all unchanged |
+
+### Key Link Verification
+
+| From | To | Via | Status | Details |
+| ------------------------------------- | ------------------------------- | ---------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
+| `app/layout.tsx` | `public/manifest.json` | `metadata.manifest = "/manifest.json"` | ✓ WIRED | Line 31 sets the field; Next.js 16's metadata API emits `` automatically |
+| `public/manifest.json` | `/mobile` | `start_url` field | ✓ WIRED | Line 5: `"start_url": "/mobile"` (exact spec match) |
+| `app/layout.tsx` (viewport export) | rendered `` | Next.js viewport export → viewport-fit=cover | ✓ WIRED | Line 45: `viewportFit: "cover"`; Next 16 documented to serialize this as `viewport-fit=cover` in the rendered meta tag |
+| `app/styles/brand.css` (`@utility pt-safe`) (NEW) | rendered CSS class `.pt-safe` | Tailwind 4 `@utility` block compilation | ✓ WIRED | Line 157 defines the block; Tailwind 4 build emits `.pt-safe{padding-top:env(safe-area-inset-top)}` into `.next/static/chunks/3c3ee60b60fe53db.css` |
+| `app/styles/brand.css` (`@utility pb-safe`) (NEW) | rendered CSS class `.pb-safe` | Tailwind 4 `@utility` block compilation | ✓ WIRED | Line 161 defines the block; Tailwind 4 build emits `.pb-safe{padding-bottom:env(safe-area-inset-bottom)}` into the same CSS chunk |
+| `app/globals.css` (NEW link traced) | `app/styles/brand.css` | `@import "./styles/brand.css"` on line 125 | ✓ WIRED | Pre-existing import — no change required; pulls the new utilities into the global stylesheet automatically |
+
+### Data-Flow Trace (Level 4)
+
+N/A — this phase produces only static metadata (manifest JSON + Next.js metadata/viewport exports + Tailwind 4 `@utility` blocks). No dynamic data flow to trace. The CSS-bundle inspection in Truth #3 acts as the equivalent "did the artifact actually flow through compilation" check for static styles.
+
+### Behavioral Spot-Checks
+
+| Behavior | Command | Result | Status |
+| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------- | ------ |
+| Manifest is valid JSON with all spec fields | `jq -e '.name=="Pulse" and .display=="standalone" and .start_url=="/mobile" and .theme_color=="#0075AD" and .background_color=="#FFFFFF" and (.icons\|length)>=1' public/manifest.json` | `true` | ✓ PASS |
+| Manifest contains no service-worker field | `jq -e '.serviceworker == null' public/manifest.json` | `true` | ✓ PASS |
+| Layout references manifest | `grep 'manifest: "/manifest.json"' app/layout.tsx` | match | ✓ PASS |
+| Layout exports `viewportFit: "cover"` | `grep 'viewportFit: "cover"' app/layout.tsx` | match | ✓ PASS |
+| `Metadata, Viewport` both imported from `next` | `grep 'import type { Metadata, Viewport } from "next"' app/layout.tsx` | match | ✓ PASS |
+| **`pt-safe` `@utility` block exists** (NEW) | `grep -E '@utility pt-safe' app/styles/brand.css` | match (line 157) | ✓ PASS |
+| **`pb-safe` `@utility` block exists** (NEW) | `grep -E '@utility pb-safe' app/styles/brand.css` | match (line 161) | ✓ PASS |
+| **`safe-area-inset-top` declaration correct** (NEW) | `grep -E 'padding-top:\s*env\(safe-area-inset-top\)' app/styles/brand.css` | match (line 158) | ✓ PASS |
+| **`safe-area-inset-bottom` declaration correct** (NEW) | `grep -E 'padding-bottom:\s*env\(safe-area-inset-bottom\)' app/styles/brand.css` | match (line 162) | ✓ PASS |
+| **`brand.css` is still imported by `globals.css`** (NEW) | `grep -E '@import "\./styles/brand\.css"' app/globals.css` | match (line 125) | ✓ PASS |
+| **Out-of-scope `pl-safe` / `pr-safe` NOT added** (NEW) | `grep -E '@utility (pl-safe\|pr-safe)' app/styles/brand.css` | no match (exit 1) | ✓ PASS |
+| **Production build succeeds with new utilities** (NEW — load-bearing) | `npm run build` | exit 0, "Compiled successfully" | ✓ PASS |
+| **Compiled `.pt-safe` rule in production CSS bundle** (NEW) | `grep -oE '\.pt-safe[^,{]*\{[^}]*\}' .next/static/chunks/3c3ee60b60fe53db.css` | `.pt-safe{padding-top:env(safe-area-inset-top)}` | ✓ PASS |
+| **Compiled `.pb-safe` rule in production CSS bundle** (NEW) | `grep -oE '\.pb-safe[^,{]*\{[^}]*\}' .next/static/chunks/3c3ee60b60fe53db.css` | `.pb-safe{padding-bottom:env(safe-area-inset-bottom)}` | ✓ PASS |
+| TypeScript type check passes | `npx tsc --noEmit --pretty` | exit 0 | ✓ PASS |
+| No service worker file shipped | `test ! -f public/sw.js && test ! -f public/service-worker.js` | exit 0 | ✓ PASS |
+| `next-pwa` not added as dependency | `! grep '"next-pwa"' package.json` | not found | ✓ PASS |
+| **No `tailwind.config.*` created** (NEW) | `test ! -f tailwind.config.{ts,js,mjs}` | exit 0 | ✓ PASS |
+| All referenced icon PNGs exist on disk | `test -f public/wulff-logo.png && test -f public/favicon.png && test -f public/branding/wulf-mark.png` | exit 0 | ✓ PASS |
+| Plan commits exist in git history | `git log --oneline \| grep -E '3e3df24\|d196d22\|dff0264'` | all three found | ✓ PASS |
+| Live manifest fetch (dev server) | `curl -sf http://localhost:3100/manifest.json` | dev server not up | ? SKIP |
+| Live viewport meta tag rendering | `curl -s http://localhost:3100/ \| grep viewport-fit=cover` | dev server not up | ? SKIP |
+
+### Requirements Coverage
+
+| Requirement | Source Plan | Description | Status | Evidence |
+| ----------- | ----------- | -------------------------------------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------- |
+| PWA-01 | 01-01 | manifest.json exists with name "Pulse", short_name "Pulse", display "standalone", start_url "/mobile", theme/background colors | ✓ SATISFIED | `public/manifest.json` lines 2-10; jq validation passes |
+| PWA-02 | 01-01 | Manifest referenced from `app/layout.tsx` via `` | ✓ SATISFIED | `app/layout.tsx:31` `manifest: "/manifest.json"` (Next 16 metadata API emits the link tag) |
+| PWA-03 | 01-01 | Viewport meta in `app/layout.tsx` includes `viewport-fit=cover` | ✓ SATISFIED | `app/layout.tsx:42-50` exports `viewport: Viewport` with `viewportFit: "cover"` |
+| PWA-04 | **01-02** (gap closure) | Header and bottom tab bar respect `env(safe-area-inset-top/bottom)` (Tailwind arbitrary values or shared utility class) | ✓ SATISFIED (was ✗ BLOCKED / ORPHANED) | `01-02-PLAN.md` declares `requirements: [PWA-04]` and `01-02-SUMMARY.md` lists it under "Requirements Satisfied". `app/styles/brand.css` defines `@utility pt-safe` (line 157) and `@utility pb-safe` (line 161), compiled into production CSS bundle as `.pt-safe{padding-top:env(safe-area-inset-top)}` and `.pb-safe{padding-bottom:env(safe-area-inset-bottom)}`. Available for Phase 2's sticky header (SHELL-05) and fixed bottom nav (SHELL-06) to consume. ROADMAP Phase 1 SC #3 also satisfied. |
+
+**Orphaned-requirement state CLOSED.** Every requirement ID assigned to Phase 1 in REQUIREMENTS.md (PWA-01, PWA-02, PWA-03, PWA-04) is now claimed by a plan in this phase and verified against the codebase.
+
+### Anti-Patterns Found
+
+| File | Line | Pattern | Severity | Impact |
+| --------------------- | ---- | ------------------------------------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+
+No anti-patterns found in the gap-closure scope. The previous warnings on `01-01-PLAN.md` line 281 and `01-01-SUMMARY.md` lines 119-121 (which flagged the silent deferral of PWA-04 to Phase 2) are resolved by the explicit `01-02-PLAN.md` + `01-02-SUMMARY.md` trail that claims and closes PWA-04 in Phase 1.
+
+The artifacts shipped by 01-02 are clean — no TODOs, no stubs, no hardcoded empty data, no out-of-scope additions (`pl-safe` / `pr-safe`), no `tailwind.config.*` introduced, no service worker, no new dependencies.
+
+### Human Verification Required
+
+See frontmatter `human_verification` section. Three items, two strictly required for ROADMAP Phase 1 SC #4 (real iPhone install + real Android install) and one optional smoke test (live dev server view-source + CSS bundle inspection). All three are unchanged from the initial verification — they document real-device install behavior that no static check can confirm. The new `.pt-safe` / `.pb-safe` runtime rendering has been confirmed in the production CSS bundle artifact during this re-verification; the live dev-server check is a small extension of the existing item, not a new gate.
+
+### Gaps Summary
+
+**No gaps remaining.** The single gap from the initial verification (PWA-04 / Truth #3 — safe-area utility availability) was closed by `01-02-PLAN.md`, executed in commits `dff0264` (utility blocks) and `a293a4f` (summary). All four observable truths now verify; all four Phase 1 requirements (PWA-01..04) trace to claiming plans and verified artifacts; production build and type check both exit 0; the compiled CSS bundle contains the new rules.
+
+The phase status moves from `gaps_found` to `human_needed` because real-device install verification (ROADMAP SC #4) is the only outstanding item — and that was always going to require human testing.
+
+### Phase Boundary Restored
+
+Phase 2's contract (SHELL-05, SHELL-06) only mandates **consumption** of safe-area padding via the available utility — Phase 2 can now write `` and `