Files
Chandler Copeland 3ab49004cf docs(16): fix spacing violations flagged by ui-checker
- Replace gap-1.5 (6px) with gap-2 (8px) in signer row
- Replace padding 6px 8px with py-1 px-2 (4px/8px) in signer row
- Raise touch target exception from 28px to 32px, name override as touch-target-compact-remove with rationale
- Rename "Add" button to "Add Signer" (verb+noun CTA per Dimension 1)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:07:45 -06:00

14 KiB
Raw Permalink Blame History

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 32px × 32px (compact panel context, not primary CTA — named override: touch-target-compact-remove)

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 #dc2626 to distinguish validation from permanent agent-red fields)
  • Error message text below Send button: text-red-600 (#dc2626), consistent with existing result.ok === false style 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 Signer 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, hover color: #DC2626, no border, cursor pointer, minimum 32px touch target via padding (touch-target-compact-remove named override)
  • Row gap: gap-2 (8px) between dot, email, and × button
  • Row background: white with 1px #E5E7EB border, border-radius: 0.375rem, padding py-1 px-2 (4px vertical, 8px horizontal)

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 Signer" button: flex row, gap: 8px

Add Signer button spec:

  • Label: "Add Signer"
  • Style: bg-gray-700 text-white hover bg-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.signerEmail is set AND a matching signer exists in documents.signers: use signer.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

  1. Agent types email in signer input
  2. Agent presses Enter or clicks "Add Signer"
  3. Input validates: non-empty, valid email format, not duplicate
  4. On valid: signer appended to list with auto-assigned color, input cleared
  5. On invalid: input border turns red (border-red-400), no addition, no persistent error message (transient only)
  6. Signers saved to DB: either on each add/remove OR on "Prepare and Send" click (planner decides, per CONTEXT.md discretion)

Signer Remove Flow

  1. Agent clicks × on a signer row
  2. Signer removed immediately from local list
  3. If any fields had that signer's email assigned: those fields lose their signerEmail and revert to type color — no confirmation dialog required (reversible action, not destructive data)
  4. Signer saved/removed from DB per same timing as add

Active Signer Selection

  1. Agent selects signer from dropdown before dragging a field
  2. All subsequent field drops auto-assign signerEmail to selected signer's email
  3. Field renders in signer color immediately on drop
  4. Agent can switch active signer and drag more fields — each new field picks up current active signer

Send Validation

  1. Agent clicks "Prepare and Send"
  2. PreparePanel counts isClientVisibleField fields with no signerEmail
  3. If count > 0 AND signers.length > 0: block call, show error, pass unassignedFieldIds to FieldPlacer for red highlight
  4. If signers.length === 0 AND client-visible fields exist: block call, show "Add at least one signer before sending."
  5. 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 NULL vs 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"
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 in DocumentPageClient — passed as prop to both PreparePanel (for signer list UI + send validation) and FieldPlacer (for active signer selector + color lookup)
  • unassignedFieldIds: Set<string> computed in DocumentPageClient (or PreparePanel) at send-click time — passed to FieldPlacer via prop
  • activeSigner: DocumentSigner | null lives in FieldPlacer (local state) — default to signers[0] on load; reset when signers prop changes
  • FieldPlacer.handleDragEnd sets newField.signerEmail = activeSigner?.email when 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