12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-agent-saved-signature-and-signing-workflow | 02 | execute | 2 |
|
|
true |
|
|
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).
<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/STATE.md @.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.mdFrom src/lib/pdf/prepare-document.ts (current function signature — BEFORE Phase 11):
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record<string, string>,
sigFields: SignatureFieldData[],
// Phase 11 adds: agentSignatureData: string | null = null
): Promise<void>
Current agent-signature stub at line ~138 (to be replaced):
} 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):
// 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):
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):
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:
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record<string, string>,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null, // ADD THIS
): Promise<void>
2. Embed the agent signature PNG once before the field loop:
After const pages = pdfDoc.getPages(); and before the field loop, add:
// 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:
} 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):
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:
// 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:
// 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:npx tsc --noEmitpasses with zero errorsnpm run buildsucceeds (ornpm run devstarts clean)- 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
- 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' } - 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
<success_criteria>
- 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 </success_criteria>