- 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>
- 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>
- 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>
- 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>
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>
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>
- 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
- 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
- 13-02-SUMMARY.md created with accomplishments, decisions, file list
- STATE.md: plan position advanced to 2/4, decisions logged, session updated
- ROADMAP.md: phase 13 progress updated to 2 summaries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create 13-01-SUMMARY.md with TDD execution results and decisions
- Update STATE.md: phase 13 in progress, plan 1/4 complete
- Update ROADMAP.md: phase 13 status set to In Progress (1/4 summaries)
- Mark AI-01, AI-02 requirements complete in REQUIREMENTS.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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
4 plans in 4 sequential waves covering:
- Plan 01 (TDD): openai install, extract-text.ts, field-placement.ts, aiCoordsToPagePdfSpace unit test
- Plan 02: POST /api/documents/[id]/ai-prepare route with all guards
- Plan 03: UI wiring — aiPlacementKey in FieldPlacer, AI Auto-place button in PreparePanel
- Plan 04: Unit test gate + human E2E verification checkpoint
Satisfies AI-01, AI-02. Completes v1.1 Smart Document Preparation milestone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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)
- 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
- 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
- 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
- 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)
- 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
- '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
- 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()
- 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
- 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