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:
@@ -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*
|
||||
|
||||
@@ -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 | - |
|
||||
|
||||
484
.planning/phases/11.1-agent-and-client-initials/11.1-01-PLAN.md
Normal file
484
.planning/phases/11.1-agent-and-client-initials/11.1-01-PLAN.md
Normal 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 "Agent Initials" 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>
|
||||
300
.planning/phases/11.1-agent-and-client-initials/11.1-02-PLAN.md
Normal file
300
.planning/phases/11.1-agent-and-client-initials/11.1-02-PLAN.md
Normal 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>
|
||||
129
.planning/phases/11.1-agent-and-client-initials/11.1-03-PLAN.md
Normal file
129
.planning/phases/11.1-agent-and-client-initials/11.1-03-PLAN.md
Normal 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>
|
||||
Reference in New Issue
Block a user