---
phase: 11-agent-saved-signature-and-signing-workflow
plan: "01"
type: execute
wave: 1
depends_on: []
files_modified:
  - teressa-copeland-homes/src/lib/db/schema.ts
  - teressa-copeland-homes/drizzle/0008_agent_signature.sql
  - teressa-copeland-homes/src/app/api/agent/signature/route.ts
  - teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx
  - teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx
  - teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx
  - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
autonomous: true
requirements:
  - AGENT-01
  - AGENT-02
  - AGENT-03

must_haves:
  truths:
    - "Agent can navigate to /portal/profile via the nav"
    - "Agent can draw a signature on the canvas and save it"
    - "A thumbnail of the saved signature appears after saving"
    - "Agent can click 'Update Signature' to redraw and replace their saved signature"
    - "Agent signature palette token appears in the FieldPlacer (red 'Agent Signature' token)"
    - "Placing an agent-signature field saves it in signatureFields with type 'agent-signature'"
  artifacts:
    - path: "teressa-copeland-homes/src/lib/db/schema.ts"
      provides: "agentSignatureData TEXT column on users table"
      contains: "agentSignatureData"
    - path: "teressa-copeland-homes/src/app/api/agent/signature/route.ts"
      provides: "GET/PUT endpoints for reading and writing agent signature"
      exports: ["GET", "PUT"]
    - path: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
      provides: "Client component with signature_pad canvas, save/update/thumbnail flow"
    - path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
      provides: "Server component fetching agentSignatureData and rendering AgentSignaturePanel"
  key_links:
    - from: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
      to: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
      via: "initialData prop (string | null)"
      pattern: "initialData.*agentSignatureData"
    - from: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
      to: "/api/agent/signature"
      via: "fetch PUT with { dataURL }"
      pattern: "fetch.*api/agent/signature"
    - from: "teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx"
      to: "/portal/profile"
      via: "navLinks entry"
      pattern: "portal/profile"
---

<objective>
Establish the agent signature storage and UI layer: DB migration adds `agentSignatureData TEXT` to users, GET/PUT API routes read/write the base64 PNG dataURL, a new `/portal/profile` page hosts the `AgentSignaturePanel` draw-and-save canvas component, the portal nav gains a Profile link, and the FieldPlacer palette gets the red "Agent Signature" token.

Purpose: Once the agent has saved their signature and the palette token exists, Plan 02 can wire up the preparePdf() embedding without any further schema or UI work.
Output: Profile page with working draw/save/update signature flow; agent-signature token in field palette; zero new npm packages.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/10-expanded-field-types-end-to-end/10-03-SUMMARY.md

<interfaces>
<!-- Key types and confirmed patterns extracted from codebase (2026-03-21 inspection). -->
<!-- Executor uses these directly — no codebase exploration needed. -->

From src/lib/db/schema.ts (current users table — agentSignatureData NOT YET present):
```typescript
export const users = pgTable("users", {
  id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
  email: text("email").notNull().unique(),
  passwordHash: text("password_hash").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  // Phase 11 adds: agentSignatureData: text("agent_signature_data"),
});
```

From src/lib/db/schema.ts (SignatureFieldType — agent-signature already valid):
```typescript
export type SignatureFieldType =
  | 'client-signature'
  | 'initials'
  | 'text'
  | 'checkbox'
  | 'date'
  | 'agent-signature';
```

From FieldPlacer.tsx (PALETTE_TOKENS — agent-signature NOT YET present, validTypes already includes it):
```typescript
// Current PALETTE_TOKENS (5 entries — Phase 11 adds the 6th):
const PALETTE_TOKENS: Array<{ id: SignatureFieldType; label: string; color: string }> = [
  { id: 'client-signature', label: 'Signature',  color: '#2563eb' },
  { id: 'initials',         label: 'Initials',   color: '#7c3aed' },
  { id: 'checkbox',         label: 'Checkbox',   color: '#059669' },
  { id: 'date',             label: 'Date',       color: '#d97706' },
  { id: 'text',             label: 'Text',       color: '#64748b' },
  // ADD: { id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' },
];

// validTypes already has 'agent-signature' at line 258:
const validTypes = new Set<string>(['client-signature','initials','text','checkbox','date','agent-signature']);
```

From src/app/sign/[token]/_components/SignatureModal.tsx (DPR canvas init — confirmed working):
```typescript
useEffect(() => {
  if (!isOpen || tab !== 'draw' || !canvasRef.current) return;
  const canvas = canvasRef.current;
  const ratio = Math.max(window.devicePixelRatio || 1, 1);
  canvas.width = canvas.offsetWidth * ratio;
  canvas.height = canvas.offsetHeight * ratio;
  canvas.getContext('2d')?.scale(ratio, ratio);
  sigPadRef.current = new SignaturePad(canvas, {
    backgroundColor: 'rgba(0,0,0,0)',
    penColor: '#1B2B4B',
  });
  return () => sigPadRef.current?.off();
}, [isOpen, tab]);
```

From src/lib/auth.config.ts (session.user.id is set):
```typescript
// callbacks.jwt: if (user) token.id = user.id;
// callbacks.session: session.user.id = token.id as string;
// Usage in any protected API route:
const session = await auth();
if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });
```

Migration pattern from drizzle/0007_equal_nekra.sql:
```sql
-- Pattern for nullable TEXT column:
ALTER TABLE "clients" ADD COLUMN "property_address" text;
-- Phase 11 equivalent:
ALTER TABLE "users" ADD COLUMN "agent_signature_data" text;
```

From src/app/portal/_components/PortalNav.tsx (navLinks pattern):
```typescript
// Current navLinks:
const navLinks = [
  { href: '/portal/dashboard', label: 'Dashboard' },
  { href: '/portal/clients', label: 'Clients' },
  // Phase 11 adds: { href: '/portal/profile', label: 'Profile' }
];
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: DB migration and API routes for agent signature storage</name>
  <files>
    teressa-copeland-homes/src/lib/db/schema.ts
    teressa-copeland-homes/drizzle/0008_agent_signature.sql
    teressa-copeland-homes/src/app/api/agent/signature/route.ts
  </files>
  <action>
Three changes in sequence:

**1. schema.ts — add column to users table:**
Add `agentSignatureData: text("agent_signature_data")` as a nullable column (no `.notNull()`, no default) to the `users` pgTable definition. This is the only change to schema.ts.

**2. Run migration generation and apply:**
```bash
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate
```
The generated SQL file will be at `drizzle/0008_agent_signature.sql` and will contain:
```sql
ALTER TABLE "users" ADD COLUMN "agent_signature_data" text;
```

**3. Create GET/PUT route at src/app/api/agent/signature/route.ts:**

```typescript
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { users } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';

export async function GET() {
  const session = await auth();
  if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });

  const user = await db.query.users.findFirst({
    where: eq(users.id, session.user.id),
    columns: { agentSignatureData: true },
  });

  return Response.json({ agentSignatureData: user?.agentSignatureData ?? null });
}

export async function PUT(req: Request) {
  const session = await auth();
  if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });

  const { dataURL } = await req.json() as { dataURL: string };

  if (!dataURL || !dataURL.startsWith('data:image/png;base64,')) {
    return Response.json({ error: 'Invalid signature data' }, { status: 422 });
  }
  if (dataURL.length > 50_000) {
    return Response.json({ error: 'Signature data too large' }, { status: 422 });
  }

  await db.update(users)
    .set({ agentSignatureData: dataURL })
    .where(eq(users.id, session.user.id));

  return Response.json({ ok: true });
}
```

The directory `src/app/api/agent/signature/` must be created (mkdir -p).
  </action>
  <verify>
    <automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20</automated>
  </verify>
  <done>TypeScript compiles clean; migration SQL file exists at drizzle/0008_agent_signature.sql; GET /api/agent/signature returns { agentSignatureData: null } when called with a valid session.</done>
</task>

<task type="auto">
  <name>Task 2: AgentSignaturePanel component, profile page, PortalNav link, FieldPlacer token</name>
  <files>
    teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx
    teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx
    teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx
    teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
  </files>
  <action>
Four files to create/modify:

**1. Create AgentSignaturePanel.tsx** at `src/app/portal/_components/AgentSignaturePanel.tsx`:

```typescript
'use client';
import { useEffect, useRef, useState } from 'react';
import SignaturePad from 'signature_pad';

interface AgentSignaturePanelProps {
  initialData: string | null;
}

export function AgentSignaturePanel({ initialData }: AgentSignaturePanelProps) {
  const [savedData, setSavedData] = useState<string | null>(initialData);
  const [isDrawing, setIsDrawing] = useState(!initialData);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const sigPadRef = useRef<SignaturePad | null>(null);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!isDrawing || !canvasRef.current) return;
    const canvas = canvasRef.current;
    const ratio = Math.max(window.devicePixelRatio || 1, 1);
    canvas.width = canvas.offsetWidth * ratio;
    canvas.height = canvas.offsetHeight * ratio;
    canvas.getContext('2d')?.scale(ratio, ratio);
    sigPadRef.current = new SignaturePad(canvas, {
      backgroundColor: 'rgba(0,0,0,0)',
      penColor: '#1B2B4B',
    });
    return () => sigPadRef.current?.off();
  }, [isDrawing]);

  async function handleSave() {
    if (!sigPadRef.current || sigPadRef.current.isEmpty()) {
      setError('Please draw your signature first');
      return;
    }
    const dataURL = sigPadRef.current.toDataURL('image/png');
    setSaving(true);
    setError(null);
    try {
      const res = await fetch('/api/agent/signature', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ dataURL }),
      });
      if (res.ok) {
        setSavedData(dataURL);
        setIsDrawing(false);
      } else {
        const data = await res.json().catch(() => ({ error: 'Save failed' }));
        setError(data.error ?? 'Save failed');
      }
    } catch {
      setError('Network error — please try again');
    } finally {
      setSaving(false);
    }
  }

  if (!isDrawing && savedData) {
    return (
      <div className="space-y-4">
        <p className="text-sm text-gray-600">Your saved signature:</p>
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={savedData}
          alt="Saved agent signature"
          className="max-h-20 border border-gray-200 rounded bg-white p-2"
        />
        <button
          onClick={() => { setIsDrawing(true); setError(null); }}
          className="px-4 py-2 text-sm bg-white border border-gray-300 rounded hover:bg-gray-50"
        >
          Update Signature
        </button>
      </div>
    );
  }

  return (
    <div className="space-y-3">
      <canvas
        ref={canvasRef}
        className="w-full border border-gray-300 rounded bg-white"
        style={{ height: '140px', touchAction: 'none', display: 'block' }}
      />
      <div className="flex gap-2">
        <button
          onClick={() => sigPadRef.current?.clear()}
          className="px-3 py-2 text-sm bg-white border border-gray-300 rounded hover:bg-gray-50"
        >
          Clear
        </button>
        <button
          onClick={handleSave}
          disabled={saving}
          className="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
        >
          {saving ? 'Saving...' : (savedData ? 'Save Updated Signature' : 'Save Signature')}
        </button>
        {savedData && (
          <button
            onClick={() => { setIsDrawing(false); setError(null); }}
            className="px-3 py-2 text-sm bg-white border border-gray-300 rounded hover:bg-gray-50"
          >
            Cancel
          </button>
        )}
      </div>
      {error && <p className="text-sm text-red-600">{error}</p>}
    </div>
  );
}
```

**2. Create profile/page.tsx** at `src/app/portal/(protected)/profile/page.tsx`:

```typescript
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';
import { db } from '@/lib/db';
import { users } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';
import { AgentSignaturePanel } from '../../_components/AgentSignaturePanel';

export default async function ProfilePage() {
  const session = await auth();
  if (!session?.user?.id) redirect('/agent/login');

  const user = await db.query.users.findFirst({
    where: eq(users.id, session.user.id),
    columns: { agentSignatureData: true },
  });

  return (
    <div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
      <h1 className="text-2xl font-semibold text-gray-900">Profile</h1>
      <section className="bg-white border border-gray-200 rounded-lg p-6 space-y-4">
        <div>
          <h2 className="text-lg font-medium text-gray-900">Agent Signature</h2>
          <p className="text-sm text-gray-500 mt-1">
            Draw your signature once. It will be embedded in any "Agent Signature" fields when you prepare a document.
          </p>
        </div>
        <AgentSignaturePanel initialData={user?.agentSignatureData ?? null} />
      </section>
    </div>
  );
}
```

**3. Modify PortalNav.tsx** — add Profile to navLinks:
Find the `navLinks` array (currently has Dashboard and Clients entries). Add a third entry: `{ href: '/portal/profile', label: 'Profile' }`.

**4. Modify FieldPlacer.tsx** — add agent-signature palette token:
Find the `PALETTE_TOKENS` array (currently has 5 entries). Add the agent-signature token as the 6th entry:
```typescript
{ id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' },
```
This is the only change needed to FieldPlacer.tsx — the `validTypes` set already includes `'agent-signature'`.
  </action>
  <verify>
    <automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20</automated>
  </verify>
  <done>TypeScript compiles clean; `npm run dev` starts without error; visiting /portal/profile shows the AgentSignaturePanel; PortalNav shows "Profile" link; FieldPlacer palette shows a red "Agent Signature" token.</done>
</task>

</tasks>

<verification>
After both tasks complete, verify the full Plan 01 state:

1. `npx tsc --noEmit` passes with zero errors
2. `npm run dev` starts without errors
3. Migration applied: `psql -d [db] -c "SELECT column_name FROM information_schema.columns WHERE table_name='users' AND column_name='agent_signature_data';"` returns one row
4. GET /api/agent/signature returns 401 without session; returns `{ agentSignatureData: null }` with valid session
5. /portal/profile renders the AgentSignaturePanel canvas
6. Agent can draw on canvas and click "Save Signature" — PUT returns 200, thumbnail appears
7. Agent can click "Update Signature" — canvas reappears for redraw
8. FieldPlacer on a document shows "Agent Signature" as a draggable red token
</verification>

<success_criteria>
- AGENT-01: Agent can draw and save a signature from /portal/profile; thumbnail shows after save
- AGENT-02: "Update Signature" button replaces saved signature with a new one (same PUT route)
- AGENT-03: "Agent Signature" token appears in FieldPlacer palette; placed fields have type 'agent-signature'
- TypeScript build clean, zero new npm packages
</success_criteria>

<output>
After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md`
</output>
