95 lines
4.9 KiB
Markdown
95 lines
4.9 KiB
Markdown
|
|
# Phase 14: Multi-Signer Schema - Context
|
|||
|
|
|
|||
|
|
**Gathered:** 2026-04-03
|
|||
|
|
**Status:** Ready for planning
|
|||
|
|
|
|||
|
|
<domain>
|
|||
|
|
## Phase Boundary
|
|||
|
|
|
|||
|
|
Additive DB migration only — no backend logic changes, no UI changes. Add three new nullable columns to existing tables and extend the `SignatureFieldData` TypeScript interface with an optional `signerEmail` field. Every new column is nullable so all existing single-signer documents continue to work without any backfill or code changes. Phase 14 is schema-only; the sign handler rewrite and completion detection logic belong in Phase 15.
|
|||
|
|
|
|||
|
|
</domain>
|
|||
|
|
|
|||
|
|
<decisions>
|
|||
|
|
## Implementation Decisions
|
|||
|
|
|
|||
|
|
### Schema Shape
|
|||
|
|
|
|||
|
|
- **D-01:** `documents.signers` JSONB shape is `{ email: string; color: string }[]` — not a plain `string[]`. Color is stored alongside email so Phase 16 can retrieve consistent per-signer colors from the DB without recalculating by index. Example: `[{ email: "buyer@x.com", color: "#6366f1" }]`.
|
|||
|
|
- **D-02:** `signingTokens.signerEmail` is a nullable `TEXT` column. NULL means legacy single-signer token (existing behavior unchanged).
|
|||
|
|
- **D-03:** `documents.completionTriggeredAt` is a nullable `TIMESTAMP` column. Used as an atomic completion guard in Phase 15: `UPDATE documents SET completionTriggeredAt = NOW() WHERE id = $1 AND completionTriggeredAt IS NULL RETURNING id`. Only Phase 15 writes to it.
|
|||
|
|
- **D-04:** `SignatureFieldData` interface gets an optional `signerEmail?: string` field — the same additive-nullable pattern already used for `type?: SignatureFieldType`.
|
|||
|
|
|
|||
|
|
### Status Tracking
|
|||
|
|
|
|||
|
|
- **D-05:** No `Partially Signed` value added to `documentStatusEnum`. Partial-signed state is computed dynamically in Phase 16 by counting `signingTokens WHERE usedAt IS NOT NULL` vs total tokens for the document. Avoids `ALTER TYPE` migration complexity.
|
|||
|
|
|
|||
|
|
### Bug Fix Scope
|
|||
|
|
|
|||
|
|
- **D-06:** The first-signer-wins bug (sign handler unconditionally sets `status='Signed'` on any submission) is **NOT fixed in Phase 14**. Phase 14 is schema-only. Phase 15 owns the full sign handler rewrite including this fix.
|
|||
|
|
|
|||
|
|
### Migration Approach
|
|||
|
|
|
|||
|
|
- **D-07:** Use existing pattern: edit `schema.ts`, run `drizzle-kit generate` to produce `drizzle/0010_*.sql`, commit both, run `drizzle-kit migrate` against Neon in deployment. Migration must be additive-only (no column removals, no type changes, no backfills).
|
|||
|
|
|
|||
|
|
### Claude's Discretion
|
|||
|
|
- Migration file naming and exact SQL details — Drizzle generates these automatically from schema.ts changes.
|
|||
|
|
- Order of changes within the migration (columns vs interface) — planner decides.
|
|||
|
|
|
|||
|
|
</decisions>
|
|||
|
|
|
|||
|
|
<canonical_refs>
|
|||
|
|
## Canonical References
|
|||
|
|
|
|||
|
|
**Downstream agents MUST read these before planning or implementing.**
|
|||
|
|
|
|||
|
|
### Schema
|
|||
|
|
- `teressa-copeland-homes/src/lib/db/schema.ts` — Current schema; all new columns and interface changes go here
|
|||
|
|
- `teressa-copeland-homes/drizzle/` — Existing migration files (0000–0009); new migration is 0010
|
|||
|
|
|
|||
|
|
### Drizzle Config
|
|||
|
|
- `teressa-copeland-homes/drizzle.config.ts` — Migration output dir and DB credentials config
|
|||
|
|
|
|||
|
|
### Research
|
|||
|
|
- `.planning/research/ARCHITECTURE.md` — Multi-signer schema design section (additive-only pattern, advisory lock approach, migration strategy)
|
|||
|
|
- `.planning/research/PITFALLS.md` — First-signer-wins bug description (Phase 14 adds the column Phase 15 needs to fix it)
|
|||
|
|
|
|||
|
|
</canonical_refs>
|
|||
|
|
|
|||
|
|
<code_context>
|
|||
|
|
## Existing Code Insights
|
|||
|
|
|
|||
|
|
### Reusable Assets
|
|||
|
|
- `getFieldType(field)` in `schema.ts` — Pattern for safe nullable field reads with fallback; `signerEmail` should follow the same additive-nullable pattern
|
|||
|
|
- `isClientVisibleField(field)` in `schema.ts` — Already filters by field type; Phase 15 will add signer filtering here or alongside it
|
|||
|
|
|
|||
|
|
### Established Patterns
|
|||
|
|
- **Additive nullable columns**: Prior migrations (0006–0009) show the pattern: add nullable columns to schema.ts, generate migration, migrate. No backfills needed.
|
|||
|
|
- **JSONB typed columns**: `signatureFields`, `textFillData`, `emailAddresses` all use `jsonb().$type<T>()` — `documents.signers` follows the same pattern with `$type<{ email: string; color: string }[]>()`
|
|||
|
|
- **`drizzle-kit generate` + `drizzle-kit migrate`**: The project has 10 migrations already. Always generate, never hand-write SQL.
|
|||
|
|
|
|||
|
|
### Integration Points
|
|||
|
|
- `signingTokens` table: new `signerEmail` column is nullable; existing token creation code in `POST /api/documents/[id]/send` does not need to change in Phase 14
|
|||
|
|
- `SignatureFieldData` interface: adding `signerEmail?: string` is backwards-compatible — all existing field JSONB rows silently get `undefined` for this field
|
|||
|
|
|
|||
|
|
</code_context>
|
|||
|
|
|
|||
|
|
<specifics>
|
|||
|
|
## Specific Ideas
|
|||
|
|
|
|||
|
|
- The `{ email, color }` shape for `documents.signers` was chosen over `string[]` so Phase 16's PreparePanel can display the signer list with consistent colors without needing to recalculate or store colors elsewhere.
|
|||
|
|
|
|||
|
|
</specifics>
|
|||
|
|
|
|||
|
|
<deferred>
|
|||
|
|
## Deferred Ideas
|
|||
|
|
|
|||
|
|
None — discussion stayed within phase scope.
|
|||
|
|
|
|||
|
|
</deferred>
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*Phase: 14-multi-signer-schema*
|
|||
|
|
*Context gathered: 2026-04-03*
|