diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 7c42e97..44d63ac 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -272,7 +272,11 @@ Plans:
3. When a text field is selected, PreparePanel shows quick-fill suggestion buttons (Client Name, Property Address, Client Email) that insert the corresponding value into the selected field
4. Text fill values entered per-field appear correctly embedded in the preview PDF and the final prepared PDF
5. The staleness token (previewToken) is reset when any text field value changes
-**Plans**: TBD
+**Plans**: 2 plans
+
+Plans:
+- [ ] 12.1-01-PLAN.md — preparePdf() field-ID-keyed text lookup + Strategy B removal; FieldPlacer click-to-select inline input; PdfViewer + PdfViewerWrapper optional prop chain
+- [ ] 12.1-02-PLAN.md — DocumentPageClient selectedFieldId + textFillData shared state; PreparePanel QuickFillPanel + TextFillForm removal; human verification checkpoint
### Phase 13: AI Field Placement and Pre-fill
**Goal**: Agent clicks one button and AI auto-places all field types on the PDF in correct positions and pre-fills text fields with known client and property data
@@ -311,4 +315,5 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 11. Agent Saved Signature and Signing Workflow | 3/3 | Complete | 2026-03-21 | - |
| 11.1. Agent and Client Initials (INSERTED) | 3/3 | Complete | 2026-03-21 | - |
| 12. Filled Document Preview | 2/2 | Complete | 2026-03-21 | - |
+| 12.1. Per-Field Text Editing and Quick-Fill (INSERTED) | v1.1 | 0/2 | Not started | - |
| 13. AI Field Placement and Pre-fill | v1.1 | 0/4 | Not started | - |
diff --git a/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-01-PLAN.md b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-01-PLAN.md
new file mode 100644
index 0000000..cd1738a
--- /dev/null
+++ b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-01-PLAN.md
@@ -0,0 +1,435 @@
+---
+phase: 12.1-per-field-text-editing-and-quick-fill
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - teressa-copeland-homes/src/lib/pdf/prepare-document.ts
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx
+autonomous: true
+requirements:
+ - TXTF-01
+ - TXTF-03
+
+must_haves:
+ truths:
+ - "preparePdf draws text at the correct field-box position using the UUID field ID as the lookup key — not positional index"
+ - "Strategy B top-of-page fallback stamping is removed — no UUID strings appear at the top of page 1"
+ - "FieldPlacer accepts selectedFieldId, textFillData, onFieldSelect, onFieldValueChange as optional props without TypeScript errors"
+ - "PdfViewer and PdfViewerWrapper compile cleanly with the new optional props threaded through"
+ - "Clicking a text field box on the PDF selects it and renders an inline input"
+ - "The inline input does not trigger the move/drag handler (data-no-move + stopPropagation)"
+ - "Clicking a non-text field or the PDF background deselects the current field"
+ artifacts:
+ - path: "teressa-copeland-homes/src/lib/pdf/prepare-document.ts"
+ provides: "Field-ID-keyed text drawing; Strategy B and positional loop removed"
+ contains: "textFields[field.id]"
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx"
+ provides: "Per-field click-to-select + inline input overlay for text fields"
+ exports: ["FieldPlacer"]
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx"
+ provides: "Optional prop forwarding to FieldPlacer"
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx"
+ provides: "Optional prop forwarding to PdfViewer"
+ key_links:
+ - from: "DocumentPageClient.tsx (Plan 02)"
+ to: "PdfViewerWrapper.tsx"
+ via: "selectedFieldId, textFillData, onFieldSelect, onFieldValueChange optional props"
+ pattern: "selectedFieldId\\?"
+ - from: "FieldPlacer.tsx renderFields()"
+ to: "text field box inline input"
+ via: "onClick calls onFieldSelect?(field.id); input onChange calls onFieldValueChange?(field.id, value)"
+ pattern: "onFieldSelect\\?\\."
+ - from: "prepare-document.ts"
+ to: "field-ID lookup"
+ via: "textFields[field.id] direct lookup"
+ pattern: "textFields\\[field\\.id\\]"
+---
+
+
+Replace the broken positional text fill pipeline with a field-ID-keyed approach, and add the per-field click-to-select + inline input interaction to FieldPlacer.
+
+Purpose: The current preparePdf() assigns textFillData values positionally (sort by page/y, match by index), which breaks when field order or count doesn't align with the textFillData entries. Strategy B then stamps UUID keys as visible text at the top of page 1. This plan fixes both issues at the infrastructure and interaction layers.
+
+Output:
+- prepare-document.ts: direct field-ID lookup replaces positional loop; Strategy B removed
+- FieldPlacer.tsx: text fields render inline input when selected; DroppableZone deselects on background click
+- PdfViewer.tsx + PdfViewerWrapper.tsx: optional prop chain threaded through so Plan 02 can wire them
+
+
+
+@/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/phases/12.1-per-field-text-editing-and-quick-fill/12.1-RESEARCH.md
+@.planning/phases/12-filled-document-preview/12-02-SUMMARY.md
+
+
+
+
+From src/lib/pdf/prepare-document.ts (current):
+```typescript
+export async function preparePdf(
+ srcPath: string,
+ destPath: string,
+ textFields: Record, // currently keyed by label — changing to fieldId
+ sigFields: SignatureFieldData[],
+ agentSignatureData: string | null = null,
+ agentInitialsData: string | null = null,
+): Promise
+```
+Current broken positional block (lines 77-112) to REMOVE:
+```typescript
+const remainingEntries: Array<[string, string]> = Object.entries(textFields).filter(...)
+const textFields_sorted = sigFields.filter(f => getFieldType(f) === 'text').sort(...)
+const fieldConsumedKeys = new Set();
+textFields_sorted.forEach((field, idx) => { const entry = remainingEntries[idx]; ... });
+```
+Current Strategy B block (lines 118-147) to REMOVE:
+```typescript
+const unstampedEntries = remainingEntries.filter(([key]) => !fieldConsumedKeys.has(key));
+if (unstampedEntries.length > 0) { ... firstPage.drawText(`${key}: ${value}`, ...) }
+```
+
+From src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx (current):
+```typescript
+interface FieldPlacerProps {
+ docId: string;
+ pageInfo: PageInfo | null;
+ currentPage: number;
+ children: React.ReactNode;
+ readOnly?: boolean;
+ onFieldsChanged?: () => void;
+}
+```
+DroppableZone currently at line 726 — needs onClick handler added.
+renderFields() per-field div at line 612 — text fields need click-to-select rendering.
+
+From src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx (current):
+```typescript
+export function PdfViewer({ docId, docStatus, onFieldsChanged }: { docId: string; docStatus?: string; onFieldsChanged?: () => void })
+```
+FieldPlacer render at line 72 — needs new optional props forwarded.
+
+From src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx (current):
+```typescript
+export function PdfViewerWrapper({ docId, docStatus, onFieldsChanged }: { docId: string; docStatus?: string; onFieldsChanged?: () => void })
+```
+Wraps PdfViewer via Next.js dynamic() — needs 4 new optional props added and forwarded.
+
+
+
+
+
+
+ Task 1: Replace positional text fill with field-ID-keyed lookup in preparePdf
+ teressa-copeland-homes/src/lib/pdf/prepare-document.ts
+
+In `teressa-copeland-homes/src/lib/pdf/prepare-document.ts`:
+
+1. **Remove the positional block** (lines ~47-112): Delete the entire section that includes:
+ - `acroFilledKeys` Set declaration and population
+ - `remainingEntries` array construction
+ - `textFields_sorted` sort
+ - `fieldConsumedKeys` Set
+ - `textFields_sorted.forEach(...)` loop that draws text at field positions
+
+2. **Remove Strategy A** (AcroForm filling, lines ~52-69): Remove the try/catch block that calls `pdfDoc.getForm()`, fills AcroForm fields, and calls `form.flatten()`. Also remove `hasAcroForm` variable. The AcroForm strategy is no longer needed — text values are always drawn at field box positions.
+
+3. **Remove Strategy B** (lines ~118-147): Delete the `unstampedEntries` block that stamps key/value pairs at the top of page 1.
+
+4. **Add the new field-ID-keyed drawing loop** in their place, before the existing `for (const field of sigFields)` loop that handles non-text field types. Insert:
+ ```typescript
+ // Phase 12.1: Draw text values at placed text field boxes — keyed by field ID (UUID)
+ for (const field of sigFields) {
+ if (getFieldType(field) !== 'text') continue;
+ const value = textFields[field.id];
+ if (!value) continue;
+ const page = pages[field.page - 1];
+ if (!page) continue;
+ const fontSize = Math.max(6, Math.min(11, field.height - 4));
+ page.drawText(value, {
+ x: field.x + 4,
+ y: field.y + 4,
+ size: fontSize,
+ font: helvetica,
+ color: rgb(0.05, 0.05, 0.05),
+ });
+ }
+ ```
+
+5. **In the field-type dispatch loop** (the existing `for (const field of sigFields)` block that renders client-signature, initials, checkbox, date, agent-signature, agent-initials), the `text` case currently says "Text value drawn above". Keep that comment as-is (or update to "Drawn in field-ID loop above") — no additional drawing needed there.
+
+6. **Update the JSDoc comment** at the top of preparePdf to remove the reference to AcroForm Strategy A and Strategy B. Replace with:
+ ```
+ * Text fill: textFields is keyed by SignatureFieldData.id (UUID). Each text-type
+ * placed field box has its value looked up directly by field ID and drawn at
+ * the field's coordinates.
+ ```
+
+7. Remove `acroFilledKeys`, `hasAcroForm`, `remainingEntries`, `fieldConsumedKeys`, `textFields_sorted` variables since none exist in the new code.
+
+Note: The `textFields` parameter type is still `Record` — no signature change needed. The calling routes pass `body.textFillData` directly, which will now be `{ [fieldId]: value }` once Plan 02 wires the UI. The function signature remains backward-compatible.
+
+
+ cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30
+
+
+ - `textFields[field.id]` lookup exists in prepare-document.ts
+ - No `remainingEntries`, `fieldConsumedKeys`, `textFields_sorted`, or `unstampedEntries` variables remain
+ - No AcroForm `getForm()`/`flatten()` calls remain
+ - No Strategy B `drawText` at top of page 1 remains
+ - `npx tsc --noEmit` passes with zero errors
+
+
+
+
+ Task 2: Add optional text-edit props to PdfViewerWrapper, PdfViewer, and FieldPlacer click-to-select
+
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
+
+
+**PdfViewerWrapper.tsx** — Add 4 optional props and forward to PdfViewer:
+```typescript
+export function PdfViewerWrapper({
+ docId,
+ docStatus,
+ onFieldsChanged,
+ selectedFieldId,
+ textFillData,
+ onFieldSelect,
+ onFieldValueChange,
+}: {
+ docId: string;
+ docStatus?: string;
+ onFieldsChanged?: () => void;
+ selectedFieldId?: string | null;
+ textFillData?: Record;
+ onFieldSelect?: (fieldId: string | null) => void;
+ onFieldValueChange?: (fieldId: string, value: string) => void;
+}) {
+ return (
+
+ );
+}
+```
+
+**PdfViewer.tsx** — Same 4 optional props; forward to FieldPlacer; clear selectedFieldId on page change:
+1. Add the same 4 optional props to the PdfViewer function signature.
+2. In the `` render (line 72), add the 4 new props:
+ ```tsx
+
+ ```
+3. In the Prev/Next page buttons' onClick handlers, also call `onFieldSelect?.(null)` to deselect when the agent navigates pages. The next button becomes:
+ ```tsx
+ onClick={() => { setPageNumber(p => Math.min(numPages, p + 1)); onFieldSelect?.(null); }}
+ ```
+ And prev button:
+ ```tsx
+ onClick={() => { setPageNumber(p => Math.max(1, p - 1)); onFieldSelect?.(null); }}
+ ```
+
+**FieldPlacer.tsx** — Add 4 optional props; wire click-to-select on text fields; deselect on background click:
+
+1. Extend `FieldPlacerProps` interface:
+ ```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;
+ }
+ ```
+
+2. Destructure the 4 new props in the function signature alongside the existing props.
+
+3. In `DroppableZone`, add an `onClick` prop that deselects when the agent clicks the PDF background (not a field):
+ ```tsx
+ {
+ // Deselect if clicking the zone background (not a field box)
+ if (!(e.target as HTMLElement).closest('[data-field-id]')) {
+ onFieldSelect?.(null);
+ }
+ }}
+ >
+ ```
+ Also update the `DroppableZone` component definition (lines 120-150) to accept and spread an `onClick` prop:
+ ```typescript
+ function DroppableZone({
+ id, containerRef, children, onZonePointerMove, onZonePointerUp, onClick,
+ }: {
+ id: string;
+ containerRef: React.RefObject;
+ children: React.ReactNode;
+ onZonePointerMove: (e: React.PointerEvent) => void;
+ onZonePointerUp: (e: React.PointerEvent) => void;
+ onClick?: (e: React.MouseEvent) => void;
+ }) {
+ // ...existing body, add onClick to the div:
+ return (
+
+ {children}
+
+ );
+ }
+ ```
+
+4. In `renderFields()`, change the text field rendering. Currently text fields use the same div as other field types. Replace the entire per-field div for `text` type fields with this pattern (inside the `.map()` callback in `renderFields()`):
+
+ Add these variables at the top of the `.map()` callback (alongside existing `isMoving`, `fieldType`, etc.):
+ ```typescript
+ const isSelected = selectedFieldId === field.id;
+ const currentValue = textFillData?.[field.id] ?? '';
+ ```
+
+ Then, inside the field box div, replace the current content:
+ ```tsx
+ // Current (for all types):
+ {fieldType !== 'checkbox' && (
+ {fieldLabel}
+ )}
+ ```
+ With a conditional that handles text differently:
+ ```tsx
+ {fieldType === 'text' ? (
+ isSelected ? (
+ onFieldValueChange?.(field.id, e.target.value)}
+ onPointerDown={(e) => e.stopPropagation()}
+ style={{
+ flex: 1,
+ background: 'transparent',
+ border: 'none',
+ outline: 'none',
+ fontSize: '10px',
+ color: fieldColor,
+ width: '100%',
+ cursor: 'text',
+ padding: 0,
+ }}
+ placeholder="Type value..."
+ />
+ ) : (
+
+ {currentValue || fieldLabel}
+
+ )
+ ) : (
+ fieldType !== 'checkbox' && (
+ {fieldLabel}
+ )
+ )}
+ ```
+
+5. Add an `onClick` handler to the per-field div (alongside `onPointerDown`) that fires for text field selection:
+ ```tsx
+ onClick={(e) => {
+ if (readOnly) return;
+ if (getFieldType(field) === 'text') {
+ e.stopPropagation(); // prevent DroppableZone's deselect handler from firing
+ onFieldSelect?.(field.id);
+ } else {
+ // Non-text field click: deselect any selected text field
+ onFieldSelect?.(null);
+ }
+ }}
+ ```
+
+ Important: The existing `onPointerDown` handler is NOT removed — move/resize still uses it. `onClick` fires after pointerdown+pointerup with no movement (< 5px — MouseSensor `{ distance: 5 }` threshold ensures drags don't trigger onClick).
+
+6. Update the field box div's `cursor` style to reflect selection state for text fields:
+ ```typescript
+ cursor: readOnly ? 'default' : (isMoving ? 'grabbing' : (fieldType === 'text' ? 'text' : 'grab')),
+ ```
+
+7. Update the border style to highlight selected text fields:
+ ```typescript
+ border: `2px solid ${isSelected && fieldType === 'text' ? fieldColor : fieldColor}`,
+ // Also thicken border when selected:
+ border: isSelected && fieldType === 'text' ? `2px solid ${fieldColor}` : `2px solid ${fieldColor}`,
+ // Add a box shadow for selected state:
+ boxShadow: isSelected && fieldType === 'text'
+ ? `0 0 0 2px ${fieldColor}66, ${isMoving ? `0 4px 12px ${fieldColor}59` : ''}`
+ : (isMoving ? `0 4px 12px ${fieldColor}59` : undefined),
+ ```
+
+
+ cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30
+
+
+ - PdfViewerWrapper, PdfViewer, FieldPlacer all compile with zero TypeScript errors
+ - FieldPlacerProps has selectedFieldId?, textFillData?, onFieldSelect?, onFieldValueChange? optional props
+ - DroppableZone has onClick prop accepted and applied
+ - Text field boxes in renderFields() show inline input when isSelected === true
+ - Non-text field onClick calls onFieldSelect?.(null)
+ - npx tsc --noEmit passes with zero errors
+
+
+
+
+
+
+After both tasks complete:
+- `npx tsc --noEmit` in teressa-copeland-homes passes with zero errors
+- `prepare-document.ts` contains `textFields[field.id]` and does NOT contain `remainingEntries`, `unstampedEntries`, `fieldConsumedKeys`, `getForm()`, or `form.flatten()`
+- `FieldPlacer.tsx` exports `FieldPlacer` with the 4 new optional props in its interface
+- `PdfViewerWrapper.tsx` and `PdfViewer.tsx` accept and forward the 4 new optional props
+
+
+
+- preparePdf draws text at field-ID-keyed positions — no positional assignment, no Strategy B stamps
+- FieldPlacer text fields show inline input on click (isSelected state); show value or label otherwise
+- Prop chain complete from PdfViewerWrapper through PdfViewer to FieldPlacer — Plan 02 can wire state from DocumentPageClient without any additional changes to these files
+- TypeScript: zero errors
+
+
+
diff --git a/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-02-PLAN.md b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-02-PLAN.md
new file mode 100644
index 0000000..389c30a
--- /dev/null
+++ b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-02-PLAN.md
@@ -0,0 +1,431 @@
+---
+phase: 12.1-per-field-text-editing-and-quick-fill
+plan: 02
+type: execute
+wave: 2
+depends_on:
+ - 12.1-01
+files_modified:
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx
+ - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx
+autonomous: false
+requirements:
+ - TXTF-01
+ - TXTF-02
+ - TXTF-03
+
+must_haves:
+ truths:
+ - "Agent can click a placed text field box and type a value — the value is stored under the field's UUID, not a label"
+ - "Changing any text field value resets previewToken to null, re-disabling the Send button"
+ - "When a text field is selected, PreparePanel shows Client Name / Property Address / Client Email quick-fill buttons"
+ - "Quick-fill buttons insert the client profile value into the currently selected field and reset previewToken"
+ - "textFillData is no longer local state in PreparePanel — it lives in DocumentPageClient and is passed as a prop"
+ - "TextFillForm is removed from PreparePanel and the file is deleted"
+ - "PreparePanel's handlePreview and handlePrepare use textFillData prop (not local state)"
+ - "Agent can preview and send a document with per-field text fill values correctly embedded"
+ artifacts:
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx"
+ provides: "selectedFieldId + textFillData shared state; handleFieldValueChange + handleQuickFill callbacks"
+ contains: "selectedFieldId"
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx"
+ provides: "QuickFillPanel when field selected; no TextFillForm; textFillData as prop"
+ contains: "onQuickFill"
+ - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx"
+ provides: "Deleted — no longer used"
+ key_links:
+ - from: "DocumentPageClient.tsx handleFieldValueChange"
+ to: "FieldPlacer.tsx onFieldValueChange prop"
+ via: "PdfViewerWrapper onFieldValueChange prop -> PdfViewer -> FieldPlacer"
+ pattern: "onFieldValueChange"
+ - from: "DocumentPageClient.tsx handleQuickFill"
+ to: "PreparePanel.tsx onQuickFill prop"
+ via: "direct prop passing"
+ pattern: "onQuickFill"
+ - from: "PreparePanel.tsx handlePreview"
+ to: "/api/documents/[id]/preview"
+ via: "textFillData prop (now Record)"
+ pattern: "textFillData"
+ - from: "PreparePanel.tsx handlePrepare"
+ to: "/api/documents/[id]/prepare"
+ via: "textFillData prop (now Record)"
+ pattern: "textFillData"
+---
+
+
+Wire the per-field text editing state bridge in DocumentPageClient, replace TextFillForm in PreparePanel with the QuickFillPanel, and delete TextFillForm.tsx.
+
+Purpose: Plan 01 created the prop chain and field interaction UI. This plan creates the shared state that drives it — selectedFieldId and textFillData lifted to DocumentPageClient so FieldPlacer (left column) and PreparePanel (right sidebar) stay in sync.
+
+Output:
+- DocumentPageClient.tsx: selectedFieldId + textFillData state + two new callbacks; props threaded to PdfViewerWrapper and PreparePanel
+- PreparePanel.tsx: TextFillForm removed; QuickFillPanel added; textFillData + selectedFieldId + onQuickFill props added; textFillData moved from local state to prop
+- TextFillForm.tsx: deleted
+
+
+
+@/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/phases/12.1-per-field-text-editing-and-quick-fill/12.1-RESEARCH.md
+@.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-01-SUMMARY.md
+
+
+
+
+From DocumentPageClient.tsx (current — to be extended):
+```typescript
+'use client';
+import { useState, useCallback } from 'react';
+import { PdfViewerWrapper } from './PdfViewerWrapper';
+import { PreparePanel } from './PreparePanel';
+
+interface DocumentPageClientProps {
+ docId: string;
+ docStatus: string;
+ defaultEmail: string;
+ clientName: string;
+ agentDownloadUrl?: string | null;
+ signedAt?: Date | null;
+ clientPropertyAddress?: string | null;
+}
+// Currently has: [previewToken, setPreviewToken], handleFieldsChanged
+// Passes to PdfViewerWrapper: { docId, docStatus, onFieldsChanged }
+// Passes to PreparePanel: { docId, defaultEmail, clientName, currentStatus, agentDownloadUrl, signedAt, clientPropertyAddress, previewToken, onPreviewTokenChange }
+```
+
+From PreparePanel.tsx (current — to be overhauled):
+```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;
+}
+// Currently has:
+// - textFillData local state (seeded from clientPropertyAddress — REMOVE)
+// - TextFillForm component (REMOVE)
+// - handleTextFillChange (REMOVE — textFillData is now a prop)
+// - handlePreview: uses local textFillData (update to use prop)
+// - handlePrepare: uses local textFillData (update to use prop)
+```
+
+From PdfViewerWrapper.tsx (after Plan 01):
+```typescript
+export function PdfViewerWrapper({
+ docId, docStatus, onFieldsChanged,
+ selectedFieldId, textFillData, onFieldSelect, onFieldValueChange,
+}: {
+ docId: string; docStatus?: string; onFieldsChanged?: () => void;
+ selectedFieldId?: string | null;
+ textFillData?: Record;
+ onFieldSelect?: (fieldId: string | null) => void;
+ onFieldValueChange?: (fieldId: string, value: string) => void;
+})
+```
+
+TextFillForm.tsx (to be deleted — the entire component is replaced):
+```typescript
+// Generic label/value row form — no longer used after this plan
+export function TextFillForm({ onChange, initialData }: TextFillFormProps)
+```
+
+
+
+
+
+
+ Task 1: Extend DocumentPageClient with selectedFieldId + textFillData shared state
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx
+
+Rewrite `DocumentPageClient.tsx` in full (it is a small file — 51 lines currently). The new version:
+
+```typescript
+'use client';
+import { useState, useCallback } from 'react';
+import { PdfViewerWrapper } from './PdfViewerWrapper';
+import { PreparePanel } from './PreparePanel';
+
+interface DocumentPageClientProps {
+ docId: string;
+ docStatus: string;
+ defaultEmail: string;
+ clientName: string;
+ agentDownloadUrl?: string | null;
+ signedAt?: Date | null;
+ clientPropertyAddress?: string | null;
+}
+
+export function DocumentPageClient({
+ docId,
+ docStatus,
+ defaultEmail,
+ clientName,
+ agentDownloadUrl,
+ signedAt,
+ clientPropertyAddress,
+}: DocumentPageClientProps) {
+ const [previewToken, setPreviewToken] = useState(null);
+ const [selectedFieldId, setSelectedFieldId] = useState(null);
+ const [textFillData, setTextFillData] = useState>({});
+
+ const handleFieldsChanged = useCallback(() => {
+ setPreviewToken(null);
+ }, []);
+
+ const handleFieldValueChange = useCallback((fieldId: string, value: string) => {
+ setTextFillData(prev => ({ ...prev, [fieldId]: value }));
+ setPreviewToken(null); // TXTF-03: reset staleness on any text value change
+ }, []);
+
+ const handleQuickFill = useCallback((fieldId: string, value: string) => {
+ setTextFillData(prev => ({ ...prev, [fieldId]: value }));
+ setPreviewToken(null); // TXTF-03: reset staleness on quick fill
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+Key notes:
+- `textFillData` starts as `{}` — do NOT seed from `clientPropertyAddress`. The old seeding (`{ propertyAddress: clientPropertyAddress }`) mapped to a label key, which is the broken pattern being replaced. Quick-fill makes it trivial to insert the value once a field is selected.
+- Both `handleFieldValueChange` and `handleQuickFill` call `setPreviewToken(null)` to satisfy TXTF-03 (staleness reset).
+- `setSelectedFieldId` is passed directly as `onFieldSelect` — no wrapper needed.
+
+
+ cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30
+
+
+ - DocumentPageClient.tsx has selectedFieldId, textFillData state variables
+ - handleFieldValueChange and handleQuickFill both call setPreviewToken(null)
+ - PdfViewerWrapper receives selectedFieldId, textFillData, onFieldSelect, onFieldValueChange
+ - PreparePanel receives textFillData, selectedFieldId, onQuickFill (in addition to existing props)
+ - textFillData starts as {} (no clientPropertyAddress seeding)
+ - npx tsc --noEmit passes with zero errors
+
+
+
+
+ Task 2: Replace TextFillForm with QuickFillPanel in PreparePanel and delete TextFillForm.tsx
+
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx
+ teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx
+
+
+**PreparePanel.tsx** — make these changes:
+
+1. **Add 3 new props** to `PreparePanelProps`:
+ ```typescript
+ textFillData: Record;
+ selectedFieldId: string | null;
+ onQuickFill: (fieldId: string, value: string) => void;
+ ```
+
+2. **Remove `textFillData` local state** — delete lines:
+ ```typescript
+ const [textFillData, setTextFillData] = useState>(
+ () => (clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {} as Record)
+ );
+ ```
+
+3. **Remove `handleTextFillChange`** function — it was the wrapper that called `setTextFillData` and `onPreviewTokenChange(null)`. This is no longer needed; the parent handles it.
+
+4. **Remove the `TextFillForm` import** at the top of the file.
+
+5. **Remove the TextFillForm JSX block** in the return:
+ ```tsx
+ // REMOVE THIS:
+
+
+
+
+ ```
+
+6. **Replace it with the QuickFillPanel**. Insert this in place of the TextFillForm block, before the Preview button:
+ ```tsx
+ {/* Quick-fill panel — only shown when a text field is selected */}
+
+
+ {selectedFieldId ? (
+
+
+ Click a suggestion to fill the selected field.
+
+ Click a text field on the document to edit or quick-fill it.
+
+ )}
+
+ ```
+
+ Note: `defaultEmail` is already a prop on PreparePanel (used for the recipients textarea pre-fill). It IS the client email — reuse it for the "Client Email" quick-fill button. No new prop needed (per research Pitfall 3).
+
+7. **Update `handlePreview`** — `textFillData` is now a prop, not local state. The body is already correct (`body: JSON.stringify({ textFillData })`) but verify the prop is used, not a stale reference to the old local state.
+
+8. **Update `handlePrepare`** — same: `textFillData` prop is used in `body: JSON.stringify({ textFillData, emailAddresses })`. Verify.
+
+9. **Destructure the 3 new props** in the function signature:
+ ```typescript
+ export function PreparePanel({
+ docId, defaultEmail, clientName, currentStatus,
+ agentDownloadUrl, signedAt, clientPropertyAddress,
+ previewToken, onPreviewTokenChange,
+ textFillData, selectedFieldId, onQuickFill,
+ }: PreparePanelProps)
+ ```
+
+**TextFillForm.tsx** — delete the file:
+Use the Bash tool or Write tool to delete the file. Since Write overwrites, you cannot delete with it. Use:
+```bash
+rm teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx
+```
+(Run from the project root `/Users/ccopeland/temp/red`)
+
+If for any reason the delete fails, leave the file in place but ensure it is no longer imported anywhere — TypeScript will tree-shake it. The important thing is PreparePanel no longer imports or uses it.
+
+
+ cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30 && echo "TSC_CLEAN" && grep -r "TextFillForm" src/ --include="*.tsx" --include="*.ts" | grep -v "\.tsx:.*import" | head -5 || echo "NO_REMAINING_TEXTFILLFORM_IMPORTS"
+
+
+ - PreparePanel.tsx compiles with zero TypeScript errors
+ - PreparePanel has no import of TextFillForm
+ - PreparePanel has no local textFillData state
+ - PreparePanel has selectedFieldId, textFillData, onQuickFill in its props interface
+ - QuickFillPanel renders when selectedFieldId is non-null
+ - TextFillForm.tsx is deleted (or at minimum has no remaining imports in the codebase)
+ - handlePreview and handlePrepare use textFillData from props
+ - npx tsc --noEmit passes with zero errors
+
+
+
+
+ Task 3: Human verification — per-field text editing and quick-fill end-to-end
+
+ Full per-field text editing flow:
+ - Placed text field boxes on PDF are clickable — click selects the field and shows an inline input
+ - Typing in the inline input stores the value under the field's UUID in textFillData
+ - PreparePanel sidebar shows quick-fill buttons for Client Name, Property Address, Client Email when a text field is selected
+ - Clicking a quick-fill button inserts the value into the selected field
+ - Every text value change resets previewToken (Send button re-disabled)
+ - Preview PDF embeds text values at the correct field-box positions (not at page top)
+ - TextFillForm is gone from PreparePanel
+ - Full Preview-gate-Send flow works with the new per-field data model
+
+
+ 1. Open a document in Draft status that has at least 2 placed text field boxes
+ 2. Click one text field box — verify it highlights and shows an inline cursor/input
+ 3. Verify the PreparePanel sidebar now shows "Text field fill" section with quick-fill buttons
+ 4. Click "Client Name" quick-fill button — verify the field shows the client's name
+ 5. Click the second text field box — verify it is now selected and the first field shows the filled value as a label
+ 6. Type a custom value in the second field — verify it persists in the box
+ 7. Click "Preview" — verify the preview PDF shows both text values at their field box positions (not at the top of page 1)
+ 8. Verify the Send button is now enabled (previewToken set)
+ 9. Type in the first field again — verify the Send button becomes disabled again (previewToken reset)
+ 10. Click "Preview" again — verify Send becomes enabled
+ 11. Click "Prepare and Send" — verify the final PDF embeds both text values at their field positions
+ 12. Verify there is NO generic label/value "Text fill fields" form anywhere in PreparePanel
+
+ Human verification of the complete per-field text editing and quick-fill flow. No code changes — all automated work is complete in Tasks 1 and 2. Follow the how-to-verify steps above.
+
+ MISSING — checkpoint requires human browser verification; no automated equivalent
+
+ Human has typed "approved" confirming all 12 verification steps passed
+ Type "approved" or describe any issues found
+
+
+
+
+
+After all tasks complete:
+- `npx tsc --noEmit` in teressa-copeland-homes passes with zero errors
+- No import of `TextFillForm` anywhere in the codebase
+- PreparePanel has `textFillData`, `selectedFieldId`, `onQuickFill` in its props interface
+- DocumentPageClient has `selectedFieldId` and `textFillData` state variables
+- Both `handleFieldValueChange` and `handleQuickFill` in DocumentPageClient call `setPreviewToken(null)`
+- Human verification checkpoint approved
+
+
+
+- TXTF-01: Agent clicks a text field box, types a value; the value is stored by UUID field ID
+- TXTF-02: PreparePanel quick-fill panel appears when a text field is selected; Client Name / Property Address / Client Email buttons work
+- TXTF-03: Text values appear at field positions in preview and final PDF; every value change resets the staleness token
+- TextFillForm is removed; no label-keyed or positional text fill remains anywhere in the codebase
+- Human has approved the full flow end-to-end
+
+
+