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

324 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 16
slug: multi-signer-ui
status: draft
shadcn_initialized: false
preset: none
created: 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