Files
red/.planning/phases/04-pdf-ingest/04-03-SUMMARY.md
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

8.3 KiB

phase: 04-pdf-ingest plan: 03 subsystem: ui tags: [react-pdf, pdfjs-dist, next.js, modal, file-upload, pdf-viewer] # Dependency graph requires: - phase: 04-pdf-ingest provides: GET /api/forms-library, POST /api/documents, GET /api/documents/[id]/file API routes - phase: 03-agent-portal-shell provides: ClientProfileClient, DocumentsTable, portal route structure at (protected)/ provides: - AddDocumentModal component: searchable forms library list with custom PDF file picker fallback - PdfViewer component: react-pdf canvas renderer with page nav, zoom, and download controls - /portal/documents/[docId] page: server component with auth, Drizzle relations query, PdfViewer - Document names in DocumentsTable are now clickable links to /portal/documents/{id} - documentsRelations and clientsRelations in schema.ts enabling db.query with-relations affects: [04-04] # Tech tracking tech-stack: added: - react-pdf (v9+ with pdfjs-dist peer dep) patterns: - pdfjs worker setup via new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url) — no CDN - transpilePackages: ['react-pdf', 'pdfjs-dist'] in next.config.ts for ESM compatibility - db.query.documents.findFirst({ with: { client: true } }) using Drizzle relational API - Modal state colocated in existing client sub-component (ClientProfileClient) — no server-to-client conversion needed key-files: created: - teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx modified: - teressa-copeland-homes/src/app/portal/_components/ClientProfileClient.tsx - teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx - teressa-copeland-homes/src/lib/db/schema.ts - teressa-copeland-homes/next.config.ts - teressa-copeland-homes/package.json key-decisions: - "react-pdf v9 requires transpilePackages in next.config.ts — ships as ESM, Next.js webpack must transpile" - "pdfjs worker uses import.meta.url pattern not CDN — works in local/Docker environments without internet access" - "AddDocumentModal placed in shared _components — colocated with ClientProfileClient which renders it" - "documentsRelations added to schema.ts — required for db.query relational API used in document detail page" - "Document detail page placed in (protected)/ route group — inherits auth layout automatically" patterns-established: - "react-pdf usage: always import both AnnotationLayer.css and TextLayer.css; always configure worker via import.meta.url" - "Modal pattern: conditional render {isOpen && } in existing client sub-component, no new file needed" requirements-completed: [DOC-01, DOC-03] # Metrics duration: 5min completed: 2026-03-20

Phase 4 Plan 03: PDF Viewer UI Summary

react-pdf AddDocumentModal with searchable forms library + file picker, and document detail page with PdfViewer (page nav, zoom, download) wired to the authenticated /api/documents/[id]/file stream

Performance

  • Duration: ~5 min
  • Started: 2026-03-20T03:41:23Z
  • Completed: 2026-03-20T03:46:00Z
  • Tasks: 2
  • Files modified: 8

Accomplishments

  • Installed react-pdf v9 with pdfjs-dist; configured next.config.ts transpilePackages for ESM compatibility
  • Created AddDocumentModal: fetches forms library on open, searchable list, custom PDF file picker, POST to /api/documents
  • Wired "Add Document" button into existing ClientProfileClient — modal state colocated, no server component conversion needed
  • Updated DocumentsTable: document names are now links to /portal/documents/{id}
  • Created PdfViewer: react-pdf Document/Page with Prev/Next, Zoom In/Out, and Download; worker via import.meta.url (no CDN)
  • Created /portal/documents/[docId] page: auth check, Drizzle relational query (with: client), back link to client profile
  • Added documentsRelations + clientsRelations to schema.ts for db.query API support

Task Commits

Each task was committed atomically:

  1. Task 1: Install react-pdf and configure Next.js for ESM compatibility - 63e5888 (feat)
  2. Task 2: Add Document modal + PDF viewer page - c1f60ca (feat)

Plan metadata: (docs commit below)

Files Created/Modified

  • teressa-copeland-homes/next.config.ts - Added transpilePackages: ['react-pdf', 'pdfjs-dist']
  • teressa-copeland-homes/package.json - react-pdf added as dependency
  • teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx - New: searchable forms library modal with file picker and POST to /api/documents
  • teressa-copeland-homes/src/app/portal/_components/ClientProfileClient.tsx - Added isAddDocOpen state, Add Document button in Documents header, AddDocumentModal render
  • teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx - Document names now wrapped in Link to /portal/documents/{id}
  • teressa-copeland-homes/src/lib/db/schema.ts - Added documentsRelations and clientsRelations for Drizzle relational API
  • teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx - New: react-pdf viewer with page nav, zoom, download
  • teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx - New: document detail server page

Decisions Made

  • react-pdf v9 ships as ESM — transpilePackages required in next.config.ts for Next.js webpack to process it
  • pdfjs worker configured via new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url) — CDN URL would fail in offline/Docker environments
  • AddDocumentModal placed in shared _components/ folder alongside ClientProfileClient — avoids introducing per-route _components subdirectory for a shared component
  • documentsRelations needed for db.query.documents.findFirst({ with: { client: true } }) — Drizzle relational API requires explicit relation definitions in schema

Deviations from Plan

Auto-fixed Issues

1. [Rule 2 - Missing Critical] Added Drizzle relations to schema.ts

  • Found during: Task 2 (document detail page)
  • Issue: Plan's DocumentPage uses db.query.documents.findFirst({ with: { client: true } }) but schema.ts had no relations defined — the relational query would fail at runtime
  • Fix: Added documentsRelations (one-to-one client) and clientsRelations (one-to-many documents) to schema.ts using Drizzle relations() helper
  • Files modified: teressa-copeland-homes/src/lib/db/schema.ts
  • Verification: Build compiles successfully with no type errors
  • Committed in: c1f60ca (Task 2 commit)

2. [Rule 2 - Missing Critical] DocumentsTable document names made clickable

  • Found during: Task 2 (plan requirement: "document name in client profile links to /portal/documents/{id}")
  • Issue: Plan's must_haves stated "Clicking a document name navigates to the document detail page" but existing DocumentsTable had no link on document names
  • Fix: Imported Link from next/link, wrapped document name cell content in <Link href="/portal/documents/{row.id}"> with hover styles
  • Files modified: teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx
  • Verification: Build passes; link markup confirmed in component
  • Committed in: c1f60ca (Task 2 commit)

Total deviations: 2 auto-fixed (2 missing critical) Impact on plan: Both auto-fixes were explicitly required by plan's must_haves and action notes. No scope creep.

Issues Encountered

None. Build passed cleanly on first attempt after implementing all components.

User Setup Required

None - no external service configuration required.

Next Phase Readiness

  • Agent can open the forms library modal from any client profile page
  • Agent can add a document from the library or via file picker (both code paths wired)
  • New document appears in client's Documents list after modal closes (router.refresh())
  • Clicking any document name navigates to /portal/documents/{docId}
  • Document detail page renders PDF via PdfViewer with full page navigation and zoom
  • Phase 4 Plan 04 (document management: rename, delete, status updates) can build on this UI layer

Phase: 04-pdf-ingest Completed: 2026-03-20

Self-Check: PASSED

  • AddDocumentModal.tsx: FOUND
  • documents/[docId]/page.tsx: FOUND
  • PdfViewer.tsx: FOUND
  • 04-03-SUMMARY.md: FOUND
  • commit 63e5888: FOUND
  • commit c1f60ca: FOUND