From 77835431581c7077d8878969819c7cd0e867c629 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Sat, 21 Mar 2026 13:55:38 -0600 Subject: [PATCH] docs(11-agent-saved-signature-and-signing-workflow): create phase plan --- .planning/ROADMAP.md | 6 +- .../11-01-PLAN.md | 438 ++++++++++++++++++ .../11-02-PLAN.md | 258 +++++++++++ .../11-03-PLAN.md | 124 +++++ 4 files changed, 823 insertions(+), 3 deletions(-) create mode 100644 .planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-PLAN.md create mode 100644 .planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-PLAN.md create mode 100644 .planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 6ee3b12..3e5b947 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -220,11 +220,11 @@ Plans: 2. Agent can update (replace) their saved signature at any time 3. Agent can place agent signature field markers on a PDF from the FieldPlacer palette 4. When the agent prepares a document, their saved signature PNG is embedded at each agent-signature field coordinate — the field does not appear in the client signing session -**Plans**: TBD +**Plans**: 3 plans Plans: -- [ ] 11-01-PLAN.md — DB migration (agentSignatureData TEXT on users table), GET/PUT /api/agent/signature routes, AgentSignaturePanel component (draw + save + thumbnail) -- [ ] 11-02-PLAN.md — FieldPlacer palette token for agent-signature type, prepare-document.ts PNG embed at agent-sig coordinates +- [ ] 11-01-PLAN.md — DB migration (agentSignatureData TEXT on users), GET/PUT /api/agent/signature routes, AgentSignaturePanel (draw + save + thumbnail), /portal/profile page, PortalNav Profile link, FieldPlacer agent-signature palette token +- [ ] 11-02-PLAN.md — preparePdf() agentSignatureData param + embedPng/drawImage at agent-sig coordinates; prepare route fetches agentSignatureData + 422 guard when sig missing - [ ] 11-03-PLAN.md — Full Phase 11 human verification checkpoint (draw, save, place, prepare, verify embedded in PDF + absent from client signing page) ### Phase 12: Filled Document Preview diff --git a/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-PLAN.md b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-PLAN.md new file mode 100644 index 0000000..c48dab6 --- /dev/null +++ b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-PLAN.md @@ -0,0 +1,438 @@ +--- +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" +--- + + +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. + + + +@/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/10-expanded-field-types-end-to-end/10-03-SUMMARY.md + + + + + +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(['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' } +]; +``` + + + + + + + Task 1: DB migration and API routes for agent signature storage + + 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 + + +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). + + + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + 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. + + + + Task 2: AgentSignaturePanel component, profile page, PortalNav link, FieldPlacer token + + 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 + + +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(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 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 ( +
+

Your saved signature:

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

{error}

} +
+ ); +} +``` + +**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 ( +
+

Profile

+
+
+

Agent Signature

+

+ Draw your signature once. It will be embedded in any "Agent Signature" fields when you prepare a document. +

+
+ +
+
+ ); +} +``` + +**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'`. +
+ + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + 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. +
+ +
+ + +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 + + + +- 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 + + + +After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md` + diff --git a/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-PLAN.md b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-PLAN.md new file mode 100644 index 0000000..daff9f4 --- /dev/null +++ b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-PLAN.md @@ -0,0 +1,258 @@ +--- +phase: 11-agent-saved-signature-and-signing-workflow +plan: "02" +type: execute +wave: 2 +depends_on: + - "11-01" +files_modified: + - teressa-copeland-homes/src/lib/pdf/prepare-document.ts + - teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts +autonomous: true +requirements: + - AGENT-04 + +must_haves: + truths: + - "When agent prepares a document with agent-signature fields and a saved signature, the prepared PDF contains the signature image at each agent-sig field coordinate" + - "The agent signature is invisible to the client — it is never returned by GET /api/sign/[token]" + - "When agent prepares a document with agent-signature fields but no saved signature, the prepare route returns 422 with { error: 'agent-signature-missing' } — no silent failure" + - "When agent prepares a document with no agent-signature fields, the prepare route succeeds normally regardless of whether a signature is saved" + artifacts: + - path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts" + provides: "preparePdf() with agentSignatureData param; embedPng + drawImage at agent-sig field coordinates" + contains: "agentSignatureData" + - path: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts" + provides: "Fetches agentSignatureData from DB; 422 guard when agent-sig fields present but no sig saved; passes to preparePdf()" + contains: "agent-signature-missing" + key_links: + - from: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts" + to: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts" + via: "preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData)" + pattern: "preparePdf.*agentSignatureData" + - from: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts" + to: "teressa-copeland-homes/src/lib/db/schema.ts" + via: "db.query.users.findFirst({ columns: { agentSignatureData: true } })" + pattern: "agentSignatureData.*findFirst" +--- + + +Wire the agent signature into the prepare pipeline: `preparePdf()` gains an `agentSignatureData` parameter and embeds the PNG at each agent-signature field coordinate using the same `pdfDoc.embedPng()` + `page.drawImage()` pattern already used for client signatures. The prepare route fetches the agent's saved signature from the DB and passes it to `preparePdf()`, with a 422 guard if agent-signature fields are present but no signature has been saved. + +Purpose: Fulfills AGENT-04 — agent's saved signature is baked into the prepared PDF before it reaches the client. +Output: Prepared PDFs with embedded agent signatures; 422 error when signature is missing; client signing session unaffected (isClientVisibleField filter already excludes agent-signature). + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/STATE.md +@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md + + + + +From src/lib/pdf/prepare-document.ts (current function signature — BEFORE Phase 11): +```typescript +export async function preparePdf( + srcPath: string, + destPath: string, + textFields: Record, + sigFields: SignatureFieldData[], + // Phase 11 adds: agentSignatureData: string | null = null +): Promise +``` + +Current agent-signature stub at line ~138 (to be replaced): +```typescript +} else if (fieldType === 'agent-signature') { + // Skip — agent signature handled by Phase 11; no placeholder drawn here +} +``` + +From src/lib/signing/embed-signature.ts (confirmed working embedPng pattern): +```typescript +// embedPng accepts base64 DataURL directly (no Buffer conversion needed) +const pngImage = await pdfDoc.embedPng(sig.dataURL); // 'data:image/png;base64,...' +page.drawImage(pngImage, { + x: sig.x, + y: sig.y, + width: sig.width, + height: sig.height, +}); +``` + +From src/app/api/documents/[id]/prepare/route.ts (current POST handler structure): +```typescript +export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + // ... body parsing, doc lookup, path resolution ... + await preparePdf(srcPath, destPath, textFields, sigFields); // 4 args currently + // ... DB update, audit log, return ... +} +``` + +From src/lib/db/schema.ts (getFieldType — confirmed working pattern): +```typescript +export function getFieldType(field: SignatureFieldData): SignatureFieldType { + return field.type ?? 'client-signature'; +} +``` + +CRITICAL: embedPng is called ONCE per document (not once per field). The returned PDFImage +object is reused across multiple drawImage calls. Call pdfDoc.embedPng(agentSignatureData) +before the field loop, store as agentSigImage, then reference agentSigImage inside the loop. + +COORDINATE SYSTEM: Use field.x, field.y, field.width, field.height directly. These are already +stored in PDF user-space (bottom-left origin) by FieldPlacer's screenToPdfCoords() conversion. +Do NOT invert or adjust Y — this is identical to how embedSignatureInPdf() works for client sigs. + +IMPORT NOTE: PDFImage type from @cantoo/pdf-lib is needed for the agentSigImage variable type. +Check prepare-document.ts existing imports — PDFDocument, rgb, StandardFonts, etc. are already +imported from @cantoo/pdf-lib. Add PDFImage to that import if not already present. + + + + + + + Task 1: preparePdf() — add agentSignatureData param and embed at agent-sig field coordinates + + teressa-copeland-homes/src/lib/pdf/prepare-document.ts + + +Two targeted changes to `preparePdf()`: + +**1. Add `agentSignatureData` parameter** with a default of `null` so existing call sites compile without change until the prepare route is updated in Task 2: +```typescript +export async function preparePdf( + srcPath: string, + destPath: string, + textFields: Record, + sigFields: SignatureFieldData[], + agentSignatureData: string | null = null, // ADD THIS +): Promise +``` + +**2. Embed the agent signature PNG once before the field loop:** +After `const pages = pdfDoc.getPages();` and before the field loop, add: +```typescript +// Embed agent signature image once — reused across all agent-sig fields +let agentSigImage: import('@cantoo/pdf-lib').PDFImage | null = null; +if (agentSignatureData) { + agentSigImage = await pdfDoc.embedPng(agentSignatureData); +} +``` + +If PDFImage is already imported from @cantoo/pdf-lib at the top of the file (check the existing imports), use the named import directly instead of the inline import type. Only add `PDFImage` to the destructured import if it is not already there — do not change any other imports. + +**3. Replace the agent-signature stub** in the field loop with real embedding: +Find the existing `else if (fieldType === 'agent-signature') { // Skip — ... }` block and replace it with: +```typescript +} else if (fieldType === 'agent-signature') { + if (agentSigImage) { + page.drawImage(agentSigImage, { + x: field.x, + y: field.y, + width: field.width, + height: field.height, + }); + } + // If no signature saved: the prepare route guards against this with 422 before calling preparePdf +} +``` + +No other changes to prepare-document.ts. The function body, other field type branches, and the atomic write logic remain untouched. + + + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + TypeScript compiles clean; preparePdf() function signature now has 5 parameters (last is optional); agent-signature stub replaced with drawImage call; no regressions in other field type branches. + + + + Task 2: prepare route — fetch agentSignatureData, 422 guard, pass to preparePdf + + teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts + + +Three additions to the POST handler in `src/app/api/documents/[id]/prepare/route.ts`: + +**1. Import `users` from schema** (if not already imported — check existing imports at top of file): +```typescript +import { users } from '@/lib/db/schema'; +``` +Also ensure `getFieldType` is imported from `@/lib/db/schema` (needed for the guard check). If already present, do not duplicate. + +**2. Fetch agentSignatureData from DB** — add this block AFTER the existing doc lookup and sigFields parsing, BEFORE the `preparePdf()` call: +```typescript +// Fetch agent's saved signature for embedding at agent-signature field coordinates +const agentUser = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { agentSignatureData: true }, +}); +const agentSignatureData = agentUser?.agentSignatureData ?? null; + +// Guard: if document has agent-signature fields but no signature saved, block prepare +const hasAgentSigFields = sigFields.some(f => getFieldType(f) === 'agent-signature'); +if (hasAgentSigFields && !agentSignatureData) { + return Response.json( + { error: 'agent-signature-missing', message: 'No agent signature saved. Go to Profile to save your signature first.' }, + { status: 422 } + ); +} +``` + +**3. Pass `agentSignatureData` to `preparePdf()`** — update the existing call: +```typescript +// BEFORE (4 args): +await preparePdf(srcPath, destPath, textFields, sigFields); + +// AFTER (5 args): +await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData); +``` + +Do not change any other logic in the route handler — the DB update, audit logging, and response all remain unchanged. + + + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + TypeScript compiles clean; prepare route fetches agentSignatureData; guard returns 422 with { error: 'agent-signature-missing' } when agent-sig fields exist but no signature saved; preparePdf called with 5 args including agentSignatureData. + + + + + +After both tasks complete, verify the full Plan 02 state: + +1. `npx tsc --noEmit` passes with zero errors +2. `npm run build` succeeds (or `npm run dev` starts clean) +3. Functional test — draw save place prepare round-trip: + a. Visit /portal/profile, draw and save a signature + b. Open a document in the portal, place an "Agent Signature" field token on any page + c. Fill text fields and click Prepare + d. Prepare succeeds (200 response) + e. Download the prepared PDF and verify the agent signature PNG is embedded at the correct position +4. Guard test — prepare with no saved signature: + a. Use a fresh test account (or temporarily clear agentSignatureData in DB) + b. Place an agent-signature field, attempt to prepare + c. Prepare route returns 422 with `{ error: 'agent-signature-missing' }` +5. Client signing page test — open the signing link for the prepared document: + a. Signing page does NOT show an agent-signature field overlay (isClientVisibleField already filters it) + b. Client can sign normally — only client-signature and initials overlays appear + + + +- AGENT-04: Agent's saved PNG is embedded at each agent-signature field coordinate in the prepared PDF +- No agent-signature content is exposed to the client via GET /api/sign/[token] +- Prepare fails with actionable 422 when agent-sig fields exist but no signature saved +- TypeScript build clean; zero new npm packages; no regressions on other field types + + + +After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-SUMMARY.md` + diff --git a/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-PLAN.md b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-PLAN.md new file mode 100644 index 0000000..06d8f86 --- /dev/null +++ b/.planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-PLAN.md @@ -0,0 +1,124 @@ +--- +phase: 11-agent-saved-signature-and-signing-workflow +plan: "03" +type: execute +wave: 3 +depends_on: + - "11-01" + - "11-02" +files_modified: [] +autonomous: false +requirements: + - AGENT-01 + - AGENT-02 + - AGENT-03 + - AGENT-04 + +must_haves: + truths: + - "Agent can draw, save, and see a thumbnail of their signature from /portal/profile" + - "Agent can update (replace) their saved signature" + - "Agent can place an Agent Signature token on a PDF document in the FieldPlacer" + - "Preparing a document with agent-signature fields embeds the saved PNG in the correct position in the prepared PDF" + - "Opening the client signing link for the prepared document does NOT show any agent-signature overlay" + artifacts: + - path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx" + provides: "Accessible profile page with draw/save/thumbnail signature flow" + - path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts" + provides: "preparePdf with agent PNG embedding at agent-sig field coordinates" + key_links: + - from: "Agent saves signature at /portal/profile" + to: "Prepared PDF" + via: "PUT /api/agent/signature → users.agentSignatureData → preparePdf() → pdfDoc.embedPng + drawImage" + pattern: "full pipeline" +--- + + +Human verification of the complete Phase 11 agent signature workflow: draw once at profile, place fields on a document, prepare the document, verify the signature is embedded in the correct position in the PDF, and confirm the client signing session is unaffected. + +Purpose: All four AGENT requirements are verified end-to-end by a human using the portal. +Output: Phase 11 marked complete; AGENT-01 through AGENT-04 confirmed working. + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/STATE.md +@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md +@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-SUMMARY.md + + + + + + Task 1: Human verification — full Phase 11 agent signature end-to-end + Run the dev server and execute the 5-step verification below. There are no code changes in this plan — all code was delivered in Plans 01 and 02. + + Complete Phase 11 agent signature workflow: + - /portal/profile page with signature draw canvas and thumbnail + - "Agent Signature" token in FieldPlacer palette (red, draggable) + - preparePdf() embeds saved PNG at agent-signature field coordinates + - Prepare route fetches agentSignatureData and passes 422 guard + - Client signing session filters out agent-signature fields (unchanged from Phase 8) + + + Start the dev server: `cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run dev` + + **Step 1 — Save your signature (AGENT-01):** + - Visit http://localhost:3000/portal/profile + - Confirm "Profile" appears in the portal nav + - Draw a distinctive signature on the canvas + - Click "Save Signature" + - Expected: Canvas is replaced by a thumbnail image of your signature + + **Step 2 — Update signature (AGENT-02):** + - Click "Update Signature" on the profile page + - Draw a slightly different signature + - Click "Save Updated Signature" + - Expected: New thumbnail replaces the old one + + **Step 3 — Place agent-signature field (AGENT-03):** + - Open any document in the portal and go to the prepare tab + - Confirm the FieldPlacer palette shows a red "Agent Signature" token + - Drag the "Agent Signature" token and drop it onto the document (any page, any position) + - Expected: A red field box appears on the PDF where you dropped it + + **Step 4 — Prepare with embedded signature (AGENT-04):** + - Assign the document to a client, fill any required text fields + - Click Prepare + - Expected: Prepare succeeds (no error, document status changes) + - Download the prepared PDF from the portal + - Open the downloaded PDF and navigate to the page where you placed the agent-signature field + - Expected: Your saved signature image appears at that position in the PDF + + **Step 5 — Client signing session is unaffected:** + - Send the prepared document to a client (or use a test signing link) + - Open the signing link in the browser + - Expected: The client signing page does NOT show any overlay or prompt for the agent-signature position + - Expected: Client-signature and initials overlays (if placed) still appear normally for the client + + Type "approved" if all 5 steps pass. Describe any issues found if not. + All 5 steps pass and human types "approved" + Full end-to-end round-trip confirmed: agent draws, saves, places field, prepares, and embedded signature appears in prepared PDF; client signing session is unaffected + + + + + +Human verification approved — all four AGENT requirements confirmed by live testing: +- AGENT-01: Agent drew and saved signature; thumbnail visible at /portal/profile +- AGENT-02: "Update Signature" replaced saved signature with a new one +- AGENT-03: Red "Agent Signature" token visible and usable in FieldPlacer palette +- AGENT-04: Prepared PDF contains agent signature PNG at the placed field coordinates; client signing session shows no agent-signature overlay + + + +Human approves all 5 verification steps. Phase 11 is complete and all AGENT requirements are satisfied. + + + +After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-SUMMARY.md` +