docs(11-agent-saved-signature-and-signing-workflow): create phase plan
This commit is contained in:
@@ -220,11 +220,11 @@ Plans:
|
|||||||
2. Agent can update (replace) their saved signature at any time
|
2. Agent can update (replace) their saved signature at any time
|
||||||
3. Agent can place agent signature field markers on a PDF from the FieldPlacer palette
|
3. Agent can place agent signature field markers on a PDF from the FieldPlacer palette
|
||||||
4. When the agent prepares a document, their saved signature PNG is embedded at each agent-signature field coordinate — the field does not appear in the client signing session
|
4. When the agent prepares a document, their saved signature PNG is embedded at each agent-signature field coordinate — the field does not appear in the client signing session
|
||||||
**Plans**: TBD
|
**Plans**: 3 plans
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
- [ ] 11-01-PLAN.md — DB migration (agentSignatureData TEXT on users table), GET/PUT /api/agent/signature routes, AgentSignaturePanel component (draw + save + thumbnail)
|
- [ ] 11-01-PLAN.md — DB migration (agentSignatureData TEXT on users), GET/PUT /api/agent/signature routes, AgentSignaturePanel (draw + save + thumbnail), /portal/profile page, PortalNav Profile link, FieldPlacer agent-signature palette token
|
||||||
- [ ] 11-02-PLAN.md — FieldPlacer palette token for agent-signature type, prepare-document.ts PNG embed at agent-sig coordinates
|
- [ ] 11-02-PLAN.md — preparePdf() agentSignatureData param + embedPng/drawImage at agent-sig coordinates; prepare route fetches agentSignatureData + 422 guard when sig missing
|
||||||
- [ ] 11-03-PLAN.md — Full Phase 11 human verification checkpoint (draw, save, place, prepare, verify embedded in PDF + absent from client signing page)
|
- [ ] 11-03-PLAN.md — Full Phase 11 human verification checkpoint (draw, save, place, prepare, verify embedded in PDF + absent from client signing page)
|
||||||
|
|
||||||
### Phase 12: Filled Document Preview
|
### Phase 12: Filled Document Preview
|
||||||
|
|||||||
@@ -0,0 +1,438 @@
|
|||||||
|
---
|
||||||
|
phase: 11-agent-saved-signature-and-signing-workflow
|
||||||
|
plan: "01"
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- teressa-copeland-homes/src/lib/db/schema.ts
|
||||||
|
- teressa-copeland-homes/drizzle/0008_agent_signature.sql
|
||||||
|
- teressa-copeland-homes/src/app/api/agent/signature/route.ts
|
||||||
|
- teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx
|
||||||
|
- teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx
|
||||||
|
- teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx
|
||||||
|
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- AGENT-01
|
||||||
|
- AGENT-02
|
||||||
|
- AGENT-03
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Agent can navigate to /portal/profile via the nav"
|
||||||
|
- "Agent can draw a signature on the canvas and save it"
|
||||||
|
- "A thumbnail of the saved signature appears after saving"
|
||||||
|
- "Agent can click 'Update Signature' to redraw and replace their saved signature"
|
||||||
|
- "Agent signature palette token appears in the FieldPlacer (red 'Agent Signature' token)"
|
||||||
|
- "Placing an agent-signature field saves it in signatureFields with type 'agent-signature'"
|
||||||
|
artifacts:
|
||||||
|
- path: "teressa-copeland-homes/src/lib/db/schema.ts"
|
||||||
|
provides: "agentSignatureData TEXT column on users table"
|
||||||
|
contains: "agentSignatureData"
|
||||||
|
- path: "teressa-copeland-homes/src/app/api/agent/signature/route.ts"
|
||||||
|
provides: "GET/PUT endpoints for reading and writing agent signature"
|
||||||
|
exports: ["GET", "PUT"]
|
||||||
|
- path: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
|
||||||
|
provides: "Client component with signature_pad canvas, save/update/thumbnail flow"
|
||||||
|
- path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
|
||||||
|
provides: "Server component fetching agentSignatureData and rendering AgentSignaturePanel"
|
||||||
|
key_links:
|
||||||
|
- from: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
|
||||||
|
to: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
|
||||||
|
via: "initialData prop (string | null)"
|
||||||
|
pattern: "initialData.*agentSignatureData"
|
||||||
|
- from: "teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx"
|
||||||
|
to: "/api/agent/signature"
|
||||||
|
via: "fetch PUT with { dataURL }"
|
||||||
|
pattern: "fetch.*api/agent/signature"
|
||||||
|
- from: "teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx"
|
||||||
|
to: "/portal/profile"
|
||||||
|
via: "navLinks entry"
|
||||||
|
pattern: "portal/profile"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Establish the agent signature storage and UI layer: DB migration adds `agentSignatureData TEXT` to users, GET/PUT API routes read/write the base64 PNG dataURL, a new `/portal/profile` page hosts the `AgentSignaturePanel` draw-and-save canvas component, the portal nav gains a Profile link, and the FieldPlacer palette gets the red "Agent Signature" token.
|
||||||
|
|
||||||
|
Purpose: Once the agent has saved their signature and the palette token exists, Plan 02 can wire up the preparePdf() embedding without any further schema or UI work.
|
||||||
|
Output: Profile page with working draw/save/update signature flow; agent-signature token in field palette; 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/10-expanded-field-types-end-to-end/10-03-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 users table — agentSignatureData 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(),
|
||||||
|
// Phase 11 adds: agentSignatureData: text("agent_signature_data"),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/lib/db/schema.ts (SignatureFieldType — agent-signature already valid):
|
||||||
|
```typescript
|
||||||
|
export type SignatureFieldType =
|
||||||
|
| 'client-signature'
|
||||||
|
| 'initials'
|
||||||
|
| 'text'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'date'
|
||||||
|
| 'agent-signature';
|
||||||
|
```
|
||||||
|
|
||||||
|
From FieldPlacer.tsx (PALETTE_TOKENS — agent-signature NOT YET present, validTypes already includes it):
|
||||||
|
```typescript
|
||||||
|
// Current PALETTE_TOKENS (5 entries — Phase 11 adds the 6th):
|
||||||
|
const PALETTE_TOKENS: Array<{ id: SignatureFieldType; label: string; color: string }> = [
|
||||||
|
{ id: 'client-signature', label: 'Signature', color: '#2563eb' },
|
||||||
|
{ id: 'initials', label: 'Initials', color: '#7c3aed' },
|
||||||
|
{ id: 'checkbox', label: 'Checkbox', color: '#059669' },
|
||||||
|
{ id: 'date', label: 'Date', color: '#d97706' },
|
||||||
|
{ id: 'text', label: 'Text', color: '#64748b' },
|
||||||
|
// ADD: { id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// validTypes already has 'agent-signature' at line 258:
|
||||||
|
const validTypes = new Set<string>(['client-signature','initials','text','checkbox','date','agent-signature']);
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/app/sign/[token]/_components/SignatureModal.tsx (DPR canvas init — confirmed working):
|
||||||
|
```typescript
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen || tab !== 'draw' || !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();
|
||||||
|
}, [isOpen, tab]);
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/lib/auth.config.ts (session.user.id is set):
|
||||||
|
```typescript
|
||||||
|
// callbacks.jwt: if (user) token.id = user.id;
|
||||||
|
// callbacks.session: session.user.id = token.id as string;
|
||||||
|
// Usage in any protected API route:
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration pattern from drizzle/0007_equal_nekra.sql:
|
||||||
|
```sql
|
||||||
|
-- Pattern for nullable TEXT column:
|
||||||
|
ALTER TABLE "clients" ADD COLUMN "property_address" text;
|
||||||
|
-- Phase 11 equivalent:
|
||||||
|
ALTER TABLE "users" ADD COLUMN "agent_signature_data" text;
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/app/portal/_components/PortalNav.tsx (navLinks pattern):
|
||||||
|
```typescript
|
||||||
|
// Current navLinks:
|
||||||
|
const navLinks = [
|
||||||
|
{ href: '/portal/dashboard', label: 'Dashboard' },
|
||||||
|
{ href: '/portal/clients', label: 'Clients' },
|
||||||
|
// Phase 11 adds: { href: '/portal/profile', label: 'Profile' }
|
||||||
|
];
|
||||||
|
```
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: DB migration and API routes for agent signature storage</name>
|
||||||
|
<files>
|
||||||
|
teressa-copeland-homes/src/lib/db/schema.ts
|
||||||
|
teressa-copeland-homes/drizzle/0008_agent_signature.sql
|
||||||
|
teressa-copeland-homes/src/app/api/agent/signature/route.ts
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
Three changes in sequence:
|
||||||
|
|
||||||
|
**1. schema.ts — add column to users table:**
|
||||||
|
Add `agentSignatureData: text("agent_signature_data")` as a nullable column (no `.notNull()`, no default) to the `users` pgTable definition. This is the only change to schema.ts.
|
||||||
|
|
||||||
|
**2. Run migration generation and apply:**
|
||||||
|
```bash
|
||||||
|
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate && npm run db:migrate
|
||||||
|
```
|
||||||
|
The generated SQL file will be at `drizzle/0008_agent_signature.sql` and will contain:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE "users" ADD COLUMN "agent_signature_data" text;
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Create GET/PUT route at src/app/api/agent/signature/route.ts:**
|
||||||
|
|
||||||
|
```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: { agentSignatureData: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return Response.json({ agentSignatureData: user?.agentSignatureData ?? 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 signature data' }, { status: 422 });
|
||||||
|
}
|
||||||
|
if (dataURL.length > 50_000) {
|
||||||
|
return Response.json({ error: 'Signature data too large' }, { status: 422 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.update(users)
|
||||||
|
.set({ agentSignatureData: dataURL })
|
||||||
|
.where(eq(users.id, session.user.id));
|
||||||
|
|
||||||
|
return Response.json({ ok: true });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The directory `src/app/api/agent/signature/` must be created (mkdir -p).
|
||||||
|
</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/0008_agent_signature.sql; GET /api/agent/signature returns { agentSignatureData: null } when called with a valid session.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: AgentSignaturePanel component, profile page, PortalNav link, FieldPlacer token</name>
|
||||||
|
<files>
|
||||||
|
teressa-copeland-homes/src/app/portal/_components/AgentSignaturePanel.tsx
|
||||||
|
teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx
|
||||||
|
teressa-copeland-homes/src/app/portal/_components/PortalNav.tsx
|
||||||
|
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
Four files to create/modify:
|
||||||
|
|
||||||
|
**1. Create AgentSignaturePanel.tsx** at `src/app/portal/_components/AgentSignaturePanel.tsx`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
'use client';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import SignaturePad from 'signature_pad';
|
||||||
|
|
||||||
|
interface AgentSignaturePanelProps {
|
||||||
|
initialData: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AgentSignaturePanel({ initialData }: AgentSignaturePanelProps) {
|
||||||
|
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 signature first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dataURL = sigPadRef.current.toDataURL('image/png');
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/agent/signature', {
|
||||||
|
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 signature:</p>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={savedData}
|
||||||
|
alt="Saved agent signature"
|
||||||
|
className="max-h-20 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 Signature
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="w-full border border-gray-300 rounded bg-white"
|
||||||
|
style={{ height: '140px', 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 Signature' : 'Save Signature')}
|
||||||
|
</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. Create profile/page.tsx** at `src/app/portal/(protected)/profile/page.tsx`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { auth } from '@/lib/auth';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
import { users } from '@/lib/db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { AgentSignaturePanel } from '../../_components/AgentSignaturePanel';
|
||||||
|
|
||||||
|
export default async function ProfilePage() {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) redirect('/agent/login');
|
||||||
|
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, session.user.id),
|
||||||
|
columns: { agentSignatureData: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto py-8 px-4 space-y-8">
|
||||||
|
<h1 className="text-2xl font-semibold text-gray-900">Profile</h1>
|
||||||
|
<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 Signature</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
Draw your signature once. It will be embedded in any "Agent Signature" fields when you prepare a document.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<AgentSignaturePanel initialData={user?.agentSignatureData ?? null} />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Modify PortalNav.tsx** — add Profile to navLinks:
|
||||||
|
Find the `navLinks` array (currently has Dashboard and Clients entries). Add a third entry: `{ href: '/portal/profile', label: 'Profile' }`.
|
||||||
|
|
||||||
|
**4. Modify FieldPlacer.tsx** — add agent-signature palette token:
|
||||||
|
Find the `PALETTE_TOKENS` array (currently has 5 entries). Add the agent-signature token as the 6th entry:
|
||||||
|
```typescript
|
||||||
|
{ id: 'agent-signature', label: 'Agent Signature', color: '#dc2626' },
|
||||||
|
```
|
||||||
|
This is the only change needed to FieldPlacer.tsx — the `validTypes` set already includes `'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; visiting /portal/profile shows the AgentSignaturePanel; PortalNav shows "Profile" link; FieldPlacer palette shows a red "Agent Signature" token.</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_signature_data';"` returns one row
|
||||||
|
4. GET /api/agent/signature returns 401 without session; returns `{ agentSignatureData: null }` with valid session
|
||||||
|
5. /portal/profile renders the AgentSignaturePanel canvas
|
||||||
|
6. Agent can draw on canvas and click "Save Signature" — PUT returns 200, thumbnail appears
|
||||||
|
7. Agent can click "Update Signature" — canvas reappears for redraw
|
||||||
|
8. FieldPlacer on a document shows "Agent Signature" as a draggable red token
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- AGENT-01: Agent can draw and save a signature from /portal/profile; thumbnail shows after save
|
||||||
|
- AGENT-02: "Update Signature" button replaces saved signature with a new one (same PUT route)
|
||||||
|
- AGENT-03: "Agent Signature" token appears in FieldPlacer palette; placed fields have type 'agent-signature'
|
||||||
|
- TypeScript build clean, zero new npm packages
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
---
|
||||||
|
phase: 11-agent-saved-signature-and-signing-workflow
|
||||||
|
plan: "02"
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on:
|
||||||
|
- "11-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:
|
||||||
|
- AGENT-04
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "When agent prepares a document with agent-signature fields and a saved signature, the prepared PDF contains the signature image at each agent-sig field coordinate"
|
||||||
|
- "The agent signature is invisible to the client — it is never returned by GET /api/sign/[token]"
|
||||||
|
- "When agent prepares a document with agent-signature fields but no saved signature, the prepare route returns 422 with { error: 'agent-signature-missing' } — no silent failure"
|
||||||
|
- "When agent prepares a document with no agent-signature fields, the prepare route succeeds normally regardless of whether a signature is saved"
|
||||||
|
artifacts:
|
||||||
|
- path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
|
||||||
|
provides: "preparePdf() with agentSignatureData param; embedPng + drawImage at agent-sig field coordinates"
|
||||||
|
contains: "agentSignatureData"
|
||||||
|
- path: "teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts"
|
||||||
|
provides: "Fetches agentSignatureData from DB; 422 guard when agent-sig fields present but no sig saved; passes to preparePdf()"
|
||||||
|
contains: "agent-signature-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)"
|
||||||
|
pattern: "preparePdf.*agentSignatureData"
|
||||||
|
- 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 } })"
|
||||||
|
pattern: "agentSignatureData.*findFirst"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Wire the agent signature into the prepare pipeline: `preparePdf()` gains an `agentSignatureData` parameter and embeds the PNG at each agent-signature field coordinate using the same `pdfDoc.embedPng()` + `page.drawImage()` pattern already used for client signatures. The prepare route fetches the agent's saved signature from the DB and passes it to `preparePdf()`, with a 422 guard if agent-signature fields are present but no signature has been saved.
|
||||||
|
|
||||||
|
Purpose: Fulfills AGENT-04 — agent's saved signature is baked into the prepared PDF before it reaches the client.
|
||||||
|
Output: Prepared PDFs with embedded agent signatures; 422 error when signature is missing; client signing session unaffected (isClientVisibleField filter already excludes agent-signature).
|
||||||
|
</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-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key types and confirmed patterns from codebase (2026-03-21 inspection). -->
|
||||||
|
|
||||||
|
From src/lib/pdf/prepare-document.ts (current function signature — BEFORE Phase 11):
|
||||||
|
```typescript
|
||||||
|
export async function preparePdf(
|
||||||
|
srcPath: string,
|
||||||
|
destPath: string,
|
||||||
|
textFields: Record<string, string>,
|
||||||
|
sigFields: SignatureFieldData[],
|
||||||
|
// Phase 11 adds: agentSignatureData: string | null = null
|
||||||
|
): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
Current agent-signature stub at line ~138 (to be replaced):
|
||||||
|
```typescript
|
||||||
|
} else if (fieldType === 'agent-signature') {
|
||||||
|
// Skip — agent signature handled by Phase 11; no placeholder drawn here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/lib/signing/embed-signature.ts (confirmed working embedPng pattern):
|
||||||
|
```typescript
|
||||||
|
// embedPng accepts base64 DataURL directly (no Buffer conversion needed)
|
||||||
|
const pngImage = await pdfDoc.embedPng(sig.dataURL); // 'data:image/png;base64,...'
|
||||||
|
page.drawImage(pngImage, {
|
||||||
|
x: sig.x,
|
||||||
|
y: sig.y,
|
||||||
|
width: sig.width,
|
||||||
|
height: sig.height,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/app/api/documents/[id]/prepare/route.ts (current POST handler structure):
|
||||||
|
```typescript
|
||||||
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session?.user?.id) return new Response('Unauthorized', { status: 401 });
|
||||||
|
// ... body parsing, doc lookup, path resolution ...
|
||||||
|
await preparePdf(srcPath, destPath, textFields, sigFields); // 4 args currently
|
||||||
|
// ... DB update, audit log, return ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From src/lib/db/schema.ts (getFieldType — confirmed working pattern):
|
||||||
|
```typescript
|
||||||
|
export function getFieldType(field: SignatureFieldData): SignatureFieldType {
|
||||||
|
return field.type ?? 'client-signature';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CRITICAL: embedPng is called ONCE per document (not once per field). The returned PDFImage
|
||||||
|
object is reused across multiple drawImage calls. Call pdfDoc.embedPng(agentSignatureData)
|
||||||
|
before the field loop, store as agentSigImage, then reference agentSigImage inside the loop.
|
||||||
|
|
||||||
|
COORDINATE SYSTEM: Use field.x, field.y, field.width, field.height directly. These are already
|
||||||
|
stored in PDF user-space (bottom-left origin) by FieldPlacer's screenToPdfCoords() conversion.
|
||||||
|
Do NOT invert or adjust Y — this is identical to how embedSignatureInPdf() works for client sigs.
|
||||||
|
|
||||||
|
IMPORT NOTE: PDFImage type from @cantoo/pdf-lib is needed for the agentSigImage variable type.
|
||||||
|
Check prepare-document.ts existing imports — PDFDocument, rgb, StandardFonts, etc. are already
|
||||||
|
imported from @cantoo/pdf-lib. Add PDFImage to that import if not already present.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: preparePdf() — add agentSignatureData param and embed at agent-sig field coordinates</name>
|
||||||
|
<files>
|
||||||
|
teressa-copeland-homes/src/lib/pdf/prepare-document.ts
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
Two targeted changes to `preparePdf()`:
|
||||||
|
|
||||||
|
**1. Add `agentSignatureData` parameter** with a default of `null` so existing call sites compile without change until the prepare route is updated in Task 2:
|
||||||
|
```typescript
|
||||||
|
export async function preparePdf(
|
||||||
|
srcPath: string,
|
||||||
|
destPath: string,
|
||||||
|
textFields: Record<string, string>,
|
||||||
|
sigFields: SignatureFieldData[],
|
||||||
|
agentSignatureData: string | null = null, // ADD THIS
|
||||||
|
): Promise<void>
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Embed the agent signature PNG once before the field loop:**
|
||||||
|
After `const pages = pdfDoc.getPages();` and before the field loop, add:
|
||||||
|
```typescript
|
||||||
|
// Embed agent signature image once — reused across all agent-sig fields
|
||||||
|
let agentSigImage: import('@cantoo/pdf-lib').PDFImage | null = null;
|
||||||
|
if (agentSignatureData) {
|
||||||
|
agentSigImage = await pdfDoc.embedPng(agentSignatureData);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If PDFImage is already imported from @cantoo/pdf-lib at the top of the file (check the existing imports), use the named import directly instead of the inline import type. Only add `PDFImage` to the destructured import if it is not already there — do not change any other imports.
|
||||||
|
|
||||||
|
**3. Replace the agent-signature stub** in the field loop with real embedding:
|
||||||
|
Find the existing `else if (fieldType === 'agent-signature') { // Skip — ... }` block and replace it with:
|
||||||
|
```typescript
|
||||||
|
} else if (fieldType === 'agent-signature') {
|
||||||
|
if (agentSigImage) {
|
||||||
|
page.drawImage(agentSigImage, {
|
||||||
|
x: field.x,
|
||||||
|
y: field.y,
|
||||||
|
width: field.width,
|
||||||
|
height: field.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If no signature saved: the prepare route guards against this with 422 before calling preparePdf
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
No other changes to prepare-document.ts. The function body, other field type branches, and the atomic write logic remain untouched.
|
||||||
|
</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 5 parameters (last is optional); agent-signature stub replaced with drawImage call; no regressions in other field type branches.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: prepare route — fetch agentSignatureData, 422 guard, pass to preparePdf</name>
|
||||||
|
<files>
|
||||||
|
teressa-copeland-homes/src/app/api/documents/[id]/prepare/route.ts
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
Three additions to the POST handler in `src/app/api/documents/[id]/prepare/route.ts`:
|
||||||
|
|
||||||
|
**1. Import `users` from schema** (if not already imported — check existing imports at top of file):
|
||||||
|
```typescript
|
||||||
|
import { users } from '@/lib/db/schema';
|
||||||
|
```
|
||||||
|
Also ensure `getFieldType` is imported from `@/lib/db/schema` (needed for the guard check). If already present, do not duplicate.
|
||||||
|
|
||||||
|
**2. Fetch agentSignatureData from DB** — add this block AFTER the existing doc lookup and sigFields parsing, BEFORE the `preparePdf()` call:
|
||||||
|
```typescript
|
||||||
|
// Fetch agent's saved signature for embedding at agent-signature field coordinates
|
||||||
|
const agentUser = await db.query.users.findFirst({
|
||||||
|
where: eq(users.id, session.user.id),
|
||||||
|
columns: { agentSignatureData: true },
|
||||||
|
});
|
||||||
|
const agentSignatureData = agentUser?.agentSignatureData ?? null;
|
||||||
|
|
||||||
|
// Guard: if document has agent-signature fields but no signature saved, block prepare
|
||||||
|
const hasAgentSigFields = sigFields.some(f => getFieldType(f) === 'agent-signature');
|
||||||
|
if (hasAgentSigFields && !agentSignatureData) {
|
||||||
|
return Response.json(
|
||||||
|
{ error: 'agent-signature-missing', message: 'No agent signature saved. Go to Profile to save your signature first.' },
|
||||||
|
{ status: 422 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Pass `agentSignatureData` to `preparePdf()`** — update the existing call:
|
||||||
|
```typescript
|
||||||
|
// BEFORE (4 args):
|
||||||
|
await preparePdf(srcPath, destPath, textFields, sigFields);
|
||||||
|
|
||||||
|
// AFTER (5 args):
|
||||||
|
await preparePdf(srcPath, destPath, textFields, sigFields, agentSignatureData);
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not change any other logic in the route handler — the 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 agentSignatureData; guard returns 422 with { error: 'agent-signature-missing' } when agent-sig fields exist but no signature saved; preparePdf called with 5 args including agentSignatureData.</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 build` succeeds (or `npm run dev` starts clean)
|
||||||
|
3. Functional test — draw save place prepare round-trip:
|
||||||
|
a. Visit /portal/profile, draw and save a signature
|
||||||
|
b. Open a document in the portal, place an "Agent Signature" field 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 signature PNG is embedded at the correct position
|
||||||
|
4. Guard test — prepare with no saved signature:
|
||||||
|
a. Use a fresh test account (or temporarily clear agentSignatureData in DB)
|
||||||
|
b. Place an agent-signature field, attempt to prepare
|
||||||
|
c. Prepare route returns 422 with `{ error: 'agent-signature-missing' }`
|
||||||
|
5. Client signing page test — open the signing link for the prepared document:
|
||||||
|
a. Signing page does NOT show an agent-signature field overlay (isClientVisibleField already filters it)
|
||||||
|
b. Client can sign normally — only client-signature and initials overlays appear
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- AGENT-04: Agent's saved PNG is embedded at each agent-signature field coordinate in the prepared PDF
|
||||||
|
- No agent-signature content is exposed to the client via GET /api/sign/[token]
|
||||||
|
- Prepare fails with actionable 422 when agent-sig fields exist but no signature saved
|
||||||
|
- TypeScript build clean; zero new npm packages; no regressions on other field types
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
phase: 11-agent-saved-signature-and-signing-workflow
|
||||||
|
plan: "03"
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on:
|
||||||
|
- "11-01"
|
||||||
|
- "11-02"
|
||||||
|
files_modified: []
|
||||||
|
autonomous: false
|
||||||
|
requirements:
|
||||||
|
- AGENT-01
|
||||||
|
- AGENT-02
|
||||||
|
- AGENT-03
|
||||||
|
- AGENT-04
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Agent can draw, save, and see a thumbnail of their signature from /portal/profile"
|
||||||
|
- "Agent can update (replace) their saved signature"
|
||||||
|
- "Agent can place an Agent Signature token on a PDF document in the FieldPlacer"
|
||||||
|
- "Preparing a document with agent-signature fields embeds the saved PNG in the correct position in the prepared PDF"
|
||||||
|
- "Opening the client signing link for the prepared document does NOT show any agent-signature overlay"
|
||||||
|
artifacts:
|
||||||
|
- path: "teressa-copeland-homes/src/app/portal/(protected)/profile/page.tsx"
|
||||||
|
provides: "Accessible profile page with draw/save/thumbnail signature flow"
|
||||||
|
- path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
|
||||||
|
provides: "preparePdf with agent PNG embedding at agent-sig field coordinates"
|
||||||
|
key_links:
|
||||||
|
- from: "Agent saves signature at /portal/profile"
|
||||||
|
to: "Prepared PDF"
|
||||||
|
via: "PUT /api/agent/signature → users.agentSignatureData → preparePdf() → pdfDoc.embedPng + drawImage"
|
||||||
|
pattern: "full pipeline"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Human verification of the complete Phase 11 agent signature workflow: draw once at profile, place fields on a document, prepare the document, verify the signature is embedded in the correct position in the PDF, and confirm the client signing session is unaffected.
|
||||||
|
|
||||||
|
Purpose: All four AGENT requirements are verified end-to-end by a human using the portal.
|
||||||
|
Output: Phase 11 marked complete; AGENT-01 through AGENT-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-agent-saved-signature-and-signing-workflow/11-01-SUMMARY.md
|
||||||
|
@.planning/phases/11-agent-saved-signature-and-signing-workflow/11-02-SUMMARY.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="checkpoint:human-verify" gate="blocking">
|
||||||
|
<name>Task 1: Human verification — full Phase 11 agent signature end-to-end</name>
|
||||||
|
<action>Run the dev server and execute the 5-step verification below. There are no code changes in this plan — all code was delivered in Plans 01 and 02.</action>
|
||||||
|
<what-built>
|
||||||
|
Complete Phase 11 agent signature workflow:
|
||||||
|
- /portal/profile page with signature draw canvas and thumbnail
|
||||||
|
- "Agent Signature" token in FieldPlacer palette (red, draggable)
|
||||||
|
- preparePdf() embeds saved PNG at agent-signature field coordinates
|
||||||
|
- Prepare route fetches agentSignatureData and passes 422 guard
|
||||||
|
- Client signing session filters out agent-signature fields (unchanged from Phase 8)
|
||||||
|
</what-built>
|
||||||
|
<how-to-verify>
|
||||||
|
Start the dev server: `cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run dev`
|
||||||
|
|
||||||
|
**Step 1 — Save your signature (AGENT-01):**
|
||||||
|
- Visit http://localhost:3000/portal/profile
|
||||||
|
- Confirm "Profile" appears in the portal nav
|
||||||
|
- Draw a distinctive signature on the canvas
|
||||||
|
- Click "Save Signature"
|
||||||
|
- Expected: Canvas is replaced by a thumbnail image of your signature
|
||||||
|
|
||||||
|
**Step 2 — Update signature (AGENT-02):**
|
||||||
|
- Click "Update Signature" on the profile page
|
||||||
|
- Draw a slightly different signature
|
||||||
|
- Click "Save Updated Signature"
|
||||||
|
- Expected: New thumbnail replaces the old one
|
||||||
|
|
||||||
|
**Step 3 — Place agent-signature field (AGENT-03):**
|
||||||
|
- Open any document in the portal and go to the prepare tab
|
||||||
|
- Confirm the FieldPlacer palette shows a red "Agent Signature" token
|
||||||
|
- Drag the "Agent Signature" token and drop it onto the document (any page, any position)
|
||||||
|
- Expected: A red field box appears on the PDF where you dropped it
|
||||||
|
|
||||||
|
**Step 4 — Prepare with embedded signature (AGENT-04):**
|
||||||
|
- Assign the document to a client, fill any required text fields
|
||||||
|
- Click Prepare
|
||||||
|
- Expected: Prepare succeeds (no error, document status changes)
|
||||||
|
- Download the prepared PDF from the portal
|
||||||
|
- Open the downloaded PDF and navigate to the page where you placed the agent-signature field
|
||||||
|
- Expected: Your saved signature image appears at that position in the PDF
|
||||||
|
|
||||||
|
**Step 5 — Client signing session is unaffected:**
|
||||||
|
- Send the prepared document to a client (or use a test signing link)
|
||||||
|
- Open the signing link in the browser
|
||||||
|
- Expected: The client signing page does NOT show any overlay or prompt for the agent-signature position
|
||||||
|
- Expected: Client-signature and initials overlays (if placed) still appear normally for the client
|
||||||
|
</how-to-verify>
|
||||||
|
<resume-signal>Type "approved" if all 5 steps pass. Describe any issues found if not.</resume-signal>
|
||||||
|
<verify>All 5 steps pass and human types "approved"</verify>
|
||||||
|
<done>Full end-to-end round-trip confirmed: agent draws, saves, places field, prepares, and embedded signature appears in prepared PDF; client signing session is unaffected</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Human verification approved — all four AGENT requirements confirmed by live testing:
|
||||||
|
- AGENT-01: Agent drew and saved signature; thumbnail visible at /portal/profile
|
||||||
|
- AGENT-02: "Update Signature" replaced saved signature with a new one
|
||||||
|
- AGENT-03: Red "Agent Signature" token visible and usable in FieldPlacer palette
|
||||||
|
- AGENT-04: Prepared PDF contains agent signature PNG at the placed field coordinates; client signing session shows no agent-signature overlay
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
Human approves all 5 verification steps. Phase 11 is complete and all AGENT requirements are satisfied.
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/11-agent-saved-signature-and-signing-workflow/11-03-SUMMARY.md`
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user