--- phase: 11.1-agent-and-client-initials plan: "01" type: execute wave: 1 depends_on: [] files_modified: - teressa-copeland-homes/src/lib/db/schema.ts - teressa-copeland-homes/drizzle/0009_agent_initials.sql - teressa-copeland-homes/src/app/api/agent/initials/route.ts - teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx - teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx autonomous: true requirements: - INIT-01 - INIT-02 - INIT-03 - INIT-04 must_haves: truths: - "Agent can navigate to /portal/profile and see both the Agent Signature section and a new Agent Initials section below it" - "Agent can draw initials on the canvas in the Agent Initials section and save them — a thumbnail appears after saving" - "Agent can click 'Update Initials' to redraw and replace their saved initials" - "FieldPlacer palette shows an orange 'Agent Initials' token (7th token, distinct from the purple 'Initials' client token)" - "Placing an agent-initials field saves it in signatureFields with type 'agent-initials'" - "The existing purple 'Initials' token still appears in FieldPlacer and continues to work — no regression" - "'agent-initials' is NOT returned to the client signing page (isClientVisibleField returns false for it)" artifacts: - path: "teressa-copeland-homes/src/lib/db/schema.ts" provides: "agentInitialsData TEXT column on users table; 'agent-initials' added to SignatureFieldType union; isClientVisibleField() guards both agent-signature and agent-initials" contains: "agentInitialsData" - path: "teressa-copeland-homes/src/app/api/agent/initials/route.ts" provides: "GET/PUT endpoints for reading and writing agent initials" exports: ["GET", "PUT"] - path: "teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx" provides: "Client component with signature_pad canvas (80px height), save/update/thumbnail flow" - path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx" provides: "Server component fetching both agentSignatureData and agentInitialsData; renders AgentSignaturePanel + AgentInitialsPanel" key_links: - from: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx" to: "teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx" via: "initialData prop (string | null)" pattern: "initialData.*agentInitialsData" - from: "teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx" to: "/api/agent/initials" via: "fetch PUT with { dataURL }" pattern: "fetch.*api/agent/initials" - from: "teressa-copeland-homes/src/lib/db/schema.ts" to: "isClientVisibleField" via: "returns false for both agent-signature and agent-initials" pattern: "agent-initials.*isClientVisibleField" --- Establish the agent initials storage and UI layer: DB migration adds `agentInitialsData TEXT` to users, GET/PUT API routes read/write the base64 PNG dataURL, a new `AgentInitialsPanel` component (clone of `AgentSignaturePanel` with 80px canvas) is added to the existing `/portal/profile` page below the signature section, and the FieldPlacer palette gets an orange "Agent Initials" token. The `SignatureFieldType` union gains `'agent-initials'` and `isClientVisibleField()` is updated to exclude it from client-visible fields. Purpose: Once the agent has saved their initials and the palette token exists, Plan 02 can wire up preparePdf() embedding without any further schema or UI work. The existing `'initials'` type (client-initials) requires zero changes — it is already fully wired end-to-end from Phase 10. Output: Profile page with working draw/save/update initials flow; agent-initials token in field palette; security boundary in isClientVisibleField(); zero new npm packages. @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md From src/lib/db/schema.ts (current state — Phase 11 complete, agentInitialsData 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(), agentSignatureData: text("agent_signature_data"), // Existing Phase 11 column // Phase 11.1 adds: agentInitialsData: text("agent_initials_data"), }); ``` From src/lib/db/schema.ts (current SignatureFieldType — Phase 11 complete): ```typescript export type SignatureFieldType = | 'client-signature' | 'initials' // This IS client-initials — fully wired, DO NOT RENAME | 'text' | 'checkbox' | 'date' | 'agent-signature'; // Phase 11.1 adds: | 'agent-initials' ``` From src/lib/db/schema.ts (current isClientVisibleField — Phase 11 complete): ```typescript // CURRENT — only guards agent-signature: export function isClientVisibleField(field: SignatureFieldData): boolean { return getFieldType(field) !== 'agent-signature'; } // AFTER Phase 11.1 modification — must guard both: export function isClientVisibleField(field: SignatureFieldData): boolean { const t = getFieldType(field); return t !== 'agent-signature' && t !== 'agent-initials'; } ``` From FieldPlacer.tsx (PALETTE_TOKENS — Phase 11 complete, agent-initials NOT YET present): ```typescript // Current PALETTE_TOKENS (6 entries after Phase 11 — Phase 11.1 adds the 7th): const PALETTE_TOKENS: Array<{ id: SignatureFieldType; label: string; color: string }> = [ { id: 'client-signature', label: 'Signature', color: '#2563eb' }, // blue { id: 'initials', label: 'Initials', color: '#7c3aed' }, // purple (CLIENT initials — leave as-is) { id: 'checkbox', label: 'Checkbox', color: '#059669' }, // green { id: 'date', label: 'Date', color: '#d97706' }, // amber { id: 'text', label: 'Text', color: '#64748b' }, // slate { id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' }, // red // ADD: { id: 'agent-initials', label: 'Agent Initials', color: '#ea580c' }, // orange ]; // validTypes set — currently 6 entries: const validTypes = new Set(['client-signature', 'initials', 'text', 'checkbox', 'date', 'agent-signature']); // Phase 11.1 adds: 'agent-initials' to this set ``` From src/app/portal/_components/AgentSignaturePanel.tsx (template to clone — Phase 11 complete): ```typescript // AgentInitialsPanel is a clone of AgentSignaturePanel with these differences: // 1. API endpoint: '/api/agent/initials' (not '/api/agent/signature') // 2. Canvas height: 80px (not 140px) — initials are compact // 3. Thumbnail max-height: max-h-16 (not max-h-20) — proportionally shorter // 4. Labels: "initials" instead of "signature" throughout // 5. Prop name: initialData (same — matches AgentSignaturePanel) // 6. Error message: 'Please draw your initials first' // 7. Save button text: 'Save Initials' / 'Save Updated Initials' // 8. Update button text: 'Update Initials' // 9. Alt text: 'Saved agent initials' // DPR canvas init, signature_pad, save/error/loading state — identical pattern ``` From src/app/portal/(protected)/profile/page.tsx (Phase 11 state — needs second section): ```typescript // Current query fetches only agentSignatureData: const user = await db.query.users.findFirst({ where: eq(users.id, session.user.id), columns: { agentSignatureData: true }, // Phase 11.1 adds: agentInitialsData: true }); // Phase 11.1 adds AgentInitialsPanel import and a second
below the existing one ``` Migration pattern from drizzle/0008 (accept whatever name drizzle-kit generates for 0009): ```sql -- The generated SQL will be: ALTER TABLE "users" ADD COLUMN "agent_initials_data" text; -- File will be: drizzle/0009_[auto-generated-words].sql ``` Auth pattern (identical to all protected routes): ```typescript import { auth } from '@/lib/auth'; const session = await auth(); if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); ``` Task 1: DB migration, API routes, schema type updates for agent initials storage teressa-copeland-homes/src/lib/db/schema.ts teressa-copeland-homes/drizzle/0009_[auto-named].sql teressa-copeland-homes/src/app/api/agent/initials/route.ts Four changes in sequence: **1. schema.ts — three targeted modifications:** a) Add `agentInitialsData` column to `users` pgTable (alongside existing `agentSignatureData`): ```typescript agentSignatureData: text("agent_signature_data"), // existing agentInitialsData: text("agent_initials_data"), // ADD — nullable, no default ``` b) Extend `SignatureFieldType` union — add `'agent-initials'` as the last member: ```typescript export type SignatureFieldType = | 'client-signature' | 'initials' | 'text' | 'checkbox' | 'date' | 'agent-signature' | 'agent-initials'; // ADD ``` c) Update `isClientVisibleField()` to exclude `'agent-initials'` (CRITICAL — prevents agent-initials coordinates from surfacing in the client signing session): ```typescript export function isClientVisibleField(field: SignatureFieldData): boolean { const t = getFieldType(field); return t !== 'agent-signature' && t !== 'agent-initials'; } ``` **2. Run migration generation and apply:** ```bash cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate ``` Accept whatever filename drizzle-kit generates (will be `0009_[random_words].sql`). The SQL content will be: ```sql ALTER TABLE "users" ADD COLUMN "agent_initials_data" text; ``` **3. Create GET/PUT route at `src/app/api/agent/initials/route.ts`:** Create the directory first: `mkdir -p src/app/api/agent/initials` ```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: { agentInitialsData: true }, }); return Response.json({ agentInitialsData: user?.agentInitialsData ?? 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 initials data' }, { status: 422 }); } if (dataURL.length > 50_000) { return Response.json({ error: 'Initials data too large' }, { status: 422 }); } await db.update(users) .set({ agentInitialsData: dataURL }) .where(eq(users.id, session.user.id)); return Response.json({ ok: true }); } ``` cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 TypeScript compiles clean; migration SQL file exists at drizzle/0009_*.sql; schema.ts has agentInitialsData column, 'agent-initials' in SignatureFieldType, and updated isClientVisibleField(); GET /api/agent/initials returns 401 without session and { agentInitialsData: null } with valid session. Task 2: AgentInitialsPanel component, profile page section update, FieldPlacer token teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx Three files to create/modify: **1. Create AgentInitialsPanel.tsx** at `src/app/portal/_components/AgentInitialsPanel.tsx`: This is a clone of `AgentSignaturePanel.tsx` with the following differences applied (do NOT change anything else): - API endpoint: `/api/agent/initials` - Canvas height: `80px` (initials are compact; signature uses 140px) - Thumbnail: `max-h-16` (vs `max-h-20` for signature) - All user-visible strings use "initials" instead of "signature" - Error message: `'Please draw your initials first'` - Alt text: `'Saved agent initials'` ```typescript 'use client'; import { useEffect, useRef, useState } from 'react'; import SignaturePad from 'signature_pad'; interface AgentInitialsPanelProps { initialData: string | null; } export function AgentInitialsPanel({ initialData }: AgentInitialsPanelProps) { const [savedData, setSavedData] = useState(initialData); const [isDrawing, setIsDrawing] = useState(!initialData); const canvasRef = useRef(null); const sigPadRef = useRef(null); const [saving, setSaving] = useState(false); const [error, setError] = useState(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 initials first'); return; } const dataURL = sigPadRef.current.toDataURL('image/png'); setSaving(true); setError(null); try { const res = await fetch('/api/agent/initials', { 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 (

Your saved initials:

{/* eslint-disable-next-line @next/next/no-img-element */} Saved agent initials
); } return (
{savedData && ( )}
{error &&

{error}

}
); } ``` **2. Modify profile/page.tsx** — two targeted changes: a) Update the DB query to fetch `agentInitialsData` alongside `agentSignatureData` (single query, one round-trip): ```typescript // BEFORE: columns: { agentSignatureData: true }, // AFTER: columns: { agentSignatureData: true, agentInitialsData: true }, ``` b) Add import for `AgentInitialsPanel` and a second `
` below the existing Agent Signature section: ```typescript import { AgentInitialsPanel } from '../../_components/AgentInitialsPanel'; ``` Add this section after the closing `
` of the Agent Signature block: ```tsx

Agent Initials

Draw your initials once. They will be embedded in any "Agent Initials" fields when you prepare a document.

``` **3. Modify FieldPlacer.tsx** — two targeted changes: a) Add `'agent-initials'` token to `PALETTE_TOKENS` (7th entry, after `agent-signature`): ```typescript { id: 'agent-initials', label: 'Agent Initials', color: '#ea580c' }, ``` Orange (`#ea580c`) is unused by any existing token. It visually groups with red (agent-signature) as "agent-owned" while remaining distinct from client-visible tokens. b) Add `'agent-initials'` to the `validTypes` Set: ```typescript // Add 'agent-initials' to the existing set — do not change any other entries const validTypes = new Set(['client-signature', 'initials', 'text', 'checkbox', 'date', 'agent-signature', 'agent-initials']); ``` Do NOT change any other FieldPlacer logic — drag behavior, overlay rendering, and coordinate conversion all apply to agent-initials the same way they apply to agent-signature.
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 TypeScript compiles clean; `npm run dev` starts without error; /portal/profile shows both the Agent Signature section (unchanged) and the new Agent Initials section below it; FieldPlacer palette shows an orange "Agent Initials" token as the 7th entry; the existing purple "Initials" client token is still present and unchanged.
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_initials_data';"` returns one row 4. GET /api/agent/initials returns 401 without session; returns `{ agentInitialsData: null }` with valid session 5. /portal/profile renders both sections — Agent Signature (existing, unchanged) and Agent Initials (new) — with their respective canvases 6. Agent can draw on the initials canvas and click "Save Initials" — PUT /api/agent/initials returns 200, thumbnail appears 7. Agent can click "Update Initials" — canvas reappears for redraw 8. FieldPlacer shows "Agent Initials" as a draggable orange token (7th entry); purple "Initials" client token still present 9. Placing an "Agent Initials" field on a document page saves it with `type: 'agent-initials'` in signatureFields 10. GET /api/sign/[token] does NOT return agent-initials fields (isClientVisibleField returns false) - INIT-01: Agent can draw and save initials from /portal/profile; thumbnail shows after save - INIT-02: "Update Initials" button replaces saved initials with a new one (same PUT route) - INIT-03 (partial): "Agent Initials" token appears in FieldPlacer palette; placed fields have type 'agent-initials'; isClientVisibleField() excludes them from client signing session (Plan 02 completes the embedding) - INIT-04 (confirmed): Existing purple "Initials" token still present in palette; no changes to SigningPageClient.tsx or any client-initials pipeline - TypeScript build clean; zero new npm packages After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-01-SUMMARY.md`