docs(11.1): create phase plan — agent and client initials

Added INIT-01 through INIT-04 requirements to REQUIREMENTS.md. Updated
ROADMAP.md with Phase 11.1 goal, success criteria, and plan list. Created
three plan files mirroring Phase 11 structure: 11.1-01 (DB migration, API
routes, AgentInitialsPanel, FieldPlacer token), 11.1-02 (preparePdf
agentInitialsData param + prepare route guard), 11.1-03 (human E2E
verification checkpoint).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chandler Copeland
2026-03-21 14:55:00 -06:00
parent e673799246
commit ae17e017d3
5 changed files with 948 additions and 4 deletions

View File

@@ -83,6 +83,13 @@
- [x] **AGENT-03**: Agent can place agent signature field markers on a PDF - [x] **AGENT-03**: Agent can place agent signature field markers on a PDF
- [x] **AGENT-04**: Agent applies their saved signature to agent signature fields during document preparation (before sending to client) - [x] **AGENT-04**: Agent applies their saved signature to agent signature fields during document preparation (before sending to client)
### Initials
- [ ] **INIT-01**: Agent can draw and save initials to their account profile — thumbnail is displayed on the profile page (drawn once, reused)
- [ ] **INIT-02**: Agent can update (replace) their saved initials at any time
- [ ] **INIT-03**: Agent can place agent-initials field markers on a PDF — saved initials PNG is embedded at prepare time, invisible to the client during signing
- [ ] **INIT-04**: Agent can place client-initials (existing 'initials' type) fields on a PDF — client must initial each one during the signing session (the 'initials' field type is already fully wired end-to-end; this requirement is satisfied by confirming FieldPlacer exposes the token and the signing page handles it correctly)
### Document Preview ### Document Preview
- [ ] **PREV-01**: Agent sees a live filled preview of the fully-prepared document (text filled, signatures embedded) before sending to client - [ ] **PREV-01**: Agent sees a live filled preview of the fully-prepared document (text filled, signatures embedded) before sending to client
@@ -177,16 +184,20 @@ Which phases cover which requirements. Updated during roadmap creation.
| AGENT-02 | Phase 11 | Complete | | AGENT-02 | Phase 11 | Complete |
| AGENT-03 | Phase 11 | Complete | | AGENT-03 | Phase 11 | Complete |
| AGENT-04 | Phase 11 | Complete | | AGENT-04 | Phase 11 | Complete |
| INIT-01 | Phase 11.1 | Pending |
| INIT-02 | Phase 11.1 | Pending |
| INIT-03 | Phase 11.1 | Pending |
| INIT-04 | Phase 11.1 | Pending |
| PREV-01 | Phase 12 | Pending | | PREV-01 | Phase 12 | Pending |
| AI-01 | Phase 13 | Pending | | AI-01 | Phase 13 | Pending |
| AI-02 | Phase 13 | Pending | | AI-02 | Phase 13 | Pending |
**Coverage:** **Coverage:**
- v1.0 requirements: 28 total — mapped to phases 1-7 — all Complete - v1.0 requirements: 28 total — mapped to phases 1-7 — all Complete
- v1.1 requirements: 13 total — mapped to phases 8-13 — all Pending - v1.1 requirements: 17 total — mapped to phases 8-13 — 13 Complete, 4 Pending (INIT-01 through INIT-04)
- Total mapped: 41 - Total mapped: 45
- Unmapped: 0 - Unmapped: 0
--- ---
*Requirements defined: 2026-03-19* *Requirements defined: 2026-03-19*
*Last updated: 2026-03-21 after v1.1 roadmap creation (phases 8-13)* *Last updated: 2026-03-21 after Phase 11.1 insertion — INIT-01 through INIT-04 added*

View File

@@ -162,6 +162,7 @@ Plans:
- [x] **Phase 9: Client Property Address** - Add property address to client profiles for AI pre-fill data sourcing (completed 2026-03-21) - [x] **Phase 9: Client Property Address** - Add property address to client profiles for AI pre-fill data sourcing (completed 2026-03-21)
- [x] **Phase 10: Expanded Field Types End-to-End** - All new field types (text, checkbox, initials, date) available in field mapper UI and embedded correctly by the prepare pipeline (completed 2026-03-21) - [x] **Phase 10: Expanded Field Types End-to-End** - All new field types (text, checkbox, initials, date) available in field mapper UI and embedded correctly by the prepare pipeline (completed 2026-03-21)
- [x] **Phase 11: Agent Saved Signature and Signing Workflow** - Draw once, save, apply to agent signature fields during document preparation before sending to client (completed 2026-03-21) - [x] **Phase 11: Agent Saved Signature and Signing Workflow** - Draw once, save, apply to agent signature fields during document preparation before sending to client (completed 2026-03-21)
- [ ] **Phase 11.1: Agent and Client Initials (INSERTED)** - Agent draws initials once, saves to profile, places agent-initials fields (embedded at prepare time); client-initials fields already wired via existing 'initials' type
- [ ] **Phase 12: Filled Document Preview** - Agent sees a live filled preview of the fully-prepared document before the Send button is available - [ ] **Phase 12: Filled Document Preview** - Agent sees a live filled preview of the fully-prepared document before the Send button is available
- [ ] **Phase 13: AI Field Placement and Pre-fill** - One-click AI auto-placement of all field types plus pre-fill of text fields from client profile data - [ ] **Phase 13: AI Field Placement and Pre-fill** - One-click AI auto-placement of all field types plus pre-fill of text fields from client profile data
@@ -227,6 +228,24 @@ Plans:
- [ ] 11-02-PLAN.md — preparePdf() agentSignatureData param + embedPng/drawImage at agent-sig coordinates; prepare route fetches agentSignatureData + 422 guard when sig missing - [ ] 11-02-PLAN.md — preparePdf() agentSignatureData param + embedPng/drawImage at agent-sig coordinates; prepare route fetches agentSignatureData + 422 guard when sig missing
- [x] 11-03-PLAN.md — Full Phase 11 human verification checkpoint (draw, save, place, prepare, verify embedded in PDF + absent from client signing page) (completed 2026-03-21) - [x] 11-03-PLAN.md — Full Phase 11 human verification checkpoint (draw, save, place, prepare, verify embedded in PDF + absent from client signing page) (completed 2026-03-21)
### Phase 11.1: Agent and Client Initials (INSERTED)
**Goal**: Agent draws initials once, saves to their profile, places agent-initials fields on documents (embedded at prepare time, invisible to client); the existing 'initials' type already handles client-initials end-to-end with no changes required
**Requirements**: INIT-01, INIT-02, INIT-03, INIT-04
**Depends on**: Phase 11
**Plans**: 3 plans
**Success Criteria** (what must be TRUE):
1. Agent can draw and save initials on the profile page — thumbnail displayed
2. Agent can update (replace) saved initials at any time
3. Agent can place agent-initials token in FieldPlacer; prepared PDF embeds the initials PNG at each agent-initials field coordinate; field never surfaces to client during signing
4. The existing 'initials' token in FieldPlacer continues to work for client-initials; client is prompted to initial each field during signing session (no code changes needed — confirmed by Plan 01 verification)
Plans:
- [ ] 11.1-01-PLAN.md — DB migration (agentInitialsData TEXT on users), GET/PUT /api/agent/initials routes, AgentInitialsPanel component, profile page section, FieldPlacer agent-initials palette token (orange), isClientVisibleField() + SignatureFieldType updates
- [ ] 11.1-02-PLAN.md — preparePdf() agentInitialsData param + embedPng/drawImage at agent-initials coordinates; prepare route fetches agentInitialsData + 422 guard; confirm 'initials' (client-initials) unchanged
- [ ] 11.1-03-PLAN.md — Full Phase 11.1 human verification checkpoint
### Phase 12: Filled Document Preview ### Phase 12: Filled Document Preview
**Goal**: Agent sees a live filled preview of the fully-prepared document — with all text, signatures, and field stamps embedded — before the Send button becomes available **Goal**: Agent sees a live filled preview of the fully-prepared document — with all text, signatures, and field stamps embedded — before the Send button becomes available
**Depends on**: Phase 11 **Depends on**: Phase 11
@@ -262,7 +281,7 @@ Plans:
## Progress ## Progress
**Execution Order:** **Execution Order:**
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.1 → 12 → 13
| Phase | Milestone | Plans Complete | Status | Completed | | Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------| |-------|-----------|----------------|--------|-----------|
@@ -277,5 +296,6 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 9. Client Property Address | 1/1 | Complete | 2026-03-21 | - | | 9. Client Property Address | 1/1 | Complete | 2026-03-21 | - |
| 10. Expanded Field Types End-to-End | v1.1 | 3/3 | Complete | 2026-03-21 | | 10. Expanded Field Types End-to-End | v1.1 | 3/3 | Complete | 2026-03-21 |
| 11. Agent Saved Signature and Signing Workflow | 3/3 | Complete | 2026-03-21 | - | | 11. Agent Saved Signature and Signing Workflow | 3/3 | Complete | 2026-03-21 | - |
| 11.1. Agent and Client Initials (INSERTED) | v1.1 | 0/3 | Not started | - |
| 12. Filled Document Preview | v1.1 | 0/2 | Not started | - | | 12. Filled Document Preview | v1.1 | 0/2 | Not started | - |
| 13. AI Field Placement and Pre-fill | v1.1 | 0/4 | Not started | - | | 13. AI Field Placement and Pre-fill | v1.1 | 0/4 | Not started | - |

View File

@@ -0,0 +1,484 @@
---
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"
---
<objective>
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.
</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/11-agent-saved-signature-and-signing-workflow/11-01-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 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<string>(['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 <section> 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 });
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: DB migration, API routes, schema type updates for agent initials storage</name>
<files>
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
</files>
<action>
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 });
}
```
</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/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.</done>
</task>
<task type="auto">
<name>Task 2: AgentInitialsPanel component, profile page section update, FieldPlacer token</name>
<files>
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
</files>
<action>
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<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 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 (
<div className="space-y-4">
<p className="text-sm text-gray-600">Your saved initials:</p>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={savedData}
alt="Saved agent initials"
className="max-h-16 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 Initials
</button>
</div>
);
}
return (
<div className="space-y-3">
<canvas
ref={canvasRef}
className="w-full border border-gray-300 rounded bg-white"
style={{ height: '80px', 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 Initials' : 'Save Initials')}
</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. 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 `<section>` below the existing Agent Signature section:
```typescript
import { AgentInitialsPanel } from '../../_components/AgentInitialsPanel';
```
Add this section after the closing `</section>` of the Agent Signature block:
```tsx
<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 Initials</h2>
<p className="text-sm text-gray-500 mt-1">
Draw your initials once. They will be embedded in any &quot;Agent Initials&quot; fields when you prepare a document.
</p>
</div>
<AgentInitialsPanel initialData={user?.agentInitialsData ?? null} />
</section>
```
**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<string>(['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.
</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; /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.</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_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)
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,300 @@
---
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"
---
<objective>
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.
</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/STATE.md
@.planning/phases/11.1-agent-and-client-initials/11.1-01-SUMMARY.md
<interfaces>
<!-- Key types and confirmed patterns from codebase (2026-03-21 inspection + Phase 11 Plan 02 established pattern). -->
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<string, string>,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null, // Added Phase 11
// Phase 11.1 adds: agentInitialsData: string | null = null,
): Promise<void>
```
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.
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: preparePdf() — add agentInitialsData param and embed at agent-initials field coordinates</name>
<files>
teressa-copeland-homes/src/lib/pdf/prepare-document.ts
</files>
<action>
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<string, string>,
sigFields: SignatureFieldData[],
agentSignatureData: string | null = null,
agentInitialsData: string | null = null, // ADD THIS
): Promise<void>
```
**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
</action>
<verify>
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20</automated>
</verify>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: prepare route — fetch agentInitialsData in same query, 422 guard, pass to preparePdf</name>
<files>
teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts
</files>
<action>
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.
</action>
<verify>
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<verification>
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)
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,129 @@
---
phase: 11.1-agent-and-client-initials
plan: "03"
type: execute
wave: 3
depends_on:
- "11.1-01"
- "11.1-02"
files_modified: []
autonomous: false
requirements:
- INIT-01
- INIT-02
- INIT-03
- INIT-04
must_haves:
truths:
- "All four INIT requirements (INIT-01 through INIT-04) verified by human in a single live test"
- "Agent initials draw, save, place, prepare, and PDF embedding round-trip confirmed working"
- "Client-initials (existing 'initials' type) confirmed unchanged — client can still initial fields during signing"
- "Agent-initials field never surfaces to the client signing page"
artifacts:
- path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
provides: "Profile page with both Agent Signature and Agent Initials sections visible"
- path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
provides: "preparePdf() embeds agent initials at agent-initials field coordinates"
key_links:
- from: "Agent saves initials at /portal/profile"
to: "Prepared PDF"
via: "PUT /api/agent/initials → users.agentInitialsData → preparePdf() → pdfDoc.embedPng + drawImage"
pattern: "full pipeline"
---
<objective>
Human end-to-end verification of all Phase 11.1 requirements: draw and save initials on the profile page, place an agent-initials field, prepare the document, confirm the initials are embedded in the prepared PDF and absent from the client signing session, and confirm the existing client-initials flow is unaffected.
Purpose: All four INIT requirements are verified end-to-end by a human using the portal. No code changes in this plan — all deliverables were completed in Plans 01 and 02.
Output: Phase 11.1 marked complete; INIT-01 through INIT-04 confirmed working.
</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/STATE.md
@.planning/phases/11.1-agent-and-client-initials/11.1-01-SUMMARY.md
@.planning/phases/11.1-agent-and-client-initials/11.1-02-SUMMARY.md
</context>
<tasks>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 1: Human verification — full Phase 11.1 agent and client initials end-to-end</name>
<action>Run the dev server and execute the verification steps below. There are no code changes in this plan — all code was delivered in Plans 01 and 02.</action>
<what-built>
Complete Phase 11.1 agent and client initials implementation:
- DB migration: agentInitialsData TEXT column on users table
- GET/PUT /api/agent/initials API routes
- AgentInitialsPanel component on /portal/profile (below Agent Signature section)
- Orange "Agent Initials" token in FieldPlacer palette (7th token)
- isClientVisibleField() updated to exclude agent-initials from client signing session
- preparePdf() embeds agentInitialsData PNG at agent-initials field coordinates
- prepare route 422 guard for missing initials + combined single-query fetch
- Existing 'initials' (client-initials) pipeline confirmed untouched throughout
</what-built>
<how-to-verify>
Start the dev server: `cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run dev`
**Step 1 — Save agent initials (INIT-01):**
- Visit http://localhost:3000/portal/profile
- Confirm the page shows BOTH the "Agent Signature" section (existing, unchanged) AND a new "Agent Initials" section below it
- Draw your initials on the Agent Initials canvas
- Click "Save Initials"
- Expected: Canvas is replaced by a thumbnail of your initials with an "Update Initials" button
**Step 2 — Update agent initials (INIT-02):**
- Click "Update Initials" on the profile page
- Draw different initials
- Click "Save Updated Initials"
- Expected: New thumbnail replaces the old one
**Step 3 — Place agent-initials field and prepare (INIT-03):**
- Open any document in the portal and go to the prepare tab
- Confirm the FieldPlacer palette shows an orange "Agent Initials" token (7th token, distinct from the purple "Initials" client token)
- Drag the orange "Agent Initials" token and drop it onto the document (any page, any position)
- Expected: An orange field box appears on the PDF where you dropped it
- Assign the document to a client and click Prepare
- Expected: Prepare succeeds (no 422 error)
- Download the prepared PDF
- Open the prepared PDF — confirm your initials PNG is embedded at the position where you placed the field
**Step 4 — Confirm agent-initials absent from client signing page (INIT-03):**
- Send the prepared document to a client (or use the signing link directly)
- Open the signing link in the browser
- Expected: NO interactive overlay appears at the agent-initials field position
- Expected: Client-signature and initials overlays (if placed) still appear normally for the client
**Step 5 — Confirm client-initials unchanged (INIT-04):**
- Open a document and place the purple "Initials" token on it
- Prepare the document — confirm the prepared PDF shows a purple "Initials" placeholder at that position
- Open the signing link — confirm the purple initials overlay appears with animation
- Click the overlay — confirm the modal opens with "Add Initials" as the title
- Submit initials — confirm they are embedded in the signed PDF
</how-to-verify>
<resume-signal>Type "approved" if all 5 steps pass. Describe any issues found if not (include step number and observed vs expected behavior).</resume-signal>
<verify>All 5 steps pass and human types "approved"</verify>
<done>Full end-to-end round-trip confirmed: agent draws/saves initials, places agent-initials field, prepares document with embedded initials PNG, client signing session shows no agent-initials overlay; existing client-initials ('initials' type) flow is unaffected</done>
</task>
</tasks>
<verification>
Human verification approved — all four INIT requirements confirmed by live testing:
- INIT-01: Agent drew and saved initials; thumbnail visible at /portal/profile
- INIT-02: "Update Initials" replaced saved initials with a new one
- INIT-03: Orange "Agent Initials" token visible and usable in FieldPlacer palette; initials PNG embedded in prepared PDF at correct position; absent from client signing session
- INIT-04: Existing purple "Initials" client token unchanged; client still prompted to initial during signing; modal shows "Add Initials"
</verification>
<success_criteria>
Human approves all 5 verification steps. Phase 11.1 is complete and all INIT requirements are satisfied.
</success_criteria>
<output>
After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-03-SUMMARY.md`
</output>