Commit Graph

315 Commits

Author SHA1 Message Date
Chandler Copeland
1749e10e9c feat(15-03): signer-aware POST handler with accumulate PDF and atomic completion
- Step 3.5: fetch signerEmail from tokenRow after atomic claim
- Step 7: accumulate pattern — read from signedFilePath or preparedFilePath as working PDF
- Step 7: write to JTI-keyed _partial_ path to prevent concurrent signer collisions
- Step 8a: date stamps scoped to this signer's date fields (D-09)
- Step 8b: signable fields scoped to this signer's fields (Pitfall 4)
- Step 9.5: JTI-keyed datestamped temp file to prevent collision
- Step 10.5: update signedFilePath to this signer's partial after each signing
- Step 11: remaining-token count check before completion attempt
- Step 11: completionTriggeredAt atomic guard (UPDATE WHERE IS NULL RETURNING)
- Step 12: status='Signed' only in completion winner block (fixes first-signer-wins bug)
- Step 13: agent notification + signer completion emails only at completion
- Legacy null-signerEmail tokens fall through all signer filters unchanged
2026-04-03 15:47:37 -06:00
Chandler Copeland
7a04a4f617 feat(15-02): rewrite send route with multi-signer token loop and legacy fallback
- Loop over doc.signers when populated: one createSigningToken per signer with signerEmail
- Dispatch all signing emails in parallel via Promise.all
- Preserve legacy single-signer path unchanged when signers is null/empty
- Replace NEXT_PUBLIC_BASE_URL with APP_BASE_URL for signing URLs
- Add audit event with metadata.signerEmail for each signer in multi-signer path
- Import DocumentSigner from schema for type casting
2026-04-03 15:46:44 -06:00
Chandler Copeland
0f97c4233f feat(15-03): signer-aware GET field filter and updated imports
- Filter signatureFields by tokenRow.signerEmail for multi-signer tokens (D-04)
- Legacy null-signerEmail tokens return all isClientVisibleField fields (D-05)
- Added imports: createSignerDownloadToken, sendSignerCompletionEmail, DocumentSigner, sql
2026-04-03 15:45:59 -06:00
Chandler Copeland
00c5e5df33 docs(15-01): complete multi-signer utility building blocks plan
- SUMMARY.md created for 15-01
- STATE.md: plan advanced to 01/02, progress 94%, decisions added
- ROADMAP.md: phase 15 progress updated (1/3 plans)
- REQUIREMENTS.md: MSIGN-10, MSIGN-11 marked complete
2026-04-03 15:44:37 -06:00
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
cdd4b8b38c docs(15): create phase plan — multi-signer backend (3 plans, 2 waves) 2026-04-03 15:39:09 -06:00
Chandler Copeland
89b4b13f8a docs(phase-15): add research and validation strategy 2026-04-03 15:32:47 -06:00
Chandler Copeland
4a3895f8a0 docs(15): research multi-signer backend phase 2026-04-03 15:31:57 -06:00
Chandler Copeland
817d53ae12 docs(phase-15): gather context for multi-signer backend phase 2026-04-03 15:26:34 -06:00
Chandler Copeland
71e1191dd1 docs(phase-14): verification passed — 6/6 must-haves, migration 0010 applied cleanly 2026-04-03 15:20:26 -06:00
Chandler Copeland
cdb1f4924e docs(14-01): complete multi-signer schema plan — migration 0010 applied
- SUMMARY.md: 2 tasks complete, schema.ts + migration 0010 committed
- STATE.md: advanced to plan 1 complete, decisions logged, 98% progress
- ROADMAP.md: phase 14 marked Complete (1/1 plans)
- REQUIREMENTS.md: MSIGN-08 marked complete
2026-04-03 15:17:25 -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
07555ed6c5 docs(14): create phase plan 2026-04-03 15:11:27 -06:00
Chandler Copeland
ed6b8bce23 docs(phase-14): gather context for multi-signer schema phase 2026-04-03 15:04:08 -06:00
Chandler Copeland
4d69253a94 docs: create milestone v1.2 roadmap (4 phases) 2026-04-03 14:59:31 -06:00
Chandler Copeland
64ccb750fa docs: define milestone v1.2 requirements 2026-04-03 14:49:03 -06:00
Chandler Copeland
622ca3dc21 docs: complete project research 2026-04-03 14:47:06 -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
b792971dac docs(13): re-research — text-extraction approach, coordinate system, plan-04 gap analysis 2026-04-03 14:29:08 -06:00
Chandler Copeland
34ee72004b docs: start milestone v1.2 Multi-Signer and Deployment Hardening 2026-04-03 13:48:53 -06:00
Chandler Copeland
7c1e480ef7 wip: phase-13 paused at 13-04 human verification — coordinate offset bug active 2026-03-21 17:57:39 -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
136eaf0723 docs(13-03): complete AI Auto-place UI wire-up plan — violet button, aiPlacementKey, handleAiAutoPlace
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:10:01 -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
970bb4f7cf docs(13-02): complete ai-prepare route plan — POST handler, guard chain, AI pipeline
- 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>
2026-03-21 17:05:37 -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
24e1f5aa00 docs(13-01): complete AI foundation layer plan — extract-text, field-placement, unit tests
- 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>
2026-03-21 17:02:25 -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
df4676c23c docs(13): create phase 13 AI field placement plan
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>
2026-03-21 16:54:43 -06:00
Chandler Copeland
c317eb2f53 docs(13): research AI field placement and pre-fill phase 2026-03-21 16:47:28 -06:00
Chandler Copeland
2f30503a56 docs(phase-12.1): complete phase execution 2026-03-21 16:36:47 -06:00
Chandler Copeland
698b414249 docs(12.1-02): complete per-field text editing and quick-fill plan — human verification approved
- Task 3 (human-verify checkpoint) approved: all 12 verification steps passed
- Updated SUMMARY.md: tasks_completed 3, requirements-completed TXTF-01/02/03, verification section
- STATE.md: Phase 12.1 marked complete; session stopped-at updated; decision recorded
- ROADMAP.md: 12.1-01 and 12.1-02 plans marked complete ([x]); roadmap update-plan-progress confirmed 2/2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:32:30 -06:00
Chandler Copeland
dbca290dbb docs(12.1-02): complete state bridge plan; awaiting human-verify checkpoint
- Create 12.1-02-SUMMARY.md: selectedFieldId + textFillData state lifting; QuickFillPanel replacing TextFillForm
- Update STATE.md: position at checkpoint Task 3 (human verification); 3 new decisions logged
- Update ROADMAP.md: phase 12.1 progress (2/2 SUMMARYs)
- Mark TXTF-02 complete in REQUIREMENTS.md
2026-03-21 16:27:35 -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
46c7aaa7d5 docs(12.1-01): complete per-field text fill and click-to-select plan
- SUMMARY.md: document field-ID-keyed text fill and click-to-select interaction
- STATE.md: advance to Phase 12.1 Plan 1 complete; add 3 key decisions
- ROADMAP.md: update phase 12.1 progress (1/2 plans complete)
- REQUIREMENTS.md: mark TXTF-01 and TXTF-03 complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 16:23:42 -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