20 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12.1-per-field-text-editing-and-quick-fill | 02 | execute | 2 |
|
|
false |
|
|
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
<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_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.mdFrom DocumentPageClient.tsx (current — to be extended):
'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):
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):
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):
// Generic label/value row form — no longer used after this plan
export function TextFillForm({ onChange, initialData }: TextFillFormProps)
'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:
textFillDatastarts as{}— do NOT seed fromclientPropertyAddress. 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
handleFieldValueChangeandhandleQuickFillcallsetPreviewToken(null)to satisfy TXTF-03 (staleness reset). setSelectedFieldIdis passed directly asonFieldSelect— 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
-
Add 3 new props to
PreparePanelProps:textFillData: Record<string, string>; selectedFieldId: string | null; onQuickFill: (fieldId: string, value: string) => void; -
Remove
textFillDatalocal state — delete lines:const [textFillData, setTextFillData] = useState<Record<string, string>>( () => (clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {} as Record<string, string>) ); -
Remove
handleTextFillChangefunction — it was the wrapper that calledsetTextFillDataandonPreviewTokenChange(null). This is no longer needed; the parent handles it. -
Remove the
TextFillFormimport at the top of the file. -
Remove the TextFillForm JSX block in the return:
// 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> -
Replace it with the QuickFillPanel. Insert this in place of the TextFillForm block, before the Preview button:
{/* 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:
defaultEmailis 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). -
Update
handlePreview—textFillDatais 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. -
Update
handlePrepare— same:textFillDataprop is used inbody: JSON.stringify({ textFillData, emailAddresses }). Verify. -
Destructure the 3 new props in the function signature:
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:
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<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>