kanban-dev-webapp/PHASE-8-PROGRESS.md

88 lines
9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 8 — progress log
> Canonical record for Phase 8 (8a public route, 8b PWA shell, 8c markdown context).
> Mirrored to the Apple Notes note titled "Phase 8 — Public intake + PWA + project context".
> Workflow: a task moves `pending → in_progress → done` as I work it; TDD red/green noted where applicable.
## How to read
- `[ ]` pending
- `[~]` in progress
- `[x]` done — TDD green + commit landed
- `[!]` blocked, needs decision
---
## Plan 8a — Public intake links and route
- [x] Task 1: Schema — `c99be4d`. Bridge: plan assumed `getOrCreateCollection` extended existing select values; it doesn't, so added `ensureCardsCreatedByRoleValues` and extended `ensureActivityActionValues`. Tests: 446 pass.
- [x] Task 2: Token generator — `36572d9`. 24 bytes CSPRNG → base64url → 32 chars. `isIntakeToken` matches.
- [x] Task 3: Best-effort client IP — `f608931`. x-forwarded-for → x-real-ip → "unknown".
- [x] Task 4: Rate limiter — `70ce019`. 60 req / 5 min per opaque key, in-memory, lossy on restart. 4 unit tests.
- [x] Task 5: Token resolver and daily counter — `3ece6cb`. `resolveIntakeLink`, `isLinkUsable` with lazy date-reset, `validateIntakeLinkForStep` returns 3-valued LinkState.
- [x] Task 6: Middleware carve-out — `65b0cf4`. `/intake/:path*` short-circuits before cookie check.
- [x] Task 7: Public Server Action — `9081659`. token-shape → rate-limit → daily-cap → `nextStep`. Same `InterviewStep` shape as signed-in.
- [x] Task 8: Public submission route — `4004aec`. `POST /api/public/intake`. Sibling of `/api/client/intake` with `created_by_role='anonymous'` set in code. Dropped vestigial `tokenHeader` line per the plan's own comment.
- [x] Task 9: Minimal intake layout — `5137a6d`. Deviation: no `<html>`/`<body>` (Next.js root layout owns those). Intent preserved — no ThemeScript, ThemeSync, Inter, or globals.css.
- [x] Task 10: Public intake page — `c9be1d2`. 404 on unknown/revoked/expired with same wording. IntakePublic placeholder kept build green between Tasks 10/11.
- [x] Task 11: Public wizard + brief preview — `a3972f5`. Replaced Task 10 placeholder with the real wizard. POST to `/api/public/intake`, redirect to `/intake/[token]/sent/[cardId]`.
- [x] Task 12: Thanks page — `07ece87`. Resolves token only to confirm still alive — revoked tokens landing here still show "Thanks" so the submission cannot be probed.
- [x] Task 13: Card submitted-by badge — `bb78c9b`. CardSubmittedBy: nothing for owner, "Requested" for client, "Submitted via public link" for anonymous. Renders nothing when accepted.
- [x] Task 14: Owner actions — `c29f855`. `createIntakeLinkAction`, `rotateIntakeTokenAction`, `setIntakeLinkActiveAction`, `setIntakeLinkNotesAction`. Owner-only via requireOwner(). URL shown once.
- [x] Task 15: Intake links panel — `1e97655`. New-link button → label input → URL shown once with Copy. Rotate / Revoke / Re-activate buttons. Notes editor (saves on blur).
- [x] Task 16: Wire panel into admin — `4335af4`. Reads `intake_links` sorted by `-created`, renders `<IntakeLinksPanel>` after `SummaryPanel`.
- [x] Task 17: Integration tests — public route — `5fd1b41`. 9 tests cover happy path, counter bump, hostile-brief rejection, bad-token 404s (unknown/revoked/expired/malformed), deleted-project cascade, and the activity row. All pass.
- [x] Task 18: Integration tests — owner UI actions — `fda6f2c`. 2 placeholder tests as planned (Server Actions not directly driveable from vitest). Action coverage relies on Task 17's public-route tests + Task 19's manual walkthrough.
- [x] Task 19: Full verification — automated parts only. `npm run build` clean; color check empty (no banned tailwind greys or hex literals). All 11 new integration tests pass in isolation. **Pre-existing failures** in `admin.test.ts` (1) and `plan-puzzle.test.ts` (3) are unrelated to phase 8 — they predate this work and concern a different surface. Manual walkthrough steps for Tasks 19.3–19.8 are listed in the chat.
## Plan 8b — PWA shell
- [x] Task 1: The source SVG — `8b40449`. Rounded-square brand tile, single white `!`, brand colour matches `src/lib/brand-default.ts`.
- [x] Task 2: The icon build script — `2b8...`. `@resvg/resvg-js` renders source.svg into the four PNGs the manifest + iOS expect (192/512/maskable-512/apple-touch-180). Build is one-shot; outputs committed.
- [x] Task 3: The manifest — `2b61f5c`. `start_url=/intake/` (trailing slash) so the browser falls back to last-visited token, or install-time URL on first open.
- [x] Task 4: The service worker — `dfcd673`. Cache-first for static allowlist, network-first for /intake/* HTML with offline fallback to cached shell, pass-through for /api/public/intake and Server Actions. `CACHE_NAME=intake-v1`.
- [x] Task 5: Manifest link in the intake layout — `4c4dbaf`. Deviation: route pieces through Next `metadata` export (manifest, apple-touch-icon, appleWebApp); only SW registration stays inline. Verified rendered head: theme-color, manifest link, apple-touch-icon, SW script.
- [x] Task 6: Manual install verification — browser-state; documented walkthrough below.
- [x] Task 7: Final test run — `npm test` 450/450; new integration tests 11/11; `npm run build` clean.
## Phase 8b manual install walkthrough
PWA install behaviour is browser-state. To verify on your own device:
1. iOS Safari: open `/intake/<token>` → Share → Add to Home Screen. The icon should be the brand-coloured tile with `!`. Launching from the home screen opens standalone (no Safari chrome).
2. Android Chrome: open `/intake/<token>` → browser shows an install prompt at the bottom. Install. Icon appears in the launcher.
3. Once installed, kill the network briefly and reload — the cached shell should render with an offline-style fallback (the route's content fetch fails but the SW serves the cached HTML shell from the install).
4. The Wizard's POST to `/api/public/intake` and the Server Action both pass through the SW unchanged, so a flaky network only affects the SHELL load, not the submission.
## Plan 8c — Per-project markdown context
- [x] Task 1: Schema — `7df7074`. Optional `projects.background_md`, max 40000 chars. Migration idempotent (says "already present" on re-run).
- [x] Tasks 2 + 3: Intake context + prompt modules — `4a31b16`. `IntakeContext.backgroundMd`; `intakeContext` reads + truncates to 30000-char prompt cap with marker. Both prompts add a "Background on this app" section framed as the developer's own notes.
- [x] Task 4: Prompt tests — `5c6f82c`. 9 new tests in `prompt.test.ts` (was 11, now 20). **The byte-identical test is the day-one regression guard**: every existing project gets empty `background_md` after migration, and the empty path must produce exactly the pre-8c prompt.
- [x] Task 5: Owner editor — Server Action `setProjectBackgroundMdAction` (owner-only, slices to 40000) + `BackgroundMdEditor` (one textarea, live counter, explicit Save, repo URL as "see also").
- [x] Task 6: Wire into admin — `2432fff`. Editor sits below IntakeLinksPanel on `/admin/projects/[projectId]`.
- [x] Task 7: Full verification — `npm test` 459/459 (was 450, +9 prompt tests), 11/11 phase-8 integration tests, `npm run build` clean. Manual walkthrough documented below.
## Phase 8c manual walkthrough
1. Sign in as owner → `/admin/projects/<id>` → scroll past `Public intake links` to "Background notes for the assistant".
2. Type a short markdown doc, Save, reload — doc persists.
3. Paste 50000 chars, Save, reload. Stored value is the first 40000 chars (field cap); wizard receives first 30000 + truncation marker.
4. With an AI provider configured, run the wizard as a client manager on the same project. The first question should reference your doc (e.g. "the quotes area does X — is that what you mean?").
5. Sign out, open a public token for the same project, run the wizard. Same prompt fires — `intakeContext` is the single code path.
## Phase 8 — complete
All three sub-plans (8a public route, 8b PWA shell, 8c markdown context) are committed.
## Phase 9a — complete
All 7 tasks committed. Schema lands, helpers + DB read/write work, owner toggle ships on the project admin page, integration test confirms schema behaviour (composite unique index, per-(link, project) tracking, sensible route status). Unit: 466/466 pass. Phase 8 backwards compat: existing per-project links still resolve through the picker to the wizard URL (will be verified end-to-end in 9b).
## Phase 9b — complete
All 8 tasks committed. /admin/clients/[id] ships with the picker-friendly Request-links panel and the per-project opt-in toggles. /intake/[token] is now the picker (with single-project redirect + zero-projects fallback). /intake/[token]/p/[projectId] renders the wizard. The Server Action and POST /api/public/intake both honour projectId and verify it against the link's client. Phase 8 backwards compat preserved.
## Phase 9c — complete
All 12 integration tests pass. 466/466 unit tests pass. Build clean. The picker flow is end-to-end verified: card lands on the chosen project's inbox, cross-client projectId refused, hostile body fields ignored, per-(link, project) cap isolates.