Files

281 lines
12 KiB
Markdown
Raw Permalink Normal View History

---
phase: 16-multi-signer-ui
plan: 03
type: execute
wave: 2
depends_on: ["16-01"]
files_modified:
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
autonomous: true
requirements: [MSIGN-02, MSIGN-03]
must_haves:
truths:
- "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"
artifacts:
- path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx"
provides: "Active signer selector, per-signer field coloring, validation overlay"
contains: "Active signer:"
key_links:
- from: "FieldPlacer handleDragEnd"
to: "newField.signerEmail"
via: "activeSigner.email assignment on field creation"
pattern: "signerEmail.*activeSigner"
- from: "FieldPlacer renderFields"
to: "signer color lookup"
via: "signers.find matching field.signerEmail for color"
pattern: "signers.*find.*signerEmail.*color"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<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
<interfaces>
<!-- From Plan 01: signers and unassignedFieldIds are now props on FieldPlacer -->
From FieldPlacer (after Plan 01):
```typescript
interface FieldPlacerProps {
// ... existing props ...
signers?: DocumentSigner[]; // from DocumentPageClient via PdfViewerWrapper
unassignedFieldIds?: Set<string>; // from DocumentPageClient for send-block validation
}
```
From schema.ts:
```typescript
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):
```typescript
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
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add active signer selector and signer-aware field coloring</name>
<read_first>
- 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)
</read_first>
<files>teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx</files>
<action>
**1. Active signer state (inside FieldPlacer function body):**
```typescript
const [activeSignerEmail, setActiveSignerEmail] = useState<string | null>(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
```
</action>
<verify>
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<acceptance_criteria>
- `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
</acceptance_criteria>
<done>
- 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
</done>
</task>
</tasks>
<verification>
- `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
</verification>
<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>
<output>
After completion, create `.planning/phases/16-multi-signer-ui/16-03-SUMMARY.md`
</output>