12 KiB
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 |
|
|
true |
|
|
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.mdFrom 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
**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>