# 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*