docs(07): phase 7 planning complete — 2 plans (schema migration + admin crud)

Wave 1: 07-01-PLAN.md — Unified services table (audit trail, backfill, validation)
Wave 2: 07-02-PLAN.md — Admin catalog CRUD rewire (preserve historical references)

Ready for execution: /gsd-execute-phase 7

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 06:17:29 +02:00
parent bdcdd26f17
commit 23cb057f0b
65 changed files with 22681 additions and 0 deletions
@@ -0,0 +1,67 @@
---
plan: "04-04"
phase: "04-progetti-multi-project"
status: complete
completed_at: "2026-05-22"
---
# Summary: 04-04 — Slug Resolution + Multi-Project Dashboard + Slug Edit
## What Was Built
**`/api/internal/validate-slug/route.ts`** — Internal API route for Edge middleware. Queries `clients.slug`, returns 200 `{ valid: true }` if found, 404 otherwise. Same pattern as `validate-token`.
**`src/proxy.ts`** — Updated client guard with slug-first resolution (D-06): tries `/api/internal/validate-slug` first, then falls back to `/api/internal/validate-token`. Route path is `/client/[token]` (not `/c/` as referenced in the plan — adapted accordingly).
**`src/lib/client-view.ts`** — Complete rewrite for multi-project model. Exports:
- `getClientWithProjectsByToken(tokenOrSlug)` — resolves slug → client (slug first, then token fallback), returns `ClientProjectSummary` with active (non-archived) projects only
- `getProjectView(projectId)` — returns full `ProjectView` scoped to one project; **payments select excludes amount** (DASH-07 + CLAUDE.md); **no quote_items** anywhere in the file
**`src/app/client/[token]/page.tsx`** — Rewritten for multi-project logic:
- 0 active projects → placeholder screen
- 1 project → `ClientDashboard` directly (no selector), using `projectViewToClientView` adapter
- 2+ projects → shared header + shadcn `Tabs` (project names as triggers) + `ClientDashboard` per tab
**`src/app/admin/clients/[id]/edit/page.tsx`** — Added slug field with:
- Pattern hint: `[a-z0-9-]{3,50}`
- Link preview showing `/client/{slug || token}`
- Error banner when redirected back with `?error=slug_taken`
**`src/app/admin/clients/[id]/actions.ts`** — Updated `clientSchema` to include `slug` (Zod regex + `.transform(v => v === "" ? null : v)` to convert empty string to null). `updateClient` now:
1. Parses slug from form data
2. Persists to `clients.slug` via db.update
3. Catches unique constraint violation → redirects to edit page with `?error=slug_taken`
4. On success: redirects to `/admin/clients/[id]` (previously no redirect)
## Key Architectural Decisions
**Adapter pattern `projectViewToClientView`**: `ClientDashboard` was written for `ClientView` shape. Rather than modifying it, an adapter function in the page converts `ProjectView` + `ClientProjectSummary.client``ClientView`. Key mappings:
- `view.project.accepted_total``clientView.client.accepted_total` (project-level, not client-level)
- `view.project.client_id``clientView.client.id` (used by ChatSection for comment threading)
- `deliverables[].approved_at: Date | null``.toISOString() | null` (ClientView expects string)
- `documents[].created_at` stripped (ClientView.documents only has id/label/url)
**Multi-project tabs with full ClientDashboard**: Each tab content renders a complete `ClientDashboard` (including its header/footer). shadcn Tabs shows only the active one via CSS. This is MVP-acceptable: the outer page provides the tab navigation, and within each tab the full dashboard renders.
**Slug empty string → null**: Zod schema transforms `""` to `null` so saving with empty slug removes it. This lets admins clear a slug without special UI.
**Security invariants verified** (grep confirmed):
- `grep "quote_items" src/lib/client-view.ts` → only in comments, not in queries
- `grep "amount" src/lib/client-view.ts` → only in comments documenting the exclusion
## Files Changed
| File | Action |
|------|--------|
| `src/app/api/internal/validate-slug/route.ts` | Created |
| `src/proxy.ts` | Updated — slug-first resolution in client guard |
| `src/lib/client-view.ts` | Complete rewrite — getClientWithProjectsByToken + getProjectView |
| `src/app/client/[token]/page.tsx` | Complete rewrite — multi-project dashboard |
| `src/app/admin/clients/[id]/edit/page.tsx` | Updated — slug field + error banner |
| `src/app/admin/clients/[id]/actions.ts` | Updated — clientSchema + slug in updateClient |
## Notes
- The plan referenced `/c/[token]` path but actual route has always been `/client/[token]`. Proxy and all references use `/client/` consistently.
- `ClientDashboard` component unchanged — the adapter absorbs all type differences.
- `getClientWithProjectsByToken` filters out archived projects; archived projects are invisible to the client dashboard.