# 08 — Implementation Plan (Phases 2–10) Each phase ends with: run tests, run lint, build, fix all errors, verify `docker compose up -d` still works end-to-end for whatever exists so far. No phase starts until the previous one is green. ## Phase 2 — Bootstrap project & Docker Compose - npm/pnpm workspace scaffold (`apps/server`, `apps/web`, `packages/shared`) - Fastify skeleton with the two listeners (8080/8081), `/health`, `/ready` - Vite + React + Tailwind skeleton, dark theme tokens, empty shell page - Drizzle configured against Postgres; `docker-compose.yml` (dev) brings up `notizarr` (hot-reload) + `notizarr-postgres` - `Dockerfile` (multi-stage: build web → build server → copy web dist into server's static dir → slim runtime image), non-root user, healthcheck script - **Exit criteria:** `docker compose up -d` succeeds, `notizarr-postgres` is healthy, `GET :8080/health` returns 200, empty React shell loads. ## Phase 3 — Database + backend API skeleton - All tables from `03-database-model.md` as Drizzle schema + migrations - CRUD-level modules for `topics`, `notifications` (insert, list, get) with no auth yet (stubbed), so the data layer can be exercised - OpenAPI schema generation wired up, `/docs` serving it - Unit tests for schema constraints (unique slugs, FK cascades, retention math) - **Exit criteria:** migrations run clean on a fresh DB; can insert/list a topic and a notification via the API without auth. ## Phase 4 — Authentication + API tokens - OIDC module (`openid-client`): login/callback/logout, PKCE, state/nonce validation, session cookie issuance, bootstrap-first-admin logic - Proxy-auth module: trusted-CIDR check, header parsing, session creation - `AUTH_MODE=disabled` (local dev convenience: single implicit admin user) - API token module: create/list/revoke/rotate, hashing, scoping (topics/source/CIDR/expiry), last-used tracking - CSRF protection (Origin check + custom header) on listener A mutations - Tests: each auth mode, token scope enforcement, revoked/expired token rejection, CSRF rejection on cross-origin mutation attempts - **Exit criteria:** MVP criteria #5–7 (auth works, admin can create a topic, admin can create a scoped token) pass manually and via tests. ## Phase 5 — Notification ingestion + history - Listener B: canonical publish endpoint, `/api/v1/topics/:slug` alias, ntfy-compat endpoint, webhook endpoint - Rate limiter (per-token, per-IP), `INGESTION_ALLOWED_CIDRS` enforcement - Deduplication logic, occurrence_count increment - SSE stream on listener A (`notification.created`/`updated` events) - Read/unread + starred state, unread-count aggregation per topic - Retention cleanup interval job - Tests: ingestion auth/scoping, rate limiting (burst + sustained), dedup window behavior, SSE fan-out, retention deletion - **Exit criteria:** MVP criteria #8–10 (LAN client can publish, appears instantly via SSE, persisted) pass. ## Phase 6 — Frontend UI - Navigation shells: mobile bottom nav (Inbox/Topics/Search/Starred/ Settings), desktop sidebar + stream + optional detail panel - Topic list (unread/total/latest, bell state), topic settings screen (push ON/OFF toggle) - Notification list (cards per spec), notification detail (`/topics/:slug/messages/:id`), search + filters - Empty states, loading skeletons, dark-first theme, responsive breakpoints - Manual + automated visual smoke checks in the Browser tool (mobile and desktop viewport) - **Exit criteria:** MVP criteria #22 (polished on iPhone and desktop) — judged by hand in the browser tool at this stage, refined further in Phase 7/8. ## Phase 7 — PWA + Web Push - `vite-plugin-pwa` with `injectManifest`, manifest.webmanifest, icon set (incl. maskable + apple-touch-icon), safe-area CSS - Service worker: precache, runtime caching, `push`/`notificationclick` handlers - VAPID generation/storage in `system_settings`, `web-push` send integration wired into the Phase 5 fan-out step (previously a no-op stub) - Subscribe/unsubscribe flow, devices screen, diagnostics screen - Manual iPhone test pass (install to Home Screen, enable notifications, trigger a real push, confirm deep link opens the right message) - **Exit criteria:** MVP criteria #11–18 (PWA installable, push permission, subscription stored, push ON sends / OFF doesn't, arrives on iPhone, tapping opens exact notification) pass. ## Phase 8 — Admin UI + rate limiting polish + diagnostics - Admin dashboard widgets (`04-api-design.md`'s `/admin/stats/dashboard`) - Noisy-source detection thresholds + admin warnings - Tokens/Users/Devices/Push/Security/System admin screens - Audit log viewer - Settings screens (user + admin) per `01-architecture.md` - **Exit criteria:** full admin IA from the spec is navigable and functional; rate-limit rejections are visible end-to-end (trigger one, see it flagged). ## Phase 9 — Migration helpers + ntfy compatibility hardening - `docs/migration-ntfy.md`: side-by-side ntfy → Notizarr endpoint mapping - `docs/home-assistant.md`: `rest_command` example replacing `ntfy.publish` (as given in the spec), plus one example per currently-used service (Seerr, Uptime Kuma, generic webhook) - Compatibility test matrix: header-based, JSON-based, query-param-based ntfy-style requests all produce equivalent stored notifications - **Exit criteria:** MVP criteria #8 re-verified using an ntfy-style request shape, not just the canonical API. ## Phase 10 — Testing + hardening + documentation - Fill any remaining gaps in the automated test list from the original spec (stale push subscription cleanup, device removal, authorization matrix across roles, retention cleanup edge cases) - Security pass: CSP, HSTS, frame-ancestors, request size limits, dependency audit - Top-level `README.md`: install, configure `.env`, deploy on Unraid, backup/restore, troubleshooting (linking the diagnostics screen) - Full MVP acceptance checklist run end-to-end, including container restart persistence checks (#19–21) - **Exit criteria:** all 22 MVP acceptance criteria pass. ## Cross-cutting, done incrementally rather than as a separate phase - Structured logging + secret redaction: introduced in Phase 2, extended as new modules land - Metrics: introduced alongside the ingestion endpoints in Phase 5 - Lint/typecheck/test/build as CI-equivalent local commands: set up in Phase 2, run at the end of every subsequent phase