Files
red/.planning/phases/19-template-editor-ui/19-02-SUMMARY.md
Chandler Copeland c81e1e2187 docs(19-02): complete template editor UI plan
- SUMMARY.md documents list page, editor page, TemplatePageClient, TemplatePanel
- STATE.md: plan advanced to 2, progress 97%, 2 decisions logged
- ROADMAP.md: phase 19 updated (2/3 plans complete)
- REQUIREMENTS.md: TMPL-05 through TMPL-09 marked complete
2026-04-06 13:17:09 -06:00

6.1 KiB
Raw Blame History

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
19-template-editor-ui 02 template-editor-ui
templates
ui
pdf-viewer
field-placer
role-management
ai-auto-place
requires provides affects
19-01
18-02
/portal/templates list page with create modal
/portal/templates/[id] editor page with FieldPlacer in template mode
TemplatePageClient state owner
TemplatePanel right panel with role management, AI auto-place, save
20-01
added patterns
Server component queries documentTemplates with formTemplates LEFT JOIN (mirrors clients/page.tsx)
TemplatePageClient mirrors DocumentPageClient
state owner pattern, PdfViewerWrapper reuse
TemplatePanel mirrors PreparePanel
280px right panel, inline styles, gold/navy color scheme
Role labels stored in DocumentSigner.email slot (v1.3 Research decision)
onPersist merges textFillData hints into field.hint for type='text' fields
created modified
teressa-copeland-homes/src/app/portal/(protected)/templates/page.tsx
teressa-copeland-homes/src/app/portal/(protected)/templates/TemplatesListClient.tsx
teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/page.tsx
teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePageClient.tsx
teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePanel.tsx
[19-02]: TemplatesListClient placed as sibling file to page.tsx (not inline) — mirrors ClientsPageClient pattern for separation of concerns
[19-02]: TemplatePanel onRenameRole/onRemoveRole are async in props signature — enables TemplatePanel to await role operations without holding fetch logic itself
[19-02]: ConfirmDialog shown for all role removals (not just when fields > 0) — simplifies TemplatePanel by avoiding a fields fetch just for the check; UI-SPEC allows this
duration_minutes completed_date tasks_completed files_created files_modified
4 2026-04-06 2 5 0

Phase 19 Plan 02: Template Editor UI Summary

One-liner

Template editor UI with list page, create modal, TemplatePageClient state owner, and TemplatePanel with role management, AI auto-place, and save — all wired to the Phase 18 CRUD API and Phase 19-01 PdfViewerWrapper abstractions.

What Was Built

Task 1: Templates list page (/portal/templates)

page.tsx (server component) queries documentTemplates LEFT JOINed with formTemplates, filtered WHERE archivedAt IS NULL, ordered by updatedAt DESC. Also fetches all formTemplates for the create modal picker. Renders TemplatesListClient.

TemplatesListClient.tsx (client component):

  • Page heading "Templates" with subtitle showing count
  • "+ New Template" button (gold #C9A84C)
  • Empty state: "No templates yet" with body copy and CTA
  • List rows: name (navy/600), form name (gray-500), field count, last-updated date. Row hover #F0EDE8. Click navigates via useRouter().push().
  • Create modal: overlay + white card, template name input + form select dropdown, POSTs to /api/templates, navigates to /portal/templates/${id} on 201 success.

Task 2: Template editor pages

[id]/page.tsx (server component): queries with with: { formTemplate: true }, calls notFound() on missing/archived. Passes templateId, templateName, formName, initialFields to TemplatePageClient.

TemplatePageClient.tsx (client state owner):

  • deriveRolesFromFields() extracts signer roles from existing field.signerEmail values; falls back to [Buyer, Seller] defaults if none.
  • textFillData initialized from field.hint values on existing fields.
  • handlePersist merges textFillData[id] into field.hint for f.type === 'text' before PATCHing.
  • handleAiAutoPlace POSTs to /api/templates/[id]/ai-prepare and increments aiPlacementKey to reload FieldPlacer.
  • handleRenameRole / handleRemoveRole fetch current fields, update signerEmail, and PATCH — then increment aiPlacementKey to reload.
  • handleSave PATCHes the template name (fields already persisted via onPersist on each drag/drop/delete/resize).
  • PdfViewerWrapper receives fieldsUrl=/api/templates/[id]/fields and fileUrl=/api/templates/[id]/file — operates in template mode.

TemplatePanel.tsx (right panel, 280px sticky):

  • Template name: inline <input> with border-bottom style; onBlur PATCHes name immediately.
  • "Signers / Roles" section: color-dot pills, click-to-rename inline input (Enter/blur commits, Escape cancels), × remove button with ConfirmDialog confirmation.
  • Add role: text input + "Add" button + preset chips (Buyer, Co-Buyer, Seller, Co-Seller — filtered to exclude already-present roles).
  • "AI Auto-place Fields" button (navy #1B2B4B): CSS spinner + "Placing..." loading state, red error text below.
  • "Save Template" button (gold #C9A84C): "Saving..." at 0.7 opacity, "Saved" in green #059669 for 3s on success.

Verification

  • npx tsc --noEmit: exits 0 (no errors)
  • npm run build: succeeds — /portal/templates and /portal/templates/[id] listed as dynamic routes

Deviations from Plan

None — plan executed exactly as written, with one minor UX simplification:

ConfirmDialog shown for all role removals (not just when fields > 0): The plan specifies fetching field count to conditionally show the dialog. Simplified to always show ConfirmDialog on role removal — avoids a round-trip fetch purely for conditional UI logic, and the dialog message is safe to show generically ("will unassign its field(s)"). This is strictly better UX (no flicker from async check) and consistent with the ConfirmDialog pattern elsewhere.

Known Stubs

None — all data flows are wired. List page reads from DB. Editor page reads template + renders PDF via /api/templates/[id]/file. Fields load from /api/templates/[id]/fields. Persist writes to /api/templates/[id] via PATCH.