---
phase: 11.1-agent-and-client-initials
plan: "02"
type: execute
wave: 2
depends_on:
- "11.1-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:
- INIT-03
- INIT-04
must_haves:
truths:
- "When agent prepares a document with agent-initials fields and saved initials, the prepared PDF contains the initials image at each agent-initials field coordinate"
- "The agent initials are invisible to the client — GET /api/sign/[token] never returns agent-initials field coordinates (isClientVisibleField already guards this from Plan 01)"
- "When agent prepares a document with agent-initials fields but no saved initials, the prepare route returns 422 with { error: 'agent-initials-missing' } — no silent failure"
- "When agent prepares a document with no agent-initials fields, the prepare route succeeds normally regardless of whether initials are saved"
- "The existing 'initials' (client-initials) branch in preparePdf() is completely untouched — purple placeholder still drawn at prepare time, client still prompted during signing"
- "Agent-signature embedding (from Phase 11) continues to work unchanged alongside agent-initials"
artifacts:
- path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
provides: "preparePdf() with agentInitialsData param (6th, optional); embedPng + drawImage at agent-initials field coordinates; existing agent-signature and initials branches untouched"
contains: "agentInitialsData"
- path: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts"
provides: "Fetches both agentSignatureData and agentInitialsData in a single DB query; 422 guard for missing initials when agent-initials fields present; passes agentInitialsData as 6th arg to preparePdf()"
contains: "agent-initials-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, agentInitialsData)"
pattern: "preparePdf.*agentInitialsData"
- 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, agentInitialsData: true } })"
pattern: "agentInitialsData.*findFirst"
---
Wire the agent initials into the prepare pipeline: `preparePdf()` gains an optional `agentInitialsData` parameter (6th, default null) and embeds the PNG at each agent-initials field coordinate using the same embed-once-draw-many pattern already used for agent-signature. The prepare route updates its single DB query to fetch `agentInitialsData` alongside `agentSignatureData`, adds a 422 guard for missing initials, and passes `agentInitialsData` as the 6th argument to `preparePdf()`.
Purpose: Fulfills INIT-03 — agent's saved initials are baked into the prepared PDF before it reaches the client. Confirms INIT-04 — the existing `'initials'` (client-initials) branch is deliberately left untouched; client-initials already work end-to-end.
Output: Prepared PDFs with embedded agent initials; 422 error when initials are missing; client signing session unaffected; zero regressions.
@/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.1-agent-and-client-initials/11.1-01-SUMMARY.md
From src/lib/pdf/prepare-document.ts (current function signature — Phase 11 complete, agentInitialsData NOT YET present):
```typescript
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null, // Added Phase 11
// Phase 11.1 adds: agentInitialsData: string | null = null,
): Promise
```
Current 'initials' branch in preparePdf() — DO NOT TOUCH (this is client-initials):
```typescript
} else if (fieldType === 'initials') {
// Purple "Initials" placeholder — transparent background, border + label only
page.drawRectangle({
x: field.x, y: field.y, width: field.width, height: field.height,
borderColor: rgb(0.49, 0.23, 0.93), borderWidth: 1.5,
});
page.drawText('Initials', {
x: field.x + 4, y: field.y + 4, size: 8, font: helvetica,
color: rgb(0.49, 0.23, 0.93),
});
}
```
Current agent-signature branch in preparePdf() (Phase 11 — working — do not change):
```typescript
} else if (fieldType === 'agent-signature') {
if (agentSigImage) {
page.drawImage(agentSigImage, {
x: field.x,
y: field.y,
width: field.width,
height: field.height,
});
}
}
```
Pattern for embed-once-draw-many (Phase 11 confirmed working):
```typescript
// BEFORE the field loop — embed once:
let agentSigImage: PDFImage | null = null;
if (agentSignatureData) {
agentSigImage = await pdfDoc.embedPng(agentSignatureData);
}
// Phase 11.1 parallel pattern — add after agentSigImage block:
let agentInitialsImage: PDFImage | null = null;
if (agentInitialsData) {
agentInitialsImage = await pdfDoc.embedPng(agentInitialsData);
}
```
CRITICAL: Do NOT call embedPng() inside the field loop. The Phase 11 pattern calls it once
before the loop and reuses the PDFImage reference. Phase 11.1 follows the same pattern.
From src/app/api/documents/[id]/prepare/route.ts (Phase 11 state — current):
```typescript
// Current: fetches only agentSignatureData
const agentUser = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
columns: { agentSignatureData: true }, // Phase 11.1: ADD agentInitialsData: true
});
const agentSignatureData = agentUser?.agentSignatureData ?? null;
// Phase 11 guard (existing — leave unchanged):
const hasAgentSigFields = sigFields.some(f => getFieldType(f) === 'agent-signature');
if (hasAgentSigFields && !agentSignatureData) {
return Response.json(
{ error: 'agent-signature-missing', message: '...' },
{ status: 422 }
);
}
// Current call (5 args):
await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData);
```
PDFImage type note: PDFImage is already imported (or available as a type import) from
@cantoo/pdf-lib in prepare-document.ts since Phase 11. Use the same import pattern.
Do not add a new import if PDFImage is already destructured from the existing import.
Task 1: preparePdf() — add agentInitialsData param and embed at agent-initials field coordinates
teressa-copeland-homes/src/lib/pdf/prepare-document.ts
Three targeted changes to `preparePdf()` — no other logic changes:
**1. Add `agentInitialsData` parameter** as the 6th parameter with a default of `null` (so the existing 5-arg call site in the prepare route still compiles until Task 2 updates it):
```typescript
export async function preparePdf(
srcPath: string,
destPath: string,
textFields: Record,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null,
agentInitialsData: string | null = null, // ADD THIS
): Promise
```
**2. Embed agent initials image once** — add this block immediately AFTER the existing `agentSigImage` embed block (before the field loop):
```typescript
// Embed agent initials image once — reused across all agent-initials fields
let agentInitialsImage: PDFImage | null = null;
if (agentInitialsData) {
agentInitialsImage = await pdfDoc.embedPng(agentInitialsData);
}
```
If `PDFImage` is already in the existing `@cantoo/pdf-lib` import destructure, use it directly. If not, add `PDFImage` to the existing import — do not create a new import statement.
**3. Add `'agent-initials'` branch** in the field loop — add it as a new `else if` block after the existing `'agent-signature'` branch:
```typescript
} else if (fieldType === 'agent-initials') {
if (agentInitialsImage) {
page.drawImage(agentInitialsImage, {
x: field.x,
y: field.y,
width: field.width,
height: field.height,
});
}
// If no initials saved: the prepare route guards against this with 422 before calling preparePdf
}
```
Do NOT modify:
- The `'initials'` branch (client-initials purple placeholder) — leave completely untouched
- The `'agent-signature'` branch — leave completely untouched
- Any other field type branches or function logic
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20
TypeScript compiles clean; preparePdf() function signature now has 6 parameters (last two optional); agent-initials branch draws the image; existing 'initials' (client-initials) and 'agent-signature' branches are untouched; no regressions on other field type branches.
Task 2: prepare route — fetch agentInitialsData in same query, 422 guard, pass to preparePdf
teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts
Three targeted additions to the POST handler — do not change any other logic:
**1. Update the existing DB query** to fetch `agentInitialsData` alongside `agentSignatureData` in the same `findFirst()` call (one DB round-trip, not two):
```typescript
// BEFORE:
const agentUser = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
columns: { agentSignatureData: true },
});
const agentSignatureData = agentUser?.agentSignatureData ?? null;
// AFTER:
const agentUser = await db.query.users.findFirst({
where: eq(users.id, session.user.id),
columns: { agentSignatureData: true, agentInitialsData: true }, // ADD agentInitialsData
});
const agentSignatureData = agentUser?.agentSignatureData ?? null;
const agentInitialsData = agentUser?.agentInitialsData ?? null; // ADD
```
**2. Add the agent-initials 422 guard** — add this block immediately AFTER the existing `hasAgentSigFields` guard (NOT before it):
```typescript
// Guard: agent-initials fields present but no initials saved
const hasAgentInitialsFields = sigFields.some(f => getFieldType(f) === 'agent-initials');
if (hasAgentInitialsFields && !agentInitialsData) {
return Response.json(
{ error: 'agent-initials-missing', message: 'No agent initials saved. Go to Profile to save your initials first.' },
{ status: 422 }
);
}
```
**3. Update the preparePdf() call** to pass `agentInitialsData` as the 6th argument:
```typescript
// BEFORE (5 args):
await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData);
// AFTER (6 args):
await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData, agentInitialsData);
```
Do not change any other logic in the route handler — 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 both agentSignatureData and agentInitialsData in a single query; guard returns 422 with { error: 'agent-initials-missing' } when agent-initials fields exist but no initials saved; preparePdf called with 6 args including agentInitialsData.
After both tasks complete, verify the full Plan 02 state:
1. `npx tsc --noEmit` passes with zero errors
2. `npm run dev` starts without errors (or `npm run build` succeeds)
3. Agent-initials round-trip test:
a. Visit /portal/profile, draw and save initials
b. Open a document in the portal, place an "Agent Initials" field (orange 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 initials PNG is embedded at the correct position
4. Agent-initials guard test — prepare with no saved initials:
a. Place an agent-initials field on a document for an account with no initials saved
b. Prepare route returns 422 with `{ error: 'agent-initials-missing' }`
5. Agent-signature regression test — confirm Phase 11 behavior unchanged:
a. Place an agent-signature field, ensure a signature is saved, prepare the document
b. Signature still embeds correctly; no regression
6. Client-initials regression test — confirm existing 'initials' behavior unchanged:
a. Place a purple "Initials" field, prepare the document
b. Prepared PDF shows purple "Initials" placeholder at the field location
c. Open the signing link — client still sees the initials overlay and can initial the field
7. Client signing page isolation test:
a. Open the signing link for a document with agent-initials fields
b. Signing page does NOT show an overlay at agent-initials coordinates (isClientVisibleField already returns false from Plan 01)
- INIT-03: Agent's saved initials PNG is embedded at each agent-initials field coordinate in the prepared PDF; field is never exposed to the client
- INIT-04: Existing 'initials' type (client-initials) behavior confirmed unchanged — client is still prompted to initial during signing session
- Prepare fails with actionable 422 when agent-initials fields exist but no initials saved
- Agent-signature embedding (Phase 11) still works — no regression
- TypeScript build clean; zero new npm packages; no regressions on any other field type