Commit Graph

223 Commits

Author SHA1 Message Date
Chandler Copeland
04c3720096 fix(06): correct clientName in agent notification email 2026-03-21 09:49:06 -06:00
Chandler Copeland
04e3d5cb54 docs(06-06): complete DNS verification plan — LEGAL-04 satisfied, Phase 6 complete
- 06-06-SUMMARY.md: SPF/DKIM/DMARC verified green via Resend for tcopelandhomes.com
- STATE.md: Plan 06 complete, completed_plans 24/24, Phase 6 fully complete
- ROADMAP.md: Phase 6 marked complete (6/6 plans), completed 2026-03-21
- REQUIREMENTS.md: LEGAL-04 marked complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 09:44:23 -06:00
Chandler Copeland
7121279654 feat(06-06): update domain to tcopelandhomes.com, configure Resend SMTP 2026-03-21 09:42:10 -06:00
Chandler Copeland
32ea324504 chore(06-06): automated DNS verification check results
- SPF: MISSING — no TXT records at root domain
- DKIM: MISSING — NXDOMAIN for all common selectors
- DMARC: EXISTS but rua points to hosting default, not teressa@
- SMTP: credentials still placeholder in .env.local
- DNS provider: GoDaddy (ns63/ns64.domaincontrol.com)
2026-03-20 11:46:49 -06:00
Chandler Copeland
119edc2491 docs(06-05): complete confirmation page + download route plan — SUMMARY, STATE, ROADMAP updated
- Post-signing confirmation page with success checkmark, document name, timestamp, 15-min download token
- GET /api/sign/[token]/download streams signedFilePath PDF via short-lived download JWT
- Phase 6 (signing flow) all 5 plans complete
- SIGN-06 requirement marked complete
2026-03-20 11:44:32 -06:00
Chandler Copeland
4cdd9eea80 feat(06-05): confirmation page + router.push redirect after signing
- Rewrite confirmed/page.tsx: verifies signing token, shows document name + signed timestamp + download button
- Generate 15-min download token server-side; pass as dt= query param to /api/sign/[token]/download
- Success checkmark (navy circle + gold checkmark), document name, formatted signed date
- Download link valid for 15 minutes note shown below button
- Update SigningPageClient.tsx: replace window.location.href with router.push for SPA navigation
2026-03-20 11:42:24 -06:00
Chandler Copeland
a276da0da1 feat(06-05): download token utilities + download API route
- Add createDownloadToken and verifyDownloadToken to token.ts (15-min TTL, purpose:'download' claim)
- Create GET /api/sign/[token]/download route: validates dt query param JWT, streams signedFilePath as PDF
- Path traversal guard: signedFilePath must start with UPLOADS_DIR
- Auto-fix: Buffer cast to Uint8Array for Response BodyInit compatibility (Next.js 16 / TypeScript strict)
2026-03-20 11:41:18 -06:00
Chandler Copeland
5c1ea3568e docs(06-04): complete signature modal + submission plan — SUMMARY, STATE, ROADMAP updated
- SignatureModal with Draw/Type/Use Saved tabs (signature_pad DPR scaling)
- POST /api/sign/[token] atomic one-time claim, PDF embed, SHA-256 hash, audit trail
- SIGN-04, SIGN-05, LEGAL-01, LEGAL-02 requirements complete
2026-03-20 11:39:10 -06:00
Chandler Copeland
d445c282c1 feat(06-04): POST /api/sign/[token] atomic submission + confirmed page
- Add POST handler to sign/[token]/route.ts with atomic one-time enforcement
- UPDATE signing_tokens SET usedAt WHERE usedAt IS NULL RETURNING — 0 rows = 409
- Log signature_submitted and pdf_hash_computed audit events
- Merge client dataURLs with server-stored field coordinates (NEVER trust client coords)
- Call embedSignatureInPdf, store pdfHash + signedFilePath in documents table
- Update document status to Signed with signedAt timestamp
- Fire-and-forget sendAgentNotificationEmail (catches errors without failing response)
- Create /sign/[token]/confirmed success page for POST redirect destination
2026-03-20 11:37:00 -06:00
Chandler Copeland
05b5207305 feat(06-04): SignatureModal with Draw/Type/Use Saved tabs + wire SigningPageClient
- Create SignatureModal.tsx with signature_pad Draw tab (devicePixelRatio scaling, touch-none)
- Type tab renders name in Dancing Script cursive via offscreen canvas
- Use Saved tab conditionally shown when localStorage has saved signature
- Save for later checkbox persists drawn/typed sig to localStorage on confirm
- Update SigningPageClient.tsx: import modal, track signedFields as Map<string,string>
- Field overlay shows signature preview image after signing
- handleSubmit POSTs to /api/sign/[token] and redirects to /sign/[token]/confirmed on 200
2026-03-20 11:35:40 -06:00
Chandler Copeland
a3026fb44f docs(06-03): complete signing page plan — SUMMARY, STATE, ROADMAP updated
- 06-03-SUMMARY.md created with decisions, deviations, file list
- STATE.md advanced to Plan 3 complete; two new decisions recorded
- ROADMAP.md phase 6 updated to 3/6 summaries complete
2026-03-20 11:32:40 -06:00
Chandler Copeland
90bd066016 docs(06-02): complete email delivery layer plan
- Add 06-02-SUMMARY.md: SigningRequestEmail, signing-mailer, send route, prepare audit log
- Update STATE.md: Plan 2 complete, decisions logged, session updated
- Update ROADMAP.md: Phase 6 plan progress (2 of 5 summaries)
- Mark SIGN-01 complete in REQUIREMENTS.md
2026-03-20 11:31:35 -06:00
Chandler Copeland
dcf503dfea feat(06-03): signing page — server component, PDF viewer, field overlays, progress bar
- page.tsx: server component validates JWT + one-time-use before rendering any UI
- Three error states (expired/used/invalid) show static pages with no canvas
- SigningPageClientWrapper: dynamic import (ssr:false) for react-pdf browser requirement
- SigningPageClient: full-scroll PDF viewer with pulsing blue field overlays
- Field overlay coordinates convert PDF user-space (bottom-left) to screen (top-left)
- SigningProgressBar: sticky bottom bar with X/Y count + jump-to-next + submit button
- api/sign/[token]/pdf: token-authenticated PDF streaming route (no agent auth)
2026-03-20 11:30:38 -06:00
Chandler Copeland
877ad66ead feat(06-02): POST /api/documents/[id]/send + document_prepared audit log
- Add send/route.ts: creates signing token, sends branded email, logs email_sent, updates status to Sent
- Auth guard returns 401 for unauthenticated requests; 422 if not prepared; 409 if already signed
- Wraps sendMail in try/catch — returns 502 without DB update if email delivery fails
- Add logAuditEvent(document_prepared) to prepare/route.ts after successful PDF preparation
2026-03-20 11:29:54 -06:00
Chandler Copeland
f41db49ff7 feat(06-02): branded signing request email + mailer utilities
- Add SigningRequestEmail.tsx React Email component (navy/gold brand colors, CTA button)
- Add signing-mailer.tsx with sendSigningRequestEmail() and sendAgentNotificationEmail()
- Uses CONTACT_SMTP_* env vars (same SMTP provider as contact form)
- Sender: "Teressa Copeland" <teressa@teressacopelandhomes.com>
2026-03-20 11:29:05 -06:00
Chandler Copeland
e1306dab69 feat(06-03): GET /api/sign/[token] route — token validation + audit logging
- Validates JWT with verifySigningToken(); returns expired/invalid/used/pending
- Checks signingTokens.usedAt for one-time-use enforcement
- Logs link_opened + document_viewed audit events on valid pending access
- Extracts IP from x-forwarded-for/x-real-ip headers for audit trail
- Public route — no auth() import or session required
2026-03-20 11:28:51 -06:00
Chandler Copeland
4bca04f988 docs(06-01): complete signing foundation plan — SUMMARY, STATE, ROADMAP updated
- created 06-01-SUMMARY.md with full task and decision documentation
- STATE.md: advanced to phase 6 plan 1, added 5 signing foundation decisions
- ROADMAP.md: marked 06-01-PLAN.md complete, Signing Flow at 1/6
- REQUIREMENTS.md: marked SIGN-02, LEGAL-01, LEGAL-02 complete
2026-03-20 11:27:07 -06:00
Chandler Copeland
2929581ab9 feat(06-01): create signing utility library (token, audit, embed)
- token.ts: createSigningToken() + verifySigningToken() using jose HS256
- audit.ts: logAuditEvent() inserts typed audit events with server timestamp
- embed-signature.ts: embedSignatureInPdf() embeds PNG sigs, returns SHA-256 hash
- added SIGNING_JWT_SECRET to .env.local (random 32-char base64 secret)
2026-03-20 11:24:56 -06:00
Chandler Copeland
fa68a1bcb4 feat(06-01): install packages + extend schema + generate migration
- installed signature_pad, @react-email/render, @react-email/components
- added signingTokens table (jti pk, documentId, expiresAt, usedAt)
- added auditEvents table with auditEventTypeEnum (6 event types)
- added signedFilePath, pdfHash, signedAt columns to documents table
- generated and applied migration 0005_signing_flow.sql
2026-03-20 11:24:02 -06:00
Chandler Copeland
6cf228c779 docs(06-signing-flow): create phase plan 2026-03-20 11:18:47 -06:00
Chandler Copeland
d049f92c61 docs(06): research signing flow phase 2026-03-20 11:07:33 -06:00
Chandler Copeland
08bf795646 docs(06): capture phase context 2026-03-20 10:55:47 -06:00
Chandler Copeland
d24dd54062 fix(05-04): reject field updates when document status is not Draft 2026-03-20 10:44:21 -06:00
Chandler Copeland
c6f5800394 wip: phase 05-04 paused — readOnly lock not working after Sent transition 2026-03-20 10:42:35 -06:00
Chandler Copeland
bd73f0cc76 feat(05-04): lock field placer to read-only when document is Sent
- Add readOnly prop to FieldPlacer; when true: hide palette, disable all pointer
  events on field boxes, show fields at 60% opacity, suppress delete button and
  all four resize handles
- PdfViewer accepts docStatus prop and derives readOnly={docStatus==='Sent'||'Signed'}
- PdfViewerWrapper forwards docStatus prop to PdfViewer
- page.tsx passes docStatus={doc.status} to PdfViewerWrapper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:36:25 -06:00
Chandler Copeland
08719a6109 feat(05-04): replace single resize handle with 4-corner resize handles
- Add ResizeCorner type ('se' | 'sw' | 'ne' | 'nw')
- handleResizeStart now accepts a corner argument, stored in DraggingState
- Screen-space math: compute start rect (top/left/right/bottom px), apply delta
  per corner, enforce 40px/20px minimums, then convert back to PDF units on pointerup
- renderFields renders four 10x10 blue square handles at each corner with the
  correct CSS resize cursor (nw-resize, ne-resize, sw-resize, se-resize)
- Opposite corner is always the anchor; only the two edges adjacent to the dragged
  corner move

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 10:36:04 -06:00
Chandler Copeland
51a77ef7d2 feat(05-04): add move and resize to placed signature fields
- Native pointer events on field body for move drag (no dnd-kit conflict)
- 10x10 resize handle in bottom-right corner with se-resize cursor
- Event delegation via DroppableZone onPointerMove/onPointerUp
- DOM mutation during drag for smooth performance; commit to state on pointerUp
- data-no-move attribute prevents resize handle and delete button from triggering move
- draggingRef tracks in-progress drag; activeDragFieldId drives grabbing cursor + box-shadow
- Minimum field size enforced: 40x20 PDF units
2026-03-20 01:02:45 -06:00
Chandler Copeland
3d6f0ea68c fix(05-04): disable DragOverlay drop animation to eliminate snap-back 2026-03-20 01:00:02 -06:00
Chandler Copeland
cbad0a1a34 fix(05-04): remove transform from DraggableToken to prevent snap-back animation 2026-03-20 00:59:27 -06:00
Chandler Copeland
f8897cce34 fix(05-04): sync defaultEmail into state after hydration 2026-03-20 00:55:22 -06:00
Chandler Copeland
0a719c9d60 fix(05-04): fetch client email via direct join, improve recipients hint text 2026-03-20 00:53:46 -06:00
Chandler Copeland
1319d4310e fix(05-04): guard parseEmails against undefined defaultEmail 2026-03-20 00:51:32 -06:00
Chandler Copeland
7f97bbc5e5 fix(05-04): simplify recipients to single pre-filled textarea, remove client dropdown 2026-03-20 00:50:41 -06:00
Chandler Copeland
d669b11a50 docs(05-04): update STATE.md with three additional UI fixes
- Ghost-rect field placement fix (f0ecfd1)
- Editable primary email input fix (73ba6d5)
- Text stamp 60pt/10pt fix (c4e8d01)
2026-03-20 00:43:02 -06:00
Chandler Copeland
c4e8d01784 fix(05-04): move text stamp to 60pt from top, increase font size to 10pt
- 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
2026-03-20 00:42:03 -06:00
Chandler Copeland
73ba6d5a0d fix(05-04): replace locked client display with editable email input
- Remove isLocked variable and read-only div for assigned client
- Add primaryEmail state pre-filled with assigned client's email, user-editable
- Show client name as helper text below the input for reference
- Update buildEmailAddresses() to use primaryEmail when assignedClientId is set
- Update button disabled logic to gate on primaryEmail when assigned client exists
2026-03-20 00:41:49 -06:00
Chandler Copeland
f0ecfd1545 fix(05-04): use ghost rect for field placement, canvas offset for overlays
- Replace cursor+delta calculation with active.rect.current.translated (ghost bounding rect)
- Measure coordinates relative to the inner <canvas> element, not the DroppableZone wrapper
- Add canvasOffset state + useLayoutEffect to offset field overlays by canvas position within wrapper
- Inline PDF coordinate math in handleDragEnd; clamp uses field pixel dimensions
2026-03-20 00:40:18 -06:00
Chandler Copeland
13cdd150f1 docs(05-04): complete pdf-fill-and-field-mapping plan 04 bug fixes
Four post-testing bugs fixed: signature field placement coordinate math,
delete button clickability, client selector pre-selection with manual
email entry, and text fill fallback to drawText for flat PDFs.
SUMMARY.md updated with full bug fix documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:24:50 -06:00
Chandler Copeland
ef10dd5089 fix(05-04): always stamp text fill data into prepared PDF
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>
2026-03-20 00:22:17 -06:00
Chandler Copeland
05915aa562 fix(05-04): pre-select document client and add manual email entry
- 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>
2026-03-20 00:21:34 -06:00
Chandler Copeland
126e10dc1d fix(05-04): fix signature field placement coordinate math
- Use pageInfo.width/height (authoritative rendered canvas size from
  react-pdf) instead of containerRect.width/height for all coordinate
  math; containerRect dimensions could differ if the wrapper div has
  extra decoration not matching the canvas
- Track containerSize in state (updated via useLayoutEffect when
  pageInfo changes) so renderFields() uses stable values from state
  instead of calling getBoundingClientRect() during render
- Refactor screenToPdfCoords/pdfToScreenCoords to take renderedW/H
  as explicit params instead of a DOMRect, making the contract clearer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:18:23 -06:00
Chandler Copeland
5bd77c1368 docs(05-04): complete pdf-fill-and-field-mapping plan 04 checkpoint
- Created 05-04-SUMMARY.md documenting Phase 5 human verification checkpoint
- Updated STATE.md: checkpoint:human-verify reached, awaiting sign-off on 10-step browser verification
- All Phase 5 implementation (DOC-04, DOC-05, DOC-06) complete in plans 05-01 through 05-03

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:09:26 -06:00
Chandler Copeland
3ad0382b96 docs(05-03): complete pdf-fill-and-field-mapping plan 03
- 05-03-SUMMARY.md created: TextFillForm + PreparePanel + extended document page
- STATE.md updated: phase 5 complete, plan 05-03, decisions logged
- ROADMAP.md updated: phase 5 plan progress
2026-03-20 00:07:03 -06:00
Chandler Copeland
296ef482bb feat(05-03): extend document detail page with PreparePanel
- Fetch allClients in parallel with doc via Promise.all
- Import PreparePanel and render in 1/3 right column of 2/3 + 1/3 grid
- currentClientId defaults to doc.assignedClientId ?? doc.clientId
- asc import added from drizzle-orm for ordered client list
- npm run build is clean with no TypeScript errors
2026-03-20 00:04:55 -06:00
Chandler Copeland
df6eb76bd0 feat(05-03): create TextFillForm and PreparePanel client components
- TextFillForm: key-value pair builder (up to 10 rows, individually removable)
- PreparePanel: client selector + text fill form + Prepare and Send button
- PreparePanel calls POST /api/documents/[id]/prepare and calls router.refresh() on success
- PreparePanel shows read-only message for non-Draft documents
- Rule 1 fix: PdfViewer page.scale -> scale (PageCallback has no .scale property; use state var)
2026-03-20 00:04:08 -06:00
Chandler Copeland
37f8691cac docs(05-02): complete pdf-fill-and-field-mapping plan 02
- FieldPlacer.tsx: dnd-kit drag-and-drop field placer with Y-flip coordinate conversion
- PdfViewer.tsx: extended with pageInfo state and FieldPlacer integration
- @dnd-kit/core and @dnd-kit/utilities installed
- Fields persist via PUT /api/documents/[id]/fields on every add/remove
- 05-02-SUMMARY.md created, STATE.md and ROADMAP.md updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:01:59 -06:00
Chandler Copeland
7a367363b1 feat(05-02): extend PdfViewer with pageInfo state and FieldPlacer integration
- Add pageInfo state (PageInfo | null) to track rendered PDF page dimensions
- Wire onLoadSuccess callback on Page to capture originalWidth/Height using Math.max mediaBox pattern
- Import and wrap Document/Page tree inside FieldPlacer component
- Pass docId, pageInfo, currentPage to FieldPlacer for coordinate conversion
- Only scale prop used on Page (not both width+scale — avoids double scaling)
- All existing controls (Prev, Next, Zoom In/Out, Download) preserved unchanged
2026-03-20 00:00:22 -06:00
Chandler Copeland
6069ae5e06 feat(05-02): install dnd-kit and create FieldPlacer component
- Install @dnd-kit/core@^6.3.1 and @dnd-kit/utilities@^3.2.2
- Create FieldPlacer.tsx with DndContext, draggable palette token, droppable PDF zone
- Implements Y-flip coordinate conversion (screenToPdfCoords / pdfToScreenCoords)
- Fetches existing fields from GET /api/documents/[id]/fields on mount
- Persists fields via PUT /api/documents/[id]/fields on every add/remove
- Renders placed fields as absolute-positioned blue-bordered overlays with remove button
- Default field size: 144x36 PDF points (2in x 0.5in at 72 DPI)
2026-03-19 23:59:51 -06:00
Chandler Copeland
f1cb526213 docs(05-01): complete pdf-fill-and-field-mapping plan 01
- Created 05-01-SUMMARY.md documenting all tasks, decisions, and deviations
- Updated STATE.md: advanced to Phase 5 Plan 1 complete, added 6 new decisions
- Updated ROADMAP.md: marked 05-01-PLAN.md complete, Phase 5 progress 1/4
- Marked requirements DOC-04, DOC-05, DOC-06 as complete in REQUIREMENTS.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:57:34 -06:00
Chandler Copeland
34ed0baa43 test(05-01): add unit tests for Y-flip coordinate conversion formula
- 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>
2026-03-19 23:55:27 -06:00