219 lines
9.6 KiB
Markdown
219 lines
9.6 KiB
Markdown
---
|
|
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>
|