docs(08): create phase 8 plan files
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
---
|
||||
phase: 08-schema-foundation-and-signing-page-safety
|
||||
plan: "01"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- teressa-copeland-homes/src/lib/db/schema.ts
|
||||
- teressa-copeland-homes/drizzle/0006_type_discriminant.sql
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FIELD-01
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "SignatureFieldData interface has an optional 'type' field accepting all six SignatureFieldType literals"
|
||||
- "getFieldType() helper exported from schema.ts returns field.type ?? 'client-signature' — never returns undefined"
|
||||
- "isClientVisibleField() pure function exported from schema.ts returns false for agent-signature, true for all others"
|
||||
- "Drizzle meta snapshot is in sync with schema.ts (npm run db:generate runs clean)"
|
||||
- "Existing documents with no 'type' on their JSONB fields continue to work (backward-compat)"
|
||||
artifacts:
|
||||
- path: "teressa-copeland-homes/src/lib/db/schema.ts"
|
||||
provides: "SignatureFieldType union, extended SignatureFieldData interface, getFieldType helper, isClientVisibleField predicate"
|
||||
contains: "SignatureFieldType"
|
||||
- path: "teressa-copeland-homes/drizzle/0006_type_discriminant.sql"
|
||||
provides: "Drizzle migration snapshot sync (may be empty SQL — that is correct)"
|
||||
key_links:
|
||||
- from: "teressa-copeland-homes/src/lib/db/schema.ts"
|
||||
to: "teressa-copeland-homes/src/app/api/sign/[token]/route.ts"
|
||||
via: "isClientVisibleField import (added in plan 08-02)"
|
||||
pattern: "isClientVisibleField"
|
||||
- from: "teressa-copeland-homes/src/lib/db/schema.ts"
|
||||
to: "teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx"
|
||||
via: "getFieldType import (added in plan 08-02)"
|
||||
pattern: "getFieldType"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Extend the SignatureFieldData TypeScript interface with a type discriminant and export two pure helper functions that all field-reading code will use. Run the Drizzle migration to keep the meta snapshot current.
|
||||
|
||||
Purpose: Every phase 9-13 feature that places a new field type on a document depends on this discriminant. Without it, the signing page treats every field as a required client-signature, and an agent-signature field would surface as an unsigned required field to the client. This plan establishes the single source of truth for field type resolution.
|
||||
|
||||
Output: Extended schema.ts with SignatureFieldType, updated SignatureFieldData interface, getFieldType() and isClientVisibleField() helpers, and migration file 0006.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/Users/ccopeland/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/08-schema-foundation-and-signing-page-safety/08-RESEARCH.md
|
||||
|
||||
IMPORTANT: Read AGENTS.md in the Next.js app root before writing any code. The app uses a version of Next.js that may differ from training data.
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- Current SignatureFieldData (to be extended) — from src/lib/db/schema.ts -->
|
||||
```typescript
|
||||
export interface SignatureFieldData {
|
||||
id: string;
|
||||
page: number; // 1-indexed
|
||||
x: number; // PDF user space, bottom-left origin, points
|
||||
y: number; // PDF user space, bottom-left origin, points
|
||||
width: number; // PDF points (default: 144 — 2 inches)
|
||||
height: number; // PDF points (default: 36 — 0.5 inches)
|
||||
}
|
||||
|
||||
// Current JSONB column annotation (documents table):
|
||||
signatureFields: jsonb("signature_fields").$type<SignatureFieldData[]>(),
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Extend SignatureFieldData in schema.ts with type discriminant and helper exports</name>
|
||||
<files>teressa-copeland-homes/src/lib/db/schema.ts</files>
|
||||
<action>
|
||||
Add the following to schema.ts BEFORE the `SignatureFieldData` interface declaration (i.e., above line 4):
|
||||
|
||||
```typescript
|
||||
export type SignatureFieldType =
|
||||
| 'client-signature'
|
||||
| 'initials'
|
||||
| 'text'
|
||||
| 'checkbox'
|
||||
| 'date'
|
||||
| 'agent-signature';
|
||||
```
|
||||
|
||||
Then update the existing `SignatureFieldData` interface by adding one optional field after `height`:
|
||||
|
||||
```typescript
|
||||
type?: SignatureFieldType; // Optional — v1.0 documents have no type; fallback = 'client-signature'
|
||||
```
|
||||
|
||||
Then add two exported helper functions immediately after the `SignatureFieldData` interface (before the `users` table declaration):
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Safe field type reader — always returns a SignatureFieldType, never undefined.
|
||||
* v1.0 documents have no `type` on their JSONB fields; this coalesces to 'client-signature'.
|
||||
* ALWAYS use this instead of reading field.type directly.
|
||||
*/
|
||||
export function getFieldType(field: SignatureFieldData): SignatureFieldType {
|
||||
return field.type ?? 'client-signature';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true for field types that should be visible in the client signing session.
|
||||
* agent-signature fields are embedded during document preparation and must never
|
||||
* surface to the client as required unsigned fields.
|
||||
*/
|
||||
export function isClientVisibleField(field: SignatureFieldData): boolean {
|
||||
return getFieldType(field) !== 'agent-signature';
|
||||
}
|
||||
```
|
||||
|
||||
Do NOT change the `.$type<SignatureFieldData[]>()` annotation on the `signatureFields` column — it already annotates the JSONB column with the correct type and will automatically reflect the updated interface.
|
||||
|
||||
Do NOT touch any other tables, enums, or imports. The rest of schema.ts is unchanged.
|
||||
|
||||
Why type is optional: all v1.0 documents in the database have JSONB field objects with no `type` property. Making it required would break TypeScript on every FieldPlacer call site that constructs a SignatureFieldData without a type. The `getFieldType()` fallback handles the coercion centrally.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- schema.ts exports SignatureFieldType, getFieldType, and isClientVisibleField
|
||||
- TypeScript compilation passes with no errors related to schema.ts
|
||||
- getFieldType({ id: 'x', page: 1, x: 0, y: 0, width: 144, height: 36 }) returns 'client-signature' (no type property = backward compat)
|
||||
- getFieldType({ ..., type: 'agent-signature' }) returns 'agent-signature'
|
||||
- isClientVisibleField({ ..., type: 'agent-signature' }) returns false
|
||||
- isClientVisibleField({ ..., type: 'client-signature' }) returns true
|
||||
- isClientVisibleField({ id: 'x', page: 1, x: 0, y: 0, width: 144, height: 36 }) returns true (no type = backward compat)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Run Drizzle migration to sync meta snapshot</name>
|
||||
<files>teressa-copeland-homes/drizzle/0006_type_discriminant.sql</files>
|
||||
<action>
|
||||
Run the migration generator from the app directory:
|
||||
|
||||
```bash
|
||||
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:generate
|
||||
```
|
||||
|
||||
This produces `drizzle/0006_*.sql`. The SQL content will be empty or contain only a comment — that is CORRECT and expected. The `.$type<T>()` annotation on the JSONB column is a TypeScript-only change and generates no DDL.
|
||||
|
||||
After generation, rename the file to have a predictable name if the generated name is not `0006_type_discriminant.sql`:
|
||||
|
||||
```bash
|
||||
cd /Users/ccopeland/temp/red/teressa-copeland-homes && ls drizzle/0006_*.sql
|
||||
```
|
||||
|
||||
If the file was generated with a random name (e.g., `0006_milky_black_cat.sql`), rename it:
|
||||
```bash
|
||||
mv drizzle/0006_*.sql drizzle/0006_type_discriminant.sql
|
||||
```
|
||||
|
||||
Then apply the migration to the local database:
|
||||
|
||||
```bash
|
||||
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run db:migrate
|
||||
```
|
||||
|
||||
If `db:migrate` fails because the database is not running, note the failure in the summary but DO NOT block plan completion — the TypeScript changes are the deliverable. The migration file itself is what must exist and be committed.
|
||||
|
||||
Why run even an empty migration: Drizzle stores a JSON snapshot of the TypeScript schema in `drizzle/meta/`. Skipping `db:generate` after a schema.ts change causes the next `db:generate` run (Phase 9 or later) to produce an incorrect diff that re-adds or re-creates things that already exist. The snapshot must stay in sync.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>ls /Users/ccopeland/temp/red/teressa-copeland-homes/drizzle/0006_*.sql 2>/dev/null && echo "Migration file exists" || echo "MISSING"</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- drizzle/0006_type_discriminant.sql (or 0006_*.sql) exists
|
||||
- File content is empty SQL or a comment — no DDL statements (correct)
|
||||
- drizzle/meta/ directory has been updated by db:generate
|
||||
- npm run db:migrate ran without error (or was noted as pending if DB unavailable)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Run TypeScript compilation to confirm no type errors introduced:
|
||||
|
||||
```bash
|
||||
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit
|
||||
```
|
||||
|
||||
Confirm all three exports are present:
|
||||
|
||||
```bash
|
||||
grep -n "SignatureFieldType\|getFieldType\|isClientVisibleField" /Users/ccopeland/temp/red/teressa-copeland-homes/src/lib/db/schema.ts
|
||||
```
|
||||
|
||||
Confirm migration file exists:
|
||||
|
||||
```bash
|
||||
ls /Users/ccopeland/temp/red/teressa-copeland-homes/drizzle/0006_*.sql
|
||||
```
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- schema.ts exports: SignatureFieldType (union type), updated SignatureFieldData (with optional type field), getFieldType() (backward-compat fallback), isClientVisibleField() (pure predicate)
|
||||
- TypeScript compilation passes: npx tsc --noEmit exits 0
|
||||
- drizzle/0006_*.sql exists and was generated by db:generate (not hand-written)
|
||||
- All existing v1.0 SignatureFieldData objects (no type property) continue to resolve as 'client-signature' via getFieldType()
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/08-schema-foundation-and-signing-page-safety/08-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,344 @@
|
||||
---
|
||||
phase: 08-schema-foundation-and-signing-page-safety
|
||||
plan: "02"
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on:
|
||||
- "08-01"
|
||||
files_modified:
|
||||
- teressa-copeland-homes/src/app/api/sign/[token]/route.ts
|
||||
- teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx
|
||||
autonomous: false
|
||||
requirements:
|
||||
- FIELD-01
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "GET /api/sign/[token] never returns agent-signature fields to the client — they are filtered server-side before the JSON response"
|
||||
- "SigningPageClient.handleFieldClick does not open the signature modal for non-client-signature field types"
|
||||
- "SigningPageClient.handleSubmit counts only client-signature fields in its completeness check — submit is not blocked by non-signable fields"
|
||||
- "SigningProgressBar receives the correct total (client-signature fields only, not all fields)"
|
||||
- "Existing v1.0 signing sessions are unaffected — all existing fields have no type property and read as client-signature"
|
||||
- "The POST /api/sign/[token] route is NOT modified — signature embedding pipeline is unchanged"
|
||||
artifacts:
|
||||
- path: "teressa-copeland-homes/src/app/api/sign/[token]/route.ts"
|
||||
provides: "Server-side agent-signature filter in GET response"
|
||||
contains: "isClientVisibleField"
|
||||
- path: "teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx"
|
||||
provides: "Type-guarded handleFieldClick, type-filtered handleSubmit, correct SigningProgressBar total"
|
||||
contains: "getFieldType"
|
||||
key_links:
|
||||
- from: "teressa-copeland-homes/src/app/api/sign/[token]/route.ts (GET)"
|
||||
to: "client signing page JSON response"
|
||||
via: ".filter(isClientVisibleField) on doc.signatureFields"
|
||||
pattern: "isClientVisibleField"
|
||||
- from: "teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx"
|
||||
to: "signature modal"
|
||||
via: "handleFieldClick type guard — getFieldType(field) !== 'client-signature' returns early"
|
||||
pattern: "getFieldType"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Apply the server-side field filter in the signing GET route and add type-branching guards to SigningPageClient. This is the security-critical half of Phase 8 — the schema change in 08-01 defines the types, this plan enforces them at the boundaries.
|
||||
|
||||
Purpose: Without the server-side filter, agent-signature field coordinates are leaked to the client network response. Without the client-side guard, clicking a non-client-signature field overlay would open the signature modal (a broken UX that will only get worse as Phase 10 adds more field types). Both guards must ship together.
|
||||
|
||||
Output: Modified route.ts (GET filter), modified SigningPageClient.tsx (type guards), human verification checkpoint.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/Users/ccopeland/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/08-schema-foundation-and-signing-page-safety/08-RESEARCH.md
|
||||
@.planning/phases/08-schema-foundation-and-signing-page-safety/08-01-SUMMARY.md
|
||||
|
||||
IMPORTANT: Read AGENTS.md in the Next.js app root before writing any code.
|
||||
</context>
|
||||
|
||||
<interfaces>
|
||||
<!-- From 08-01: helpers exported from schema.ts -->
|
||||
```typescript
|
||||
export function getFieldType(field: SignatureFieldData): SignatureFieldType {
|
||||
return field.type ?? 'client-signature';
|
||||
}
|
||||
|
||||
export function isClientVisibleField(field: SignatureFieldData): boolean {
|
||||
return getFieldType(field) !== 'agent-signature';
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Current unguarded line in GET /api/sign/[token] route.ts (line 88) -->
|
||||
```typescript
|
||||
// CURRENT — returns ALL fields including agent-signature
|
||||
signatureFields: doc.signatureFields ?? [],
|
||||
|
||||
// TARGET — filters before response
|
||||
signatureFields: (doc.signatureFields ?? []).filter(isClientVisibleField),
|
||||
```
|
||||
|
||||
<!-- Current handleFieldClick in SigningPageClient.tsx (lines 85-92) — opens modal for ANY field -->
|
||||
```typescript
|
||||
const handleFieldClick = useCallback(
|
||||
(fieldId: string) => {
|
||||
if (signedFields.has(fieldId)) return;
|
||||
setActiveFieldId(fieldId);
|
||||
setModalOpen(true);
|
||||
},
|
||||
[signedFields]
|
||||
);
|
||||
```
|
||||
|
||||
<!-- Current handleSubmit count in SigningPageClient.tsx (line 129) — counts ALL fields -->
|
||||
```typescript
|
||||
if (signedFields.size < signatureFields.length || submitting) return;
|
||||
```
|
||||
|
||||
<!-- Current SigningProgressBar total (line 328) — counts ALL fields -->
|
||||
```typescript
|
||||
<SigningProgressBar
|
||||
total={signatureFields.length}
|
||||
...
|
||||
```
|
||||
</interfaces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add isClientVisibleField filter to GET /api/sign/[token] route</name>
|
||||
<files>teressa-copeland-homes/src/app/api/sign/[token]/route.ts</files>
|
||||
<action>
|
||||
Make two changes to the GET handler in `src/app/api/sign/[token]/route.ts`:
|
||||
|
||||
1. Add `isClientVisibleField` to the import from `@/lib/db/schema` (line 6 currently imports `signingTokens, documents, clients`):
|
||||
|
||||
```typescript
|
||||
// Current line 6:
|
||||
import { signingTokens, documents, clients } from '@/lib/db/schema';
|
||||
|
||||
// Updated (add isClientVisibleField):
|
||||
import { signingTokens, documents, clients, isClientVisibleField } from '@/lib/db/schema';
|
||||
```
|
||||
|
||||
2. On line 88 in the GET response (inside the `return NextResponse.json({...})` block), replace the unguarded field assignment:
|
||||
|
||||
```typescript
|
||||
// REMOVE:
|
||||
signatureFields: doc.signatureFields ?? [],
|
||||
|
||||
// ADD:
|
||||
signatureFields: (doc.signatureFields ?? []).filter(isClientVisibleField),
|
||||
```
|
||||
|
||||
That is the ONLY change to route.ts. Do NOT modify:
|
||||
- The POST handler at all — the signing submission pipeline is correct and reads signatureFields from the DB directly (not from client), so it must remain untouched
|
||||
- The token verification logic
|
||||
- The audit logging logic
|
||||
- Any other field in the GET response
|
||||
|
||||
Why server-side: If the filter were only in SigningPageClient, a caller could hit GET /api/sign/[token] directly (via curl, browser DevTools, etc.) and see agent-signature field coordinates and IDs. The server-side filter is the primary protection.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 && grep -n "isClientVisibleField" src/app/api/sign/\[token\]/route.ts</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- isClientVisibleField is imported from @/lib/db/schema in route.ts
|
||||
- Line 88 uses .filter(isClientVisibleField) on signatureFields
|
||||
- TypeScript compilation passes (no new errors)
|
||||
- POST handler is untouched (verify by diffing — only the import line and line 88 changed)
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Add type-branching guards to SigningPageClient.tsx</name>
|
||||
<files>teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx</files>
|
||||
<action>
|
||||
Make three targeted changes to `SigningPageClient.tsx`. All changes require importing `getFieldType` first.
|
||||
|
||||
**Change 1 — Add import.**
|
||||
|
||||
The current import on line 10 is:
|
||||
```typescript
|
||||
import type { SignatureFieldData } from '@/lib/db/schema';
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```typescript
|
||||
import type { SignatureFieldData } from '@/lib/db/schema';
|
||||
import { getFieldType } from '@/lib/db/schema';
|
||||
```
|
||||
|
||||
(Keep the `import type` separate — it is a type-only import. The `getFieldType` function import is a value import and must be on its own non-type import line.)
|
||||
|
||||
**Change 2 — Guard handleFieldClick.**
|
||||
|
||||
The current handleFieldClick (lines 85-92):
|
||||
```typescript
|
||||
const handleFieldClick = useCallback(
|
||||
(fieldId: string) => {
|
||||
if (signedFields.has(fieldId)) return;
|
||||
setActiveFieldId(fieldId);
|
||||
setModalOpen(true);
|
||||
},
|
||||
[signedFields]
|
||||
);
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```typescript
|
||||
const handleFieldClick = useCallback(
|
||||
(fieldId: string) => {
|
||||
const field = signatureFields.find((f) => f.id === fieldId);
|
||||
if (!field) return;
|
||||
// Defense-in-depth: primary protection is the server filter in GET /api/sign/[token]
|
||||
// Only client-signature fields open the modal; Phase 10 will expand this for initials
|
||||
if (getFieldType(field) !== 'client-signature') return;
|
||||
if (signedFields.has(fieldId)) return;
|
||||
setActiveFieldId(fieldId);
|
||||
setModalOpen(true);
|
||||
},
|
||||
[signatureFields, signedFields]
|
||||
);
|
||||
```
|
||||
|
||||
Note: `signatureFields` is now in the dependency array. This is correct — the callback uses `signatureFields.find`.
|
||||
|
||||
**Change 3 — Fix handleSubmit count and SigningProgressBar total.**
|
||||
|
||||
In `handleSubmit`, find the guard (currently line 129):
|
||||
```typescript
|
||||
if (signedFields.size < signatureFields.length || submitting) return;
|
||||
```
|
||||
|
||||
Replace with:
|
||||
```typescript
|
||||
const clientSigFields = signatureFields.filter(
|
||||
(f) => getFieldType(f) === 'client-signature'
|
||||
);
|
||||
if (signedFields.size < clientSigFields.length || submitting) return;
|
||||
```
|
||||
|
||||
In the JSX near line 328, find the SigningProgressBar:
|
||||
```typescript
|
||||
<SigningProgressBar
|
||||
total={signatureFields.length}
|
||||
signed={signedFields.size}
|
||||
onJumpToNext={handleJumpToNext}
|
||||
onSubmit={handleSubmit}
|
||||
submitting={submitting}
|
||||
/>
|
||||
```
|
||||
|
||||
Replace `total={signatureFields.length}` with:
|
||||
```typescript
|
||||
total={signatureFields.filter((f) => getFieldType(f) === 'client-signature').length}
|
||||
```
|
||||
|
||||
Why these guards are needed even though the server already filters: The GET route filter is the primary protection. These client-side guards are defense-in-depth. They also prepare the component for Phase 10, which will add `initials` handling — at that point `handleFieldClick` will be expanded with an `else if (getFieldType(field) === 'initials')` branch. Establishing the type-switch pattern now makes Phase 10 a clean extension rather than a surgical patch.
|
||||
|
||||
Do NOT change:
|
||||
- The `handleJumpToNext` callback (it finds the next unsigned field from signatureFields — this is fine because agent-signature fields are filtered by the GET route before the component receives them)
|
||||
- The `fieldsByPage` grouping
|
||||
- The PDF render logic
|
||||
- The signature modal integration
|
||||
- Any styling or overlay rendering
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 && grep -n "getFieldType" src/app/sign/\[token\]/_components/SigningPageClient.tsx</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- getFieldType imported from @/lib/db/schema in SigningPageClient.tsx
|
||||
- handleFieldClick returns early for any field where getFieldType(field) !== 'client-signature'
|
||||
- signatureFields is in the handleFieldClick dependency array
|
||||
- handleSubmit counts only client-signature fields for completeness check
|
||||
- SigningProgressBar receives total = client-signature count (not all fields)
|
||||
- TypeScript compilation passes
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 3: Human verification of Phase 8 safety gate</name>
|
||||
<what-built>
|
||||
Complete Phase 8 safety gate:
|
||||
- 08-01: SignatureFieldData type discriminant, getFieldType(), isClientVisibleField() in schema.ts + Drizzle migration 0006
|
||||
- 08-02: Server-side agent-signature filter in GET /api/sign/[token], type-branching guards in SigningPageClient.tsx
|
||||
</what-built>
|
||||
<how-to-verify>
|
||||
1. Start the dev server if not running: cd /Users/ccopeland/temp/red/teressa-copeland-homes && npm run dev
|
||||
|
||||
2. TypeScript check — must pass clean:
|
||||
npx tsc --noEmit
|
||||
|
||||
3. Verify the schema exports:
|
||||
grep -n "SignatureFieldType\|getFieldType\|isClientVisibleField" /Users/ccopeland/temp/red/teressa-copeland-homes/src/lib/db/schema.ts
|
||||
Expected: all three present with correct signatures
|
||||
|
||||
4. Verify the GET route filter:
|
||||
grep -n "isClientVisibleField" /Users/ccopeland/temp/red/teressa-copeland-homes/src/app/api/sign/\[token\]/route.ts
|
||||
Expected: import line + filter applied to signatureFields in GET response
|
||||
|
||||
5. Verify SigningPageClient guards:
|
||||
grep -n "getFieldType" /Users/ccopeland/temp/red/teressa-copeland-homes/src/app/sign/\[token\]/_components/SigningPageClient.tsx
|
||||
Expected: 3+ lines (import, handleFieldClick guard, handleSubmit filter, progressbar total)
|
||||
|
||||
6. Verify POST handler is untouched (no isClientVisibleField/getFieldType references in POST section):
|
||||
The POST handler begins at line 98 of route.ts. Confirm no changes below that line.
|
||||
|
||||
7. Verify the migration file exists:
|
||||
ls /Users/ccopeland/temp/red/teressa-copeland-homes/drizzle/0006_*.sql
|
||||
|
||||
8. Manual backward-compat smoke test (if DB is running):
|
||||
- Log in to the portal at http://localhost:3000/portal
|
||||
- Open an existing signed or prepared document
|
||||
- If a signing token exists, visit the /sign/[token] URL — the page should load and display fields normally
|
||||
- No visual change expected (all existing fields have no type property and default to client-signature)
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" if all checks pass, or describe any issues found</resume-signal>
|
||||
<action>Human reviews and approves all automated checks completed in Tasks 1 and 2. No code changes required — this is a verification-only step.</action>
|
||||
<verify><automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -5</automated></verify>
|
||||
<done>Human has approved Phase 8 safety gate: TypeScript compiles clean, server filter confirmed, client guards confirmed, backward-compat verified</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
TypeScript compilation passes:
|
||||
```bash
|
||||
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit
|
||||
```
|
||||
|
||||
All three schema exports present:
|
||||
```bash
|
||||
grep -c "getFieldType\|isClientVisibleField\|SignatureFieldType" /Users/ccopeland/temp/red/teressa-copeland-homes/src/lib/db/schema.ts
|
||||
```
|
||||
Expected: 3 (at minimum)
|
||||
|
||||
Server filter applied:
|
||||
```bash
|
||||
grep "isClientVisibleField" /Users/ccopeland/temp/red/teressa-copeland-homes/src/app/api/sign/\[token\]/route.ts
|
||||
```
|
||||
|
||||
Client guard applied:
|
||||
```bash
|
||||
grep "getFieldType" /Users/ccopeland/temp/red/teressa-copeland-homes/src/app/sign/\[token\]/_components/SigningPageClient.tsx
|
||||
```
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- GET /api/sign/[token] response only contains client-visible field types (agent-signature excluded)
|
||||
- SigningPageClient does not open signature modal for non-client-signature field types
|
||||
- handleSubmit completeness check counts only client-signature fields
|
||||
- SigningProgressBar total reflects client-signature field count only
|
||||
- TypeScript compilation passes across the entire project
|
||||
- All existing v1.0 signing behavior is unchanged (no type = client-signature fallback)
|
||||
- Phase 8 ships as an atomic unit: schema foundation + signing page safety active simultaneously
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/08-schema-foundation-and-signing-page-safety/08-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user