---
phase: "01-foundation-client-dashboard"
plan: 04
type: execute
wave: 2
depends_on:
  - "01-01"
  - "01-02"
  - "01-03"
files_modified:
  - app/c/[token]/page.tsx
  - src/components/client-dashboard.tsx
  - src/components/phase-timeline.tsx
  - src/components/payment-status.tsx
  - src/components/documents-section.tsx
  - src/components/notes-section.tsx
  - src/app/globals.css
  - tailwind.config.ts
autonomous: true
requirements:
  - DASH-02
  - DASH-03
  - DASH-04
  - DASH-07
  - DASH-08
  - DASH-09
  - DASH-10

must_haves:
  truths:
    - "Client dashboard displays client brand name prominently with iamcavalli logo in corner"
    - "Global progress bar at top shows % of all tasks completed"
    - "Phases are displayed as lateral timeline (left indicator, content right)"
    - "Each phase shows progress bar (% from completed tasks) + task list with status badges"
    - "Tasks are nested within phases with status visible (todo/in_progress/done)"
    - "Payment section always visible: accepted_total + Acconto 50% status + Saldo 50% status (NO amounts)"
    - "Document links are clickable (opens external URL)"
    - "Notes/decision log is visible (read-only, may be empty)"
    - "Layout is mobile-responsive and light & clean visual style"
  artifacts:
    - path: "app/c/[token]/page.tsx"
      provides: "Server Component rendering ClientDashboard"
      min_lines: 20
    - path: "src/components/client-dashboard.tsx"
      provides: "Layout wrapper + main sections (header, progress, phases, payments, documents, notes)"
      min_lines: 50
    - path: "src/components/phase-timeline.tsx"
      provides: "Lateral timeline rendering with phase cards and task lists"
      min_lines: 80
    - path: "src/components/payment-status.tsx"
      provides: "Payment section: accepted_total + 2 payment rows with status"
      min_lines: 30
    - path: "src/components/documents-section.tsx"
      provides: "List of external document links"
      min_lines: 20
    - path: "src/components/notes-section.tsx"
      provides: "Read-only notes list with timestamps"
      min_lines: 20
    - path: "tailwind.config.ts"
      provides: "Light & clean design tokens (updated from bootstrap)"
      contains: "colors"
  key_links:
    - from: "app/c/[token]/page.tsx"
      to: "ClientDashboard component"
      via: "import { ClientDashboard }"
      pattern: "<ClientDashboard"
    - from: "ClientDashboard"
      to: "PhaseTimeline + PaymentStatus + DocumentsSection + NotesSection"
      via: "nested component props"
      pattern: "view\\.phases"
    - from: "PhaseTimeline"
      to: "task status badges"
      via: "status className mapping"
      pattern: "status.*todo.*in_progress.*done"

---

<objective>
**Client Dashboard UI — Vertical Slice:** Render the complete client dashboard with all UI sections: header with branding, global progress bar, lateral phase timeline, task lists with status, payment status section, external document links, and read-only notes log. Implement light & clean visual style with mobile-first responsive design using Tailwind CSS and shadcn/ui components.

Purpose: Deliver the core user-facing product: a client can open their secret link and see the complete project status at a glance, with clear progress indicators, task hierarchy, payment overview, and documents.

Output: Fully rendered client portal with all DASH-02 through DASH-10 requirements implemented in the UI.
</objective>

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

<context>
@.planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (Decisions D-04 through D-12)
@.planning/phases/01-foundation-client-dashboard/01-03-SUMMARY.md
@src/lib/client-view.ts (ClientView interface)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Configure design tokens (tailwind.config.ts + globals.css) and wire app/c/[token]/page.tsx to ClientDashboard</name>
  <files>
    tailwind.config.ts
    src/app/globals.css
    app/c/[token]/page.tsx
  </files>
  <read_first>
    tailwind.config.ts (current bootstrap)
    src/app/globals.css (current bootstrap)
    src/components/client-dashboard.tsx (will exist after Task 2 — read after Task 2 completes)
    src/lib/client-view.ts (ClientView interface)
  </read_first>
  <action>
    Update `tailwind.config.ts` to define light & clean design tokens:
    
    ```typescript
    import type { Config } from 'tailwindcss';

    const config: Config = {
      content: [
        './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
        './src/components/**/*.{js,ts,jsx,tsx,mdx}',
        './src/app/**/*.{js,ts,jsx,tsx,mdx}',
      ],
      theme: {
        extend: {
          colors: {
            // Light & clean palette
            'primary': '#1a1a1a',      // deep charcoal for text
            'secondary': '#666666',    // medium gray for secondary text
            'tertiary': '#999999',     // light gray for hints
            'bg-light': '#ffffff',     // pure white
            'bg-subtle': '#f9f9f9',    // very light gray
            'border-light': '#e5e5e5', // subtle border
            'accent': '#0066cc',       // blue accent (will be brand-aware in Phase 2)
            'success': '#22c55e',      // green for done
            'warning': '#eab308',      // yellow for in-progress
            'info': '#3b82f6',         // blue for pending
          },
          spacing: {
            'xs': '0.5rem',
            'sm': '1rem',
            'md': '1.5rem',
            'lg': '2rem',
            'xl': '3rem',
          },
          fontSize: {
            'xs': '0.75rem',
            'sm': '0.875rem',
            'base': '1rem',
            'lg': '1.125rem',
            'xl': '1.25rem',
            '2xl': '1.5rem',
            '3xl': '1.875rem',
          },
          fontFamily: {
            'sans': [
              'system-ui',
              '-apple-system',
              'BlinkMacSystemFont',
              '"Segoe UI"',
              'Roboto',
              '"Helvetica Neue"',
              'sans-serif',
            ],
          },
        },
      },
      plugins: [],
    };

    export default config;
    ```
    
    Update `src/app/globals.css`:
    
    ```css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    html {
      scroll-behavior: smooth;
    }

    body {
      @apply bg-white text-primary font-sans;
      line-height: 1.6;
    }

    h1 {
      @apply text-3xl font-bold tracking-tight;
    }

    h2 {
      @apply text-2xl font-bold;
    }

    h3 {
      @apply text-xl font-semibold;
    }

    p {
      @apply text-base text-secondary;
    }

    a {
      @apply text-accent hover:underline transition-colors;
    }

    .border-subtle {
      @apply border border-border-light;
    }

    .bg-subtle {
      @apply bg-bg-subtle;
    }
    ```

    Update `app/c/[token]/page.tsx` to replace the Plan 03 placeholder with the full ClientDashboard render:

    ```typescript
    import { getClientView } from '@/lib/client-view';
    import { ClientDashboard } from '@/components/client-dashboard';
    import { notFound } from 'next/navigation';

    export const revalidate = 60;

    export async function generateMetadata({
      params,
    }: {
      params: { token: string };
    }) {
      const view = await getClientView(params.token);

      if (!view) {
        return { title: 'Not Found' };
      }

      return {
        title: `${view.client.brand_name} — Project Status | iamcavalli`,
        description: view.client.brief || 'Project status dashboard',
      };
    }

    export default async function ClientPage({
      params,
    }: {
      params: { token: string };
    }) {
      const view = await getClientView(params.token);

      if (!view) {
        notFound();
      }

      return <ClientDashboard view={view} />;
    }
    ```

    Note: `getClientView` is called twice (once in `generateMetadata`, once in `ClientPage`). Next.js 15 deduplicates fetch calls within the same render, and since this is a DB query via Drizzle (not fetch), use React `cache()` in `client-view.ts` if double-call is a concern — acceptable for Phase 1 given low traffic.
  </action>
  <verify>
    <automated>grep -q "colors:" tailwind.config.ts && echo "Color tokens defined"</automated>
    <automated>grep -q "primary\|accent\|success" tailwind.config.ts && echo "Key colors present"</automated>
    <automated>grep -q "@tailwind" src/app/globals.css && echo "Tailwind directives in globals.css"</automated>
    <automated>grep -q "ClientDashboard" app/c/\[token\]/page.tsx && echo "ClientDashboard wired in page"</automated>
    <automated>grep -q "generateMetadata" app/c/\[token\]/page.tsx && echo "Dynamic metadata present"</automated>
  </verify>
  <acceptance_criteria>
    - `tailwind.config.ts` contains color tokens: primary, secondary, accent, success, warning
    - `globals.css` includes Tailwind directives and base typography
    - `app/c/[token]/page.tsx` renders `<ClientDashboard view={view} />` with dynamic metadata
    - 404 returned if token invalid
    - `npm run build` succeeds
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 2: Create ClientDashboard wrapper component with header, global progress, and section layout</name>
  <files>
    src/components/client-dashboard.tsx
  </files>
  <read_first>
    src/lib/client-view.ts (ClientView interface)
    .planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-06 through D-10)
  </read_first>
  <action>
    Create `src/components/client-dashboard.tsx`:
    
    ```typescript
    'use client';

    import { ClientView } from '@/lib/client-view';
    import { Progress } from '@/components/ui/progress';
    import { PhaseTimeline } from './phase-timeline';
    import { PaymentStatus } from './payment-status';
    import { DocumentsSection } from './documents-section';
    import { NotesSection } from './notes-section';

    interface ClientDashboardProps {
      view: ClientView;
    }

    export function ClientDashboard({ view }: ClientDashboardProps) {
      return (
        <div className="min-h-screen bg-white">
          {/* Header: Logo + Brand Name */}
          <header className="bg-white border-b border-subtle sticky top-0 z-10">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
              <div className="flex items-center justify-between">
                {/* iamcavalli logo (small, corner) */}
                <div className="text-xs font-semibold text-tertiary">iamcavalli</div>

                {/* Client brand name (prominent) */}
                <h1 className="text-2xl sm:text-3xl font-bold text-primary flex-1 text-center mx-4">
                  {view.client.brand_name}
                </h1>

                {/* Spacer for balance */}
                <div className="w-20" />
              </div>
            </div>
          </header>

          {/* Global Progress Bar */}
          <section className="bg-bg-subtle border-b border-subtle">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
              <div className="space-y-2">
                <p className="text-sm font-semibold text-primary">Project Progress</p>
                <Progress 
                  value={view.global_progress_pct} 
                  className="h-2"
                />
                <p className="text-xs text-tertiary">
                  {view.global_progress_pct}% Complete
                </p>
              </div>
            </div>
          </section>

          {/* Main Content */}
          <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
            {/* Brief */}
            {view.client.brief && (
              <section className="mb-12">
                <p className="text-lg text-secondary italic">
                  "{view.client.brief}"
                </p>
              </section>
            )}

            {/* Phase Timeline */}
            <section className="mb-12">
              <h2 className="text-2xl font-bold mb-8">Project Phases</h2>
              <PhaseTimeline phases={view.phases} />
            </section>

            {/* Payment Status */}
            <section className="mb-12">
              <h2 className="text-2xl font-bold mb-6">Payment Status</h2>
              <PaymentStatus 
                accepted_total={view.client.accepted_total}
                payments={view.payments}
              />
            </section>

            {/* Documents */}
            {view.documents.length > 0 && (
              <section className="mb-12">
                <h2 className="text-2xl font-bold mb-6">Documents & Files</h2>
                <DocumentsSection documents={view.documents} />
              </section>
            )}

            {/* Notes / Decision Log */}
            {view.notes.length > 0 && (
              <section className="mb-12">
                <h2 className="text-2xl font-bold mb-6">Notes & Decisions</h2>
                <NotesSection notes={view.notes} />
              </section>
            )}
          </main>

          {/* Footer */}
          <footer className="bg-bg-subtle border-t border-subtle mt-16">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
              <p className="text-xs text-tertiary text-center">
                This is a private project dashboard. Do not share your unique link.
              </p>
            </div>
          </footer>
        </div>
      );
    }
    ```
    
    Key points:
    - Header: small "iamcavalli" logo (top-left), client brand_name centered (prominent)
    - Global progress bar shows % of all tasks done
    - Section headers are h2 (consistent sizing)
    - Responsive layout: max-width container with mobile padding
    - Brief is quoted and italicized
    - Documents and Notes sections show only if data exists
  </action>
  <verify>
    <automated>test -f src/components/client-dashboard.tsx && echo "ClientDashboard component exists"</automated>
    <automated>grep -q "export function ClientDashboard" src/components/client-dashboard.tsx && echo "Component exported"</automated>
    <automated>grep -q "iamcavalli" src/components/client-dashboard.tsx && echo "Logo text present"</automated>
    <automated>grep -q "brand_name" src/components/client-dashboard.tsx && echo "Brand name rendered"</automated>
    <automated>grep -q "global_progress_pct" src/components/client-dashboard.tsx && echo "Progress bar displays"</automated>
  </verify>
  <acceptance_criteria>
    - Component is exported and accepts ClientView props
    - Header displays iamcavalli logo (small) + brand_name (prominent)
    - Global progress bar shows project completion %
    - Main sections: brief, phases, payments, documents (conditional), notes (conditional)
    - Responsive layout with max-width container
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 3: Create PhaseTimeline component for lateral timeline layout with task lists</name>
  <files>
    src/components/phase-timeline.tsx
  </files>
  <read_first>
    src/lib/client-view.ts (phase and task structure)
    .planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-07, D-08)
  </read_first>
  <action>
    Create `src/components/phase-timeline.tsx`:
    
    ```typescript
    'use client';

    import { ClientView } from '@/lib/client-view';
    import { Progress } from '@/components/ui/progress';
    import { Card } from '@/components/ui/card';
    import { Badge } from '@/components/ui/badge';
    import { CheckCircle2, Circle, Clock } from 'lucide-react';

    interface PhaseTimelineProps {
      phases: ClientView['phases'];
    }

    export function PhaseTimeline({ phases }: PhaseTimelineProps) {
      return (
        <div className="space-y-8">
          {phases.map((phase, index) => (
            <div key={phase.id} className="flex gap-6">
              {/* Left: Timeline Indicator */}
              <div className="flex flex-col items-center gap-2">
                {/* Circle indicator */}
                <div className="relative z-10 w-10 h-10 bg-white border-2 border-accent rounded-full flex items-center justify-center shadow-sm">
                  {phase.status === 'done' ? (
                    <CheckCircle2 className="w-6 h-6 text-success" />
                  ) : phase.status === 'active' ? (
                    <Circle className="w-6 h-6 text-accent" />
                  ) : (
                    <Clock className="w-6 h-6 text-tertiary" />
                  )}
                </div>

                {/* Vertical line (not on last) */}
                {index < phases.length - 1 && (
                  <div className="flex-1 w-0.5 bg-border-light" style={{ minHeight: '120px' }} />
                )}
              </div>

              {/* Right: Phase Content */}
              <div className="flex-1 pb-8">
                {/* Phase Card */}
                <Card className="p-6 border-subtle hover:shadow-md transition-shadow">
                  {/* Phase Header */}
                  <div className="flex items-start justify-between mb-4">
                    <h3 className="text-xl font-bold text-primary">
                      {phase.title}
                    </h3>
                    <Badge 
                      className={`capitalize ${
                        phase.status === 'done' ? 'bg-success text-white' :
                        phase.status === 'active' ? 'bg-accent text-white' :
                        'bg-tertiary text-white'
                      }`}
                    >
                      {phase.status === 'upcoming' ? 'Upcoming' : 
                       phase.status === 'active' ? 'In Progress' : 'Done'}
                    </Badge>
                  </div>

                  {/* Phase Progress Bar */}
                  <div className="mb-6 space-y-2">
                    <div className="flex justify-between items-center">
                      <p className="text-xs font-semibold text-secondary">
                        Phase Progress
                      </p>
                      <p className="text-xs text-tertiary">
                        {phase.progress_pct}%
                      </p>
                    </div>
                    <Progress value={phase.progress_pct} className="h-2" />
                  </div>

                  {/* Task List */}
                  <div className="space-y-3">
                    <p className="text-sm font-semibold text-secondary">
                      Tasks ({phase.tasks.filter(t => t.status === 'done').length} of {phase.tasks.length})
                    </p>
                    {phase.tasks.length === 0 ? (
                      <p className="text-sm text-tertiary italic">No tasks yet</p>
                    ) : (
                      <ul className="space-y-2">
                        {phase.tasks.map((task) => (
                          <li 
                            key={task.id} 
                            className="flex items-start gap-3 p-2 rounded hover:bg-bg-subtle transition-colors"
                          >
                            {/* Task Status Icon */}
                            {task.status === 'done' ? (
                              <CheckCircle2 className="w-5 h-5 text-success mt-0.5 flex-shrink-0" />
                            ) : task.status === 'in_progress' ? (
                              <Circle className="w-5 h-5 text-warning mt-0.5 flex-shrink-0" />
                            ) : (
                              <Circle className="w-5 h-5 text-info mt-0.5 flex-shrink-0" />
                            )}

                            {/* Task Content */}
                            <div className="flex-1">
                              <p className={`text-sm ${task.status === 'done' ? 'line-through text-tertiary' : 'text-primary'}`}>
                                {task.title}
                              </p>
                              {task.description && (
                                <p className="text-xs text-tertiary mt-1">
                                  {task.description}
                                </p>
                              )}
                              {/* Deliverables */}
                              {task.deliverables.length > 0 && (
                                <div className="mt-2 space-y-1">
                                  {task.deliverables.map((d) => (
                                    <div 
                                      key={d.id} 
                                      className="text-xs p-1 bg-bg-subtle rounded flex items-center justify-between gap-2"
                                    >
                                      <span className="text-secondary truncate">
                                        {d.title}
                                      </span>
                                      {d.status === 'approved' && (
                                        <Badge className="bg-success text-white text-xs">
                                          Approved
                                        </Badge>
                                      )}
                                    </div>
                                  ))}
                                </div>
                              )}
                            </div>
                          </li>
                        ))}
                      </ul>
                    )}
                  </div>
                </Card>
              </div>
            </div>
          ))}
        </div>
      );
    }
    ```
    
    Key points:
    - Left indicator: circle with icon (checkmark for done, dot for upcoming/active)
    - Vertical line connects phases (not on last phase)
    - Right content: phase card with title, status badge, progress bar, task list
    - Task status shown with icons and colors (success/warning/info)
    - Deliverables nested under tasks with "Approved" badge if applicable
    - Empty state if phase has no tasks
  </action>
  <verify>
    <automated>test -f src/components/phase-timeline.tsx && echo "PhaseTimeline component exists"</automated>
    <automated>grep -q "export function PhaseTimeline" src/components/phase-timeline.tsx && echo "Component exported"</automated>
    <automated>grep -q "CheckCircle2\|Circle" src/components/phase-timeline.tsx && echo "Icons imported"</automated>
    <automated>grep -q "progress_pct" src/components/phase-timeline.tsx && echo "Progress bar displays"</automated>
  </verify>
  <acceptance_criteria>
    - Component renders lateral timeline layout
    - Each phase shows: title, status badge, progress bar, task count
    - Tasks show status with icons (checkmark/circle)
    - Deliverables are nested and show "Approved" badge if applicable
    - Empty state for phases with no tasks
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 4: Create PaymentStatus component (accepted_total + payment rows with status badges)</name>
  <files>
    src/components/payment-status.tsx
  </files>
  <read_first>
    src/lib/client-view.ts (payments shape, PaymentStatus type)
    .planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-10, D-11)
  </read_first>
  <action>
    Create `src/components/payment-status.tsx`:
    
    ```typescript
    'use client';

    import { Card } from '@/components/ui/card';
    import { Badge } from '@/components/ui/badge';
    import { ClientView } from '@/lib/client-view';
    import { CheckCircle2, Clock, AlertCircle } from 'lucide-react';

    interface PaymentStatusProps {
      accepted_total: string;
      payments: ClientView['payments'];
    }

    export function PaymentStatus({ accepted_total, payments }: PaymentStatusProps) {
      const statusConfig = {
        da_saldare: { color: 'bg-info', icon: Clock, label: 'Da Saldare', text: 'white' },
        inviata: { color: 'bg-warning', icon: AlertCircle, label: 'Inviata', text: 'white' },
        saldato: { color: 'bg-success', icon: CheckCircle2, label: 'Saldato', text: 'white' },
      };

      return (
        <Card className="p-6 border-subtle">
          {/* Total */}
          <div className="mb-6 pb-6 border-b border-subtle">
            <p className="text-sm text-secondary font-semibold mb-2">
              Totale Preventivo Accettato
            </p>
            <p className="text-3xl font-bold text-primary">
              €{parseFloat(accepted_total || '0').toLocaleString('it-IT', {
                minimumFractionDigits: 2,
                maximumFractionDigits: 2,
              })}
            </p>
          </div>

          {/* Payment Rows */}
          <div className="space-y-4">
            {payments.map((payment) => {
              const config = statusConfig[payment.status as keyof typeof statusConfig];
              const Icon = config?.icon || Clock;

              return (
                <div 
                  key={payment.id} 
                  className="flex items-center justify-between p-4 bg-bg-subtle rounded-lg border border-subtle"
                >
                  <div className="flex items-center gap-3">
                    <Icon className="w-5 h-5 text-secondary flex-shrink-0" />
                    <p className="text-sm font-semibold text-primary">
                      {payment.label}
                    </p>
                  </div>
                  <Badge 
                    className={`capitalize ${config?.color} text-${config?.text}`}
                  >
                    {config?.label || payment.status}
                  </Badge>
                </div>
              );
            })}
          </div>

          {/* Note */}
          <p className="text-xs text-tertiary italic mt-6 pt-6 border-t border-subtle">
            I pagamenti sono suddivisi in due rate da 50% ciascuna. 
            Contattaci per domande sui dettagli.
          </p>
        </Card>
      );
    }
    ```
    
    Key points:
    - Shows `accepted_total` formatted as Euro currency — NEVER individual line-item amounts
    - Two payment rows (Acconto 50%, Saldo 50%) with status badges only
    - Status badge colors: da_saldare = blue, inviata = yellow, saldato = green
    - Card + Badge from shadcn/ui
  </action>
  <verify>
    <automated>test -f src/components/payment-status.tsx && echo "PaymentStatus component exists"</automated>
    <automated>grep -q "export function PaymentStatus" src/components/payment-status.tsx && echo "Component exported"</automated>
    <automated>grep -q "accepted_total" src/components/payment-status.tsx && echo "Total displayed"</automated>
    <automated>grep -q "da_saldare\|inviata\|saldato" src/components/payment-status.tsx && echo "Status config present"</automated>
  </verify>
  <acceptance_criteria>
    - Component exists and is exported
    - Displays accepted_total formatted as Euro (no individual amounts)
    - Renders payment rows with status badges (da_saldare/inviata/saldato)
    - Uses shadcn/ui Card and Badge
  </acceptance_criteria>
</task>

<task type="auto">
  <name>Task 5: Create DocumentsSection and NotesSection components (external links + read-only notes)</name>
  <files>
    src/components/documents-section.tsx
    src/components/notes-section.tsx
  </files>
  <read_first>
    src/lib/client-view.ts (documents and notes shapes)
    .planning/phases/01-foundation-client-dashboard/01-CONTEXT.md (D-12)
  </read_first>
  <action>
    Create `src/components/documents-section.tsx`:
    
    ```typescript
    'use client';

    import { ClientView } from '@/lib/client-view';
    import { Card } from '@/components/ui/card';
    import { ExternalLink } from 'lucide-react';

    interface DocumentsSectionProps {
      documents: ClientView['documents'];
    }

    export function DocumentsSection({ documents }: DocumentsSectionProps) {
      return (
        <div className="space-y-3">
          {documents.map((doc) => (
            <Card 
              key={doc.id} 
              className="p-4 border-subtle hover:shadow-md transition-shadow"
            >
              <a 
                href={doc.url}
                target="_blank"
                rel="noopener noreferrer"
                className="flex items-center justify-between gap-3 text-accent hover:text-accent hover:underline group"
              >
                <span className="font-semibold text-primary group-hover:text-accent">
                  {doc.label}
                </span>
                <ExternalLink className="w-4 h-4 flex-shrink-0" />
              </a>
            </Card>
          ))}
        </div>
      );
    }
    ```
    
    Create `src/components/notes-section.tsx`:
    
    ```typescript
    'use client';

    import { ClientView } from '@/lib/client-view';
    import { Card } from '@/components/ui/card';

    interface NotesSectionProps {
      notes: ClientView['notes'];
    }

    export function NotesSection({ notes }: NotesSectionProps) {
      if (notes.length === 0) {
        return (
          <p className="text-secondary italic text-sm">
            No notes yet. Decisions will appear here as they are made.
          </p>
        );
      }

      return (
        <div className="space-y-4">
          {notes.map((note) => (
            <Card key={note.id} className="p-4 border-subtle">
              <p className="text-sm text-primary leading-relaxed">
                {note.body}
              </p>
              <p className="text-xs text-tertiary mt-3">
                {new Date(note.created_at).toLocaleDateString('it-IT', {
                  year: 'numeric',
                  month: 'long',
                  day: 'numeric',
                  hour: '2-digit',
                  minute: '2-digit',
                })}
              </p>
            </Card>
          ))}
        </div>
      );
    }
    ```
    
    Key points:
    - DocumentsSection: clickable external links with ExternalLink icon, `rel="noopener noreferrer"` for security
    - NotesSection: read-only, client never writes (admin writes in Phase 2 admin area)
    - NotesSection: empty state shown as italic hint when no notes exist
    - Timestamps formatted in Italian locale
  </action>
  <verify>
    <automated>test -f src/components/documents-section.tsx && echo "DocumentsSection component exists"</automated>
    <automated>test -f src/components/notes-section.tsx && echo "NotesSection component exists"</automated>
    <automated>grep -q "export function DocumentsSection" src/components/documents-section.tsx && echo "DocumentsSection exported"</automated>
    <automated>grep -q "export function NotesSection" src/components/notes-section.tsx && echo "NotesSection exported"</automated>
    <automated>grep -q "noopener noreferrer" src/components/documents-section.tsx && echo "External link security present"</automated>
  </verify>
  <acceptance_criteria>
    - Both components exist and are exported
    - DocumentsSection renders clickable external links with ExternalLink icon and secure rel attributes
    - NotesSection shows read-only notes with Italian-formatted timestamps
    - NotesSection shows empty state hint when notes array is empty
  </acceptance_criteria>
</task>


</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Client browser → CSS/HTML | UI rendering is client-safe; no admin secrets in HTML source |
| Link click → External URL | External document links open in new tab with `rel="noopener noreferrer"` |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-04-001 | Information Disclosure | Payment amounts | mitigate | Payments row shows status only; amounts never rendered on client dashboard |
| T-04-002 | Tampering | External links | accept | Links are user-provided URLs; client-side link validation (hostname check) could be added in Phase 2 |
| T-04-003 | Denial of Service | Image rendering | accept | Dashboard contains only text and icons; no resource-heavy assets |

</threat_model>

<verification>
After plan execution:
1. Run `npm run build` → no errors
2. Verify all component files exist: client-dashboard, phase-timeline, payment-status, documents-section, notes-section
3. Check page rendering logic in `app/c/[token]/page.tsx`
4. Verify mobile responsiveness: layout scales correctly on narrow screens
5. Check that payment amounts are NOT displayed (only status)
</verification>

<success_criteria>
- All UI components are created and exported
- Client dashboard renders complete project status
- Global progress bar and per-phase progress bars display correctly
- Payment section shows only status (no amounts)
- Document links are clickable
- Notes section shows read-only list (or empty state)
- Layout is responsive and uses light & clean design
- Mobile-first design works on small screens
- Ready to proceed to Plan 05 (Seed script + DNS)
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation-client-dashboard/01-04-SUMMARY.md`
</output>
