diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md
index 83eb3df..a4a45e2 100644
--- a/.planning/REQUIREMENTS.md
+++ b/.planning/REQUIREMENTS.md
@@ -29,7 +29,7 @@
- [x] **DOC-01**: Agent can browse and import PDF forms from the utahrealestate.com vendor API (vendor.utahrealestate.com/webapi) — investigate API capability; fall back to manual upload if forms API is not available
- [x] **DOC-02**: Forms library syncs automatically on at least a monthly basis to reflect new/updated forms
-- [ ] **DOC-03**: Agent can view an imported PDF document in the browser
+- [x] **DOC-03**: Agent can view an imported PDF document in the browser
- [ ] **DOC-04**: Agent can drag-and-drop to place signature fields on any page of a PDF document
- [ ] **DOC-05**: Agent can fill in text fields on a document (property address, client names, dates, prices) before sending
- [ ] **DOC-06**: Agent can assign a document to a specific client and send a signing request
@@ -121,7 +121,7 @@ Which phases cover which requirements. Updated during roadmap creation.
| DASH-02 | Phase 3 | Complete |
| DOC-01 | Phase 4 | Complete |
| DOC-02 | Phase 4 | Complete |
-| DOC-03 | Phase 4 | Pending |
+| DOC-03 | Phase 4 | Complete |
| DOC-04 | Phase 5 | Pending |
| DOC-05 | Phase 5 | Pending |
| DOC-06 | Phase 5 | Pending |
diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index cb2ba62..14026a9 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -141,7 +141,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| 1. Foundation | 1/3 | Complete | 2026-03-19 |
| 2. Marketing Site | 2/3 | In Progress| |
| 3. Agent Portal Shell | 4/4 | Complete | 2026-03-19 |
-| 4. PDF Ingest | 2/4 | In Progress| |
+| 4. PDF Ingest | 3/4 | In Progress| |
| 5. PDF Fill and Field Mapping | 0/? | Not started | - |
| 6. Signing Flow | 0/? | Not started | - |
| 7. Audit Trail and Download | 0/? | Not started | - |
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 455ede9..40e77a5 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -3,12 +3,12 @@ gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: unknown
-last_updated: "2026-03-20T03:38:08.328Z"
+last_updated: "2026-03-20T03:46:32.381Z"
progress:
total_phases: 4
completed_phases: 3
total_plans: 14
- completed_plans: 12
+ completed_plans: 13
---
# Project State
@@ -23,11 +23,11 @@ See: .planning/PROJECT.md (updated 2026-03-19)
## Current Position
Phase: 4 of 7 (PDF Ingest) — In Progress
-Plan: 04-02 complete (2 of 4 plans in phase complete)
-Status: Plan 04-02 complete — GET /api/forms-library + POST /api/documents + GET /api/documents/[id]/file routes deployed
-Last activity: 2026-03-20 — Plan 04-02 complete: three authenticated API routes for forms library, document creation (template copy or file upload), and PDF file streaming with path traversal guard
+Plan: 04-03 complete (3 of 4 plans in phase complete)
+Status: Plan 04-03 complete — AddDocumentModal, PdfViewer, and /portal/documents/[docId] page deployed
+Last activity: 2026-03-20 — Plan 04-03 complete: react-pdf viewer UI with searchable forms library modal, custom file upload, document detail page with page nav and zoom
-Progress: [███████░░░] 70%
+Progress: [████████░░] 80%
## Performance Metrics
@@ -55,6 +55,7 @@ Progress: [███████░░░] 70%
| Phase 03-agent-portal-shell P04 | 5 | 2 tasks | 3 files |
| Phase 04-pdf-ingest P01 | 8 | 2 tasks | 7 files |
| Phase 04-pdf-ingest P02 | 1 | 2 tasks | 3 files |
+| Phase 04-pdf-ingest P04-03 | 5 | 2 tasks | 8 files |
## Accumulated Context
@@ -96,6 +97,9 @@ Recent decisions affecting current work:
- [Phase 04-pdf-ingest]: uploads/ at project root not under public/ — PDFs never accessible as static files
- [Phase 04-pdf-ingest]: Relative paths stored in DB (clients/{clientId}/{uuid}.pdf) — absolute paths would break on server move
- [Phase 04-pdf-ingest]: Path traversal guard on both write (POST) and read (GET /file) — prevents directory escape via malicious clientId or filePath
+- [Phase 04-pdf-ingest]: react-pdf v9 requires transpilePackages in next.config.ts — ships as ESM, Next.js webpack must transpile
+- [Phase 04-pdf-ingest]: pdfjs worker uses new URL(import.meta.url) pattern — no CDN URL; works in local/Docker environments without internet access
+- [Phase 04-pdf-ingest]: documentsRelations added to schema.ts — required for Drizzle db.query relational API with-relations support
### Pending Todos
@@ -112,5 +116,5 @@ None yet.
## Session Continuity
Last session: 2026-03-20
-Stopped at: Completed 04-02-PLAN.md — GET /api/forms-library + POST /api/documents + GET /api/documents/[id]/file routes
+Stopped at: Completed 04-03-PLAN.md — AddDocumentModal, PdfViewer, and /portal/documents/[docId] page
Resume file: None
diff --git a/.planning/phases/04-pdf-ingest/04-03-SUMMARY.md b/.planning/phases/04-pdf-ingest/04-03-SUMMARY.md
new file mode 100644
index 0000000..28533ec
--- /dev/null
+++ b/.planning/phases/04-pdf-ingest/04-03-SUMMARY.md
@@ -0,0 +1,157 @@
+---
+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 `` 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