Files
clienthub/.planning/phases/01-foundation-client-dashboard/01-05-PLAN.md
T
Simone Cavalli 81c667838f docs(01-foundation-client-dashboard): complete phase 1 planning with 5-plan structure
Create comprehensive phase plans for Foundation & Client Dashboard:
- 01-01-PLAN.md: Walking Skeleton (Next.js 15 bootstrap + DB connection)
- 01-02-PLAN.md: Database schema (11 tables, Drizzle ORM, drizzle-kit push)
- 01-03-PLAN.md: Middleware token validation + ClientView type + data fetching
- 01-04-PLAN.md: Client dashboard UI (header, timeline, progress, payments, docs, notes)
- 01-05-PLAN.md: Seed script + DNS CNAME configuration

Also create SKELETON.md documenting locked architectural decisions for all future phases:
- Next.js 15 + Drizzle + postgres-js driver (Coolify Postgres)
- Token as separate rotatable field (not PK)
- ClientView enforcement (no quote_items exposed to client API)
- Approved_at immutable audit trail
- Two independent auth systems (client token + admin session)
- Vercel deployment with custom domain

Update ROADMAP.md to mark Phase 1 as planned (5 plans created) and ready for execution.

All plans follow MVP vertical-slice structure with 2-3 tasks per plan.
Walking Skeleton proves the entire stack works end-to-end.
Requirements mapping: DASH-01 through DASH-04, DASH-07 through DASH-10 covered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:27:19 +02:00

568 lines
19 KiB
Markdown

---
phase: "01-foundation-client-dashboard"
plan: 05
type: execute
wave: 3
depends_on:
- "01-01"
- "01-02"
- "01-03"
- "01-04"
files_modified:
- scripts/seed.ts
- .env.local
autonomous: true
requirements:
- DASH-01
- DASH-02
- DASH-03
- DASH-04
- DASH-07
- DASH-08
- DASH-09
- DASH-10
must_haves:
truths:
- "Seed script exists and contains TypeScript seed logic"
- "Script inserts one complete test client with all related data (phases, tasks, deliverables, payments, documents, notes)"
- "Client token is generated via nanoid (21 chars, cryptographically secure)"
- "Seed script prints shareable URL to console: http://localhost:3000/c/[token]"
- "Script can be run via: npx tsx scripts/seed.ts"
- "DNS CNAME is configured: welcomeclient.iamcavalli.net → vercel DNS"
- "DNS propagation is verified (can be checked via `dig` or online tool)"
artifacts:
- path: "scripts/seed.ts"
provides: "Seed script that inserts first real client with all data"
min_lines: 100
contains: "import.*nanoid"
- path: ".env.local (updated)"
provides: "Updated with VERCEL_URL or custom domain setting"
contains: "DATABASE_URL"
key_links:
- from: "scripts/seed.ts"
to: "src/db/schema"
via: "drizzle db.insert()"
pattern: "db.insert\\("
- from: "nanoid token"
to: "client URL"
via: "http://localhost:3000/c/[token]"
pattern: "nanoid"
---
<objective>
**Seed Script + DNS Configuration:** Create a TypeScript seed script that populates the database with one complete test client (including phases, tasks, deliverables, payments, documents, and notes), generates a secret token via nanoid, and prints a shareable dashboard URL. Configure DNS CNAME for welcomeclient.iamcavalli.net to Vercel and verify propagation.
Purpose: Enable end-to-end testing with real data. One developer can run the seed script and immediately open a working client dashboard. DNS configuration allows the project to be accessed via the production domain.
Output: Executable seed script + verified DNS CNAME + shareable client link for testing Phase 1.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/research/ARCHITECTURE.md (Data Model section)
@.planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-13)
</context>
<tasks>
<task type="auto">
<name>Task 1: Create scripts/seed.ts to insert first real client with all data</name>
<files>
scripts/seed.ts
</files>
<read_first>
src/db/schema.ts (all table definitions)
src/db/index.ts (db client)
</read_first>
<action>
Create `scripts/seed.ts`:
```typescript
/**
* Seed Script — Inserts first test client with complete project data
* Run: npx tsx scripts/seed.ts
*/
import { db } from '@/db';
import {
clients,
phases,
tasks,
deliverables,
payments,
documents,
notes,
} from '@/db/schema';
import { nanoid } from 'nanoid';
async function seed() {
console.log('🌱 Seeding database...\n');
try {
// 1. Create client
const clientToken = nanoid();
const [client] = await db
.insert(clients)
.values({
id: nanoid(),
name: 'Test Client Inc.',
brand_name: 'TestBrand',
brief:
'A comprehensive personal branding overhaul, positioning our company as a premium consultancy in the digital transformation space.',
token: clientToken,
accepted_total: '5000.00',
created_at: new Date(),
})
.returning();
console.log(
'✓ Client created: ' + client.name + ' (ID: ' + client.id + ')'
);
// 2. Create phases
const [phase1, phase2, phase3] = await db
.insert(phases)
.values([
{
id: nanoid(),
client_id: client.id,
title: 'Discovery & Strategy',
sort_order: 1,
status: 'done',
},
{
id: nanoid(),
client_id: client.id,
title: 'Design & Messaging',
sort_order: 2,
status: 'active',
},
{
id: nanoid(),
client_id: client.id,
title: 'Implementation & Launch',
sort_order: 3,
status: 'upcoming',
},
])
.returning();
console.log('✓ Phases created (3 total)');
// 3. Create tasks
const [task1, task2, task3, task4, task5, task6] = await db
.insert(tasks)
.values([
{
id: nanoid(),
phase_id: phase1.id,
title: 'Stakeholder interviews',
description: 'In-depth conversations with leadership team',
sort_order: 1,
status: 'done',
},
{
id: nanoid(),
phase_id: phase1.id,
title: 'Competitive analysis',
description: 'Research top 10 competitors in the space',
sort_order: 2,
status: 'done',
},
{
id: nanoid(),
phase_id: phase2.id,
title: 'Brand positioning document',
description:
'Write and refine the core positioning statement',
sort_order: 1,
status: 'in_progress',
},
{
id: nanoid(),
phase_id: phase2.id,
title: 'Visual identity design',
description: 'Logo, color palette, typography',
sort_order: 2,
status: 'in_progress',
},
{
id: nanoid(),
phase_id: phase3.id,
title: 'Website build & launch',
description: 'Design and develop new company website',
sort_order: 1,
status: 'todo',
},
{
id: nanoid(),
phase_id: phase3.id,
title: 'Social media rollout',
description: 'Launch branded social media accounts',
sort_order: 2,
status: 'todo',
},
])
.returning();
console.log('✓ Tasks created (6 total)');
// 4. Create deliverables
await db
.insert(deliverables)
.values([
{
id: nanoid(),
task_id: task1.id,
title: 'Interview notes & synthesis',
url: 'https://docs.google.com/document/d/1example',
status: 'approved',
approved_at: new Date('2026-04-15'),
},
{
id: nanoid(),
task_id: task2.id,
title: 'Competitive landscape report',
url: 'https://docs.google.com/presentation/d/1example',
status: 'approved',
approved_at: new Date('2026-04-20'),
},
{
id: nanoid(),
task_id: task3.id,
title: 'Brand positioning document (draft)',
url: 'https://docs.google.com/document/d/2example',
status: 'submitted',
approved_at: null,
},
{
id: nanoid(),
task_id: task4.id,
title: 'Logo concepts (3 variations)',
url: 'https://www.figma.com/file/example',
status: 'pending',
approved_at: null,
},
])
.returning();
console.log('✓ Deliverables created (4 total)');
// 5. Create payments
await db
.insert(payments)
.values([
{
id: nanoid(),
client_id: client.id,
label: 'Acconto 50%',
amount: '2500.00',
status: 'saldato',
paid_at: new Date('2026-04-01'),
},
{
id: nanoid(),
client_id: client.id,
label: 'Saldo 50%',
amount: '2500.00',
status: 'inviata',
paid_at: null,
},
])
.returning();
console.log('✓ Payments created (2 total)');
// 6. Create documents
await db
.insert(documents)
.values([
{
id: nanoid(),
client_id: client.id,
label: 'Brand Guidelines PDF',
url: 'https://example.com/brand-guidelines.pdf',
created_at: new Date(),
},
{
id: nanoid(),
client_id: client.id,
label: 'Design Mockups Figma',
url: 'https://www.figma.com/file/example',
created_at: new Date(),
},
])
.returning();
console.log('✓ Documents created (2 total)');
// 7. Create notes
await db
.insert(notes)
.values([
{
id: nanoid(),
client_id: client.id,
body: 'Initial strategy session completed. Key insight: positioning needs to emphasize tech expertise and creative thinking balance.',
created_at: new Date('2026-04-10'),
},
{
id: nanoid(),
client_id: client.id,
body: 'Phase 1 approved. Moving forward with design phase. Stakeholders excited about direction.',
created_at: new Date('2026-04-22'),
},
])
.returning();
console.log('✓ Notes created (2 total)');
// Print shareable URL
console.log('\n✨ Seed complete!\n');
console.log('📎 Shareable client link:');
console.log(
` http://localhost:3000/c/${clientToken}\n`
);
console.log(
'This link is unique and secret. Send it to the client via Slack or email.\n'
);
} catch (error) {
console.error('❌ Seed failed:', error);
process.exit(1);
}
}
seed();
```
Key points:
- Uses nanoid for token generation (21 chars, cryptographically secure)
- Inserts complete hierarchical data: 1 client → 3 phases → 6 tasks → 4 deliverables + 2 payments + 2 documents + 2 notes
- Mix of statuses: phase 1 done, phase 2 active, phase 3 upcoming; tasks have various completion states
- Deliverables show different statuses: approved (with timestamp), submitted, pending
- Payments: one paid, one sent but unpaid
- Notes: 2 decision log entries
- Prints shareable URL to console
</action>
<verify>
<automated>test -f scripts/seed.ts && echo "Seed script exists"</automated>
<automated>grep -q "import.*nanoid" scripts/seed.ts && echo "nanoid imported"</automated>
<automated>grep -q "db.insert" scripts/seed.ts && echo "Insert statements present"</automated>
<automated>grep -q "clientToken" scripts/seed.ts && echo "Token generation present"</automated>
<automated>grep -q "http://localhost:3000/c/" scripts/seed.ts && echo "URL printed"</automated>
</verify>
<acceptance_criteria>
- `scripts/seed.ts` exists as TypeScript file
- Script imports nanoid and db client
- Creates one complete client with all related data (phases, tasks, deliverables, payments, documents, notes)
- Prints shareable URL to console
- Can be executed via `npx tsx scripts/seed.ts` without errors
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 2: Test seed script execution and verify data is inserted into database</name>
<files>
None (execution only)
</files>
<read_first>
scripts/seed.ts
.env.local
</read_first>
<action>
Run the seed script:
```
npx tsx scripts/seed.ts
```
Expected output:
```
🌱 Seeding database...
✓ Client created: Test Client Inc. (ID: xxx...)
✓ Phases created (3 total)
✓ Tasks created (6 total)
✓ Deliverables created (4 total)
✓ Payments created (2 total)
✓ Documents created (2 total)
✓ Notes created (2 total)
✨ Seed complete!
📎 Shareable client link:
http://localhost:3000/c/[token]
This link is unique and secret. Send it to the client via Slack or email.
```
If the script fails:
- Verify DATABASE_URL is set and correct
- Verify Postgres on Coolify is accessible
- Check that schema exists (run `npx drizzle-kit introspect` to confirm)
</action>
<verify>
<automated>npx tsx scripts/seed.ts 2>&1 | grep -q "Seed complete" && echo "Seed script succeeded" || echo "Seed script failed"</automated>
<automated>npx tsx scripts/seed.ts 2>&1 | grep -oE "http://localhost:3000/c/[a-zA-Z0-9_-]+" | head -1 > /tmp/client_url.txt && test -s /tmp/client_url.txt && echo "Client URL generated" || echo "Client URL not found"</automated>
</verify>
<acceptance_criteria>
- Seed script executes without errors
- Output shows all entity types created (client, phases, tasks, deliverables, payments, documents, notes)
- Shareable URL is printed to console
- Data is inserted into Postgres on Coolify
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 3: Test end-to-end: Open seeded client link in browser and verify dashboard renders</name>
<files>
None (verification only)
</files>
<read_first>
None
</read_first>
<action>
Start dev server:
```
npm run dev
```
Open the seeded client link in browser:
- Copy the URL from seed script output (e.g., http://localhost:3000/c/xyz123)
- Visit in browser
- Verify dashboard renders with:
- ✓ Client brand name displayed prominently
- ✓ iamcavalli logo in corner
- ✓ Global progress bar showing % completion
- ✓ All 3 phases visible with status badges (done/active/upcoming)
- ✓ Each phase shows progress bar and task count
- ✓ Tasks nested under phases with status icons
- ✓ Deliverables shown under tasks (with Approved badge if applicable)
- ✓ Payment section shows accepted_total (€5000.00) and 2 payment rows
- ✓ Payment amounts are NOT visible (only status: saldato, inviata)
- ✓ Document section shows clickable links
- ✓ Notes section shows decision log entries
Test edge cases:
- Invalid token (http://localhost:3000/c/invalid) → should return 404
- Page refresh → data should persist (no client-side state loss)
- Mobile view (use DevTools mobile emulator) → layout should be responsive
</action>
<verify>
<automated>curl -s http://localhost:3000/c/invalid | grep -q "404\|not found" && echo "Invalid token returns 404" || echo "404 check inconclusive"</automated>
</verify>
<acceptance_criteria>
- Seeded client link opens without errors
- Dashboard renders with client data
- All sections visible: header, progress, phases, tasks, deliverables, payments, documents, notes
- Invalid token returns 404
- Layout is responsive on mobile
</acceptance_criteria>
</task>
<task type="auto">
<name>Task 4: Configure DNS CNAME for welcomeclient.iamcavalli.net → Vercel DNS</name>
<files>
None (external DNS configuration)
</files>
<read_first>
.planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-03)
</read_first>
<action>
**DNS Configuration Steps:**
1. Log into your domain registrar (where iamcavalli.net is registered)
2. Navigate to DNS settings for iamcavalli.net
3. Create a new CNAME record:
- **Name:** welcomeclient
- **Type:** CNAME
- **Value:** cname.vercel-dns.com
- **TTL:** 3600 (or default)
4. Save the record
5. Verify propagation (may take 15 minutes to 2 hours):
```
dig welcomeclient.iamcavalli.net
```
You should see:
```
welcomeclient.iamcavalli.net. 3600 IN CNAME cname.vercel-dns.com.
```
Or use an online tool: https://mxtoolbox.com/cname.aspx
**Vercel Configuration:**
1. Go to Vercel dashboard → Project Settings → Domains
2. Add domain: `welcomeclient.iamcavalli.net`
3. Vercel will show the CNAME record to configure (should match above)
4. Click "Add" and wait for verification (usually immediate after DNS propagates)
**After DNS is live:**
- You can access the dashboard via https://welcomeclient.iamcavalli.net/c/[token]
- DNS is bidirectional: localhost:3000 still works for dev
</action>
<verify>
<automated>dig welcomeclient.iamcavalli.net +short 2>/dev/null | grep -q "vercel-dns.com" && echo "DNS CNAME configured" || echo "DNS CNAME not yet live"</automated>
</verify>
<acceptance_criteria>
- CNAME record is created at registrar: welcomeclient → cname.vercel-dns.com
- Vercel project has the domain added and verified
- `dig` shows the CNAME record pointing to Vercel DNS
- Domain is accessible via browser (may take time to propagate)
</acceptance_criteria>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Client browser → Secret link | Token is in URL; HTTPS encrypts transit; never log token in server logs |
| Token generation | nanoid is cryptographically secure (126 bits entropy); non-enumerable |
| DNS configuration | CNAME points to Vercel; Vercel controls SSL/TLS for domain |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-05-001 | Information Disclosure | Token in seed output | mitigate | URL is printed to console; developer must not commit or share the seed output; regenerate token in Phase 2 if compromised |
| T-05-002 | Information Disclosure | HTTPS for domain | mitigate | Vercel automatically provisions SSL/TLS for custom domain; all traffic to welcomeclient.iamcavalli.net is encrypted |
| T-05-003 | Denial of Service | Seed script re-run | accept | Running seed script multiple times creates duplicate clients (same test data); acceptable for dev; Phase 2 adds admin UI to manage clients |
</threat_model>
<verification>
After plan execution:
1. Run `npx tsx scripts/seed.ts` → output shows "Seed complete!"
2. Copy the printed URL and visit in browser
3. Verify dashboard renders with seeded data
4. Test invalid token → 404
5. Verify DNS CNAME is live: `dig welcomeclient.iamcavalli.net`
6. (Optional) Visit https://welcomeclient.iamcavalli.net/c/[token] once DNS propagates
</verification>
<success_criteria>
- Seed script exists and inserts complete test data
- One client with 3 phases, 6 tasks, 4 deliverables, 2 payments, 2 documents, 2 notes
- Dashboard renders with seeded data via shareable link
- Invalid tokens return 404
- DNS CNAME is configured and verified
- Phase 1 is complete and ready for production (Phase 2 will add auth and CRUD)
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation-client-dashboard/01-05-SUMMARY.md`
Also update `.planning/ROADMAP.md` to mark Phase 1 complete and set up Phase 2 planning.
</output>