Files
red/.planning/phases/16-multi-signer-ui/16-03-PLAN.md
2026-04-03 16:16:07 -06:00

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
16-multi-signer-ui 03 execute 2
16-01
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
true
MSIGN-02
MSIGN-03
truths artifacts key_links
Active signer dropdown appears above palette when signers.length > 0
Dragged fields auto-get signerEmail of the active signer
Field boxes with signerEmail render in signer color instead of type color
Fields with no signerEmail render in existing type color (unchanged)
Fields in unassignedFieldIds set show red validation outline
Active signer defaults to first signer on load
path provides contains
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx Active signer selector, per-signer field coloring, validation overlay Active signer:
from to via pattern
FieldPlacer handleDragEnd newField.signerEmail activeSigner.email assignment on field creation signerEmail.*activeSigner
from to via pattern
FieldPlacer renderFields signer color lookup signers.find matching field.signerEmail for color signers.*find.*signerEmail.*color
Add the active signer selector dropdown and per-signer field coloring to FieldPlacer per D-05, D-06, D-07, D-08, D-09/D-10.

Purpose: MSIGN-02 (tag fields to signers) and MSIGN-03 (color-coded fields by signer). When a signer is active, every field dropped gets that signer's email. Field boxes render in the signer's color when assigned.

Output: FieldPlacer with active signer dropdown, signer-aware field creation, per-signer color rendering, and red validation overlay for unassigned fields.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/16-multi-signer-ui/16-CONTEXT.md @.planning/phases/16-multi-signer-ui/16-UI-SPEC.md @.planning/phases/16-multi-signer-ui/16-01-SUMMARY.md

From FieldPlacer (after Plan 01):

interface FieldPlacerProps {
  // ... existing props ...
  signers?: DocumentSigner[];          // from DocumentPageClient via PdfViewerWrapper
  unassignedFieldIds?: Set<string>;    // from DocumentPageClient for send-block validation
}

From schema.ts:

export interface DocumentSigner {
  email: string;
  color: string;
}
export interface SignatureFieldData {
  id: string; page: number; x: number; y: number;
  width: number; height: number;
  type?: SignatureFieldType;
  signerEmail?: string;
}

FieldPlacer existing color system (lines 70-78):

const PALETTE_TOKENS: Array<{ id: SignatureFieldType; label: string; color: string }> = [
  { id: 'client-signature', label: 'Signature',       color: '#2563eb' },
  { id: 'initials',         label: 'Initials',        color: '#7c3aed' },
  { id: 'checkbox',         label: 'Checkbox',        color: '#059669' },
  { id: 'date',             label: 'Date',            color: '#d97706' },
  { id: 'text',             label: 'Text',            color: '#64748b' },
  { id: 'agent-signature',  label: 'Agent Signature', color: '#dc2626' },
  { id: 'agent-initials',   label: 'Agent Initials',  color: '#ea580c' },
];

Color Override Rule (D-06, D-07):

  • field.signerEmail set AND matching signer in signers[] => use signer.color
  • field.signerEmail absent => use PALETTE_TOKENS type color (unchanged)

Validation overlay (D-09/D-10, UI-SPEC section 3):

  • unassignedFieldIds contains field.id => border: 2px solid #ef4444, bg: #ef444414
Task 1: Add active signer selector and signer-aware field coloring - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx (FULL FILE — 822 lines) - teressa-copeland-homes/src/lib/db/schema.ts (DocumentSigner, SignatureFieldData) - .planning/phases/16-multi-signer-ui/16-CONTEXT.md (D-05, D-06, D-07, D-08, D-09, D-10) - .planning/phases/16-multi-signer-ui/16-UI-SPEC.md (Component Inventory sections 2 and 3) teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx **1. Active signer state (inside FieldPlacer function body):** ```typescript const [activeSignerEmail, setActiveSignerEmail] = useState(null); ``` Default to first signer on load and when signers change (per D-08): ```typescript useEffect(() => { if (signers && signers.length > 0) { setActiveSignerEmail(prev => { // Keep current selection if still valid if (prev && signers.some(s => s.email === prev)) return prev; return signers[0].email; }); } else { setActiveSignerEmail(null); } }, [signers]); ```
**2. Active signer selector UI** — insert ABOVE the palette div (line ~756), ONLY when `signers && signers.length > 0`:
```tsx
{!readOnly && signers && signers.length > 0 && (
  <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    marginBottom: '8px',
    padding: '8px 12px',
    background: '#f8fafc',
    border: '1px solid #e2e8f0',
    borderRadius: '6px',
  }}>
    <span style={{ fontSize: '12px', color: '#6b7280', fontWeight: 500, whiteSpace: 'nowrap' }}>
      Active signer:
    </span>
    <select
      value={activeSignerEmail ?? ''}
      onChange={(e) => setActiveSignerEmail(e.target.value)}
      style={{
        flex: 1,
        height: '32px',
        border: '1px solid #D1D5DB',
        borderRadius: '0.375rem',
        fontSize: '0.875rem',
        padding: '0 8px',
        background: 'white',
      }}
    >
      {signers.map(s => (
        <option key={s.email} value={s.email}>
          {s.email}
        </option>
      ))}
    </select>
    {/* Color indicator dot next to the dropdown */}
    {(() => {
      const activeSigner = signers.find(s => s.email === activeSignerEmail);
      return activeSigner ? (
        <span style={{
          width: '8px', height: '8px', borderRadius: '50%',
          backgroundColor: activeSigner.color, flexShrink: 0,
        }} />
      ) : null;
    })()}
  </div>
)}
```

**3. handleDragEnd modification** — at line ~294 where `newField` is created, add `signerEmail`:
Change the `newField` construction to include:
```typescript
const newField: SignatureFieldData = {
  id: crypto.randomUUID(),
  page: currentPage,
  x: pdfX,
  y: pdfY,
  width: fieldW,
  height: fieldH,
  type: droppedType,
  ...(activeSignerEmail ? { signerEmail: activeSignerEmail } : {}),
};
```
Add `activeSignerEmail` to the useCallback dependency array of handleDragEnd.

**4. renderFields color override** — in the renderFields function (around line 581-584), replace the existing color lookup:
```typescript
// Per-type color and label
const fieldType = getFieldType(field);
const tokenMeta = PALETTE_TOKENS.find((t) => t.id === fieldType);

// Color override: signer color when signerEmail is set (D-06), else type color (D-07)
let fieldColor = tokenMeta?.color ?? '#2563eb';
if (field.signerEmail && signers) {
  const matchedSigner = signers.find(s => s.email === field.signerEmail);
  if (matchedSigner) {
    fieldColor = matchedSigner.color;
  }
}

// Validation overlay: unassigned field highlight (D-09/D-10)
const isUnassigned = unassignedFieldIds?.has(field.id) ?? false;
const fieldLabel = tokenMeta?.label ?? 'Signature';
```

**5. Field box style override for validation** — in the field box's `style` object (around line 627-653):
- Change `border` line to: `border: isUnassigned ? '2px solid #ef4444' : \`2px solid ${fieldColor}\``,
- Change `background` line to: `background: isUnassigned ? '#ef444414' : (readOnly ? \`${fieldColor}0d\` : \`${fieldColor}1a\`)`,

**6. DragOverlay ghost color** — in the DragOverlay section (around line 796-798), the ghost should also use signer color if active signer is set. Update:
```typescript
{isDraggingToken ? (() => {
  const tokenMeta = PALETTE_TOKENS.find((t) => t.id === isDraggingToken);
  const label = tokenMeta?.label ?? 'Field';
  // Ghost uses active signer color if one is selected, else type color
  let ghostColor = tokenMeta?.color ?? '#2563eb';
  if (activeSignerEmail && signers) {
    const activeSigner = signers.find(s => s.email === activeSignerEmail);
    if (activeSigner) ghostColor = activeSigner.color;
  }
  const isCheckbox = isDraggingToken === 'checkbox';
  // ... rest uses ghostColor instead of color
```
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30 - `grep -n "Active signer:" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows dropdown label - `grep -n "activeSignerEmail" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows state variable - `grep -n "signerEmail.*activeSignerEmail" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows field creation sets signerEmail - `grep -n "signers.*find.*signerEmail" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows signer color lookup in renderFields - `grep -n "#ef4444" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows validation overlay border color - `grep -n "#ef444414" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows validation overlay background - `grep -n "height.*32px" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows dropdown is 32px tall - `npx tsc --noEmit` passes - Active signer dropdown appears above palette when signers exist, hidden otherwise per D-05 - Dropdown defaults to first signer per D-08 - Dragged fields get signerEmail = active signer's email per D-06 - Fields with signerEmail render in signer color; fields without use type color per D-06/D-07 - Fields in unassignedFieldIds set show red #ef4444 outline and #ef444414 background per D-09/D-10 - TypeScript compiles cleanly - `npx tsc --noEmit` passes - Active signer selector renders above palette with correct styling - Field color follows signer color when assigned, type color when not - Validation overlay visible on unassigned fields

<success_criteria>

  • Agent can select active signer from dropdown before placing fields
  • Fields placed with active signer are tagged and colored accordingly
  • Unassigned fields show red validation highlight when send-block triggers
  • Existing behavior unchanged when no signers configured
  • TypeScript compiles with no errors </success_criteria>
After completion, create `.planning/phases/16-multi-signer-ui/16-03-SUMMARY.md`