Files
2026-04-06 12:24:17 -06:00

134 lines
8.2 KiB
Markdown

# Phase 19: Template Editor UI - Context
**Gathered:** 2026-04-06
**Status:** Ready for planning
<domain>
## 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
</domain>
<decisions>
## Implementation Decisions
### FieldPlacer Abstraction
- **D-01:** Add `onPersist?: (fields: SignatureFieldData[]) => Promise<void> | 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
</decisions>
<canonical_refs>
## 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
</canonical_refs>
<code_context>
## 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
</code_context>
<specifics>
## 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 }) })`
</specifics>
<deferred>
## Deferred Ideas
None — discussion stayed within phase scope.
</deferred>
---
*Phase: 19-template-editor-ui*
*Context gathered: 2026-04-06*