Phase: 12 of 13 (Filled Document Preview) — COMPLETE
Plan: 2 of 2 in phase 12 — COMPLETE (human-verified and approved)
Status: Phase 12 fully complete — PREV-01 verified by human; Preview-gate-Send flow approved; Phase 12.1 (text fill UX redesign) identified as known gap for gap closure work
Last activity: 2026-03-21 — Phase 12 Plan 02 complete: Preview button, previewToken gating, scroll lock portal modal, text fill coords fix; human approved full flow; Phase 12.1 gap noted for text fill UX redesign.
- [v1.1 Research]: Use pdfjs-dist legacy build (already installed via react-pdf) for server-side PDF text extraction — no new dependency
- [v1.1 Research]: Use manual `json_schema` response_format for OpenAI — do NOT use `zodResponseFormat` (broken with Zod v4, confirmed issues #1540, #1602, #1709)
- [v1.1 Research]: Agent signature stored as base64 PNG TEXT column on users table (2-8KB) — no new file storage needed
- [v1.1 Research]: Phase 8 must ship atomically (schema discriminant + signing page filter) before any new field type can be placed or sent
- [08-01]: SignatureFieldType.type is optional on SignatureFieldData — v1.0 JSONB documents have no type; getFieldType() coalesces to 'client-signature'
- [08-01]: isClientVisibleField() returns false only for 'agent-signature' — all other types including legacy documents are client-visible
- [08-02]: Server-side filter in route.ts is the primary security boundary — client guards in SigningPageClient are defense-in-depth; direct API callers cannot see agent-signature coordinates
- [08-02]: POST handler in route.ts intentionally untouched — signature embedding pipeline reads signatureFields from DB directly, not from client payload
- [08-02]: Phase 8 ships atomically — 08-01 schema + 08-02 boundary enforcement active simultaneously; no intermediate state where type discriminant exists but filter is absent
- [Phase 09-client-property-address]: TextFillForm initialData prop pattern: seed rows from Record<string,string> via buildInitialRows helper; pre-seeding done via prop not external controlled state
- [Phase 09-client-property-address]: Empty string from FormData coerced to NULL via || null before DB write — blank optional fields never store empty string in postgres
- [Phase 10-expanded-field-types-end-to-end]: date field signing date captured server-side (now hoisted before step 8) — not trusted from client payload
- [Phase 10-expanded-field-types-end-to-end]: signableFields filter limits POST signaturesWithCoords to client-signature and initials only — eliminates 500 on mixed-field documents
- [Phase 10-expanded-field-types-end-to-end]: all field box backgrounds transparent in preparePdf — omit color param from drawRectangle = no fill; underlying PDF content always visible
- [Phase 10-expanded-field-types-end-to-end]: checkbox draws X lines only (no rectangle); FieldPlacer hides resize handles for checkbox (fixed 24x24pt, non-resizable)
- [Phase 10-expanded-field-types-end-to-end]: date stamp at sign time draws text directly — no white overwrite rectangle needed since no placeholder was drawn at prepare time
- [Phase 12-filled-document-preview]: previewToken state lifted to DocumentPageClient — PreparePanel and FieldPlacer are siblings in server component, cannot share state directly
- [Phase 12-filled-document-preview]: DocumentPageClient created as minimal client wrapper — holds previewToken state, passes handleFieldsChanged to FieldPlacer chain, passes previewToken+setter to PreparePanel
- [Phase 12-02]: PreviewModal uses ReactDOM.createPortal to document.body — escapes sticky sidebar stacking context; z-index 9999 with body scroll lock via useEffect
- [Phase 12-02]: Text fill values drawn at placed field box coordinates (field.x+4, field.y+4) — sorted by page/y asc; font 6-11pt; fieldConsumedKeys prevents Strategy B double-render
- [Phase 12-02]: Text fill UX redesign (per-field click-to-edit, quick-fill suggestions) deferred to Phase 12.1 — known gap, not a correctness blocker; PREV-01 complete
- [Phase 12 - CARRY FORWARD]: Deployment target (Vercel serverless vs. self-hosted container) must be confirmed before production deploy — write-to-disk preview pattern silently fails on Vercel serverless (ephemeral filesystem)