432 lines
20 KiB
Markdown
432 lines
20 KiB
Markdown
|
|
---
|
||
|
|
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<fieldId, string>)"
|
||
|
|
pattern: "textFillData"
|
||
|
|
- from: "PreparePanel.tsx handlePrepare"
|
||
|
|
to: "/api/documents/[id]/prepare"
|
||
|
|
via: "textFillData prop (now Record<fieldId, string>)"
|
||
|
|
pattern: "textFillData"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
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
|
||
|
|
</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/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
|
||
|
|
|
||
|
|
<interfaces>
|
||
|
|
<!-- Current file signatures from the codebase — executor should not re-read files -->
|
||
|
|
|
||
|
|
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<string, string>;
|
||
|
|
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)
|
||
|
|
```
|
||
|
|
</interfaces>
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Extend DocumentPageClient with selectedFieldId + textFillData shared state</name>
|
||
|
|
<files>teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx</files>
|
||
|
|
<action>
|
||
|
|
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<string | null>(null);
|
||
|
|
const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null);
|
||
|
|
const [textFillData, setTextFillData] = useState<Record<string, string>>({});
|
||
|
|
|
||
|
|
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 (
|
||
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
|
|
<div className="lg:col-span-2">
|
||
|
|
<PdfViewerWrapper
|
||
|
|
docId={docId}
|
||
|
|
docStatus={docStatus}
|
||
|
|
onFieldsChanged={handleFieldsChanged}
|
||
|
|
selectedFieldId={selectedFieldId}
|
||
|
|
textFillData={textFillData}
|
||
|
|
onFieldSelect={setSelectedFieldId}
|
||
|
|
onFieldValueChange={handleFieldValueChange}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="lg:col-span-1 lg:sticky lg:top-6 lg:self-start lg:max-h-[calc(100vh-6rem)] lg:overflow-y-auto">
|
||
|
|
<PreparePanel
|
||
|
|
docId={docId}
|
||
|
|
defaultEmail={defaultEmail}
|
||
|
|
clientName={clientName}
|
||
|
|
currentStatus={docStatus}
|
||
|
|
agentDownloadUrl={agentDownloadUrl}
|
||
|
|
signedAt={signedAt}
|
||
|
|
clientPropertyAddress={clientPropertyAddress}
|
||
|
|
previewToken={previewToken}
|
||
|
|
onPreviewTokenChange={setPreviewToken}
|
||
|
|
textFillData={textFillData}
|
||
|
|
selectedFieldId={selectedFieldId}
|
||
|
|
onQuickFill={handleQuickFill}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
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.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
|
||
|
|
</verify>
|
||
|
|
<done>
|
||
|
|
- 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
|
||
|
|
</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 2: Replace TextFillForm with QuickFillPanel in PreparePanel and delete TextFillForm.tsx</name>
|
||
|
|
<files>
|
||
|
|
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx
|
||
|
|
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx
|
||
|
|
</files>
|
||
|
|
<action>
|
||
|
|
**PreparePanel.tsx** — make these changes:
|
||
|
|
|
||
|
|
1. **Add 3 new props** to `PreparePanelProps`:
|
||
|
|
```typescript
|
||
|
|
textFillData: Record<string, string>;
|
||
|
|
selectedFieldId: string | null;
|
||
|
|
onQuickFill: (fieldId: string, value: string) => void;
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Remove `textFillData` local state** — delete lines:
|
||
|
|
```typescript
|
||
|
|
const [textFillData, setTextFillData] = useState<Record<string, string>>(
|
||
|
|
() => (clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {} as Record<string, string>)
|
||
|
|
);
|
||
|
|
```
|
||
|
|
|
||
|
|
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:
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Text fill fields</label>
|
||
|
|
<TextFillForm
|
||
|
|
onChange={handleTextFillChange}
|
||
|
|
initialData={clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : undefined}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
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 */}
|
||
|
|
<div>
|
||
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">Text field fill</label>
|
||
|
|
{selectedFieldId ? (
|
||
|
|
<div className="space-y-1.5">
|
||
|
|
<p className="text-xs text-gray-400">
|
||
|
|
Click a suggestion to fill the selected field.
|
||
|
|
</p>
|
||
|
|
{clientName && (
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => onQuickFill(selectedFieldId, clientName)}
|
||
|
|
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||
|
|
>
|
||
|
|
<span className="text-xs text-gray-400 block">Client Name</span>
|
||
|
|
<span className="truncate block">{clientName}</span>
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
{clientPropertyAddress && (
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => onQuickFill(selectedFieldId, clientPropertyAddress)}
|
||
|
|
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||
|
|
>
|
||
|
|
<span className="text-xs text-gray-400 block">Property Address</span>
|
||
|
|
<span className="truncate block">{clientPropertyAddress}</span>
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={() => onQuickFill(selectedFieldId, defaultEmail)}
|
||
|
|
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||
|
|
>
|
||
|
|
<span className="text-xs text-gray-400 block">Client Email</span>
|
||
|
|
<span className="truncate block">{defaultEmail}</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<p className="text-sm text-gray-400 italic">
|
||
|
|
Click a text field on the document to edit or quick-fill it.
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
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.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>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"</automated>
|
||
|
|
</verify>
|
||
|
|
<done>
|
||
|
|
- 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
|
||
|
|
</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 3: Human verification — per-field text editing and quick-fill end-to-end</name>
|
||
|
|
<what-built>
|
||
|
|
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
|
||
|
|
</what-built>
|
||
|
|
<how-to-verify>
|
||
|
|
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
|
||
|
|
</how-to-verify>
|
||
|
|
<action>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.</action>
|
||
|
|
<verify>
|
||
|
|
<automated>MISSING — checkpoint requires human browser verification; no automated equivalent</automated>
|
||
|
|
</verify>
|
||
|
|
<done>Human has typed "approved" confirming all 12 verification steps passed</done>
|
||
|
|
<resume-signal>Type "approved" or describe any issues found</resume-signal>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
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
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- 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
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
After completion, create `.planning/phases/12.1-per-field-text-editing-and-quick-fill/12.1-02-SUMMARY.md`
|
||
|
|
</output>
|