147 lines
8.3 KiB
Markdown
147 lines
8.3 KiB
Markdown
|
|
---
|
||
|
|
phase: 09-client-property-address
|
||
|
|
plan: 01
|
||
|
|
subsystem: database, ui
|
||
|
|
tags: [drizzle, postgres, react, nextjs, form, client-data]
|
||
|
|
|
||
|
|
# Dependency graph
|
||
|
|
requires:
|
||
|
|
- phase: 01-foundation
|
||
|
|
provides: drizzle schema pattern for nullable columns
|
||
|
|
- phase: 03-agent-portal-shell
|
||
|
|
provides: ClientModal, ClientProfileClient components
|
||
|
|
- phase: 07-prepare-and-send
|
||
|
|
provides: PreparePanel, TextFillForm components
|
||
|
|
provides:
|
||
|
|
- Nullable property_address TEXT column on clients table with migration
|
||
|
|
- Extended createClient/updateClient server actions with propertyAddress
|
||
|
|
- Property address input field in ClientModal (create + edit modes)
|
||
|
|
- Property address display in ClientProfileClient profile card
|
||
|
|
- TextFillForm initialData prop for pre-seeding rows from external state
|
||
|
|
- PreparePanel seeds TextFillForm with client property address on mount
|
||
|
|
affects:
|
||
|
|
- phase: 13-ai-prefill
|
||
|
|
note: AI pre-fill pipeline reads textFillData.propertyAddress as pre-seeded client data
|
||
|
|
|
||
|
|
# Tech tracking
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- Drizzle nullable column pattern — no .notNull() no .default() on text columns
|
||
|
|
- lazy useState initializer for pre-seeding form state from server prop
|
||
|
|
- TextFillForm initialData prop pattern for controlled row hydration
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created:
|
||
|
|
- drizzle/0007_property_address.sql
|
||
|
|
modified:
|
||
|
|
- src/lib/db/schema.ts
|
||
|
|
- src/lib/actions/clients.ts
|
||
|
|
- src/app/portal/_components/ClientModal.tsx
|
||
|
|
- src/app/portal/_components/ClientProfileClient.tsx
|
||
|
|
- src/app/portal/(protected)/documents/[docId]/page.tsx
|
||
|
|
- src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx
|
||
|
|
- src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "Empty string from FormData coerced to NULL via || null before DB write — ensures blank field never stores empty string"
|
||
|
|
- "TextFillForm owns row state internally; pre-seeding is done via initialData prop (lazy useState) not via controlled external state — avoids prop-to-state sync complexity"
|
||
|
|
- "Pre-seeded rows plus one blank row appended — agent can add more fields without clearing the pre-seeded address"
|
||
|
|
- "TextFillForm instruction copy changed from AcroForm jargon to friendly agent-facing copy; added column headers for visual clarity"
|
||
|
|
|
||
|
|
patterns-established:
|
||
|
|
- "TextFillForm initialData pattern: pass Record<string,string> to seed rows; component handles conversion to TextRow[] via buildInitialRows helper"
|
||
|
|
- "Drizzle nullable column: text('col_name') with no .notNull() or .default() — matches existing sentAt/filePath pattern"
|
||
|
|
|
||
|
|
requirements-completed: [CLIENT-04, CLIENT-05]
|
||
|
|
|
||
|
|
# Metrics
|
||
|
|
duration: 25min
|
||
|
|
completed: 2026-03-21
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 9 Plan 01: Client Property Address Summary
|
||
|
|
|
||
|
|
**Nullable property_address column on clients table with full CRUD, profile display, and PreparePanel pre-seed for Phase 13 AI pre-fill pipeline**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** 25 min
|
||
|
|
- **Started:** 2026-03-21T18:30:00Z
|
||
|
|
- **Completed:** 2026-03-21T18:55:00Z
|
||
|
|
- **Tasks:** 3 (including checkpoint with fix iteration)
|
||
|
|
- **Files modified:** 7
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
- Added nullable `property_address` TEXT column via drizzle-kit migration (0007)
|
||
|
|
- Extended createClient and updateClient server actions with propertyAddress, coercing empty string to NULL
|
||
|
|
- Added Property Address input to ClientModal in both create and edit modes with pre-fill on edit
|
||
|
|
- Added property address display to ClientProfileClient profile card (hidden when null)
|
||
|
|
- Fixed TextFillForm to accept `initialData` prop so pre-seeded rows render on mount
|
||
|
|
- Polished PreparePanel text fill section: friendly copy, column headers, improved spacing
|
||
|
|
|
||
|
|
## Task Commits
|
||
|
|
|
||
|
|
Each task was committed atomically:
|
||
|
|
|
||
|
|
1. **Task 1: Schema column, migration, and server action extension** - `baa1c78` (feat)
|
||
|
|
2. **Task 2: UI layer — modal input, profile display, PreparePanel pre-seed** - `fa9981e` (feat)
|
||
|
|
3. **Task 3: Fix propertyAddress pre-seed + polish PreparePanel text fill UI** - `11f2b80` (fix)
|
||
|
|
|
||
|
|
## Files Created/Modified
|
||
|
|
- `drizzle/0007_property_address.sql` - ALTER TABLE clients ADD COLUMN property_address text migration
|
||
|
|
- `src/lib/db/schema.ts` - propertyAddress nullable column added to clients pgTable
|
||
|
|
- `src/lib/actions/clients.ts` - createClient/updateClient extended with propertyAddress (|| null coercion)
|
||
|
|
- `src/app/portal/_components/ClientModal.tsx` - Property Address input field in create/edit modal
|
||
|
|
- `src/app/portal/_components/ClientProfileClient.tsx` - Address display in header card; edit modal passes defaultPropertyAddress
|
||
|
|
- `src/app/portal/(protected)/documents/[docId]/page.tsx` - propertyAddress included in client select query, passed to PreparePanel
|
||
|
|
- `src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx` - clientPropertyAddress prop accepted, passed as initialData to TextFillForm
|
||
|
|
- `src/app/portal/(protected)/documents/[docId]/_components/TextFillForm.tsx` - initialData prop added; buildInitialRows helper seeds rows from pre-seeded data; polished UI copy and layout
|
||
|
|
|
||
|
|
## Decisions Made
|
||
|
|
- Empty string from FormData coerced to NULL via `|| null` before DB write — ensures blank optional field never stores empty string in postgres
|
||
|
|
- TextFillForm owns row state; pre-seeding done via `initialData` lazy useState prop rather than controlled external state — cleaner API, avoids prop-to-state sync on re-render
|
||
|
|
- Pre-seeded rows get a blank row appended below — agent can add more text fields without removing the pre-seeded address
|
||
|
|
- Instruction copy replaced: "AcroForm field name in the PDF" changed to "Pre-fill text fields in the PDF before sending" for agent-facing clarity
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
### Auto-fixed Issues
|
||
|
|
|
||
|
|
**1. [Rule 1 - Bug] TextFillForm initialData prop missing — pre-seed not rendering**
|
||
|
|
- **Found during:** Task 3 (human verification checkpoint)
|
||
|
|
- **Issue:** PreparePanel correctly initialized `textFillData` state with `{ propertyAddress: "..." }` via lazy useState, but `TextFillForm` had its own internal `rows` state initialized to `[{ label: '', value: '' }]` with no mechanism to receive initial data. The pre-seeded state in PreparePanel was used for the POST payload but never rendered as visible rows.
|
||
|
|
- **Fix:** Added `initialData?: Record<string, string>` prop to `TextFillForm`. Added `buildInitialRows()` helper that converts the record to `TextRow[]` and appends one blank row. Changed `useState` initializer to `() => buildInitialRows(initialData)`. Updated PreparePanel to pass `initialData` to TextFillForm.
|
||
|
|
- **Files modified:** `TextFillForm.tsx`, `PreparePanel.tsx`
|
||
|
|
- **Verification:** TypeScript compiles clean (`npx tsc --noEmit`)
|
||
|
|
- **Committed in:** `11f2b80`
|
||
|
|
|
||
|
|
**2. [Rule 1 - Bug] Text fill section visual polish — jargon and missing column headers**
|
||
|
|
- **Found during:** Task 3 (human verification checkpoint)
|
||
|
|
- **Issue:** Instruction text contained AcroForm technical jargon not appropriate for agent user. Two-column layout had no headers making it unclear which column was "field name" vs "value". Remove button was styled aggressively red at rest.
|
||
|
|
- **Fix:** Replaced instruction text with friendly copy. Added "Field name" / "Value" column headers with uppercase tracking style. Improved input padding (py-1.5). Softened remove button to gray at rest, red on hover only.
|
||
|
|
- **Files modified:** `TextFillForm.tsx`
|
||
|
|
- **Verification:** Visual inspection + TypeScript clean
|
||
|
|
- **Committed in:** `11f2b80`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Total deviations:** 2 auto-fixed (both Rule 1 bugs found during human verification checkpoint)
|
||
|
|
**Impact on plan:** Both fixes required for feature to function and present correctly. No scope creep.
|
||
|
|
|
||
|
|
## Issues Encountered
|
||
|
|
- Human verification (Task 3 checkpoint) revealed that the plan's lazy useState pattern in PreparePanel was insufficient because TextFillForm had its own independent state. This was a design gap in the plan spec — the fix required adding the initialData prop to TextFillForm rather than only fixing PreparePanel. Both files updated in a single atomic fix commit.
|
||
|
|
|
||
|
|
## User Setup Required
|
||
|
|
None - no external service configuration required.
|
||
|
|
|
||
|
|
## Next Phase Readiness
|
||
|
|
- `propertyAddress` is now stored and accessible on all client records
|
||
|
|
- PreparePanel arrives pre-seeded with `textFillData.propertyAddress` when client has an address
|
||
|
|
- Phase 13 AI pre-fill (AI-02) can read `textFillData` from the prepare POST body and use `propertyAddress` as structured input to the AI prompt
|
||
|
|
- No blockers for Phase 10 or beyond
|
||
|
|
|
||
|
|
---
|
||
|
|
*Phase: 09-client-property-address*
|
||
|
|
*Completed: 2026-03-21*
|