From 1afac9df1cf100328bfb5d332f687bf5133191c5 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Mon, 6 Apr 2026 12:08:26 -0600 Subject: [PATCH] docs(phase-18): gather context for template schema and CRUD API --- .../18-template-schema-and-crud-api/.gitkeep | 0 .../18-CONTEXT.md | 138 ++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 .planning/phases/18-template-schema-and-crud-api/.gitkeep create mode 100644 .planning/phases/18-template-schema-and-crud-api/18-CONTEXT.md diff --git a/.planning/phases/18-template-schema-and-crud-api/.gitkeep b/.planning/phases/18-template-schema-and-crud-api/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.planning/phases/18-template-schema-and-crud-api/18-CONTEXT.md b/.planning/phases/18-template-schema-and-crud-api/18-CONTEXT.md new file mode 100644 index 0000000..2fd9383 --- /dev/null +++ b/.planning/phases/18-template-schema-and-crud-api/18-CONTEXT.md @@ -0,0 +1,138 @@ +# Phase 18: Template Schema and CRUD API - Context + +**Gathered:** 2026-04-06 +**Status:** Ready for planning + + +## Phase Boundary + +Schema migration + CRUD API only. No UI, no template editor, no apply-to-document logic. Those are Phases 19 and 20. + +Deliverables: +1. Drizzle migration adding `document_templates` table +2. `GET /api/templates` — list active templates (archivedAt IS NULL) +3. `POST /api/templates` — create template (pick a form from the library) +4. `PATCH /api/templates/[id]` — rename template +5. `DELETE /api/templates/[id]` — soft-delete (set archivedAt) + + + + +## Implementation Decisions + +### Schema Design + +- **D-01:** New table `document_templates` — separate from `form_templates`. The `form_templates` table is a read-only seeded catalog. `document_templates` is agent-authored. + +- **D-02:** Table columns: + ``` + document_templates: + id TEXT PRIMARY KEY (crypto.randomUUID()) + name TEXT NOT NULL + formTemplateId TEXT NOT NULL REFERENCES form_templates(id) + signatureFields JSONB NULL — SignatureFieldData[] with roles in signerEmail slot, hints in hint slot + archivedAt TIMESTAMP NULL — soft-delete; NULL = active + createdAt TIMESTAMP NOT NULL DEFAULT NOW() + updatedAt TIMESTAMP NOT NULL DEFAULT NOW() + ``` + +- **D-03:** NO separate `signerRoles` or `textHints` columns. Both are already embedded per-field in `signatureFields`: + - Role labels stored in `field.signerEmail` (e.g. "Buyer", "Seller") + - Text hints stored in `field.hint` (already on `SignatureFieldData`) + - No duplication, no sync issues. + +- **D-04:** `signatureFields` is nullable on creation (template starts empty, editor fills it in Phase 19). Default NULL. + +- **D-05:** `updatedAt` must be set explicitly in every UPDATE query (no DB trigger). Follow existing pattern from `clients` and `formTemplates` tables. + +- **D-06:** NO `ON DELETE CASCADE` on the `formTemplateId` FK. If a form is removed from the library, templates referencing it should remain (archivedAt pattern handles cleanup). + +### Soft-Delete + +- **D-07:** Deletion sets `archivedAt = NOW()`. Hard delete is never performed. +- **D-08:** All list queries filter `WHERE archivedAt IS NULL`. Archived templates are invisible to the agent. +- **D-09:** Archived templates are NOT restored. If agent needs the template back, they create a new one. + +### API Structure + +- **D-10:** Routes at `/api/templates` (top-level, matching `/api/documents` pattern): + - `GET /api/templates` — returns active templates with: id, name, formTemplateId, formName (joined), fieldCount (derived from signatureFields length), createdAt, updatedAt + - `POST /api/templates` — body: `{ name: string; formTemplateId: string }` — creates with empty signatureFields + - `PATCH /api/templates/[id]` — body: `{ name?: string; signatureFields?: SignatureFieldData[] }` — partial update + - `DELETE /api/templates/[id]` — sets archivedAt = NOW() + +- **D-11:** All routes auth-gated (agent session required). Same `auth()` guard as all other portal API routes. + +- **D-12:** `fieldCount` on GET list is derived server-side: `(signatureFields ?? []).length`. Not stored in DB. + +### Migration + +- **D-13:** Use existing Drizzle pattern: edit `schema.ts`, run `drizzle-kit generate` → `drizzle/0012_*.sql`, commit both. Next migration after `0011_common_mystique.sql`. + +### Claude's Discretion +- Exact migration filename (Drizzle generates automatically) +- Whether to include formTemplate name JOIN in the list query (recommended — avoids extra fetches in UI) + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Schema (existing patterns to follow) +- `teressa-copeland-homes/src/lib/db/schema.ts` — existing tables; `document_templates` goes here; follow `clients` table pattern for updatedAt +- `teressa-copeland-homes/drizzle/` — existing migrations 0000–0011; new migration is 0012 +- `teressa-copeland-homes/drizzle.config.ts` — migration config + +### Existing API patterns +- `teressa-copeland-homes/src/app/api/documents/route.ts` — POST documents pattern to follow +- `teressa-copeland-homes/src/app/api/documents/[id]/route.ts` — PATCH pattern to follow + +### Research +- `.planning/research/ARCHITECTURE.md` — schema decision reasoning, build order +- `.planning/research/PITFALLS.md` — soft-delete rationale, FK constraint notes + + + + +## Existing Code Insights + +### Reusable Assets +- `auth()` from `@/lib/auth` — all existing API routes use this for session guard; follow same pattern +- `db` from `@/lib/db` — Drizzle client; import and use directly +- `SignatureFieldData` interface — already in schema.ts; `signatureFields` column types to `SignatureFieldData[]` +- `formTemplates` table — JOIN target for getting form name in list query + +### Established Patterns +- `crypto.randomUUID()` via `$defaultFn` for IDs — all existing tables use this pattern +- `timestamp("updated_at").defaultNow().notNull()` — existing pattern; updated explicitly in every PATCH +- `jsonb("column").$type()` — existing JSONB pattern (signatureFields, signers, contacts) +- `text("foreign_key_id").notNull().references(() => otherTable.id)` — FK pattern + +### Integration Points +- `formTemplates` table (existing) — `document_templates.formTemplateId` FKs to this +- Phase 19 will add the template editor UI that PATCHes `signatureFields` +- Phase 20 will add the apply-template endpoint (POST to create document from template) + + + + +## Specific Ideas + +- `fieldCount` in GET /api/templates response: `(t.signatureFields?.length ?? 0)` — computed at query time, no extra DB column +- PATCH route handles both rename (`name`) AND field save (Phase 19 calls it with `signatureFields`) — one route, two use cases + + + + +## Deferred Ideas + +None — discussion stayed within phase scope. + + + +--- + +*Phase: 18-template-schema-and-crud-api* +*Context gathered: 2026-04-06*