81c667838f
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>
568 lines
19 KiB
Markdown
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>
|