diff --git a/.planning/phases/19-template-editor-ui/.gitkeep b/.planning/phases/19-template-editor-ui/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.planning/phases/19-template-editor-ui/19-CONTEXT.md b/.planning/phases/19-template-editor-ui/19-CONTEXT.md new file mode 100644 index 0000000..73d5f41 --- /dev/null +++ b/.planning/phases/19-template-editor-ui/19-CONTEXT.md @@ -0,0 +1,133 @@ +# Phase 19: Template Editor UI - Context + +**Gathered:** 2026-04-06 +**Status:** Ready for planning + + +## Phase Boundary + +UI-only phase (plus one new API route for AI auto-place). Builds the template editor page at `/portal/templates/[id]` and the supporting infrastructure. + +Deliverables: +1. `FieldPlacer` — add optional `onPersist` callback prop (non-breaking) +2. `/portal/templates` — list page (all active templates, click to open editor) +3. `/portal/templates/[id]` — template editor page (PDF + FieldPlacer in template mode + role panel + save button) +4. `POST /api/templates/[id]/ai-prepare` — AI auto-place for templates +5. Top nav: "Templates" link + + + + +## Implementation Decisions + +### FieldPlacer Abstraction + +- **D-01:** Add `onPersist?: (fields: SignatureFieldData[]) => Promise | void` prop to FieldPlacer. When provided, this replaces the internal `persistFields(docId, fields)` call. When absent (all existing consumers), falls back to the existing internal `persistFields` behavior — fully backwards compatible. +- **D-02:** `persistFields` is called in 4 places in FieldPlacer. All 4 must be updated to call `onPersist(fields)` if provided, else `persistFields(docId, fields)`. The internal `persistFields` function stays for backwards compat. +- **D-03:** FieldPlacer's `signers` prop accepts `DocumentSigner[]` where `email` carries either a real email (document mode) OR a role label (template mode). No type change needed — the slot already accepts any string. + +### Role Labels in Template Editor + +- **D-04:** Role labels are free-form strings that the agent types OR picks from presets. Preset list: "Buyer", "Co-Buyer", "Seller", "Co-Seller". Agent can type any string (e.g. "Lender", "Trustee"). +- **D-05:** Template editor starts with two default role labels pre-configured: "Buyer" (color #6366f1 indigo) and "Seller" (color #f43f5e rose). Agent can add, remove, or rename these. +- **D-06:** Role colors: same palette as document signers — `['#6366f1', '#f43f5e', '#10b981', '#f59e0b']` cycling by index. +- **D-07:** Role labels are stored in `documentTemplates.signatureFields` via `field.signerEmail`. When the template editor saves, it calls `PATCH /api/templates/[id]` with the full `signatureFields` array (same route as Phase 18 D-10). +- **D-08:** The "Active role" selector in the template editor is identical in behavior to the "Active signer" selector in the document FieldPlacer — it's the same component, just with role label strings instead of emails. No new component needed. + +### Template Editor Page + +- **D-09:** Route: `/portal/templates/[id]` — single page view + edit (same pattern as `/portal/clients/[id]`). No separate `/edit` route. +- **D-10:** Templates list page at `/portal/templates` — shows all active templates (name, form name, field count, last updated). Click a row to open the editor. +- **D-11:** "Templates" appears in the portal top nav (alongside Dashboard, Clients, Profile). +- **D-12:** Template editor page layout: PDF viewer on the left (full FieldPlacer), slim right panel (TemplatePanel) with: template name (editable), role list (add/remove/rename), AI Auto-place button, Save button. +- **D-13:** TemplatePanel is a new component (separate from PreparePanel). PreparePanel is document-specific (prepare/send/preview). TemplatePanel is template-specific (roles, save). + +### AI Auto-place for Templates + +- **D-14:** New route `POST /api/templates/[id]/ai-prepare` — reads `formTemplate.filename` to locate the PDF in `seeds/forms/`, calls existing `extractBlanks(filePath)` + `classifyFieldsWithAI(blanks, null)` (no client data for pre-fill in template mode), writes result to `documentTemplates.signatureFields` via `PATCH`. +- **D-15:** No client name/address pre-fill in template AI mode — `classifyFieldsWithAI` receives `null` for client context. Fields are placed by type only. +- **D-16:** AI Auto-place button in TemplatePanel triggers `POST /api/templates/[id]/ai-prepare`, then reloads the FieldPlacer via a key increment (same pattern as existing `aiPlacementKey` in DocumentPageClient). + +### Navigation + +- **D-17:** Portal nav: add "Templates" between "Clients" and "Profile" in the top nav. +- **D-18:** Templates list page is a server component that fetches from the database directly (same pattern as the clients list page). + +### Claude's Discretion +- Exact TemplatePanel layout details (spacing, button order) +- Whether to show field count on the editor page header +- Loading states for AI auto-place and save operations + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Files Being Modified +- `teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` (822 lines — add onPersist prop) +- `teressa-copeland-homes/src/app/portal/_components/SiteNav.tsx` — add Templates nav link + +### New Files +- `teressa-copeland-homes/src/app/portal/(protected)/templates/page.tsx` — list page +- `teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/page.tsx` — editor server component +- `teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePageClient.tsx` — state owner (like DocumentPageClient) +- `teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePanel.tsx` — right panel (roles, AI, save) +- `teressa-copeland-homes/src/app/api/templates/[id]/ai-prepare/route.ts` — AI auto-place route + +### Existing Patterns to Reuse +- `teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx` — state owner pattern; TemplatePageClient follows same structure +- `teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` — onPersist prop addition +- `teressa-copeland-homes/src/app/api/documents/[id]/ai-prepare/route.ts` — AI route pattern to copy for templates +- `teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx` — list page pattern +- `teressa-copeland-homes/src/lib/ai/extract-text.ts` + `field-placement.ts` — reused as-is for template AI + +### Phase 18 Output +- `teressa-copeland-homes/src/lib/db/schema.ts` — `documentTemplates` table + `documentTemplatesRelations` +- `teressa-copeland-homes/src/app/api/templates/route.ts` — GET/POST +- `teressa-copeland-homes/src/app/api/templates/[id]/route.ts` — PATCH/DELETE + + + + +## Existing Code Insights + +### Reusable Assets +- `aiPlacementKey` + `setAiPlacementKey` pattern in DocumentPageClient — TemplatePageClient uses the same increment-to-reload mechanism +- `SIGNER_COLORS` constant in PreparePanel — same palette for role colors in TemplatePanel +- `isClientVisibleField()` — already imported in FieldPlacer; no changes needed +- `PdfViewerWrapper` — passes through to FieldPlacer; reused as-is in template editor + +### Established Patterns +- Server component page → `PageClient.tsx` state owner → child components (PreparePanel, FieldPlacer) +- `db.query.documentTemplates.findFirst(...)` available via Drizzle relations from Phase 18 +- Field persistence: FieldPlacer calls `persistFields(id, fields)` internally; adding `onPersist` prop makes this injectable + +### Integration Points +- `documentTemplates.signatureFields` (Phase 18) — written by template editor via PATCH +- `PATCH /api/templates/[id]` (Phase 18) — accepts `{ signatureFields?: SignatureFieldData[] }` — used by TemplatePanel save and AI auto-place +- Phase 20 reads `documentTemplates.signatureFields` to copy fields into a new document + + + + +## Specific Ideas + +- The "Save" button in TemplatePanel calls `PATCH /api/templates/[id]` with the current `signatureFields` from FieldPlacer state. +- AI Auto-place flow: button click → `POST /api/templates/[id]/ai-prepare` → route reads PDF → calls extractBlanks + classifyFieldsWithAI → writes to `documentTemplates.signatureFields` → returns fields → TemplatePageClient increments `aiPlacementKey` to reload FieldPlacer. +- The `onPersist` callback in FieldPlacer: `async (fields) => await fetch('/api/templates/{id}', { method: 'PATCH', body: JSON.stringify({ signatureFields: fields }) })` + + + + +## Deferred Ideas + +None — discussion stayed within phase scope. + + + +--- + +*Phase: 19-template-editor-ui* +*Context gathered: 2026-04-06*