---
phase: 12.1-per-field-text-editing-and-quick-fill
plan: 01
subsystem: pdf-generation, document-editor
tags: [text-fill, field-selection, pdf, ux]
dependency_graph:
  requires: []
  provides:
    - field-ID-keyed text drawing in preparePdf
    - per-field click-to-select + inline input in FieldPlacer
    - optional prop chain PdfViewerWrapper -> PdfViewer -> FieldPlacer
  affects:
    - 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
tech_stack:
  added: []
  patterns:
    - field-ID-keyed lookup (UUID direct lookup replaces positional sort)
    - click-to-select inline input overlay (data-no-move + stopPropagation)
key_files:
  created: []
  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
decisions:
  - "[Phase 12.1-01]: preparePdf text fill is now keyed by SignatureFieldData.id (UUID) — direct lookup replaces positional sort; AcroForm Strategy A and Strategy B top-of-page fallback both removed"
  - "[Phase 12.1-01]: FieldPlacer click-to-select uses onClick (fires only on no-drag clicks due to MouseSensor distance:5 threshold) — onPointerDown move handler is preserved and unaffected"
  - "[Phase 12.1-01]: data-no-move attribute on the inline input's onPointerDown stops move handler activation — consistent with existing delete button and resize handle pattern"
metrics:
  duration_minutes: 3
  completed_date: "2026-03-21"
  tasks_completed: 2
  files_modified: 4
---

# Phase 12.1 Plan 01: Per-Field Text Fill and Click-to-Select Summary

**One-liner:** Field-ID-keyed UUID text drawing in preparePdf with Strategy A/B removed; click-to-select inline input overlay on text fields in FieldPlacer; prop chain PdfViewerWrapper -> PdfViewer -> FieldPlacer wired and ready for Plan 02.

## Tasks Completed

| # | Task | Commit | Files |
|---|------|--------|-------|
| 1 | Replace positional text fill with field-ID-keyed lookup in preparePdf | df02a1e | prepare-document.ts |
| 2 | Add optional text-edit props to PdfViewerWrapper, PdfViewer, FieldPlacer click-to-select | eaf377d | PdfViewerWrapper.tsx, PdfViewer.tsx, FieldPlacer.tsx |

## What Was Built

### Task 1: preparePdf field-ID-keyed text fill

Replaced the broken positional text assignment pipeline in `prepare-document.ts`:

- **Removed** AcroForm Strategy A: `pdfDoc.getForm()`, `form.getTextField()`, `form.flatten()`, `acroFilledKeys`, `hasAcroForm`
- **Removed** positional sort loop: `remainingEntries`, `textFields_sorted`, `fieldConsumedKeys`, `textFields_sorted.forEach()`
- **Removed** Strategy B top-of-page stamp: `unstampedEntries`, `firstPage.drawText(`${key}: ${value}`, ...)`
- **Added** Phase 12.1 field-ID loop: `textFields[field.id]` direct UUID lookup, draws at `field.x+4, field.y+4` with font 6-11pt clamped to field height

The `textFields` parameter type (`Record<string, string>`) is unchanged — backward-compatible. Calling routes pass `body.textFillData` which will now be `{ [fieldId]: value }` once Plan 02 wires the UI.

### Task 2: Optional prop chain and click-to-select

**PdfViewerWrapper.tsx** and **PdfViewer.tsx:** Added 4 optional props to each and forwarded them down the chain:
- `selectedFieldId?: string | null`
- `textFillData?: Record<string, string>`
- `onFieldSelect?: (fieldId: string | null) => void`
- `onFieldValueChange?: (fieldId: string, value: string) => void`

PdfViewer also calls `onFieldSelect?.(null)` on Prev/Next page button clicks to deselect on page change.

**FieldPlacer.tsx:**
- Extended `FieldPlacerProps` with the 4 new optional props
- `DroppableZone` now accepts `onClick?: (e: React.MouseEvent<HTMLDivElement>) => void` — background clicks deselect via `onFieldSelect?.(null)` when target is not inside `[data-field-id]`
- Per-field div gets `onClick` handler: text fields call `onFieldSelect?.(field.id)` with `e.stopPropagation()` to prevent DroppableZone deselect; non-text fields call `onFieldSelect?.(null)`
- `renderFields()` computes `isSelected = selectedFieldId === field.id` and `currentValue = textFillData?.[field.id] ?? ''`
- Text field content: `isSelected` shows `<input data-no-move autoFocus>` with transparent styling; not selected shows value or label in truncating `<span>`
- Cursor set to `'text'` for text fields (vs `'grab'` for others)
- BoxShadow ring `0 0 0 2px {fieldColor}66` when text field is selected

## Deviations from Plan

None — plan executed exactly as written.

## Verification

- `npx tsc --noEmit` 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 all 4 new optional props in `FieldPlacerProps`
- `PdfViewerWrapper.tsx` and `PdfViewer.tsx` accept and forward the 4 new optional props
- Plan 02 can wire `selectedFieldId`, `textFillData`, `onFieldSelect`, `onFieldValueChange` state from `DocumentPageClient` without any additional changes to these files

## Self-Check: PASSED

- [x] `teressa-copeland-homes/src/lib/pdf/prepare-document.ts` exists and modified
- [x] `FieldPlacer.tsx`, `PdfViewer.tsx`, `PdfViewerWrapper.tsx` exist and modified
- [x] Commit df02a1e exists (Task 1)
- [x] Commit eaf377d exists (Task 2)
