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

19 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
01-foundation-client-dashboard 05 execute 3
01-01
01-02
01-03
01-04
scripts/seed.ts
.env.local
true
DASH-01
DASH-02
DASH-03
DASH-04
DASH-07
DASH-08
DASH-09
DASH-10
truths artifacts key_links
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)
path provides min_lines contains
scripts/seed.ts Seed script that inserts first real client with all data 100 import.*nanoid
path provides contains
.env.local (updated) Updated with VERCEL_URL or custom domain setting DATABASE_URL
from to via pattern
scripts/seed.ts src/db/schema drizzle db.insert() db.insert(
from to via pattern
nanoid token client URL http://localhost:3000/c/[token] nanoid
**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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/research/ARCHITECTURE.md (Data Model section) @.planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-13) Task 1: Create scripts/seed.ts to insert first real client with all data scripts/seed.ts src/db/schema.ts (all table definitions) src/db/index.ts (db client) 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
test -f scripts/seed.ts && echo "Seed script exists" grep -q "import.*nanoid" scripts/seed.ts && echo "nanoid imported" grep -q "db.insert" scripts/seed.ts && echo "Insert statements present" grep -q "clientToken" scripts/seed.ts && echo "Token generation present" grep -q "http://localhost:3000/c/" scripts/seed.ts && echo "URL printed" - `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 Task 2: Test seed script execution and verify data is inserted into database None (execution only) scripts/seed.ts .env.local 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)
npx tsx scripts/seed.ts 2>&1 | grep -q "Seed complete" && echo "Seed script succeeded" || echo "Seed script failed" 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" - 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 Task 3: Test end-to-end: Open seeded client link in browser and verify dashboard renders None (verification only) None 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
curl -s http://localhost:3000/c/invalid | grep -q "404\|not found" && echo "Invalid token returns 404" || echo "404 check inconclusive" - 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 Task 4: Configure DNS CNAME for welcomeclient.iamcavalli.net → Vercel DNS None (external DNS configuration) .planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-03) **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
dig welcomeclient.iamcavalli.net +short 2>/dev/null | grep -q "vercel-dns.com" && echo "DNS CNAME configured" || echo "DNS CNAME not yet live" - 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)

<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>

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

<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>
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.