324 lines
14 KiB
Markdown
324 lines
14 KiB
Markdown
---
|
||
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 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 `#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 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 28px touch target via padding
|
||
- Row gap: 6px between dot, email, and × button (`gap-1.5` ≈ 6px)
|
||
- Row background: white with 1px `#E5E7EB` border, `border-radius: 0.375rem`, padding `6px 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-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"
|
||
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 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
|