- 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
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>
- 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>
- 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
- 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)
- 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
- 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)
- 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
- 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
- Create scripts/seed-forms.ts: reads seeds/forms/, upserts PDFs into form_templates via onConflictDoUpdate on filename
- Add seed:forms script to package.json with DOTENV_CONFIG_PATH=.env.local prefix
- Empty seeds/forms/ prints guidance message and exits 0 (monthly-sync workflow ready)
- Added import of clients, documents, inArray from drizzle-orm
- Seeds 2 clients: Sarah Johnson and Mike Torres (onConflictDoNothing)
- Queries back seeded client IDs, then seeds 4 placeholder documents
- Documents cover Signed/Sent/Draft statuses across both clients
- Seed is idempotent via onConflictDoNothing guard
- ClientCard.tsx: server component with name, email, doc count, last activity; wrapped in Link to /portal/clients/[id]
- ClientModal.tsx: use client component with useActionState from react; supports create/edit modes via bind pattern; closes on success
- ClientsPageClient.tsx: use client wrapper holding isOpen modal state, renders card grid or empty state CTA
- clients/page.tsx: async server component fetching clients with docCount + lastActivity via Drizzle LEFT JOIN + GROUP BY
- Async server component queries all documents with LEFT JOIN to clients
- Filter state lives in URL (?status=Draft|Sent|Viewed|Signed) via DashboardFilters client component
- Rows filtered in JavaScript after fetch (tiny dataset in Phase 3)
- DashboardFilters extracted to _components/DashboardFilters.tsx (use client + useRouter)
- Displays agent first name extracted from session email
- Create portal/(protected)/layout.tsx with auth() check and redirect to /agent/login
- Create PortalNav.tsx as client component with Dashboard/Clients links and active state
- Nav uses usePathname() for active gold underline, LogoutButton for sign-out
- CSS variables --navy/--gold/--cream applied throughout portal shell
- Create TestimonialsSection with 5 placeholder reviews
- Auto-rotates every 5s, pauses on hover, clearInterval cleanup
- ChevronLeft/ChevronRight arrow controls and dot indicators
- Wire TestimonialsSection into page.tsx between hero and listings
- Add scroll-behavior: smooth and scroll-margin-top to globals.css
- Create SiteNav with sticky navy bar, desktop links, mobile hamburger
- Create HeroSection with split-panel layout and next/image
- Create ListingsPlaceholder with brand navy background and gold CTA
- Create SiteFooter with license number and TODO verify comment
- Update page.tsx to compose all section components
- Install lucide-react for icons
[Rule 1 - Bug] Fixed event handlers in server components — used CSS hover classes instead
- 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>
- LogoutButton: client component with server action calling signOut to ?signed_out=1
- AgentLayout: defense-in-depth auth() check, header with email and sign-out button
- DashboardPage: belt-and-suspenders auth() check, welcome message with agent email
- HomePage: updated to Teressa Copeland Homes placeholder (Phase 2 marketing site)
- npm run build and npx tsc --noEmit both pass with zero errors
- 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
- Created src/lib/db/schema.ts with users table (id, email, password_hash, created_at)
- Created src/lib/db/index.ts exporting db singleton via Drizzle + Neon HTTP driver
- Created drizzle.config.ts pointing to schema.ts with postgresql dialect
- Created scripts/seed.ts reading AGENT_EMAIL/AGENT_PASSWORD to create hashed user row
- Generated drizzle/0000_milky_black_cat.sql migration (committed per user decision)
- db:migrate and db:seed pending user Neon database setup