Commit Graph

254 Commits

Author SHA1 Message Date
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
Chandler Copeland
c81e8ea838 feat(05-01): add preparePdf utility and fields/prepare API routes
- 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>
2026-03-19 23:54:41 -06:00
Chandler Copeland
d67130da20 feat(05-01): extend documents schema with 4 new columns + migration 0003
- Added SignatureFieldData interface export to schema.ts
- Added signatureFields (jsonb), textFillData (jsonb), assignedClientId (text), preparedFilePath (text) nullable columns to documents table
- Added jsonb import to drizzle-orm/pg-core imports
- Generated and applied migration 0003_cool_natasha_romanoff.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:53:12 -06:00
Chandler Copeland
a997ebda20 fix(05): revise plan 05-01 based on checker feedback 2026-03-19 23:48:58 -06:00
Chandler Copeland
ceaae6eec5 docs(05-pdf-fill-and-field-mapping): create phase plan 2026-03-19 23:44:23 -06:00
Chandler Copeland
bec29f80b1 docs(05): research phase PDF fill and field mapping 2026-03-19 23:36:47 -06:00
Chandler Copeland
ac5b98fe33 wip: skyslope scraper — fix name extraction via body text parsing, preview+download flow ready 2026-03-19 23:06:17 -06:00
Chandler Copeland
1983f2c8cd wip: pause before phase 5 planning — skyslope scraper in progress 2026-03-19 22:57:14 -06:00
Chandler Copeland
27462a0ebb feat: add Playwright script to scrape SkySlope form libraries into seeds/forms/ 2026-03-19 22:27:41 -06:00
Chandler Copeland
ebb0bf03b7 docs(phase-04): complete phase execution 2026-03-19 22:13:15 -06:00
Chandler Copeland
9e89c3aeb5 docs(04-04): complete Phase 4 PDF ingest human verification — approved
- Human agent confirmed all 7 browser verification steps pass
- Forms library, modal, PDF render, file storage, and auth guards verified
- Phase 4 declared complete
- STATE.md updated: phase 4/4 complete, progress 100%
- ROADMAP.md updated: phase 4 all 4 plans complete

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 22:06:25 -06:00
Chandler Copeland
6c5e4afd86 fix(04-03): move ssr:false dynamic import into client wrapper to fix DOMMatrix SSR error 2026-03-19 22:01:58 -06:00
Chandler Copeland
025d9896ed fix(04-03): make back button more prominent with bordered pill style 2026-03-19 21:56:36 -06:00
Chandler Copeland
5c7a0fd061 fix(04-03): style Browse files as a visible button with selected filename display 2026-03-19 21:54:07 -06:00
Chandler Copeland
7ddc690482 docs(04-03): complete PDF viewer UI plan summary
- SUMMARY: AddDocumentModal, PdfViewer, /portal/documents/[docId] page
- STATE: current position updated to 04-03 complete, 3/4 plans in phase
- ROADMAP: phase 4 progress updated (3 summaries of 4 plans)
- REQUIREMENTS: DOC-03 marked complete
2026-03-19 21:47:09 -06:00
Chandler Copeland
c1f60cadf6 feat(04-03): add AddDocumentModal, PdfViewer, and document detail page
- 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
2026-03-19 21:44:17 -06:00
Chandler Copeland
63e5888968 feat(04-03): install react-pdf and configure Next.js transpilePackages
- npm install react-pdf (v9+ pulls in pdfjs-dist automatically)
- Add transpilePackages: ['react-pdf', 'pdfjs-dist'] to next.config.ts
- Build verified passing after config change
2026-03-19 21:42:06 -06:00
Chandler Copeland
7ddb920467 docs(04-02): complete PDF API routes plan summary
- Created 04-02-SUMMARY.md with task commits, decisions, and dependency graph
- Updated STATE.md: position advanced to 04-02 complete, decisions added
- Updated ROADMAP.md: phase 4 progress to 2/4 plans
2026-03-19 21:38:37 -06:00
Chandler Copeland
32e129c097 feat(04-02): create POST /api/documents and GET /api/documents/[id]/file routes
- POST handles both form-data (custom upload) and JSON (template copy) paths
- Copies seed PDF or writes uploaded file to uploads/clients/{clientId}/{uuid}.pdf
- Path traversal guard on destPath before writing
- GET streams PDF bytes with Content-Type: application/pdf
- Path traversal guard on filePath before reading
- Both routes return 401 for unauthenticated requests
2026-03-19 21:36:54 -06:00
Chandler Copeland
e0f180c3d8 feat(04-02): create GET /api/forms-library authenticated template list
- Queries form_templates ordered by name (asc)
- Returns 401 for unauthenticated requests
- Returns JSON array of { id, name, filename } for authenticated agents
2026-03-19 21:36:21 -06:00