Lint (eslint --max-warnings 0):
- index.ts: disable no-unsafe-argument on the type-only Context mismatch when
delegating to the OIDC handler inside the local-session skip wrapper
- localAuth.ts: handleLogout is sync (no await) — drop async (require-await)
- devBypass.ts: disable detect-possible-timing-attacks on the public well-known
dev-placeholder string compare (not a secret comparison)
- remove dead code / unused bindings flagged by no-unused-vars: makeTestApp
(localSession.test), makeUnauthContext + BrowserContext import (login.spec),
unused memberId (admin.test), unused txSelectCount counter (me.test)
- localAuthMiddleware.test / me.test: fix unused + reflow-detached
eslint-disable directives
Format: prettier --write across the 20 Phase-19 files that were never formatted.
Secret scan (gitleaks): allowlist two false positives — the synthetic >=32-char
TEST_SECRET in localSession.test.ts, and .planning/ design prose (a generic-api-key
regex hit on "credential atomically, 409-equivalent"). Neither is a real secret.
Verified locally: format:check, lint, typecheck, md:lint, gitleaks (no leaks),
PWA 266/266, API 452/452.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Lift AppNav from CalendarShell to App.tsx as a sibling of <Routes>
- App.tsx fetches /api/me (same query key as CalendarShell — deduplicated by TanStack Query)
- App.tsx provides the outer layout (phone: column, desktop: row) with AppNav always rendered
- CalendarShell simplified: no longer manages AppNav, outer flex layout stays in App.tsx
- AuthSplash gains overlay prop (position:fixed inset:0 z-index:999) so it covers AppNav when needed
- CalendarShell uses AuthSplash with overlay=true so auth splashes cover full viewport
- Remove onOpenSettings prop from CalendarShell (wired directly in App.tsx to SettingsSheet)
- Desktop sidebar nav (FamilySync brand, Calendar/Lists links) now persists on /lists route
- Create PermissionDeniedBanner.tsx: role=alert banner shown only when
permission=denied AND notificationsEnabled=1 (OS-revoked case, D-10)
with AlertCircle icon, 'Notifications blocked' heading, inline 'How to enable'
button that opens OS-specific instruction sheet (iOS 4-step / Android 4-step)
- Mount PermissionDeniedBanner and SettingsSheet in App.tsx; wire onOpenSettings
state from avatar tap through CalendarShell → AppNav → PhoneNav/DesktopNav
- CalendarShell accepts optional onOpenSettings prop, threads to both AppNav usages
- playwright-cli verified: banner renders with exact UI-SPEC copy when
permission=denied+was-enabled; banner absent when permission=granted;
'How to enable' opens instruction sheet with correct Android steps;
SettingsSheet opens from avatar click with toggle + permission-denied hint
- Mount /api/events, /api/sse in index.ts behind oidcAuthMiddleware; /callback + /health before guard
- Call startBrokerPoller() on boot (5-min ctag-poll background schedule)
- Add sseRouter with GET /heartbeat (streamSSE, 10s interval) for Pangolin SSE smoke test (D-08, T-04-01)
- Add CAL-08 spike script (broker/spike.ts): createFastmailClient → fetchCalendars → print calendar URLs
- Add fetchEvents() to pwa/api/client.ts with typed CalendarEvent/EventsResponse shapes
- Add EventProof.tsx: React Query ['events'], renders first event title+date or empty-state (CAL-01 broker proof)
- Update App.tsx to render MemberBadge + EventProof on landing page
- Add ical.js@2.2.1 to PWA dependencies for VEVENT summary parsing in EventProof
- All 24 API unit tests green; tsc --noEmit clean in both apps/api and apps/pwa