diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 7b457bb..ce719a8 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -315,5 +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) | 2/2 | Complete | 2026-03-21 | - |
+| 12.1. Per-Field Text Editing and Quick-Fill (INSERTED) | 2/2 | Complete | 2026-03-21 | - |
| 13. AI Field Placement and Pre-fill | v1.1 | 0/4 | Not started | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index ba245bf..30e06d0 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,7 +3,7 @@ gsd_state_version: 1.0
milestone: v1.1
milestone_name: Smart Document Preparation
status: unknown
-last_updated: "2026-03-21T22:32:08.230Z"
+last_updated: "2026-03-21T22:36:44.468Z"
progress:
total_phases: 14
completed_phases: 14
diff --git a/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-VERIFICATION.md b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-VERIFICATION.md
new file mode 100644
index 0000000..4da1fd9
--- /dev/null
+++ b/.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-VERIFICATION.md
@@ -0,0 +1,140 @@
+---
+phase: 12.1-per-field-text-editing-and-quick-fill
+verified: 2026-03-21T00:00:00Z
+status: passed
+score: 13/13 must-haves verified
+re_verification: false
+human_verification:
+ - test: "Click a placed text field box on the PDF — verify it highlights with ring shadow and shows an inline cursor/input"
+ expected: "Field shows transparent input, cursor changes to text, box shadow ring appears"
+ why_human: "Visual rendering and cursor change require browser interaction; verified in Plan 02 Task 3 human checkpoint (approved)"
+ - test: "Click a quick-fill button when a text field is selected — verify the field value updates on the PDF"
+ expected: "Selected text field immediately shows the clicked value"
+ why_human: "Cross-component state sync (PreparePanel -> DocumentPageClient -> FieldPlacer) requires browser verification; approved in Plan 02 Task 3"
+ - test: "After editing a text field, click Preview — verify text appears at field-box position (not at top of page)"
+ expected: "Preview PDF renders text at field coordinates, not stacked at top of page 1"
+ why_human: "PDF rendering position requires visual inspection; approved in Plan 02 Task 3"
+---
+
+# Phase 12.1: Per-Field Text Editing and Quick-Fill Verification Report
+
+**Phase Goal:** Agent clicks a text field box on the PDF to select it, types a value directly (or clicks a quick-fill suggestion from the PreparePanel), and each text field holds its own independent value keyed by field ID — replacing the broken positional text fill approach from Phase 12
+**Verified:** 2026-03-21
+**Status:** PASSED
+**Re-verification:** No — initial verification
+
+## Goal Achievement
+
+### Observable Truths
+
+| # | Truth | Status | Evidence |
+|---|-------|--------|---------|
+| 1 | `preparePdf` draws text at field-box position using UUID field ID as lookup key | VERIFIED | `prepare-document.ts` line 47: `const value = textFields[field.id];` |
+| 2 | Strategy B top-of-page fallback stamping removed — no UUID strings at top of page 1 | VERIFIED | `grep` confirms: no `remainingEntries`, `unstampedEntries`, `getForm()`, `form.flatten()`, `acroFilledKeys` present |
+| 3 | `FieldPlacer` accepts `selectedFieldId`, `textFillData`, `onFieldSelect`, `onFieldValueChange` as optional props without TypeScript errors | VERIFIED | `FieldPlacerProps` lines 162-165; `npx tsc --noEmit` exits clean |
+| 4 | `PdfViewer` and `PdfViewerWrapper` compile cleanly with the new optional props threaded through | VERIFIED | Both files accept and forward all 4 props; TSC zero errors |
+| 5 | Clicking a text field box on the PDF selects it and renders an inline input | VERIFIED | `FieldPlacer.tsx` lines 658-667: onClick calls `onFieldSelect?.(field.id)`; lines 670-689: `isSelected` renders `` |
+| 6 | The inline input does not trigger the move/drag handler (`data-no-move` + `stopPropagation`) | VERIFIED | Input at line 672 has `data-no-move`; line 676: `onPointerDown={(e) => e.stopPropagation()}`; move handler guards at line 655 |
+| 7 | Clicking a non-text field or PDF background deselects the current field | VERIFIED | Non-text onClick calls `onFieldSelect?.(null)` (line 665); DroppableZone background click deselects at line 783 |
+| 8 | Agent can click a text field and type — value stored under UUID, not a label | VERIFIED | `DocumentPageClient.tsx` `handleFieldValueChange` updates `textFillData[fieldId]`; no label-keyed seeding present |
+| 9 | Changing any text field value resets `previewToken` to null, re-disabling Send button | VERIFIED | Lines 35, 40 in `DocumentPageClient.tsx`: both `handleFieldValueChange` and `handleQuickFill` call `setPreviewToken(null)` with TXTF-03 comment |
+| 10 | PreparePanel shows Client Name / Property Address / Client Email quick-fill buttons when a text field is selected | VERIFIED | `PreparePanel.tsx` lines 189-227: `{selectedFieldId ? (
... buttons) : }` |
+| 11 | Quick-fill buttons insert client profile value into selected field and reset `previewToken` | VERIFIED | Buttons call `onQuickFill(selectedFieldId, value)` which invokes `handleQuickFill` in `DocumentPageClient` — calls `setTextFillData` and `setPreviewToken(null)` |
+| 12 | `textFillData` lives in `DocumentPageClient` as shared state — not local state in `PreparePanel` | VERIFIED | `PreparePanel.tsx`: `textFillData: Record` is a required prop (line 16), no local `useState` for it; `DocumentPageClient.tsx` line 27 owns the state |
+| 13 | `TextFillForm` is removed — file deleted, no imports remaining | VERIFIED | `TextFillForm.tsx` does not exist; `grep -r "TextFillForm"` returns no results anywhere in `src/` |
+
+**Score:** 13/13 truths verified
+
+### Required Artifacts
+
+| Artifact | Provides | Status | Evidence |
+|----------|----------|--------|---------|
+| `src/lib/pdf/prepare-document.ts` | Field-ID-keyed text drawing; Strategy A/B removed | VERIFIED | Exists, substantive (147 lines), `textFields[field.id]` present (line 47); no old patterns |
+| `src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` | Per-field click-to-select + inline input overlay | VERIFIED | Exists, 821 lines; `FieldPlacerProps` has 4 new props; `renderFields()` has full click/input logic |
+| `src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx` | Optional prop forwarding to FieldPlacer | VERIFIED | All 4 props accepted (lines 26-37) and forwarded (lines 94-97); deselect on page change (lines 50, 58) |
+| `src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx` | Optional prop forwarding to PdfViewer | VERIFIED | All 4 props accepted (lines 17-21) and forwarded (lines 28-31) |
+| `src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx` | `selectedFieldId` + `textFillData` shared state; `handleFieldValueChange` + `handleQuickFill` callbacks | VERIFIED | 75 lines; both state vars (lines 26-27); both callbacks with `setPreviewToken(null)` (lines 33-41); props threaded to both child components |
+| `src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx` | QuickFillPanel when field selected; no TextFillForm; `textFillData` as prop | VERIFIED | Props interface has `textFillData`, `selectedFieldId`, `onQuickFill` (lines 16-18); QuickFillPanel JSX (lines 186-228); `handlePreview` uses prop (line 103); `handlePrepare` uses prop (line 144) |
+| `src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx` | Deleted — no longer used | VERIFIED | File does not exist; zero references in codebase |
+
+### Key Link Verification
+
+| From | To | Via | Status | Evidence |
+|------|----|-----|--------|---------|
+| `DocumentPageClient.tsx handleFieldValueChange` | `FieldPlacer.tsx onFieldValueChange` | `PdfViewerWrapper` -> `PdfViewer` -> `FieldPlacer` props | WIRED | `DocumentPageClient.tsx` line 53: `onFieldValueChange={handleFieldValueChange}`; `PdfViewerWrapper.tsx` line 31: `onFieldValueChange={onFieldValueChange}`; `PdfViewer.tsx` line 97: `onFieldValueChange={onFieldValueChange}` |
+| `DocumentPageClient.tsx handleQuickFill` | `PreparePanel.tsx onQuickFill` | Direct prop | WIRED | `DocumentPageClient.tsx` line 69: `onQuickFill={handleQuickFill}`; `PreparePanel.tsx` line 33 destructures and uses at lines 197, 207, 216 |
+| `PreparePanel.tsx handlePreview` | `/api/documents/[id]/preview` | `textFillData` prop (UUID-keyed) | WIRED | Line 103: `body: JSON.stringify({ textFillData })` — uses prop, not local state |
+| `PreparePanel.tsx handlePrepare` | `/api/documents/[id]/prepare` | `textFillData` prop (UUID-keyed) | WIRED | Line 144: `body: JSON.stringify({ textFillData, emailAddresses })` — uses prop |
+| `DocumentPageClient.tsx selectedFieldId` | `PreparePanel.tsx selectedFieldId` | Direct prop | WIRED | `DocumentPageClient.tsx` line 68: `selectedFieldId={selectedFieldId}`; `PreparePanel.tsx` line 189 gates QuickFillPanel render on this value |
+| `FieldPlacer.tsx onClick` | `onFieldSelect` callback | `e.stopPropagation()` + call | WIRED | Lines 660-666: text fields call `onFieldSelect?.(field.id)` with stopPropagation; DroppableZone background click calls `onFieldSelect?.(null)` at line 783 |
+| `prepare-document.ts` | Field-ID lookup | `textFields[field.id]` direct | WIRED | Line 47: UUID direct lookup confirmed; no positional index, no sort, no label fallback |
+
+### Requirements Coverage
+
+| Requirement | Source Plan | Description | Status | Evidence |
+|-------------|------------|-------------|--------|---------|
+| TXTF-01 | 12.1-01, 12.1-02 | Agent clicks placed text field box to select and type a value; each field holds its own independent value keyed by field ID | SATISFIED | `FieldPlacer` click-to-select (lines 658-689); `DocumentPageClient` `handleFieldValueChange` stores by UUID (lines 33-36); `prepare-document.ts` draws by UUID (line 47) |
+| TXTF-02 | 12.1-02 | PreparePanel quick-fill buttons (Client Name, Property Address, Client Email) appear when a text field is selected | SATISFIED | `PreparePanel.tsx` lines 186-228: conditional QuickFillPanel with three quick-fill buttons calling `onQuickFill(selectedFieldId, value)` |
+| TXTF-03 | 12.1-01, 12.1-02 | Text fill values appear correctly embedded in preview and final PDF; staleness token resets on any text field value change | SATISFIED | `prepare-document.ts` draws at field coordinates (lines 44-59); `DocumentPageClient.tsx` both callbacks call `setPreviewToken(null)` (lines 35, 40) |
+
+**Orphaned requirements check:** REQUIREMENTS.md traceability table maps only TXTF-01, TXTF-02, TXTF-03 to Phase 12.1 — all three are claimed by the plans and verified. No orphaned requirements.
+
+### Anti-Patterns Found
+
+No anti-patterns detected in modified files:
+
+- No TODO / FIXME / HACK / PLACEHOLDER comments in any modified file
+- No empty return stubs (`return null`, `return {}`, `return []`)
+- No incomplete handlers (`onClick={() => {}}`)
+- No console-log-only implementations
+- No legacy label-keyed seeding (`{ propertyAddress: clientPropertyAddress }`) anywhere in codebase
+- No remaining references to `TextFillForm`
+- No remnants of Strategy A (`getForm`, `flatten`) or Strategy B (`unstampedEntries`, `firstPage.drawText(${key}`)
+
+### Human Verification Required
+
+The automated checks cover all structural and wiring correctness. Three items were verified by a human during Plan 02 Task 3 (human checkpoint approved):
+
+#### 1. Visual field selection rendering
+
+**Test:** Click a placed text field box in the browser
+**Expected:** Field box gains ring shadow (`0 0 0 2px {fieldColor}66`), cursor changes to `text`, inline input renders with `autoFocus`
+**Why human:** CSS shadow and cursor rendering requires browser; confirmed approved in Plan 02
+
+#### 2. Quick-fill cross-component sync
+
+**Test:** Select a text field, then click "Client Name" quick-fill button in PreparePanel sidebar
+**Expected:** The selected field immediately shows the client name value; previewToken resets (Send button re-disables)
+**Why human:** Cross-component state propagation timing requires browser; confirmed approved in Plan 02
+
+#### 3. Preview PDF text position
+
+**Test:** Fill text fields via inline input and quick-fill, click Preview
+**Expected:** Preview PDF shows text values at field-box positions, not at top of page 1
+**Why human:** PDF rendering position requires visual inspection; confirmed approved in Plan 02
+
+**Human checkpoint status:** APPROVED (Plan 02 Task 3 — all 12 verification steps passed)
+
+### Commit Verification
+
+All commits documented in SUMMARY files verified to exist:
+
+| Hash | Task | File(s) |
+|------|------|---------|
+| `df02a1e` | 12.1-01 Task 1: field-ID-keyed lookup | `prepare-document.ts` |
+| `eaf377d` | 12.1-01 Task 2: optional props + click-to-select | `PdfViewerWrapper.tsx`, `PdfViewer.tsx`, `FieldPlacer.tsx` |
+| `f395819` | 12.1-02 Task 1: DocumentPageClient shared state | `DocumentPageClient.tsx` |
+| `d2ebb2c` | 12.1-02 Task 2: QuickFillPanel + TextFillForm deletion | `PreparePanel.tsx`, `TextFillForm.tsx` (deleted) |
+
+---
+
+## Summary
+
+Phase 12.1 fully achieves its goal. The broken positional text fill approach is gone — `preparePdf` now does a direct UUID lookup (`textFields[field.id]`) and Strategy A/B are removed. The full interaction chain is wired: agent clicks a text field box in `FieldPlacer` → `onFieldSelect` bubbles up through `PdfViewer` and `PdfViewerWrapper` to `DocumentPageClient` → `selectedFieldId` state flows down to both `FieldPlacer` (shows inline input) and `PreparePanel` (shows quick-fill buttons) → any value change via typing or quick-fill calls `setPreviewToken(null)` to re-disable the Send button. `TextFillForm` is deleted with zero remaining references.
+
+All 13 must-haves verified. All 3 requirements (TXTF-01, TXTF-02, TXTF-03) satisfied. TypeScript compiles with zero errors. Human checkpoint approved.
+
+---
+
+_Verified: 2026-03-21_
+_Verifier: Claude (gsd-verifier)_