--- 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" --- 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. @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.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. ```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(), ``` Task 1: Extend SignatureFieldData in schema.ts with type discriminant and helper exports teressa-copeland-homes/src/lib/db/schema.ts 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()` 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. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30 - 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) Task 2: Run Drizzle migration to sync meta snapshot teressa-copeland-homes/drizzle/0006_type_discriminant.sql 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()` 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. ls /Users/ccopeland/temp/red/teressa-copeland-homes/drizzle/0006_*.sql 2>/dev/null && echo "Migration file exists" || echo "MISSING" - 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) 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 ``` - 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() After completion, create `.planning/phases/08-schema-foundation-and-signing-page-safety/08-01-SUMMARY.md`