Files
clienthub/.planning/phases/02-admin-area-interactive-features/02-04-SUMMARY.md
T
Simone Cavalli cf1f67229a docs(02-04): complete client interactions plan summary
- 2 tasks completed: approve + comment API routes, and ApproveButton/CommentForm/CommentList client components
- 3 auto-fixed blocking issues documented (missing dep, missing .env.local, prop signature updates)
- all STRIDE threats T-02-15 through T-02-20 mitigated
- build passes cleanly
2026-05-15 21:52:12 +02:00

8.3 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
02-admin-area-interactive-features 04 client-interactions
api-routes
client-components
approvals
comments
immutability
requires provides affects
02-03
client-approval-flow
client-comment-flow
src/app/c/[token]/page.tsx
src/components/phase-timeline.tsx
added patterns
token-in-body auth
router.refresh() for Server Component revalidation
polymorphic entity comments
created modified
src/app/api/client/approve/route.ts
src/app/api/client/comment/route.ts
src/components/client/ApproveButton.tsx
src/components/client/CommentForm.tsx
src/components/client/CommentList.tsx
src/app/c/[token]/page.tsx
src/components/client-dashboard.tsx
src/components/phase-timeline.tsx
approved_at immutability enforced at API layer: route checks approved_at !== null before UPDATE; no-op 200 response if already approved
Client components use router.refresh() (not full page reload) to re-fetch Server Component data after mutations
Comments fetched server-side in page.tsx as a single DB query across all entity IDs, passed down as prop — avoids N+1 per entity
revalidate set to 0 on client page — approvals and comments must always be fresh, ISR would serve stale state
PhaseTimeline extended (not replaced) to accept token + comments props — Phase 1 UI preserved, interactive elements additive
ApproveButton renders on all deliverables regardless of status — shows date badge if approved_at set, Approva button otherwise
duration_minutes completed_date tasks_completed tasks_total files_created files_modified
17 2026-05-15 2 2 5 3

Phase 02 Plan 04: Client Interactions — Approvals + Comments Summary

One-liner: Client-facing approval and comment API routes with token validation, ownership verification, approved_at immutability enforcement, and inline ApproveButton/CommentForm/CommentList wired into the Phase 1 dashboard via PhaseTimeline.

Tasks Completed

Task Name Commit Key Files
1 POST /api/client/approve + POST /api/client/comment c24bdde src/app/api/client/approve/route.ts, src/app/api/client/comment/route.ts
2 ApproveButton, CommentForm, CommentList + dashboard wiring dc512ec src/components/client/*, src/app/c/[token]/page.tsx, src/components/phase-timeline.tsx

What Was Built

API Routes

POST /api/client/approve (src/app/api/client/approve/route.ts):

  • Reads { token, deliverableId } from request body
  • Validates token → finds clientId via clients table
  • Verifies deliverable ownership: deliverables → tasks → phases.client_id = clientId (innerJoin chain prevents cross-client approval — T-02-16)
  • Checks approved_at !== null — if already set, returns 200 no-op (T-02-17 immutability)
  • Sets status = 'approved' and approved_at = new Date() atomically
  • Returns 404 on invalid token or missing deliverable; 500 on unexpected error

POST /api/client/comment (src/app/api/client/comment/route.ts):

  • Reads { token, entity_type, entity_id, body } from request body
  • Zod schema validates: entity_type enum, body min 1 / max 2000 chars (T-02-20 DoS mitigation)
  • Validates token → finds clientId
  • For tasks: verifies task belongs to a phase owned by clientId
  • For deliverables: verifies deliverable belongs to a task in a phase owned by clientId (T-02-18)
  • Inserts comment with author: 'client'
  • Returns 201 on success; 400 on validation failure; 404 on invalid token/entity

Client Components

ApproveButton (src/components/client/ApproveButton.tsx):

  • 'use client' directive
  • If approvedAt !== null: renders immutable green badge "Approvato il [localeDateString it-IT]"
  • Otherwise: renders "Approva" button; on click POSTs to /api/client/approve, calls router.refresh() on success
  • Loading state disables button; error message shown below on failure

CommentForm (src/components/client/CommentForm.tsx):

  • 'use client' directive
  • Textarea + submit button; disabled when body is empty or loading
  • POSTs to /api/client/comment with { token, entity_type, entity_id, body }
  • On success: clears textarea, calls router.refresh() to reload comments from server

CommentList (src/components/client/CommentList.tsx):

  • Pure presentational Server Component (no 'use client')
  • Renders nothing when empty
  • Admin comments: right-aligned, dark bubble, labelled "iamcavalli"
  • Client comments: left-aligned, gray bubble, labelled "Tu"

Dashboard Wiring

page.tsx — extended to:

  • Collect all task IDs and deliverable IDs from the ClientView
  • Run single db.select().from(comments).where(inArray(comments.entity_id, allEntityIds)) query
  • Pass token and allComments to <ClientDashboard>
  • Changed revalidate from 60 (ISR) to 0 (always fresh)

client-dashboard.tsx — updated ClientDashboardProps to include token: string and comments: Comment[]; passes both to <PhaseTimeline>

phase-timeline.tsx — extended PhaseTimelineProps with token and comments; added commentsFor() helper; renders within each task: ApproveButton on each deliverable, CommentList + CommentForm below each deliverable and below each task

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Missing @radix-ui/react-tabs dependency caused build failure

  • Found during: Task 1 build verification
  • Issue: tabs.tsx component (from Plan 02-03) imported @radix-ui/react-tabs which was listed in package.json but not installed in the worktree's node_modules
  • Fix: Ran npm install in the main repo directory to install all declared dependencies
  • Files modified: None (package install only)
  • Commit: Resolved before Task 1 commit; no separate commit needed

2. [Rule 3 - Blocking] Missing .env.local in worktree caused build page-data collection error

  • Found during: Task 1 build verification (second attempt)
  • Issue: DATABASE_URL env var is required error during page data collection; .env.local exists in main repo but not copied to worktree
  • Fix: Copied .env.local from main repo to worktree root (file is gitignored)
  • Files modified: .env.local (worktree only, not committed)
  • Commit: Not committed (gitignored)

3. [Rule 2 - Missing prop type] ClientDashboard and PhaseTimeline needed prop signature updates

  • Found during: Task 2 — IDE diagnostic after updating page.tsx
  • Issue: ClientDashboard and PhaseTimeline had no token or comments props in their interfaces — TypeScript error TS2322
  • Fix: Updated ClientDashboardProps and PhaseTimelineProps to include token: string and comments: Comment[]; updated function signatures and render logic accordingly
  • Files modified: src/components/client-dashboard.tsx, src/components/phase-timeline.tsx
  • Commit: dc512ec (included in Task 2 commit)

Known Stubs

None — all components are fully wired to live API routes and server-fetched data.

Threat Surface Scan

All threat mitigations from the plan's <threat_model> are implemented:

Threat ID Status Implementation
T-02-15 Mitigated Token validated via DB lookup before any mutation in both routes
T-02-16 Mitigated innerJoin chain (deliverable → task → phase → client_id) prevents cross-client approval
T-02-17 Mitigated approved_at !== null check before UPDATE; no-op 200 if already approved
T-02-18 Mitigated Entity ownership verified via phase → client_id chain before comment insert
T-02-19 Accepted Comments scoped to entity_ids from validated client's view; server-side filtered
T-02-20 Mitigated Zod schema enforces max(2000) on comment body; returns 400 if exceeded

No new threat surface introduced beyond what is documented in the plan.

Self-Check: PASSED

Files exist:

  • src/app/api/client/approve/route.ts: FOUND
  • src/app/api/client/comment/route.ts: FOUND
  • src/components/client/ApproveButton.tsx: FOUND
  • src/components/client/CommentForm.tsx: FOUND
  • src/components/client/CommentList.tsx: FOUND

Commits exist:

Build: PASSED (npm run build — no TypeScript errors, all routes listed in output)