--- phase: 13-ai-field-placement-and-pre-fill plan: 03 type: execute wave: 3 depends_on: - 13-01 - 13-02 files_modified: - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx autonomous: true requirements: - AI-01 - AI-02 must_haves: truths: - "PreparePanel has an 'AI Auto-place' button that calls POST /api/documents/[id]/ai-prepare and shows loading state" - "After AI placement succeeds, FieldPlacer re-fetches fields from DB and displays the AI-placed field overlays" - "After AI placement, DocumentPageClient's textFillData is updated with the returned pre-fill map and previewToken is reset to null" - "Agent can review, move, resize, or delete any AI-placed field after placement — fields are editable, not locked" artifacts: - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx" provides: "FieldPlacer with aiPlacementKey prop that triggers loadFields re-fetch" contains: "aiPlacementKey" - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx" provides: "PdfViewerWrapper threads aiPlacementKey and onAiPlacementKeyChange to PdfViewer/FieldPlacer" contains: "aiPlacementKey" - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx" provides: "AI Auto-place button with loading state, calls onAiAutoPlace callback" contains: "AI Auto-place" - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx" provides: "handleAiAutoPlace that calls route, updates textFillData, increments aiPlacementKey, resets previewToken" contains: "handleAiAutoPlace" key_links: - from: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx" to: "/api/documents/[id]/ai-prepare" via: "onAiAutoPlace callback → DocumentPageClient.handleAiAutoPlace → fetch POST" pattern: "onAiAutoPlace|handleAiAutoPlace" - from: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx" to: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx" via: "aiPlacementKey prop threaded through PdfViewerWrapper" pattern: "aiPlacementKey" --- Wire the "AI Auto-place" button into the PreparePanel and connect it to the FieldPlacer reload mechanism via DocumentPageClient state. Purpose: The AI utilities (Plan 01) and route (Plan 02) are server-side. This plan adds the client-side interaction: the button, the loading state, the post-success state updates (fields reload + textFillData update + preview reset), and the FieldPlacer re-fetch trigger. Output: Four files modified — FieldPlacer gets `aiPlacementKey` prop, PdfViewerWrapper threads it, PreparePanel gets "AI Auto-place" button, DocumentPageClient orchestrates the handler. @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/13-ai-field-placement-and-pre-fill/13-RESEARCH.md @.planning/phases/13-ai-field-placement-and-pre-fill/13-01-SUMMARY.md @.planning/phases/13-ai-field-placement-and-pre-fill/13-02-SUMMARY.md Read next.config.ts and check for Next.js guide in node_modules/next/dist/docs/ before writing component code. Current FieldPlacer props (FieldPlacer.tsx line 155-166): ```typescript interface FieldPlacerProps { docId: string; pageInfo: PageInfo | null; currentPage: number; children: React.ReactNode; readOnly?: boolean; onFieldsChanged?: () => void; selectedFieldId?: string | null; textFillData?: Record; onFieldSelect?: (fieldId: string | null) => void; onFieldValueChange?: (fieldId: string, value: string) => void; } // FieldPlacer loads fields in useEffect([docId]) — adding aiPlacementKey to dependency array triggers re-fetch ``` Current PdfViewerWrapper props: ```typescript // PdfViewerWrapper({ docId, docStatus, onFieldsChanged, selectedFieldId, textFillData, onFieldSelect, onFieldValueChange }) // It wraps PdfViewer (dynamic import, ssr: false) which renders FieldPlacer // PdfViewer is at src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx // It passes all props through to FieldPlacer ``` Current PreparePanel props (PreparePanel.tsx lines 6-18): ```typescript interface PreparePanelProps { docId: string; defaultEmail: string; clientName: string; currentStatus: string; agentDownloadUrl?: string | null; signedAt?: Date | null; clientPropertyAddress?: string | null; previewToken: string | null; onPreviewTokenChange: (token: string | null) => void; textFillData: Record; selectedFieldId: string | null; onQuickFill: (fieldId: string, value: string) => void; } ``` Current DocumentPageClient state (DocumentPageClient.tsx): ```typescript const [previewToken, setPreviewToken] = useState(null); const [selectedFieldId, setSelectedFieldId] = useState(null); const [textFillData, setTextFillData] = useState>({}); // handleFieldsChanged, handleFieldValueChange, handleQuickFill already exist ``` Task 1: Add aiPlacementKey prop to FieldPlacer and thread through PdfViewerWrapper teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx **FieldPlacer.tsx changes:** 1. Add `aiPlacementKey?: number` to `FieldPlacerProps` interface. 2. Add `aiPlacementKey = 0` to the destructured props in the function signature. 3. Update the `loadFields` useEffect dependency array to include `aiPlacementKey`: ```typescript // Change from: useEffect(() => { ... loadFields(); }, [docId]); // Change to: useEffect(() => { ... loadFields(); }, [docId, aiPlacementKey]); ``` This causes FieldPlacer to re-fetch from DB whenever `aiPlacementKey` increments after AI placement. No other changes to FieldPlacer.tsx. **PdfViewerWrapper.tsx changes:** Read PdfViewer.tsx first to understand how it passes props to FieldPlacer — the thread needs to go: `DocumentPageClient → PdfViewerWrapper → PdfViewer → FieldPlacer`. Add `aiPlacementKey?: number` to PdfViewerWrapper's props interface and pass it through to PdfViewer: ```typescript export function PdfViewerWrapper({ docId, docStatus, onFieldsChanged, selectedFieldId, textFillData, onFieldSelect, onFieldValueChange, aiPlacementKey, // NEW }: { docId: string; docStatus?: string; onFieldsChanged?: () => void; selectedFieldId?: string | null; textFillData?: Record; onFieldSelect?: (fieldId: string | null) => void; onFieldValueChange?: (fieldId: string, value: string) => void; aiPlacementKey?: number; // NEW }) { return ( ); } ``` Also read PdfViewer.tsx and add `aiPlacementKey?: number` to its props + pass through to FieldPlacer. PdfViewer is the component that actually renders FieldPlacer wrapping the PDF page canvas. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | grep -E "FieldPlacer|PdfViewer|error TS" | head -20 - FieldPlacer accepts and uses `aiPlacementKey` in the loadFields useEffect dependency array - PdfViewerWrapper and PdfViewer thread `aiPlacementKey` through to FieldPlacer - TypeScript compiles without errors for these files Task 2: Add AI Auto-place button to PreparePanel and wire DocumentPageClient handler teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx **PreparePanel.tsx changes:** 1. Add `onAiAutoPlace: () => Promise` to `PreparePanelProps` interface. 2. Destructure `onAiAutoPlace` from props. 3. Add a local `aiLoading` state: `const [aiLoading, setAiLoading] = useState(false)`. 4. Add an `handleAiAutoPlaceClick` function in the component: ```typescript async function handleAiAutoPlaceClick() { setAiLoading(true); setResult(null); try { await onAiAutoPlace(); } catch (e) { setResult({ ok: false, message: String(e) }); } finally { setAiLoading(false); } } ``` 5. Add the "AI Auto-place" button to the JSX. Position it ABOVE the Preview button, at the top of the Draft panel (after the recipients textarea section, before the quick-fill section). Only show it when `currentStatus === 'Draft'`: ```tsx ``` Use violet (#7c3aed) to visually distinguish from the gray Preview button and blue Prepare and Send button. **DocumentPageClient.tsx changes:** 1. Add `aiPlacementKey` state: `const [aiPlacementKey, setAiPlacementKey] = useState(0)`. 2. Add `handleAiAutoPlace` callback: ```typescript const handleAiAutoPlace = useCallback(async () => { const res = await fetch(`/api/documents/${docId}/ai-prepare`, { method: 'POST' }); if (!res.ok) { const err = await res.json().catch(() => ({ error: 'AI placement failed' })); throw new Error(err.error ?? err.message ?? 'AI placement failed'); } const { textFillData: aiTextFill } = await res.json() as { fields: unknown[]; textFillData: Record; }; // Merge AI pre-fill into existing textFillData (AI values take precedence) setTextFillData(prev => ({ ...prev, ...aiTextFill })); // Trigger FieldPlacer to re-fetch from DB (fields were written server-side) setAiPlacementKey(k => k + 1); // Reset preview staleness — fields changed setPreviewToken(null); }, [docId]); ``` 3. Pass `aiPlacementKey` to `PdfViewerWrapper`: ```tsx ``` 4. Pass `onAiAutoPlace={handleAiAutoPlace}` to `PreparePanel`. Key design decisions: - AI placement REPLACES all existing fields in the DB (the route's `db.update` overwrites `signatureFields`) - FieldPlacer re-fetches from DB via the aiPlacementKey increment — this is the single source of truth - textFillData is MERGED (not replaced) so any manually typed values are preserved alongside AI pre-fills - previewToken is reset to null — agent must re-preview after AI placement - Error from the route is thrown and caught in PreparePanel's handleAiAutoPlaceClick, displayed via the existing `result` state cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | grep -E "PreparePanel|DocumentPageClient|error TS" | head -20 - PreparePanel has "AI Auto-place Fields" button with violet color, loading state, error display - DocumentPageClient has handleAiAutoPlace calling /api/documents/[id]/ai-prepare - After success: textFillData merges AI pre-fills, aiPlacementKey increments, previewToken resets to null - Error from route is surfaced to user via PreparePanel result state - TypeScript compiles without errors across all 4 modified files - `npx tsc --noEmit` passes with no errors - FieldPlacer.tsx: `aiPlacementKey` in loadFields useEffect dependency array - PdfViewerWrapper.tsx + PdfViewer.tsx: `aiPlacementKey` threaded through - PreparePanel.tsx: "AI Auto-place Fields" button renders (violet, above Preview button) for Draft docs - DocumentPageClient.tsx: `handleAiAutoPlace` callback, `aiPlacementKey` state, wired to both components - Agent can still drag, move, resize, delete fields after AI placement (existing FieldPlacer behavior unchanged) - TypeScript compiles clean for all 4 files - "AI Auto-place Fields" button is visible in PreparePanel for Draft documents - Clicking the button calls POST /api/documents/[id]/ai-prepare - On success: FieldPlacer shows AI-placed fields (via DB re-fetch), textFillData merges pre-fills, previewToken is null - On error: error message displayed in PreparePanel result area - Existing functionality unchanged: drag-and-drop, click-to-edit, quick-fill, preview, prepare-and-send After completion, create `.planning/phases/13-ai-field-placement-and-pre-fill/13-03-SUMMARY.md`