docs(16): create phase plan — 4 plans in 3 waves
This commit is contained in:
280
.planning/phases/16-multi-signer-ui/16-03-PLAN.md
Normal file
280
.planning/phases/16-multi-signer-ui/16-03-PLAN.md
Normal file
@@ -0,0 +1,280 @@
|
||||
---
|
||||
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>
|
||||
Reference in New Issue
Block a user