- 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
- createSigningToken now accepts optional signerEmail param and persists to DB
- Added createSignerDownloadToken (72h TTL, purpose: signer-download)
- Added verifySignerDownloadToken with purpose claim validation
- 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
- 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>
- 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>
- 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>
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 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
- 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
- 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
- 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
- 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>
- Add SignatureFieldType union type with 6 literals (client-signature, initials, text, checkbox, date, agent-signature)
- Add optional type field to SignatureFieldData interface (backward-compat; v1.0 docs have no type)
- Export getFieldType() helper that coalesces field.type ?? 'client-signature'
- Export isClientVisibleField() predicate that returns false for agent-signature only
- Appends two new exports to token.ts (existing exports untouched)
- purpose: 'agent-download', 5-min TTL, no DB record
- Mirrors existing createDownloadToken/verifyDownloadToken pattern
- Change startY from pageHeight-20 to pageHeight-60 (~0.83 inch from top)
- Increase lineHeight from 12 to 14 for better readability
- Increase stamp font size from 8pt to 10pt for better visibility
Strategy A still attempts AcroForm filling by field name (matching
fields in the PDF's form dict). Strategy B is now a mandatory fallback:
any text field entries that did not match an AcroForm field (or when
the PDF has no AcroForm at all) are drawn as 'key: value' text lines
near the top of page 1 using @cantoo/pdf-lib drawText.
This ensures text fill data supplied in the PreparePanel is always
visible in the output PDF regardless of whether the source PDF was
built with interactive form fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PreparePanel now receives separate assignedClientId (nullable) and
defaultClientId props so it can distinguish an explicitly locked
client from just a default
- When document already has assignedClientId: show locked read-only
display; user cannot change the primary recipient
- When document has no assignedClientId: default to document owner in
dropdown but allow changing; option to clear and enter email manually
- Added textarea for additional/CC email addresses (comma or newline
separated) that is always visible for either mode
- POST /api/documents/[id]/prepare now accepts and stores emailAddresses
array alongside assignedClientId
- Added email_addresses jsonb column to documents table via migration
0004_military_maximus.sql
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Created src/lib/pdf/__tests__/prepare-document.test.ts with 10 test cases
- Tests verify Y-axis flip: screenY=0 → pdfY≈792, screenY=792 → pdfY≈0
- Tests verify scale-invariance at both 1:1 and 50% zoom ratios
- Installed jest, ts-jest, @types/jest and added jest config to package.json
- All 10 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Installed @cantoo/pdf-lib for server-side PDF mutation
- Created src/lib/pdf/prepare-document.ts with preparePdf function using atomic tmp->rename write pattern
- form.flatten() called before drawing signature rectangles
- Created GET/PUT /api/documents/[id]/fields routes for signature field storage
- Created POST /api/documents/[id]/prepare route that calls preparePdf and transitions status to Sent
- Fixed pre-existing null check error in scripts/debug-inspect2.ts (Rule 3: blocking build)
- Build compiles successfully with 2 new API routes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create AddDocumentModal: searchable forms library list + custom file picker
- Wire Add Document button into ClientProfileClient in Documents section header
- Update DocumentsTable: document names now link to /portal/documents/{id}
- Create PdfViewer with page nav, zoom, and download controls (pdfjs worker via import.meta.url)
- Create /portal/documents/[docId] page: server component with auth check, doc/client query
- Add documentsRelations and clientsRelations to schema.ts for db.query with-relations support
- Build verified: /portal/documents/[docId] route present, no errors
- Add nodemailer@^7.0.7 and @types/nodemailer (v7 required by next-auth peer dep)
- Create src/lib/contact-mailer.ts: Nodemailer SMTP transporter + sendContactEmail()
- Create src/lib/contact-action.ts: Server Action with Zod validation, honeypot check
- SMTP credentials read from CONTACT_EMAIL_USER/PASS/SMTP_HOST/PORT env vars
- Add CONTACT_* placeholder vars to .env.local (gitignored, for local setup docs)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs:
1. auth.ts imported postgres (Node.js TCP) which crashes in Edge Runtime,
causing Auth.js to silently fall back to redirecting all requests to login.
Fix: split into auth.config.ts (Edge-safe, no DB) used by middleware,
and auth.ts (full, with DB) used by server components.
2. /agent/layout.tsx applied to /agent/login, so unauthenticated login page
visits redirected to themselves in an infinite loop.
Fix: moved dashboard + layout into (protected) route group so login page
has no auth layout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neon serverless driver requires remote WebSocket — incompatible with local
PostgreSQL. Replaced with postgres.js (drizzle-orm/postgres-js) in db/index.ts,
scripts/seed.ts, and drizzle.config.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Created src/lib/auth.ts with NextAuth JWT strategy, 7-day rolling session, Credentials provider
- Created src/app/api/auth/[...nextauth]/route.ts with GET/POST handlers and force-dynamic
- Created middleware.ts at project root (not src/) protecting /agent/* routes
- Fixed db/index.ts: lazy Proxy singleton prevents neon() crash during Next.js build
- npm run build passes; /api/auth/[...nextauth] renders as Dynamic route