diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 2591b88..3d47fa0 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -190,10 +190,10 @@ Plans:
1. Agent can add or edit a property address on any client profile from the portal
2. Property address is saved to the database and displayed on the client profile page
3. Property address is returned alongside client name when the prepare-document pipeline fetches client data (available as a pre-fill source)
-**Plans**: TBD
+**Plans**: 1 plan
Plans:
-- [ ] 09-01-PLAN.md — DB migration (propertyAddress nullable TEXT on clients table), server action update, ClientModal form field, client profile display
+- [ ] 09-01-PLAN.md — DB schema migration, server actions, ClientModal input, ClientProfileClient display, PreparePanel state pre-seed (CLIENT-04, CLIENT-05)
### Phase 10: Expanded Field Types End-to-End
**Goal**: Agent can place text, checkbox, initials, and date field markers on any PDF and the prepared document embeds each type correctly
diff --git a/.planning/phases/09-client-property-address/09-01-PLAN.md b/.planning/phases/09-client-property-address/09-01-PLAN.md
new file mode 100644
index 0000000..22ff1e3
--- /dev/null
+++ b/.planning/phases/09-client-property-address/09-01-PLAN.md
@@ -0,0 +1,293 @@
+---
+phase: 09-client-property-address
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_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
+autonomous: false
+requirements:
+ - CLIENT-04
+ - CLIENT-05
+
+must_haves:
+ truths:
+ - "Agent can add or edit a property address on any client profile from the portal (create and edit modes in ClientModal)"
+ - "Property address is persisted to the database as NULL when left blank, not as empty string"
+ - "Property address is displayed on the client profile page when non-null"
+ - "When opening a prepared document for a client who has a property address, the PreparePanel's text fill area arrives pre-populated with the address under the key 'propertyAddress'"
+ - "Clients without a property address work identically to before — no regressions on existing data"
+ artifacts:
+ - path: "src/lib/db/schema.ts"
+ provides: "propertyAddress: text('property_address') nullable column on clients pgTable"
+ contains: "property_address"
+ - path: "drizzle/0007_property_address.sql"
+ provides: "ALTER TABLE clients ADD COLUMN property_address text migration"
+ contains: "property_address"
+ - path: "src/lib/actions/clients.ts"
+ provides: "createClient and updateClient server actions extended with propertyAddress"
+ contains: "propertyAddress"
+ - path: "src/app/portal/_components/ClientModal.tsx"
+ provides: "Property address input field in create/edit modal"
+ contains: "propertyAddress"
+ - path: "src/app/portal/_components/ClientProfileClient.tsx"
+ provides: "Property address display in profile card"
+ contains: "propertyAddress"
+ - path: "src/app/portal/(protected)/documents/[docId]/page.tsx"
+ provides: "propertyAddress included in client select query, passed to PreparePanel"
+ contains: "propertyAddress"
+ - path: "src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx"
+ provides: "clientPropertyAddress prop accepted, seeds textFillData state on mount"
+ contains: "clientPropertyAddress"
+ key_links:
+ - from: "src/lib/db/schema.ts"
+ to: "drizzle/0007_property_address.sql"
+ via: "npm run db:generate then npm run db:migrate"
+ pattern: "property_address"
+ - from: "src/app/portal/(protected)/documents/[docId]/page.tsx"
+ to: "src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx"
+ via: "clientPropertyAddress prop"
+ pattern: "clientPropertyAddress=\\{.*propertyAddress"
+ - from: "src/app/portal/_components/ClientProfileClient.tsx"
+ to: "src/app/portal/_components/ClientModal.tsx"
+ via: "defaultPropertyAddress prop on edit modal call"
+ pattern: "defaultPropertyAddress=\\{client\\.propertyAddress"
+---
+
+
+Add a property address field to client profiles: DB column, server actions, form UI, profile display, and pre-seed the PreparePanel text fill state so the address flows into the AI pre-fill pipeline in Phase 13.
+
+Purpose: Phase 13 AI pre-fill (AI-02) needs structured client data (name + property address) to populate text fields. This phase is the data sourcing layer — no AI work yet, just capturing and plumbing the data.
+Output: Nullable `property_address` TEXT column on `clients` table, extended server actions, ClientModal form field, profile display, and PreparePanel state seed.
+
+
+
+@/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md
+@/Users/ccopeland/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+
+@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
+
+
+
+
+From src/lib/db/schema.ts (clients table — existing pattern):
+```typescript
+export const clients = pgTable("clients", {
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
+ name: text("name").notNull(),
+ email: text("email").notNull(),
+ // propertyAddress goes here — nullable, no .notNull(), no .default()
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+// Existing nullable pattern in same file: sentAt: timestamp("sent_at"), filePath: text("file_path")
+```
+
+From src/lib/actions/clients.ts (Zod schema + action pattern):
+```typescript
+const clientSchema = z.object({
+ name: z.string().min(1, "Name is required"),
+ email: z.string().email("Valid email address required"),
+ // propertyAddress: z.string().optional() — add here
+});
+// After parse: parsed.data.propertyAddress || null (coerce "" → null before DB write)
+// revalidatePath("/portal/clients") and revalidatePath("/portal/clients/" + id) already present — keep both
+```
+
+From src/app/portal/_components/ClientModal.tsx (useActionState form pattern):
+```typescript
+type ClientModalProps = {
+ isOpen: boolean; onClose: () => void; mode?: "create" | "edit";
+ clientId?: string; defaultName?: string; defaultEmail?: string;
+ // defaultPropertyAddress?: string; — add here
+};
+// Add input after email input: name="propertyAddress", defaultValue={defaultPropertyAddress}
+// Placeholder: "123 Main St, Salt Lake City, UT 84101"
+```
+
+From src/app/portal/_components/ClientProfileClient.tsx (display + edit modal pattern):
+```typescript
+type Props = {
+ client: { id: string; name: string; email: string }; // add: propertyAddress?: string | null
+ docs: DocumentRow[];
+};
+// Edit modal call site: add defaultPropertyAddress={client.propertyAddress ?? undefined}
+// Profile display: {client.propertyAddress &&
{client.propertyAddress}
}
+```
+
+From src/app/portal/(protected)/documents/[docId]/page.tsx (client data select):
+```typescript
+// Existing: db.select({ email: clients.email, name: clients.name })
+// Extend to: db.select({ email: clients.email, name: clients.name, propertyAddress: clients.propertyAddress })
+// Pass to PreparePanel: clientPropertyAddress={docClient?.propertyAddress ?? null}
+```
+
+From src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx (textFillData state):
+```typescript
+interface PreparePanelProps {
+ // ... existing props
+ clientPropertyAddress?: string | null; // add
+}
+// Lazy initializer — runs ONCE on mount:
+const [textFillData, setTextFillData] = useState>(
+ () => clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {}
+);
+```
+
+
+
+
+
+
+ Task 1: Schema column, migration, and server action extension
+
+ src/lib/db/schema.ts
+ src/lib/actions/clients.ts
+ drizzle/0007_property_address.sql (generated by drizzle-kit)
+
+
+ 1. In `src/lib/db/schema.ts`, add `propertyAddress: text("property_address")` to the `clients` pgTable definition after the `email` column. No `.notNull()`, no `.default()` — purely nullable. Match the pattern of existing nullable columns (`sentAt`, `filePath`).
+
+ 2. Run migration generation: `npm run db:generate` from the project root. This produces `drizzle/0007_property_address.sql` with `ALTER TABLE "clients" ADD COLUMN "property_address" text;` and updates `drizzle/meta/_journal.json` and the snapshot. Do NOT write the SQL file manually — always use drizzle-kit.
+
+ 3. Apply the migration: `npm run db:migrate`. Verify the column now exists.
+
+ 4. In `src/lib/actions/clients.ts`, extend the Zod `clientSchema` to add: `propertyAddress: z.string().optional()`.
+
+ 5. In `createClient`, after `safeParse` succeeds, add `propertyAddress: parsed.data.propertyAddress || null` to the `.values({...})` object. The `|| null` coercion ensures empty string from FormData becomes NULL in the DB — not an empty string.
+
+ 6. In `updateClient`, add `propertyAddress: parsed.data.propertyAddress || null` to the `.set({...})` object alongside `name`, `email`, `updatedAt`. The existing `revalidatePath` calls cover both list and profile pages — no new paths needed.
+
+ CRITICAL: Do not add `.notNull()` or `.default('')` to the column. Existing client rows have no address — a NOT NULL default would fail the migration.
+
+
+ cd /Users/ccopeland/temp/red && npm run db:generate 2>&1 | tail -5 && npm run db:migrate 2>&1 | tail -5 && npx tsc --noEmit 2>&1 | head -20
+
+
+ drizzle/0007_property_address.sql exists and contains `ADD COLUMN "property_address" text`. Migration applied without errors. TypeScript compilation passes with no new errors in schema.ts or clients.ts.
+
+
+
+
+ Task 2: UI layer — modal input, profile display, and PreparePanel pre-seed
+
+ 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
+
+
+ 1. **ClientModal.tsx** — Add `defaultPropertyAddress?: string` to `ClientModalProps` type. Add a new input field after the email field:
+ ```tsx
+
+
+
+
+ ```
+ Match the exact className pattern used by the existing name and email inputs in this file. The field is optional — no required attribute, no validation message needed.
+
+ 2. **ClientProfileClient.tsx** — Update the `Props` type's `client` object to include `propertyAddress?: string | null`. In the header card section (after the email paragraph), add:
+ ```tsx
+ {client.propertyAddress && (
+
{client.propertyAddress}
+ )}
+ ```
+ Update the `` edit call site to pass `defaultPropertyAddress={client.propertyAddress ?? undefined}`.
+
+ 3. **documents/[docId]/page.tsx** — Find the `db.select({...})` call that fetches client data for `PreparePanel`. Extend the select to include `propertyAddress: clients.propertyAddress`. Pass the value to `PreparePanel` as `clientPropertyAddress={docClient?.propertyAddress ?? null}`. Add the prop to the `PreparePanel` JSX element call.
+
+ 4. **PreparePanel.tsx** — Add `clientPropertyAddress?: string | null` to `PreparePanelProps`. Change the `textFillData` state initialization from `useState>({})` (or equivalent) to a lazy initializer:
+ ```tsx
+ const [textFillData, setTextFillData] = useState>(
+ () => (clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {})
+ );
+ ```
+ This lazy initializer runs exactly once on mount and pre-seeds the map when the value is available. If the client has no address, `textFillData` initializes to `{}` as before — zero behavior change for the existing flow.
+
+ CRITICAL: Do NOT change anything in `/api/documents/[id]/prepare/route.ts` — it already accepts `textFillData: Record` and passes it through untouched.
+
+
+ cd /Users/ccopeland/temp/red && npx tsc --noEmit 2>&1 | head -30
+
+
+ TypeScript compiles clean across all four modified files. ClientModal renders a Property Address input. ClientProfileClient displays address when non-null. PreparePanel receives and seeds the prop. No regressions in existing client create/edit flows.
+
+
+
+
+ Task 3: Human verification — property address end-to-end
+ Pause for human verification of the complete property address feature across all touchpoints.
+
+ Property address field: DB column + migration, extended server actions, ClientModal input (create + edit), ClientProfileClient display, DocumentPage client select extension, PreparePanel state pre-seed.
+
+
+ 1. Navigate to /portal/clients — click "Add Client". Confirm the modal has a "Property Address" field below Email. Create a new test client with an address (e.g., "456 Oak Ave, Provo, UT 84601"). Save — client appears in list.
+
+ 2. Click the new client to open their profile. Confirm the property address appears under the email in the header card.
+
+ 3. Click "Edit Client" — confirm the modal opens with the address pre-filled in the Property Address field. Change the address and save. Confirm the profile now shows the updated address.
+
+ 4. Create a second client with NO property address. Confirm their profile shows no address field (no empty line or placeholder).
+
+ 5. Upload or use an existing document assigned to the client with an address. Open the document's prepare page. Confirm the TextFillForm/PreparePanel shows a "propertyAddress" row pre-populated with the client's address — without the agent typing it.
+
+ 6. Open a document assigned to the client WITHOUT an address. Confirm PreparePanel text fill area is empty as before.
+
+ Expected: All 6 checks pass. No console errors. Existing clients and documents unaffected.
+
+
+ MISSING — human verification required for visual/interactive UI checks
+
+ Human types "approved" after all 6 verification checks pass.
+ Type "approved" to complete Phase 9, or describe any issues found.
+
+
+
+
+
+- `npm run db:migrate` succeeds: `ALTER TABLE "clients" ADD COLUMN "property_address" text` applied cleanly
+- `npx tsc --noEmit` passes with zero new errors across all modified files
+- ClientModal renders Property Address input in both create and edit modes
+- Existing clients (no address) display and edit without errors
+- PreparePanel state initialized with `{ propertyAddress: "..." }` when client has address, `{}` when null
+- No changes to `/api/documents/[id]/prepare/route.ts` or `lib/pdf/prepare-document.ts`
+
+
+
+- Agent can add/edit property address in ClientModal — persisted as NULL (not "") when blank
+- Property address displays on client profile when non-null; hidden when null
+- PreparePanel arrives pre-seeded with client's property address as `textFillData.propertyAddress`
+- TypeScript clean: `npx tsc --noEmit` passes
+- Phase 9 human verification approved (checkpoint)
+- Requirements CLIENT-04 and CLIENT-05 complete
+
+
+