diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 986fa7f..bacecb6 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -83,6 +83,13 @@ - [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) +### 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 - [ ] **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-03 | 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 | | AI-01 | Phase 13 | Pending | | AI-02 | Phase 13 | Pending | **Coverage:** - v1.0 requirements: 28 total — mapped to phases 1-7 — all Complete -- v1.1 requirements: 13 total — mapped to phases 8-13 — all Pending -- Total mapped: 41 +- v1.1 requirements: 17 total — mapped to phases 8-13 — 13 Complete, 4 Pending (INIT-01 through INIT-04) +- Total mapped: 45 - Unmapped: 0 --- *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* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a10fcd8..f0cb8b1 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 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) +- [ ] **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 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 - [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 **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 @@ -262,7 +281,7 @@ Plans: ## Progress **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 | |-------|-----------|----------------|--------|-----------| @@ -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 | - | | 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.1. Agent and Client Initials (INSERTED) | v1.1 | 0/3 | 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 | - | diff --git a/.planning/phases/11.1-agent-and-client-initials/11.1-01-PLAN.md b/.planning/phases/11.1-agent-and-client-initials/11.1-01-PLAN.md new file mode 100644 index 0000000..d7a7384 --- /dev/null +++ b/.planning/phases/11.1-agent-and-client-initials/11.1-01-PLAN.md @@ -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" +--- + + +Establish the agent initials storage and UI layer: DB migration adds `agentInitialsData TEXT` to users, GET/PUT API routes read/write the base64 PNG dataURL, a new `AgentInitialsPanel` component (clone of `AgentSignaturePanel` with 80px canvas) is added to the existing `/portal/profile` page below the signature section, and the FieldPlacer palette gets an orange "Agent Initials" token. The `SignatureFieldType` union gains `'agent-initials'` and `isClientVisibleField()` is updated to exclude it from client-visible fields. + +Purpose: Once the agent has saved their initials and the palette token exists, Plan 02 can wire up preparePdf() embedding without any further schema or UI work. The existing `'initials'` type (client-initials) requires zero changes — it is already fully wired end-to-end from Phase 10. +Output: Profile page with working draw/save/update initials flow; agent-initials token in field palette; security boundary in isClientVisibleField(); zero new npm packages. + + + +@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md +@/Users/ccopeland/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md + + + + + +From src/lib/db/schema.ts (current state — Phase 11 complete, agentInitialsData NOT YET present): +```typescript +export const users = pgTable("users", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + email: text("email").notNull().unique(), + passwordHash: text("password_hash").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + agentSignatureData: text("agent_signature_data"), // Existing Phase 11 column + // Phase 11.1 adds: agentInitialsData: text("agent_initials_data"), +}); +``` + +From src/lib/db/schema.ts (current SignatureFieldType — Phase 11 complete): +```typescript +export type SignatureFieldType = + | 'client-signature' + | 'initials' // This IS client-initials — fully wired, DO NOT RENAME + | 'text' + | 'checkbox' + | 'date' + | 'agent-signature'; + // Phase 11.1 adds: | 'agent-initials' +``` + +From src/lib/db/schema.ts (current isClientVisibleField — Phase 11 complete): +```typescript +// CURRENT — only guards agent-signature: +export function isClientVisibleField(field: SignatureFieldData): boolean { + return getFieldType(field) !== 'agent-signature'; +} + +// AFTER Phase 11.1 modification — must guard both: +export function isClientVisibleField(field: SignatureFieldData): boolean { + const t = getFieldType(field); + return t !== 'agent-signature' && t !== 'agent-initials'; +} +``` + +From FieldPlacer.tsx (PALETTE_TOKENS — Phase 11 complete, agent-initials NOT YET present): +```typescript +// Current PALETTE_TOKENS (6 entries after Phase 11 — Phase 11.1 adds the 7th): +const PALETTE_TOKENS: Array<{ id: SignatureFieldType; label: string; color: string }> = [ + { id: 'client-signature', label: 'Signature', color: '#2563eb' }, // blue + { id: 'initials', label: 'Initials', color: '#7c3aed' }, // purple (CLIENT initials — leave as-is) + { id: 'checkbox', label: 'Checkbox', color: '#059669' }, // green + { id: 'date', label: 'Date', color: '#d97706' }, // amber + { id: 'text', label: 'Text', color: '#64748b' }, // slate + { id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' }, // red + // ADD: { id: 'agent-initials', label: 'Agent Initials', color: '#ea580c' }, // orange +]; + +// validTypes set — currently 6 entries: +const validTypes = new Set(['client-signature', 'initials', 'text', 'checkbox', 'date', 'agent-signature']); +// Phase 11.1 adds: 'agent-initials' to this set +``` + +From src/app/portal/_components/AgentSignaturePanel.tsx (template to clone — Phase 11 complete): +```typescript +// AgentInitialsPanel is a clone of AgentSignaturePanel with these differences: +// 1. API endpoint: '/api/agent/initials' (not '/api/agent/signature') +// 2. Canvas height: 80px (not 140px) — initials are compact +// 3. Thumbnail max-height: max-h-16 (not max-h-20) — proportionally shorter +// 4. Labels: "initials" instead of "signature" throughout +// 5. Prop name: initialData (same — matches AgentSignaturePanel) +// 6. Error message: 'Please draw your initials first' +// 7. Save button text: 'Save Initials' / 'Save Updated Initials' +// 8. Update button text: 'Update Initials' +// 9. Alt text: 'Saved agent initials' +// DPR canvas init, signature_pad, save/error/loading state — identical pattern +``` + +From src/app/portal/(protected)/profile/page.tsx (Phase 11 state — needs second section): +```typescript +// Current query fetches only agentSignatureData: +const user = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { agentSignatureData: true }, // Phase 11.1 adds: agentInitialsData: true +}); +// Phase 11.1 adds AgentInitialsPanel import and a second
below the existing one +``` + +Migration pattern from drizzle/0008 (accept whatever name drizzle-kit generates for 0009): +```sql +-- The generated SQL will be: +ALTER TABLE "users" ADD COLUMN "agent_initials_data" text; +-- File will be: drizzle/0009_[auto-generated-words].sql +``` + +Auth pattern (identical to all protected routes): +```typescript +import { auth } from '@/lib/auth'; +const session = await auth(); +if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); +``` + + + + + + + Task 1: DB migration, API routes, schema type updates for agent initials storage + + teressa-copeland-homes/src/lib/db/schema.ts + teressa-copeland-homes/drizzle/0009_[auto-named].sql + teressa-copeland-homes/src/app/api/agent/initials/route.ts + + +Four changes in sequence: + +**1. schema.ts — three targeted modifications:** + +a) Add `agentInitialsData` column to `users` pgTable (alongside existing `agentSignatureData`): +```typescript +agentSignatureData: text("agent_signature_data"), // existing +agentInitialsData: text("agent_initials_data"), // ADD — nullable, no default +``` + +b) Extend `SignatureFieldType` union — add `'agent-initials'` as the last member: +```typescript +export type SignatureFieldType = + | 'client-signature' + | 'initials' + | 'text' + | 'checkbox' + | 'date' + | 'agent-signature' + | 'agent-initials'; // ADD +``` + +c) Update `isClientVisibleField()` to exclude `'agent-initials'` (CRITICAL — prevents agent-initials coordinates from surfacing in the client signing session): +```typescript +export function isClientVisibleField(field: SignatureFieldData): boolean { + const t = getFieldType(field); + return t !== 'agent-signature' && t !== 'agent-initials'; +} +``` + +**2. Run migration generation and apply:** +```bash +cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate +``` +Accept whatever filename drizzle-kit generates (will be `0009_[random_words].sql`). The SQL content will be: +```sql +ALTER TABLE "users" ADD COLUMN "agent_initials_data" text; +``` + +**3. Create GET/PUT route at `src/app/api/agent/initials/route.ts`:** +Create the directory first: `mkdir -p src/app/api/agent/initials` + +```typescript +import { auth } from '@/lib/auth'; +import { db } from '@/lib/db'; +import { users } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; + +export async function GET() { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + + const user = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { agentInitialsData: true }, + }); + + return Response.json({ agentInitialsData: user?.agentInitialsData ?? null }); +} + +export async function PUT(req: Request) { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + + const { dataURL } = await req.json() as { dataURL: string }; + + if (!dataURL || !dataURL.startsWith('data:image/png;base64,')) { + return Response.json({ error: 'Invalid initials data' }, { status: 422 }); + } + if (dataURL.length > 50_000) { + return Response.json({ error: 'Initials data too large' }, { status: 422 }); + } + + await db.update(users) + .set({ agentInitialsData: dataURL }) + .where(eq(users.id, session.user.id)); + + return Response.json({ ok: true }); +} +``` + + + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + TypeScript compiles clean; migration SQL file exists at drizzle/0009_*.sql; schema.ts has agentInitialsData column, 'agent-initials' in SignatureFieldType, and updated isClientVisibleField(); GET /api/agent/initials returns 401 without session and { agentInitialsData: null } with valid session. + + + + Task 2: AgentInitialsPanel component, profile page section update, FieldPlacer token + + teressa-copeland-homes/src/app/portal/_components/AgentInitialsPanel.tsx + teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx + teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx + + +Three files to create/modify: + +**1. Create AgentInitialsPanel.tsx** at `src/app/portal/_components/AgentInitialsPanel.tsx`: + +This is a clone of `AgentSignaturePanel.tsx` with the following differences applied (do NOT change anything else): +- API endpoint: `/api/agent/initials` +- Canvas height: `80px` (initials are compact; signature uses 140px) +- Thumbnail: `max-h-16` (vs `max-h-20` for signature) +- All user-visible strings use "initials" instead of "signature" +- Error message: `'Please draw your initials first'` +- Alt text: `'Saved agent initials'` + +```typescript +'use client'; +import { useEffect, useRef, useState } from 'react'; +import SignaturePad from 'signature_pad'; + +interface AgentInitialsPanelProps { + initialData: string | null; +} + +export function AgentInitialsPanel({ initialData }: AgentInitialsPanelProps) { + const [savedData, setSavedData] = useState(initialData); + const [isDrawing, setIsDrawing] = useState(!initialData); + const canvasRef = useRef(null); + const sigPadRef = useRef(null); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (!isDrawing || !canvasRef.current) return; + const canvas = canvasRef.current; + const ratio = Math.max(window.devicePixelRatio || 1, 1); + canvas.width = canvas.offsetWidth * ratio; + canvas.height = canvas.offsetHeight * ratio; + canvas.getContext('2d')?.scale(ratio, ratio); + sigPadRef.current = new SignaturePad(canvas, { + backgroundColor: 'rgba(0,0,0,0)', + penColor: '#1B2B4B', + }); + return () => sigPadRef.current?.off(); + }, [isDrawing]); + + async function handleSave() { + if (!sigPadRef.current || sigPadRef.current.isEmpty()) { + setError('Please draw your initials first'); + return; + } + const dataURL = sigPadRef.current.toDataURL('image/png'); + setSaving(true); + setError(null); + try { + const res = await fetch('/api/agent/initials', { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ dataURL }), + }); + if (res.ok) { + setSavedData(dataURL); + setIsDrawing(false); + } else { + const data = await res.json().catch(() => ({ error: 'Save failed' })); + setError(data.error ?? 'Save failed'); + } + } catch { + setError('Network error — please try again'); + } finally { + setSaving(false); + } + } + + if (!isDrawing && savedData) { + return ( +
+

Your saved initials:

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

{error}

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

Agent Initials

+

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

+
+ +
+``` + +**3. Modify FieldPlacer.tsx** — two targeted changes: + +a) Add `'agent-initials'` token to `PALETTE_TOKENS` (7th entry, after `agent-signature`): +```typescript +{ id: 'agent-initials', label: 'Agent Initials', color: '#ea580c' }, +``` +Orange (`#ea580c`) is unused by any existing token. It visually groups with red (agent-signature) as "agent-owned" while remaining distinct from client-visible tokens. + +b) Add `'agent-initials'` to the `validTypes` Set: +```typescript +// Add 'agent-initials' to the existing set — do not change any other entries +const validTypes = new Set(['client-signature', 'initials', 'text', 'checkbox', 'date', 'agent-signature', 'agent-initials']); +``` + +Do NOT change any other FieldPlacer logic — drag behavior, overlay rendering, and coordinate conversion all apply to agent-initials the same way they apply to agent-signature. +
+ + cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 + + TypeScript compiles clean; `npm run dev` starts without error; /portal/profile shows both the Agent Signature section (unchanged) and the new Agent Initials section below it; FieldPlacer palette shows an orange "Agent Initials" token as the 7th entry; the existing purple "Initials" client token is still present and unchanged. +
+ +
+ + +After both tasks complete, verify the full Plan 01 state: + +1. `npx tsc --noEmit` passes with zero errors +2. `npm run dev` starts without errors +3. Migration applied: `psql -d [db] -c "SELECT column_name FROM information_schema.columns WHERE table_name='users' AND column_name='agent_initials_data';"` returns one row +4. GET /api/agent/initials returns 401 without session; returns `{ agentInitialsData: null }` with valid session +5. /portal/profile renders both sections — Agent Signature (existing, unchanged) and Agent Initials (new) — with their respective canvases +6. Agent can draw on the initials canvas and click "Save Initials" — PUT /api/agent/initials returns 200, thumbnail appears +7. Agent can click "Update Initials" — canvas reappears for redraw +8. FieldPlacer shows "Agent Initials" as a draggable orange token (7th entry); purple "Initials" client token still present +9. Placing an "Agent Initials" field on a document page saves it with `type: 'agent-initials'` in signatureFields +10. GET /api/sign/[token] does NOT return agent-initials fields (isClientVisibleField returns false) + + + +- INIT-01: Agent can draw and save initials from /portal/profile; thumbnail shows after save +- INIT-02: "Update Initials" button replaces saved initials with a new one (same PUT route) +- INIT-03 (partial): "Agent Initials" token appears in FieldPlacer palette; placed fields have type 'agent-initials'; isClientVisibleField() excludes them from client signing session (Plan 02 completes the embedding) +- INIT-04 (confirmed): Existing purple "Initials" token still present in palette; no changes to SigningPageClient.tsx or any client-initials pipeline +- TypeScript build clean; zero new npm packages + + + +After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-01-SUMMARY.md` + diff --git a/.planning/phases/11.1-agent-and-client-initials/11.1-02-PLAN.md b/.planning/phases/11.1-agent-and-client-initials/11.1-02-PLAN.md new file mode 100644 index 0000000..7c3854b --- /dev/null +++ b/.planning/phases/11.1-agent-and-client-initials/11.1-02-PLAN.md @@ -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" +--- + + +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 + + + +After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-02-SUMMARY.md` + diff --git a/.planning/phases/11.1-agent-and-client-initials/11.1-03-PLAN.md b/.planning/phases/11.1-agent-and-client-initials/11.1-03-PLAN.md new file mode 100644 index 0000000..a2df41c --- /dev/null +++ b/.planning/phases/11.1-agent-and-client-initials/11.1-03-PLAN.md @@ -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" +--- + + +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. + + + +@/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 +@.planning/phases/11.1-agent-and-client-initials/11.1-02-SUMMARY.md + + + + + + Task 1: Human verification — full Phase 11.1 agent and client initials end-to-end + 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. + +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 + + +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 + + Type "approved" if all 5 steps pass. Describe any issues found if not (include step number and observed vs expected behavior). + All 5 steps pass and human types "approved" + 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 + + + + + +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" + + + +Human approves all 5 verification steps. Phase 11.1 is complete and all INIT requirements are satisfied. + + + +After completion, create `.planning/phases/11.1-agent-and-client-initials/11.1-03-SUMMARY.md` +