Commit Graph

133 Commits

Author SHA1 Message Date
Chandler Copeland
14efa1dce4 feat(15-01): create public signer download route GET /api/sign/download/[token]
- Public route — no auth session required
- Validates signer-download JWT via verifySignerDownloadToken
- Guards: expired/invalid token (401), incomplete doc (404), path traversal (403)
- Serves signed PDF with Content-Disposition attachment header
2026-04-03 15:43:40 -06:00
Chandler Copeland
e1cdfe9b7b feat(15-01): add sendSignerCompletionEmail to signing-mailer
- Sends plain-text email with signed document download link to signer
- Follows established createTransporter() + sendMail() pattern
- Subject: 'Signed copy ready: {documentName}', 72h expiry in body text
2026-04-03 15:43:17 -06:00
Chandler Copeland
70c48cc377 feat(15-01): extend createSigningToken with signerEmail, add signer-download token pair
- createSigningToken now accepts optional signerEmail param and persists to DB
- Added createSignerDownloadToken (72h TTL, purpose: signer-download)
- Added verifySignerDownloadToken with purpose claim validation
2026-04-03 15:43:00 -06:00
Chandler Copeland
363949124c feat(14-01): generate and apply Drizzle migration 0010 for multi-signer columns
- Migration 0010_sharp_archangel.sql adds signer_email to signing_tokens
- Migration adds signers JSONB column to documents
- Migration adds completion_triggered_at TIMESTAMP to documents
- Additive-only: no DROP columns, no ALTER TYPE, no backfills
- Applied successfully to local Neon/Postgres instance
2026-04-03 15:16:15 -06:00
Chandler Copeland
c658f13ea0 feat(14-01): add multi-signer types and columns to schema.ts
- Add signerEmail?: string to SignatureFieldData interface
- Add getSignerEmail() helper function with fallback pattern
- Add DocumentSigner interface { email, color }
- Add documents.signers JSONB column typed as DocumentSigner[]
- Add documents.completionTriggeredAt nullable TIMESTAMP column
- Add signingTokens.signerEmail nullable TEXT column
2026-04-03 15:15:32 -06:00
Chandler Copeland
6265a64a50 chore(13-04): remove debug console.log from classifyFieldsWithAI
- Remove 3 console.log statements that printed blank count, all blank descriptions, and AI classifications
- These were development debug statements; not appropriate for production code
- Tests pass (prepare-document.test.ts: 10/10), TypeScript clean
2026-04-03 14:31:15 -06:00
Chandler Copeland
c80133ea58 fix(13): stamp page numbers on rendered images, fix signature block pattern in prompt
- Red PAGE N label stamped on each image so GPT-4o correctly attributes fields to pages
- Prompt: add 'blank line above label' signature block pattern (common in real estate docs)
- Prompt: explicit rule — place field on blank underline, not on the (Label) text below it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:52:31 -06:00
Chandler Copeland
8ac5acb486 fix(13): reduce field heights, nudge y-offset, tighten height prompt guidance
- Max heights reduced: text/date 16pt, initials 18pt, signature 26pt
- 0.5% y nudge pushes fields onto the underline instead of floating above it
- Prompt specifies 1.2% height for text/date, 1.8% for signatures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:49:11 -06:00
Chandler Copeland
48788dea23 fix(13): use AI-estimated field sizes with type bounds, stricter no-inline-text rule
- Replace fixed 144x36 with AI widthPct/heightPct clamped to per-type min/max
  (signatures 100-250x20-40pt, initials 36-80x16-28pt, date 60-130x14-24pt, text 60-280x14-24pt)
- Prompt: explicit 'no inline body text' rule — if text is part of a sentence, skip it
- Prompt: widthPct should match visual underline width, heightPct kept thin (~2-2.5%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:46:04 -06:00
Chandler Copeland
461abb0dc4 fix(13): mark @napi-rs/canvas as serverExternalPackages to prevent Turbopack bundling native .node binding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:43:16 -06:00
Chandler Copeland
e7bf5abb9f fix(13): switch to GPT-4o vision — render PDF pages as images for accurate field placement
- extractPdfText now renders each page to JPEG via @napi-rs/canvas + pdfjs-dist (108dpi)
- field-placement.ts sends rendered page images to GPT-4o with vision (detail: high)
- AI can now visually identify underlines, signature blocks, date fields, initials boxes
- System prompt focuses on visual cues (blank lines, boxes) not text pattern matching
- Handles multi-field lines: separate fields for signature blank and date blank on same line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:40:47 -06:00
Chandler Copeland
b5216a8542 fix(13): extract text with line positions for accurate AI field placement
- extractPdfText now returns TextLine[] with yPct/xPct per line instead of flat text blob
- AI can now see spatial layout (where blank lines/underscores actually are vs body text)
- Rewrote system prompt: explicit rules about blank lines/underscores/signature blocks,
  place ALL blanks even without prefill value, match field type to label pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:35:02 -06:00
Chandler Copeland
c67d56dc48 fix(13-01): upgrade to gpt-4o, remove checkboxes, clamp AI coords to page bounds
- gpt-4o-mini replaced with gpt-4o for better placement accuracy
- checkbox removed from schema enum and filtered in loop (positions are input-dependent)
- y coordinate clamped to [0, pageHeight - fieldHeight] to prevent fields rendering
  outside the PDF canvas when AI returns yPct near 100%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:29:49 -06:00
Chandler Copeland
72f7d20bac fix(13-03): dynamic import PreviewModal to prevent SSR DOMMatrix crash
react-pdf calls new DOMMatrix() at module level — static import causes
500 on document page during SSR. Same pattern as PdfViewer/PdfViewerWrapper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:25:14 -06:00
Chandler Copeland
3f4ca5a8e5 fix(13-01): use file:// worker path for pdfjs-dist 5.x fake-worker in Node.js
Empty string workerSrc is falsy — PDFWorker.workerSrc getter throws before
_setupFakeWorkerGlobal can dynamically import the worker file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:19:29 -06:00
Chandler Copeland
bfdaee14ed feat(13-03): add AI Auto-place button to PreparePanel and wire DocumentPageClient handler
- Add onAiAutoPlace prop and handleAiAutoPlaceClick with aiLoading state to PreparePanel
- Add violet AI Auto-place button above Preview button (Draft documents only)
- Add aiPlacementKey state and handleAiAutoPlace callback in DocumentPageClient
- handleAiAutoPlace: POST /api/documents/[id]/ai-prepare, merges textFillData, increments aiPlacementKey, resets previewToken
- Pass aiPlacementKey to PdfViewerWrapper and onAiAutoPlace to PreparePanel
2026-03-21 17:08:10 -06:00
Chandler Copeland
3e11eef1c4 feat(13-03): add aiPlacementKey prop to FieldPlacer and thread through PdfViewerWrapper/PdfViewer
- Add aiPlacementKey?: number to FieldPlacerProps interface
- Add aiPlacementKey to loadFields useEffect dependency array for re-fetch on AI placement
- Thread aiPlacementKey through PdfViewer and PdfViewerWrapper prop chains
2026-03-21 17:07:01 -06:00
Chandler Copeland
e91f29e555 feat(13-02): implement POST /api/documents/[id]/ai-prepare route
- Auth guard (401), OPENAI_API_KEY guard (503), not-found (404)
- Draft-only guard (403), path traversal guard (403)
- Loads document with client relation via Drizzle with: { client: true }
- Calls extractPdfText then classifyFieldsWithAI in try/catch (500 on error)
- Writes SignatureFieldData[] to DB signatureFields; status stays Draft
- Returns { fields, textFillData } keyed by field UUID
2026-03-21 17:04:00 -06:00
Chandler Copeland
c1e1e5ec49 feat(13-01): implement aiCoordsToPagePdfSpace and AI field utilities
- Install openai 6.32.0 (npm package, listed in dependencies)
- Create src/lib/ai/extract-text.ts — pdfjs-dist legacy build server-side text extraction
  - extractPdfText(filePath) returning PageText[] with page, text, width, height
  - GlobalWorkerOptions.workerSrc = '' for Node.js fake-worker mode
  - Text per page capped at 2000 chars for GPT-4o-mini context limit
- Create src/lib/ai/field-placement.ts — GPT-4o-mini structured output + coord conversion
  - aiCoordsToPagePdfSpace() converts AI top-left pct coords to PDF bottom-left points
  - classifyFieldsWithAI() uses manual json_schema (NOT zodResponseFormat — broken with Zod v4)
  - Standard field sizes: checkbox=24x24pt, others=144x36pt
  - textFillData keyed by field UUID (not label) per Phase 12.1 design
- All 3 unit tests pass (GREEN phase confirmed)
2026-03-21 17:00:34 -06:00
Chandler Copeland
f7d74c0523 test(13-01): add failing aiCoordsToPagePdfSpace unit tests
- Add ai-coords.test.ts with 3 test cases covering US Letter (612x792 pts)
- Tests import from lib/ai/field-placement which does not exist yet (RED phase)
- Cases: text field near top, checkbox near bottom-right, client-sig at center
2026-03-21 16:59:11 -06:00
Chandler Copeland
d2ebb2cc67 feat(12.1-02): replace TextFillForm with QuickFillPanel in PreparePanel; delete TextFillForm.tsx
- Add textFillData, selectedFieldId, onQuickFill props to PreparePanelProps
- Remove textFillData local state and handleTextFillChange (parent now owns state)
- Remove TextFillForm import and JSX block
- Add QuickFillPanel: shows Client Name, Property Address, Client Email quick-fill buttons when selectedFieldId is non-null; idle state message when no field selected
- handlePreview and handlePrepare use textFillData from props
- Delete TextFillForm.tsx (label-keyed generic form no longer used)
2026-03-21 16:25:57 -06:00
Chandler Copeland
f395819acd feat(12.1-02): extend DocumentPageClient with selectedFieldId + textFillData shared state
- Add selectedFieldId and textFillData state variables
- Add handleFieldValueChange and handleQuickFill callbacks (both call setPreviewToken(null))
- Pass selectedFieldId, textFillData, onFieldSelect, onFieldValueChange to PdfViewerWrapper
- Pass textFillData, selectedFieldId, onQuickFill to PreparePanel
- textFillData starts as {} (no clientPropertyAddress seeding)
2026-03-21 16:25:51 -06:00
Chandler Copeland
eaf377d97d feat(12.1-01): add optional text-edit props and click-to-select interaction
- PdfViewerWrapper: add selectedFieldId?, textFillData?, onFieldSelect?, onFieldValueChange? and forward to PdfViewer
- PdfViewer: add same 4 optional props, forward to FieldPlacer; call onFieldSelect?.(null) on page navigation
- FieldPlacer: extend FieldPlacerProps with 4 new optional props
- DroppableZone: add optional onClick prop for background deselect
- renderFields: text fields show inline input when selected (isSelected), value/label otherwise
- Per-field onClick: text fields call onFieldSelect(id), non-text fields call onFieldSelect(null)
- Cursor updated to 'text' for text field type; boxShadow ring on selected state
2026-03-21 16:22:02 -06:00
Chandler Copeland
df02a1e3f7 feat(12.1-01): replace positional text fill with field-ID-keyed lookup
- Remove AcroForm Strategy A (getForm/flatten) — no longer needed
- Remove positional sorting loop (textFields_sorted, remainingEntries, fieldConsumedKeys)
- Remove Strategy B top-of-page UUID stamp (unstampedEntries)
- Add Phase 12.1 field-ID-keyed loop: textFields[field.id] direct lookup
- Update JSDoc to document new keying strategy
2026-03-21 16:20:25 -06:00
Chandler Copeland
bce2a980d2 fix(12-02): draw text fill values at placed text field box coordinates
- Bug 3: text type fields previously drew nothing at field coordinates;
  textFillData values were only stamped at top of page 1 via Strategy B,
  making them invisible to the agent inspecting the placed boxes
- Sort placed text fields by page asc / y desc (reading order) and assign
  textFillData entries sequentially to each field box position
- Text drawn at field.x+4, field.y+4 with font size capped 6–11pt to fit
  within the field height; dark near-black color for legibility
- fieldConsumedKeys set tracks which entries were rendered at field coords;
  Strategy B only stamps remaining entries not consumed by a field box
- TypeScript compiles clean; zero errors
2026-03-21 15:50:30 -06:00
Chandler Copeland
43f396b4c5 fix(12-02): body scroll lock and z-index/portal fix for PreviewModal
- Bug 1: add useEffect that locks document.body.overflow on mount and
  restores original value on unmount — prevents page scrolling behind modal
- Bug 2: render modal via ReactDOM.createPortal to document.body so it
  escapes the sticky sidebar's stacking context; raise z-index to 9999
  so backdrop sits definitively above all FieldPlacer overlay boxes
  (zIndex: 10/12) and dnd-kit DragOverlay regardless of parent context
2026-03-21 15:50:22 -06:00
Chandler Copeland
720d07fd54 feat(12-02): FieldPlacer onFieldsChanged callback prop after every persistFields call
- Add onFieldsChanged?: () => void to FieldPlacerProps interface
- Destructure onFieldsChanged in function signature
- Call onFieldsChanged?.() after persistFields in handleDragEnd (new field drop)
- Call onFieldsChanged?.() after persistFields in handleZonePointerUp move case
- Call onFieldsChanged?.() after persistFields in handleZonePointerUp resize case
- Call onFieldsChanged?.() after persistFields in delete button onClick
- Add onFieldsChanged to useCallback dependency arrays (handleDragEnd, handleZonePointerUp)
2026-03-21 15:36:56 -06:00
Chandler Copeland
de195a3e80 feat(12-02): PreparePanel preview state, button, gating, modal, and DocumentPageClient wiring
- Add previewToken/onPreviewTokenChange props to PreparePanel (lifted to DocumentPageClient)
- Add handlePreview async function fetching POST /api/documents/[id]/preview
- Add Preview button (gray-700) before Send button; text cycles Preview/Preview again
- Gate Send button on previewToken === null (requires fresh preview before send)
- Wrap TextFillForm onChange to reset previewToken on text fill changes
- Render PreviewModal conditionally when showPreview && previewBytes
- Create DocumentPageClient.tsx: holds previewToken state, passes reset callback to both FieldPlacer (via PdfViewerWrapper/PdfViewer) and PreparePanel
- Update PdfViewerWrapper and PdfViewer to accept and forward onFieldsChanged prop
- Update page.tsx to use DocumentPageClient instead of direct PreparePanel/PdfViewerWrapper siblings
2026-03-21 15:36:47 -06:00
Chandler Copeland
f4589391ff feat(12-01): PreviewModal component with react-pdf Document/Page
- 'use client' component accepting ArrayBuffer prop and onClose callback
- Configures pdfjs worker independently from PdfViewer module
- Imports AnnotationLayer.css and TextLayer.css
- Prev/Next page navigation with disabled states
- Fixed overlay (rgba black 70%) with white inner container
2026-03-21 15:30:07 -06:00
Chandler Copeland
99205bca9f feat(12-01): POST /api/documents/[id]/preview route
- Auth-guarded Next.js 15 route handler with async params
- Versioned temp path (_preview_{timestamp}.pdf) — never overwrites _prepared.pdf
- Path traversal guard mirrors prepare route
- Mirrors 422 guards for agent-signature-missing and agent-initials-missing
- try/finally ensures temp file deleted after bytes are read
2026-03-21 15:29:47 -06:00
Chandler Copeland
c876579637 feat(11.1-02): update prepare route to fetch agentInitialsData, add 422 guard, pass to preparePdf
- Fetch agentInitialsData alongside agentSignatureData in single DB query
- Add 422 guard returning { error: 'agent-initials-missing' } when agent-initials fields present but no initials saved
- Pass agentInitialsData as 6th arg to preparePdf()
2026-03-21 15:03:14 -06:00
Chandler Copeland
fae1cf1b68 feat(11.1-02): add agentInitialsData param to preparePdf and embed at agent-initials fields
- Add agentInitialsData as 6th optional param (default null)
- Embed agent initials PNG once before field loop (embed-once-draw-many pattern)
- Add agent-initials branch in field loop: drawImage at field coordinates
- Existing initials (client-initials) and agent-signature branches untouched
2026-03-21 15:02:51 -06:00
Chandler Copeland
d9f618f69a feat(11.1-01): AgentInitialsPanel component, profile page section, FieldPlacer token
- Create AgentInitialsPanel.tsx (clone of AgentSignaturePanel with 80px canvas, /api/agent/initials endpoint)
- Update profile/page.tsx to fetch agentInitialsData and render AgentInitialsPanel below signature section
- Add orange 'Agent Initials' token (7th entry) to FieldPlacer PALETTE_TOKENS
- Add 'agent-initials' to FieldPlacer validTypes Set
2026-03-21 14:59:29 -06:00
Chandler Copeland
33f499c61b feat(11.1-01): DB migration, API routes, schema type updates for agent initials storage
- Add agentInitialsData text column to users table (drizzle/0009_luxuriant_catseye.sql)
- Add 'agent-initials' to SignatureFieldType union in schema.ts
- Update isClientVisibleField() to exclude both agent-signature and agent-initials
- Create GET/PUT /api/agent/initials route with auth guard and 50KB size limit
2026-03-21 14:58:39 -06:00
Chandler Copeland
b2e9810d60 feat(11-02): prepare route fetches agentSignatureData, 422 guard, passes to preparePdf
- Import users and getFieldType from @/lib/db/schema
- Strengthen session guard to !session?.user?.id (matches established pattern)
- Fetch agentSignatureData from users table for the authenticated agent
- 422 guard: return { error: 'agent-signature-missing' } when agent-sig fields exist but no signature saved
- Pass agentSignatureData as 5th arg to preparePdf()
2026-03-21 14:07:01 -06:00
Chandler Copeland
d9652e1f87 feat(11-02): preparePdf() gains agentSignatureData param and embeds at agent-sig fields
- Add optional agentSignatureData: string | null = null as 5th parameter
- Import PDFImage from @cantoo/pdf-lib for typed agentSigImage variable
- Embed PNG once before field loop, store as agentSigImage
- Replace agent-signature stub with drawImage at field.x/y/width/height
2026-03-21 14:06:12 -06:00
Chandler Copeland
f383f91445 feat(11-01): AgentSignaturePanel, profile page, PortalNav link, FieldPlacer token
- Create AgentSignaturePanel.tsx with signature_pad canvas, save/update/thumbnail flow
- Create /portal/profile page (server component fetching agentSignatureData)
- Add Profile link to PortalNav navLinks array
- Add red 'Agent Signature' token to FieldPlacer PALETTE_TOKENS (6th entry)
2026-03-21 14:02:51 -06:00
Chandler Copeland
e07ed306cd feat(11-01): DB migration and API routes for agent signature storage
- Add agentSignatureData TEXT column to users table in schema.ts
- Generate migration 0008_windy_cloak.sql (ALTER TABLE users ADD COLUMN agent_signature_data text)
- Apply migration to local postgres database
- Create GET/PUT /api/agent/signature route with auth guard and input validation
2026-03-21 14:02:01 -06:00
Chandler Copeland
e179b9284f fix(10-03): transparent field boxes and fixed-size checkbox in FieldPlacer
- preparePdf: remove opaque fill from all field type rectangles (signature,
  initials, checkbox, date, text) — underlying PDF content now shows through
- preparePdf: checkbox draws X lines only (no border rectangle); date draws
  no placeholder at all; text draws nothing (position marker only)
- sign route: remove white overwrite rectangle on date stamp — date text
  draws directly on existing PDF content
- FieldPlacer: suppress resize corner handles for checkbox fields; hide
  "Checkbox" label (too small at 24x24pt); checkbox is fixed-size only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 13:41:04 -06:00
Chandler Copeland
a5173fe455 fix(portal): serve signed PDF in viewer after document is signed
When signedFilePath is set, /api/documents/[id]/file now returns the
signed PDF so agent sees embedded signatures and initials in the portal
viewer. Falls back to original PDF when not yet signed.
2026-03-21 13:25:26 -06:00
Chandler Copeland
50f082d20f feat(10-03): extend signing page for initials, overlay suppression, and updated progress counting
- Add optional title prop to SignatureModal (defaults to "Add Signature"); button label derives from title
- Add activeFieldType state and setActiveFieldType in SigningPageClient
- handleFieldClick opens modal for both client-signature and initials fields
- handleSubmit and SigningProgressBar total now count both client-signature and initials
- handleJumpToNext jumps to next unsigned required field (client-signature or initials only)
- Non-interactive fields (text/checkbox/date) suppressed from field overlay rendering via early return
- Initials overlays use purple pulse animation (pulse-border-purple); signature overlays use blue
- SignatureModal receives title prop: "Add Initials" for initials, "Add Signature" for client-signature
2026-03-21 12:54:57 -06:00
Chandler Copeland
d395d85ebb feat(10-02): fix POST handler — signable field filter and date stamping at sign time
- Add getFieldType to schema import
- Add PDFDocument, StandardFonts, rgb from @cantoo/pdf-lib for date stamping
- Add readFile, writeFile, unlink from node:fs/promises
- Hoist const now = new Date() to before step 8 (shared for date stamp + DB update)
- Step 8a: stamp signing date onto date fields in prepared PDF before embed
- Step 8b: filter signableFields to client-signature and initials only
- signaturesWithCoords now maps only signable fields (no 500 on text/checkbox/date)
- Update embedSignatureInPdf call to use dateStampedPath
- Fire-and-forget cleanup of temporary .datestamped.tmp file after embed
2026-03-21 12:50:21 -06:00
Chandler Copeland
1e92ca363a feat(10-01): update handleDragEnd and renderFields for typed field creation
- handleDragEnd: determine droppedType from active.id with validTypes set guard
- handleDragEnd: checkbox fields drop at 24x24pt; all other types drop at 144x36pt
- handleDragEnd: newField now includes type property set to droppedType
- renderFields: use getFieldType() + PALETTE_TOKENS lookup to get per-field color and label
- renderFields: border, background, and text color are now driven by fieldColor
- renderFields: resize handle corners use fieldColor instead of hardcoded blue
- renderFields: span displays fieldLabel instead of hardcoded 'Signature'
2026-03-21 12:50:18 -06:00
Chandler Copeland
4140c220b1 feat(10-01): parameterize DraggableToken and add four new palette tokens
- Add PALETTE_TOKENS array with 5 typed tokens (Signature/blue, Initials/purple, Checkbox/green, Date/amber, Text/slate)
- Update DraggableToken to accept id, label, color props with per-type styling
- Change isDraggingToken from boolean to string | null to track active token id
- Update onDragStart to record active.id instead of just true
- Replace single static token with PALETTE_TOKENS.map() in palette JSX
- Update DragOverlay ghost to show correct label, color, and checkbox-appropriate dimensions (24x24 vs 144x36)
2026-03-21 12:49:25 -06:00
Chandler Copeland
7510c8ee08 feat(10-02): type-branched field rendering in preparePdf()
- Add getFieldType import from @/lib/db/schema
- Replace single-variant blue loop with branched rendering per field type
- client-signature: unchanged blue rectangle + "Sign Here"
- initials: purple rectangle + "Initials" label
- checkbox: gray rectangle + X diagonal lines (embedded at prepare time)
- date: amber rectangle + "Date" label (actual date stamped at POST time)
- text: light gray rectangle, no label (visual marker only)
- agent-signature: skipped (no placeholder drawn)
2026-03-21 12:49:20 -06:00
Chandler Copeland
27003af70f fix(09-01): bg-gray-100 on prepare panel, stack field name/value rows to fit narrow panel 2026-03-21 12:31:20 -06:00
Chandler Copeland
28a460e9cc fix(09-01): sticky prepare panel, bg-gray-50 background, max-height scroll 2026-03-21 12:29:01 -06:00
Chandler Copeland
a77a144f6f fix(09-01): fix hydration mismatch in TextFillForm — use useEffect for initialData seed 2026-03-21 12:27:09 -06:00
Chandler Copeland
11f2b80217 fix(09-01): fix propertyAddress pre-seed and polish PreparePanel text fill UI
- TextFillForm: add initialData prop; buildInitialRows seeds rows from
  pre-seeded data so propertyAddress row renders populated on mount
- PreparePanel: pass initialData={...} to TextFillForm so the lazy
  useState in PreparePanel correctly flows through to the visible UI
- TextFillForm: replace AcroForm jargon instruction with friendly copy
- TextFillForm: add Field name / Value column headers for clear layout
- TextFillForm: improve spacing (py-1.5), softer remove button (gray→red on hover)
- TypeScript: npx tsc --noEmit passes clean
2026-03-21 12:20:08 -06:00
Chandler Copeland
fa9981edd9 feat(09-01): UI layer — property address modal input, profile display, PreparePanel pre-seed
- ClientModal: add defaultPropertyAddress prop, property address input field after email
- ClientProfileClient: add propertyAddress to Props type, display address under email when non-null, pass defaultPropertyAddress to edit modal
- documents/[docId]/page.tsx: extend client select to include propertyAddress, pass as clientPropertyAddress to PreparePanel
- PreparePanel: add clientPropertyAddress prop, lazy-initialize textFillData with { propertyAddress } when client has address
2026-03-21 12:15:27 -06:00