Files
red/.planning/phases/08-schema-foundation-and-signing-page-safety/08-01-PLAN.md
2026-03-21 11:43:42 -06:00

9.6 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
08-schema-foundation-and-signing-page-safety 01 execute 1
teressa-copeland-homes/src/lib/db/schema.ts
teressa-copeland-homes/drizzle/0006_type_discriminant.sql
true
FIELD-01
truths artifacts key_links
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)
path provides contains
teressa-copeland-homes/src/lib/db/schema.ts SignatureFieldType union, extended SignatureFieldData interface, getFieldType helper, isClientVisibleField predicate SignatureFieldType
path provides
teressa-copeland-homes/drizzle/0006_type_discriminant.sql Drizzle migration snapshot sync (may be empty SQL — that is correct)
from to via pattern
teressa-copeland-homes/src/lib/db/schema.ts teressa-copeland-homes/src/app/api/sign/[token]/route.ts isClientVisibleField import (added in plan 08-02) isClientVisibleField
from to via pattern
teressa-copeland-homes/src/lib/db/schema.ts teressa-copeland-homes/src/app/sign/[token]/_components/SigningPageClient.tsx getFieldType import (added in plan 08-02) 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.

<execution_context> @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md </execution_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.

```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:

  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):

/**
 * 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. 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:
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:

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:

mv drizzle/0006_*.sql drizzle/0006_type_discriminant.sql

Then apply the migration to the local database:

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:
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit

Confirm all three exports are present:

grep -n "SignatureFieldType\|getFieldType\|isClientVisibleField" /Users/ccopeland/temp/red/teressa-copeland-homes/src/lib/db/schema.ts

Confirm migration file exists:

ls /Users/ccopeland/temp/red/teressa-copeland-homes/drizzle/0006_*.sql

<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>
After completion, create `.planning/phases/08-schema-foundation-and-signing-page-safety/08-01-SUMMARY.md`