14 KiB
phase, slug, status, shadcn_initialized, preset, created
| phase | slug | status | shadcn_initialized | preset | created |
|---|---|---|---|---|---|
| 16 | multi-signer-ui | draft | false | none | 2026-04-03 |
Phase 16 — UI Design Contract
Visual and interaction contract for Phase 16: Multi-Signer UI. Generated by gsd-ui-researcher, verified by gsd-ui-checker.
Design System
| Property | Value |
|---|---|
| Tool | none — fully manual Tailwind CSS |
| Preset | not applicable |
| Component library | none (custom components only) |
| Icon library | none (inline SVG or Unicode characters only, per existing patterns) |
| Font | Geist Sans (sans-serif body), Cormorant Garamond (display, marketing only) |
Source: globals.css CSS custom properties + existing component inspection.
No shadcn gate required: components.json is absent. Stack is Next.js 15 App Router + Tailwind CSS v4 (@tailwindcss/postcss). All existing portal components are hand-crafted with Tailwind utility classes and inline style={} objects. This phase continues that pattern.
Spacing Scale
Declared values (must be multiples of 4):
| Token | Value | Usage |
|---|---|---|
| xs | 4px | Icon gaps, dot-to-label gap in signer rows, badge inner padding |
| sm | 8px | Compact element spacing, signer row internal gap, input padding |
| md | 16px | Default element spacing, panel section gaps, field box padding |
| lg | 24px | Section padding within PreparePanel, table cell padding |
| xl | 32px | Layout gaps between major panel sections |
| 2xl | 48px | Major section breaks (not used within PreparePanel) |
| 3xl | 64px | Page-level spacing (not applicable to portal panels) |
Exceptions:
- Colored signer dot: 8px × 8px circle (sm × sm) — consistent with existing badge geometry
- Active signer dropdown height: 32px (matches existing PreparePanel button height convention of
py-1.5= 6px top + 6px bottom + 20px line ≈ 32px) - Signer list email input height: 32px (
py-1.5 px-2) to match textarea style in existing PreparePanel - Touch targets for remove (×) buttons: minimum 28px × 28px (compact panel context, not primary CTA)
Source: measured from existing PreparePanel.tsx button styles (py-2 px-4 = 8px + 8px vertical, matching md token) and DocumentsTable.tsx cell padding (0.875rem 1.5rem).
Typography
| Role | Size | Weight | Line Height |
|---|---|---|---|
| Body | 14px (0.875rem) | 400 (regular) | 1.5 |
| Label | 12px (0.75rem) | 500 (medium) | 1.4 |
| Heading | 16px (1rem) | 600 (semibold) | 1.3 |
| Badge / caption | 12px (0.75rem) | 500 (medium) | 1.0 |
Source: extracted directly from existing components.
- PreparePanel
<h2>:font-semibold(600) — maps to Heading - PreparePanel body labels:
text-sm= 14px regular — maps to Body - PreparePanel secondary labels (
text-xs text-gray-400): 12px medium — maps to Label - DocumentsTable
<th>:0.75rem font-weight:600 uppercase letter-spacing:0.05em— maps to Label (uppercase table headers are an established portal pattern) - StatusBadge:
text-xs font-medium= 12px 500 — maps to Badge
Only 2 weights used throughout the portal: 400 (regular) and 600 (semibold). Weight 500 is used for table data cells and badge text — treat as part of the semibold family for this contract (no additional weight token needed).
Color
| Role | Value | Usage |
|---|---|---|
| Dominant (60%) | #FAF9F7 (cream) |
Page background, panel backgrounds |
| Secondary (30%) | #F9FAFB / #F3F4F6 |
Card surfaces, table rows, input backgrounds, panel wrapper (bg-gray-100) |
| Accent (10%) | #1B2B4B (navy) |
Primary interactive text, document links, Download button, section headings |
| Destructive | #DC2626 (red-600) |
Remove signer (×) button, send-block validation error text, unassigned field outline |
Accent reserved for: document link hover, primary heading text, Download Signed PDF button background. NOT used for signer colors or type-palette colors.
Signer Color Palette (locked — source: CONTEXT.md D-01)
Auto-assigned in order when signers are added. Stored in documents.signers[].color.
| Signer Index | Color | Hex |
|---|---|---|
| 0 (first) | Indigo | #6366f1 |
| 1 (second) | Rose | #f43f5e |
| 2 (third) | Emerald | #10b981 |
| 3 (fourth) | Amber | #f59e0b |
| 4+ | Cycles back | same order |
Field Type Color Palette (existing — source: FieldPlacer.tsx PALETTE_TOKENS)
These colors are UNCHANGED. They apply only when a field has no signerEmail.
| Field Type | Color | Hex |
|---|---|---|
| client-signature | Blue | #2563eb |
| initials | Purple | #7c3aed |
| checkbox | Green | #059669 |
| date | Amber | #d97706 |
| text | Slate | #64748b |
| agent-signature | Red | #dc2626 |
| agent-initials | Orange | #ea580c |
Color Override Rule (locked — source: CONTEXT.md D-06, D-07)
When field.signerEmail is set: field box border, background tint, and drag ghost use the signer color from documents.signers. When field.signerEmail is absent (agent-owned fields or untagged fields): use existing type color from PALETTE_TOKENS.
Validation State Color
- Unassigned client-facing field outline:
2px solid #ef4444(red-500, slightly lighter than destructive#dc2626to distinguish validation from permanent agent-red fields) - Error message text below Send button:
text-red-600(#dc2626), consistent with existingresult.ok === falsestyle in PreparePanel
Source: CONTEXT.md D-09, D-10; existing PreparePanel error pattern.
Component Inventory
This section catalogs the new and modified UI elements for Phase 16.
1. PreparePanel — Signer List Section (NEW)
Location: Between the "Text field fill" section and the "AI Auto-place Fields" button. Only visible when currentStatus === 'Draft'.
Structure:
[ Prepare Document heading ]
[ Recipients section — existing ]
[ Text field fill — existing ]
──────────────────────────────
[ Signers section — NEW ]
label: "Signers"
[ email input ] [ Add button ]
helper: "Each signer receives their own signing link."
[ signer list ]
signer row: ● indigo dot email@example.com [×]
signer row: ● rose dot email@example.com [×]
──────────────────────────────
[ AI Auto-place Fields — existing ]
[ Preview — existing ]
[ Prepare and Send — existing ]
[ error/result message — existing ]
Signer Row spec:
- Colored dot: 8px × 8px circle,
background: signerColor,border-radius: 50%,flex-shrink: 0 - Email text: 14px regular,
color: #374151(gray-700),flex: 1,truncate - Remove button (×): 16px text,
color: #9CA3AF, hovercolor: #DC2626, no border, cursor pointer, minimum 28px touch target via padding - Row gap: 6px between dot, email, and × button (
gap-1.5≈ 6px) - Row background: white with 1px
#E5E7EBborder,border-radius: 0.375rem, padding6px 8px
Email input spec:
- Full-width text input,
border: 1px solid #D1D5DB,border-radius: 0.375rem,padding: 6px 8px,font-size: 0.875rem - Placeholder:
"signer@example.com" - Inline with "Add" button: flex row,
gap: 8px
Add button spec:
- Label: "Add"
- Style:
bg-gray-700 text-whitehoverbg-gray-800,px-3 py-1.5,border-radius: 0.375rem,font-size: 0.875rem font-weight: 500 - Disabled when input is empty or contains an already-added email
Section label: text-sm font-medium text-gray-700 mb-1 — matches existing label pattern
2. FieldPlacer — Active Signer Selector (NEW)
Location: Top of FieldPlacer, above the field type palette. Visible ONLY when signers.length > 0.
Structure:
[ Active signer: ▼ ● indigo first@example.com ]
[ palette tokens — existing ]
[ PDF canvas drop zone — existing ]
Dropdown spec:
- Native
<select>element or styled div-based selector (planner decides) - Label inline:
text-xs text-gray-500"Active signer:" - Selected option renders: colored dot (8px circle) + signer email truncated
- Width: full-width of palette column
- Height: 32px,
border: 1px solid #D1D5DB,border-radius: 0.375rem,font-size: 0.875rem - Default value: first signer in
documents.signers[]on component load (D-08)
3. FieldPlacer — Field Box Color Override (MODIFIED)
Existing field boxes already render with a colored border and background tint (${color}14). The only change is the color source:
- If
field.signerEmailis set AND a matching signer exists indocuments.signers: usesigner.color - Otherwise: use
PALETTE_TOKENS.find(t => t.id === fieldType)?.color(unchanged)
Validation overlay (NEW): When a field ID is in unassignedFieldIds set (passed via prop):
- Override border:
2px solid #ef4444 - Override background tint:
#ef444414(red at ~8% opacity) - This is a pure render-time override, no new state in FieldPlacer
4. PreparePanel — Send-Block Validation (MODIFIED)
The existing "Prepare and Send" button already has a disabled state. Phase 16 adds a pre-send validation check:
Blocking condition: Any isClientVisibleField with no signerEmail AND signers.length > 0
Error message (inline, below Send button):
text-sm text-red-600— matches existing error pattern- Copy:
"{N} field(s) need a signer assigned before sending."— see Copywriting Contract
No-signers blocking condition: signers.length === 0 AND client-visible fields exist:
- Copy:
"Add at least one signer before sending."(D-04)
5. Dashboard — N/M Signed Badge (NEW)
Location: Status column of DocumentsTable, appended after the existing StatusBadge when signers is non-empty AND status is "Sent".
Badge spec:
- Style:
inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5 - Text:
"N/M signed"— e.g.,"1/2 signed" - Only shown for multi-signer documents (D-12: single-signer shows nothing)
- Not shown for status "Signed" (D-13: existing Signed badge sufficient)
Interaction Contract
Signer Add Flow
- Agent types email in signer input
- Agent presses Enter or clicks "Add"
- Input validates: non-empty, valid email format, not duplicate
- On valid: signer appended to list with auto-assigned color, input cleared
- On invalid: input border turns red (
border-red-400), no addition, no persistent error message (transient only) - Signers saved to DB: either on each add/remove OR on "Prepare and Send" click (planner decides, per CONTEXT.md discretion)
Signer Remove Flow
- Agent clicks × on a signer row
- Signer removed immediately from local list
- If any fields had that signer's email assigned: those fields lose their
signerEmailand revert to type color — no confirmation dialog required (reversible action, not destructive data) - Signer saved/removed from DB per same timing as add
Active Signer Selection
- Agent selects signer from dropdown before dragging a field
- All subsequent field drops auto-assign
signerEmailto selected signer's email - Field renders in signer color immediately on drop
- Agent can switch active signer and drag more fields — each new field picks up current active signer
Send Validation
- Agent clicks "Prepare and Send"
- PreparePanel counts
isClientVisibleFieldfields with nosignerEmail - If count > 0 AND
signers.length > 0: block call, show error, passunassignedFieldIdsto FieldPlacer for red highlight - If
signers.length === 0AND client-visible fields exist: block call, show "Add at least one signer before sending." - Red highlights clear as agent assigns signers to each flagged field (reactive, no manual dismiss)
N/M Signed Badge
- Computed server-side by counting
signingTokens WHERE documentId = X AND usedAt IS NOT NULLvs total tokens - Refreshes on page load (server component — no client polling)
Copywriting Contract
| Element | Copy |
|---|---|
| Primary CTA | "Prepare and Send" (existing — unchanged) |
| Signer section label | "Signers" |
| Signer input placeholder | "signer@example.com" |
| Add signer button | "Add" |
| Signer helper text | "Each signer receives their own signing link." |
| Active signer dropdown label | "Active signer:" |
| Empty signer list (no signers added) | "No signers added yet." (shown as placeholder row in muted text, OR omitted entirely — planner decides) |
| Send-block: unassigned fields | "{N} field(s) need a signer assigned before sending." |
| Send-block: no signers at all | "Add at least one signer before sending." |
| N/M badge | "{N}/{M} signed" (e.g., "1/2 signed") |
| Signer email validation error | (transient red border only — no error text for brief validation) |
| Duplicate email error | "That email is already in the signer list." |
Source: CONTEXT.md D-03, D-04, D-09, D-11. Confirmed against existing PreparePanel error copy style (text-sm text-red-600).
No destructive confirmation dialogs in this phase. Removing a signer is reversible (add them back), so no confirm dialog is needed.
State Architecture Notes
These are not visual specs but inform the executor's component wiring.
signers: DocumentSigner[]lives inDocumentPageClient— passed as prop to bothPreparePanel(for signer list UI + send validation) andFieldPlacer(for active signer selector + color lookup)unassignedFieldIds: Set<string>computed inDocumentPageClient(orPreparePanel) at send-click time — passed toFieldPlacervia propactiveSigner: DocumentSigner | nulllives inFieldPlacer(local state) — default tosigners[0]on load; reset whensignersprop changesFieldPlacer.handleDragEndsetsnewField.signerEmail = activeSigner?.emailwhen placing a field
Source: CONTEXT.md code_context section, DocumentPageClient.tsx existing patterns.
Registry Safety
| Registry | Blocks Used | Safety Gate |
|---|---|---|
| shadcn official | none | not applicable — shadcn not initialized |
| third-party | none | not applicable |
No third-party component registries. All components are handwritten following existing project patterns.
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