10 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 19 | template-editor-ui | draft | false | none | 2026-04-06 |
Phase 19 — UI Design Contract
Visual and interaction contract for the template editor UI phase. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Design System
| Property | Value |
|---|---|
| Tool | none — manual inline styles + Tailwind utilities |
| Preset | not applicable |
| Component library | none (Radix not installed; components are custom) |
| Icon library | none (text/Unicode symbols used inline, e.g. +, ✓, ×) |
| Font (sans) | Geist Sans via var(--font-sans) / var(--font-geist-sans) |
| Font (serif/display) | Cormorant Garamond via var(--font-serif) — wordmark only |
Source: globals.css, PortalNav.tsx, layout.tsx
Note: components.json was not found. The project does not use shadcn. All new components follow the existing inline-style + selective Tailwind pattern established in PreparePanel, ClientsPageClient, and PortalNav.
Spacing Scale
Declared values (multiples of 4 only):
| Token | Value | Usage |
|---|---|---|
| xs | 4px | Icon gaps, dot indicator margin |
| sm | 8px | Inline element spacing, button padding vertical |
| md | 16px | Panel internal padding, form field gaps |
| lg | 24px | Section spacing within panels, list gaps |
| xl | 32px | Page-level top margin |
| 2xl | 48px | Empty-state vertical padding |
| 3xl | 64px | Nav height (established: PortalNav is 64px) |
Exceptions:
- Role color dot: 8px diameter (not a layout token — cosmetic)
- Signer/role pill height: 32px (matches PreparePanel signer pill pattern)
- Right panel (TemplatePanel) width: 280px fixed — matches PreparePanel column width in DocumentPageClient split layout
- AI Auto-place button and Save button minimum height: 36px (matches existing PreparePanel button height)
Source: PreparePanel.tsx, PortalNav.tsx, layout.tsx (2rem = 32px page padding)
Typography
| Role | Size | Weight | Line Height |
|---|---|---|---|
| Body | 14px (0.875rem) | 400 | 1.5 |
| Label / caption | 12px (0.75rem) | 400 | 1.4 |
| Section heading | 14px (0.875rem) | 600 | 1.3 |
| Page heading | 24px (1.5rem) | 700 | 1.2 |
Source: ClientsPageClient.tsx (h1: 1.5rem/700), PreparePanel.tsx (h2: font-semibold/text-gray-900, body: 0.875rem/400), PortalNav.tsx (links: 0.8125rem/500 uppercase)
Weights used: regular (400) and semibold/bold (600/700). No third weight.
Letter-spacing exceptions:
- Nav links:
letter-spacing: 0.08em; text-transform: uppercase— preserved exactly, no change - Wordmark:
letter-spacing: 0.02em— no change
Color
| Role | Value | Usage |
|---|---|---|
| Dominant (60%) | #FAF9F7 (cream) |
Page background, panel backgrounds, modal backgrounds |
| Secondary (30%) | #F9FAFB / #F0EDE8 (cream-mid) |
Panel interior fill, list row backgrounds, right panel |
| Accent (10%) | #C9A84C (gold) |
Primary CTA buttons only, active nav underline |
| Navy | #1B2B4B |
Page headings, nav background, secondary button fill |
| Destructive | #DC2626 (red-600) |
Delete role action button only |
Accent (#C9A84C) reserved for:
- "Save Template" button (primary CTA)
- "Add Role" button (secondary CTA within TemplatePanel)
- Active nav link underline (existing pattern, unchanged)
Navy (#1B2B4B) used for:
- "AI Auto-place" button fill (matches PreparePanel AI button pattern)
- Page heading text
- Wordmark
Semantic colors:
- Success inline:
#059669(green — "Saved" confirmation inline text, same as PreparePanel "Sent ✓") - Error inline:
#DC2626(red — error messages) - Muted text:
#6B7280(gray-500 — counts, timestamps, placeholder copy) - Border default:
#E5E7EB(gray-200)
Source: globals.css, PreparePanel.tsx, ClientsPageClient.tsx, ConfirmDialog.tsx
Component Inventory
Components this phase must produce (from CONTEXT.md D-12/D-13):
1. TemplatePanel (new)
Right panel, 280px wide, sticky within the editor view.
Sections (top to bottom):
- Template name — editable inline
<input>(14px, border-bottom-only style when focused, gray-200 border at rest). Displays current name from DB. - Roles section — heading "Signers / Roles" (12px uppercase label), list of role pills, "Add role" button.
- Role pill — colored dot (8px,
background: role.color) + role label string + rename icon (pencil, text "Edit") + remove button (×). Free-form rename via inline edit on click. - Add role — text input + "Add" button. Preset suggestions: "Buyer", "Co-Buyer", "Seller", "Co-Seller" as clickable chips below the input.
- AI Auto-place button — full-width, navy fill (
#1B2B4B), white text, 36px height. Label: "AI Auto-place Fields". Shows spinner + "Placing…" during loading. - Save button — full-width, gold fill (
#C9A84C), white text, 36px height. Label: "Save Template". Shows "Saving…" during loading. Shows inline "Saved" (green) or error message after.
2. TemplatePageClient (new)
State owner. Layout: CSS flexbox row — left: PdfViewerWrapper + FieldPlacer (flex: 1, min-width: 0), right: TemplatePanel (280px, flex-shrink: 0). Gap: 24px. Pattern mirrors DocumentPageClient.
3. Templates list page (new)
- Page heading: "Templates" (24px/700, navy)
- Subtitle: "{N} template{s}" (14px/400, gray-500)
- "New Template" button: top-right, gold fill (primary CTA). Label: "+ New Template"
- Table/list rows: each row shows template name (14px/600, navy), form name (14px/400, gray-500), field count ("N fields", 12px/400, gray-500), last updated (12px/400, gray-500). Click row → navigate to editor.
- Row hover:
background: #F0EDE8(cream-mid), cursor pointer.
4. PortalNav update
Add "Templates" link between "Clients" and "Profile" in navLinks array. No visual change — same style as existing links.
5. FieldPlacer update (non-breaking)
Add optional onPersist?: (fields: SignatureFieldData[]) => Promise<void> | void prop. When provided, replaces the 4 internal persistFields(docId, fields) call sites. Existing callers unaffected.
Interaction States
AI Auto-place button
| State | Visual |
|---|---|
| Idle | Navy fill, "AI Auto-place Fields", full opacity |
| Loading | Navy fill, "Placing…" + spinner (CSS border-radius spin), disabled |
| Success | Button returns to idle; FieldPlacer reloads via key increment |
| Error | Inline error message below button in red-600 text (14px), button returns to idle |
Save button
| State | Visual |
|---|---|
| Idle | Gold fill, "Save Template", full opacity |
| Loading | Gold fill at 0.7 opacity, "Saving…", disabled |
| Success | Inline "Saved" text in green (#059669) below button, fades after 3s |
| Error | Inline error in red below button |
Role pill inline rename
- Click role label → label becomes a text input (same font, border-bottom style)
- Enter or blur → commits rename, calls PATCH /api/templates/[id] with updated
signatureFieldsrole strings - Escape → cancels rename, reverts to original label
Remove role
- Click
×on role pill - If no fields are assigned to that role: remove immediately
- If fields are assigned to that role: show ConfirmDialog — "Remove role?" / "Removing 'Buyer' will unassign {N} field(s). This cannot be undone." / Confirm: "Remove Role" (red-600), Cancel: "Cancel"
New Template modal (from list page)
- Reuses AddDocumentModal pattern: overlay, white rounded card, form fields.
- Fields: "Template name" (text input), "Select form" (PDF form picker from library).
- CTA: "Create Template" (gold fill). On success: navigate to
/portal/templates/[id].
Layout
Template editor page (/portal/templates/[id])
[PortalNav — 64px sticky top]
[main — max-width 1200px, margin: 0 auto, padding: 32px]
[page header — flex row]
[h1 "Edit Template: {name}"] [field count badge "{N} fields placed" — optional, muted]
[editor body — flex row, gap: 24px]
[left: PdfViewerWrapper + FieldPlacer — flex:1]
[right: TemplatePanel — width: 280px, flex-shrink: 0]
Templates list page (/portal/templates)
[PortalNav — 64px sticky top]
[main — max-width 1200px, margin: 0 auto, padding: 32px]
[page header — flex row space-between]
[h1 "Templates"] [subtitle "{N} templates"] [CTA "+ New Template"]
[list — flex column, gap: 8px]
[template row × N]
Copywriting Contract
| Element | Copy |
|---|---|
| Primary CTA (list page) | "+ New Template" |
| Primary CTA (editor) | "Save Template" |
| Secondary CTA (editor) | "AI Auto-place Fields" |
| Nav link | "Templates" |
| Page heading (list) | "Templates" |
| Page heading (editor) | "Edit Template: {name}" |
| Empty state heading (list) | "No templates yet" |
| Empty state body (list) | "Create a template to reuse field placements across documents." |
| Empty state CTA (list) | "+ Create your first template" |
| Error — AI auto-place | "AI placement failed. Check that the form PDF is accessible and try again." |
| Error — save | "Save failed. Please try again." |
| Loading — AI | "Placing…" |
| Loading — save | "Saving…" |
| Success — save | "Saved" |
| Panel section label — roles | "Signers / Roles" |
| Role add placeholder | "Role label (e.g. Buyer)" |
| Role remove confirm title | "Remove role?" |
| Role remove confirm body | "Removing '{role}' will unassign {N} field(s). This cannot be undone." |
| Role remove confirm CTA | "Remove Role" |
| Role remove cancel | "Cancel" |
| Field count badge | "{N} field{s} placed" |
Source: CONTEXT.md decisions D-04, D-05, D-12, D-13; modeled on PreparePanel copy patterns.
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none — shadcn not installed | not applicable |
| Third-party | none declared | not applicable |
No third-party registry blocks in scope for this phase.
Checker Sign-Off
- Dimension 1 Copywriting: PASS
- Dimension 2 Visuals: PASS
- Dimension 3 Color: PASS
- Dimension 4 Typography: PASS
- Dimension 5 Spacing: PASS
- Dimension 6 Registry Safety: PASS
Approval: pending