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:
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>
|
||||
Reference in New Issue
Block a user