docs(16): create phase plan — 4 plans in 3 waves
This commit is contained in:
266
.planning/phases/16-multi-signer-ui/16-01-PLAN.md
Normal file
266
.planning/phases/16-multi-signer-ui/16-01-PLAN.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
phase: 16-multi-signer-ui
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx
|
||||
autonomous: true
|
||||
requirements: [MSIGN-01, MSIGN-02, MSIGN-03]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "DocumentPageClient holds signers state and threads it to both PreparePanel and PdfViewerWrapper/FieldPlacer"
|
||||
- "DocumentPageClient holds unassignedFieldIds state for send-block validation highlighting"
|
||||
- "Server page reads documents.signers from DB and passes to DocumentPageClient as initialSigners prop"
|
||||
- "PdfViewerWrapper passes signers and unassignedFieldIds through to FieldPlacer"
|
||||
artifacts:
|
||||
- path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx"
|
||||
provides: "signers state, setSigners, unassignedFieldIds state, setUnassignedFieldIds — threaded to PreparePanel and PdfViewerWrapper"
|
||||
exports: ["DocumentPageClient"]
|
||||
- path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx"
|
||||
provides: "Server-side documents.signers fetch, passed as initialSigners to DocumentPageClient"
|
||||
- path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx"
|
||||
provides: "Passes signers and unassignedFieldIds props through to PdfViewer/FieldPlacer"
|
||||
key_links:
|
||||
- from: "page.tsx"
|
||||
to: "DocumentPageClient"
|
||||
via: "initialSigners prop from db.query.documents"
|
||||
pattern: "initialSigners=.*doc\\.signers"
|
||||
- from: "DocumentPageClient"
|
||||
to: "PdfViewerWrapper"
|
||||
via: "signers and unassignedFieldIds props"
|
||||
pattern: "signers=.*unassignedFieldIds="
|
||||
- from: "DocumentPageClient"
|
||||
to: "PreparePanel"
|
||||
via: "signers, onSignersChange, unassignedFieldIds, onUnassignedFieldIdsChange props"
|
||||
pattern: "signers=.*onSignersChange="
|
||||
---
|
||||
|
||||
<objective>
|
||||
Thread multi-signer state through the component tree so PreparePanel and FieldPlacer can consume signers data in Wave 2.
|
||||
|
||||
Purpose: Foundation wiring — DocumentPageClient is the state owner for signers (from Phase 14 schema) and unassignedFieldIds (for send-block validation). Without this, Wave 2 plans cannot receive or modify signer state.
|
||||
|
||||
Output: Updated DocumentPageClient with signers + unassignedFieldIds state; server page fetching documents.signers; PdfViewerWrapper passing new props through.
|
||||
</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/STATE.md
|
||||
@.planning/phases/16-multi-signer-ui/16-CONTEXT.md
|
||||
@.planning/phases/16-multi-signer-ui/16-UI-SPEC.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||
|
||||
From src/lib/db/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;
|
||||
}
|
||||
|
||||
export function isClientVisibleField(field: SignatureFieldData): boolean;
|
||||
export function getFieldType(field: SignatureFieldData): SignatureFieldType;
|
||||
export function getSignerEmail(field: SignatureFieldData, fallbackEmail: string): string;
|
||||
```
|
||||
|
||||
From DocumentPageClient.tsx (current props):
|
||||
```typescript
|
||||
interface DocumentPageClientProps {
|
||||
docId: string;
|
||||
docStatus: string;
|
||||
defaultEmail: string;
|
||||
clientName: string;
|
||||
agentDownloadUrl?: string | null;
|
||||
signedAt?: Date | null;
|
||||
clientPropertyAddress?: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
From PdfViewerWrapper.tsx (current props):
|
||||
```typescript
|
||||
{
|
||||
docId: string;
|
||||
docStatus?: string;
|
||||
onFieldsChanged?: () => void;
|
||||
selectedFieldId?: string | null;
|
||||
textFillData?: Record<string, string>;
|
||||
onFieldSelect?: (fieldId: string | null) => void;
|
||||
onFieldValueChange?: (fieldId: string, value: string) => void;
|
||||
aiPlacementKey?: number;
|
||||
}
|
||||
```
|
||||
|
||||
From FieldPlacer.tsx (current props — FieldPlacerProps interface at line 155):
|
||||
```typescript
|
||||
interface FieldPlacerProps {
|
||||
docId: string;
|
||||
pageInfo: PageInfo | null;
|
||||
currentPage: number;
|
||||
children: React.ReactNode;
|
||||
readOnly?: boolean;
|
||||
onFieldsChanged?: () => void;
|
||||
selectedFieldId?: string | null;
|
||||
textFillData?: Record<string, string>;
|
||||
onFieldSelect?: (fieldId: string | null) => void;
|
||||
onFieldValueChange?: (fieldId: string, value: string) => void;
|
||||
aiPlacementKey?: number;
|
||||
}
|
||||
```
|
||||
|
||||
From PreparePanel.tsx (current props):
|
||||
```typescript
|
||||
interface PreparePanelProps {
|
||||
docId: string;
|
||||
defaultEmail: string;
|
||||
clientName: string;
|
||||
currentStatus: string;
|
||||
agentDownloadUrl?: string | null;
|
||||
signedAt?: Date | null;
|
||||
clientPropertyAddress?: string | null;
|
||||
previewToken: string | null;
|
||||
onPreviewTokenChange: (token: string | null) => void;
|
||||
textFillData: Record<string, string>;
|
||||
selectedFieldId: string | null;
|
||||
onQuickFill: (fieldId: string, value: string) => void;
|
||||
onAiAutoPlace: () => Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
From server page.tsx:
|
||||
```typescript
|
||||
// doc fetched via db.query.documents.findFirst — has all document columns including signers
|
||||
const doc = await db.query.documents.findFirst({ where: eq(documents.id, docId) });
|
||||
// doc.signers is DocumentSigner[] | null (JSONB column from Phase 14)
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add signers prop to server page and thread state through DocumentPageClient</name>
|
||||
<read_first>
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx
|
||||
- teressa-copeland-homes/src/lib/db/schema.ts (for DocumentSigner interface)
|
||||
- .planning/phases/16-multi-signer-ui/16-CONTEXT.md
|
||||
- .planning/phases/16-multi-signer-ui/16-UI-SPEC.md
|
||||
</read_first>
|
||||
<files>
|
||||
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx,
|
||||
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx
|
||||
</files>
|
||||
<action>
|
||||
**server page.tsx:**
|
||||
- `doc` is already fetched via `db.query.documents.findFirst` which returns all columns including `signers`. Pass `doc.signers` to DocumentPageClient as `initialSigners`:
|
||||
```
|
||||
initialSigners={doc.signers ?? []}
|
||||
```
|
||||
|
||||
**DocumentPageClient.tsx:**
|
||||
- Import `DocumentSigner` from `@/lib/db/schema`
|
||||
- Add `initialSigners: DocumentSigner[]` to `DocumentPageClientProps`
|
||||
- Add state: `const [signers, setSigners] = useState<DocumentSigner[]>(initialSigners);`
|
||||
- Add state: `const [unassignedFieldIds, setUnassignedFieldIds] = useState<Set<string>>(new Set());`
|
||||
- Pass to PreparePanel: `signers={signers}`, `onSignersChange={setSigners}`, `unassignedFieldIds={unassignedFieldIds}`, `onUnassignedFieldIdsChange={setUnassignedFieldIds}`
|
||||
- Pass to PdfViewerWrapper: `signers={signers}`, `unassignedFieldIds={unassignedFieldIds}`
|
||||
- No other changes to existing props or callbacks
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -n "initialSigners" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx` shows prop being passed
|
||||
- `grep -n "DocumentSigner" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx` shows import
|
||||
- `grep -n "signers={signers}" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx` shows props threaded to both PreparePanel and PdfViewerWrapper
|
||||
- `grep -n "unassignedFieldIds" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx` shows state and prop threading
|
||||
- `npx tsc --noEmit` passes
|
||||
</acceptance_criteria>
|
||||
<done>DocumentPageClient has signers + unassignedFieldIds state initialized from server; props threaded to PreparePanel and PdfViewerWrapper; TypeScript compiles cleanly</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Thread signers and unassignedFieldIds through PdfViewerWrapper to FieldPlacer</name>
|
||||
<read_first>
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx (to understand its prop interface — it wraps FieldPlacer)
|
||||
- teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx (lines 155-167 for FieldPlacerProps)
|
||||
- teressa-copeland-homes/src/lib/db/schema.ts (for DocumentSigner type)
|
||||
- .planning/phases/16-multi-signer-ui/16-UI-SPEC.md
|
||||
</read_first>
|
||||
<files>
|
||||
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx,
|
||||
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewer.tsx,
|
||||
teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx
|
||||
</files>
|
||||
<action>
|
||||
**PdfViewerWrapper.tsx:**
|
||||
- Import `DocumentSigner` from `@/lib/db/schema`
|
||||
- Add optional props: `signers?: DocumentSigner[]`, `unassignedFieldIds?: Set<string>`
|
||||
- Pass both through to `<PdfViewer signers={signers} unassignedFieldIds={unassignedFieldIds} ... />`
|
||||
|
||||
**PdfViewer.tsx:**
|
||||
- Add same optional props to PdfViewer's prop interface: `signers?: DocumentSigner[]`, `unassignedFieldIds?: Set<string>`
|
||||
- Pass both through to `<FieldPlacer signers={signers} unassignedFieldIds={unassignedFieldIds} ... />`
|
||||
|
||||
**FieldPlacer.tsx:**
|
||||
- Import `DocumentSigner` from `@/lib/db/schema`
|
||||
- Add to FieldPlacerProps: `signers?: DocumentSigner[]`, `unassignedFieldIds?: Set<string>`
|
||||
- Destructure in component function signature (default `signers = []`, `unassignedFieldIds = new Set()`)
|
||||
- Do NOT implement rendering changes yet (that is Plan 03) — just accept the props
|
||||
|
||||
All three files: add the two new optional props and pass them through. No rendering or behavioral changes.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `grep -n "signers" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PdfViewerWrapper.tsx` shows prop accepted and passed through
|
||||
- `grep -n "signers" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows signers in FieldPlacerProps
|
||||
- `grep -n "unassignedFieldIds" teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` shows unassignedFieldIds in FieldPlacerProps
|
||||
- `npx tsc --noEmit` passes
|
||||
</acceptance_criteria>
|
||||
<done>signers and unassignedFieldIds props flow from DocumentPageClient through PdfViewerWrapper and PdfViewer into FieldPlacer; TypeScript compiles cleanly</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` passes with no errors
|
||||
- `grep -rn "signers" teressa-copeland-homes/src/app/portal/(protected)/documents/` confirms signers threaded through page.tsx -> DocumentPageClient -> PdfViewerWrapper -> PdfViewer -> FieldPlacer AND DocumentPageClient -> PreparePanel
|
||||
- `grep -rn "unassignedFieldIds" teressa-copeland-homes/src/app/portal/(protected)/documents/` confirms unassignedFieldIds threaded through the same paths
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- DocumentPageClient has `signers` and `unassignedFieldIds` state
|
||||
- Server page passes `initialSigners` from `doc.signers`
|
||||
- Both PreparePanel and FieldPlacer (via PdfViewerWrapper chain) receive signers and unassignedFieldIds as props
|
||||
- TypeScript compiles with no errors
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/16-multi-signer-ui/16-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user