diff --git a/.planning/phases/03-agent-portal-shell/03-RESEARCH.md b/.planning/phases/03-agent-portal-shell/03-RESEARCH.md
new file mode 100644
index 0000000..bfd0457
--- /dev/null
+++ b/.planning/phases/03-agent-portal-shell/03-RESEARCH.md
@@ -0,0 +1,575 @@
+# Phase 3: Agent Portal Shell - Research
+
+**Researched:** 2026-03-19
+**Domain:** Next.js 16 App Router — authenticated portal UI, Drizzle ORM schema extension, Server Actions, Tailwind v4 utility-class modals
+**Confidence:** HIGH
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+
+**Portal Navigation**
+- Top nav (horizontal nav bar) — not sidebar
+- Agent lands on Dashboard after login
+- All portal routes live under a `/portal` prefix (e.g., `/portal/dashboard`, `/portal/clients`, `/portal/clients/[id]`)
+- Visual feel: same brand as marketing site (shared colors/fonts) but cleaner and more utilitarian — clearly a logged-in app, not a marketing page
+
+**Client List Design**
+- Card grid layout (not table/list)
+- Each card shows: client name, email, document count, last activity date
+- New client created via modal/dialog triggered from the client list page (not a separate page)
+- Empty state: friendly message ("No clients yet") with a prominent "+ Add your first client" CTA button
+
+**Dashboard Document View**
+- Table layout with color-coded status badges: Draft=gray, Sent=blue, Viewed=amber/yellow, Signed=green
+- Columns: Document name, Client, Status badge, Date sent
+- Controls: filter by status (dropdown) + sort by date (most recent first)
+- Phase 3 seeds a few placeholder document rows (real documents arrive in Phase 4)
+- Empty state not needed since data is seeded
+
+**Client Profile Page**
+- Two-section layout: client details header (name, email, edit button) above a documents table
+- Back link to Clients list
+- Edit client: modal/dialog (consistent with create pattern) — no inline editing
+- Documents section: same table style as dashboard (doc name, status badge, date)
+- Delete client: delete button on profile with confirmation dialog before removing
+
+### Claude's Discretion
+- Exact card dimensions and spacing on client grid
+- Color palette values for status badges (within brand colors)
+- Skeleton/loading states
+- Exact wording for empty states and confirmation dialogs
+- Mobile responsiveness details
+
+### Deferred Ideas (OUT OF SCOPE)
+- None — discussion stayed within phase scope
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| CLIENT-01 | Agent can create a client record with name and email address | Server Action + Drizzle insert pattern; modal form with useActionState |
+| CLIENT-02 | Agent can view a list of all clients | Drizzle select with LEFT JOIN to count documents; card grid layout |
+| CLIENT-03 | Agent can view a client's profile and their associated documents | Dynamic route `/portal/clients/[id]`; params is a Promise in Next.js 16; two-section layout |
+| DASH-01 | Agent can see all documents with their current status: Draft / Sent / Viewed / Signed | Placeholder documents table seeded in DB; status badge pattern with Tailwind |
+| DASH-02 | Agent can see which client each document was sent to and when | Drizzle JOIN across clients + documents tables; columns: document name, client, status, date sent |
+
+
+---
+
+## Summary
+
+This phase builds the authenticated portal shell for the Teressa Copeland Homes agent. The existing foundation (Phase 1) established authentication under `/agent/:path*` with Next-Auth v5 beta. Phase 3 extends to a new `/portal` prefix with a proper top-nav layout and three views: Dashboard, Clients list, and Client Profile.
+
+The most important integration challenge is the routing prefix change: CONTEXT.md requires `/portal/dashboard`, `/portal/clients`, etc., but the existing middleware and auth config protect `/agent/:path*` and redirect logins to `/agent/login`. The middleware matcher and `auth.config.ts` authorized callback must be updated to also cover `/portal/:path*`. The existing `/agent/(protected)/` route group should be migrated or the portal routes added as a new protected segment. The login redirect should remain `/agent/login` (same page, different portal prefix).
+
+The schema needs two new tables: `clients` and `documents`. The `documents` table in Phase 3 is a stub — it holds name, status, client_id, and sent_at. Real document content (PDF blobs, signature fields) arrives in Phases 4–5. Phase 3 seeds placeholder document rows using the existing `scripts/seed.ts` pattern via `npm run db:seed`.
+
+**Primary recommendation:** Add `/portal` to middleware matcher alongside `/agent`; create `src/app/portal/(protected)/` route group with its own layout.tsx containing the top-nav; add `clients` and `documents` (stub) tables to schema; all mutations via Server Actions with `revalidatePath`; modals as pure client components with `useActionState`.
+
+---
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Next.js | 16.2.0 (pinned in package.json) | App Router, Server Actions, layouts, dynamic routes | Already in project; provides SSR + Server Actions for free |
+| Drizzle ORM | ^0.45.1 (in package.json) | Schema definition, migrations, typed queries | Already established pattern in project |
+| next-auth | 5.0.0-beta.30 (pinned) | Session access in Server Components, middleware | Pinned exact version — DO NOT upgrade |
+| Zod | ^4.3.6 (in package.json) | Server Action input validation | Already used in auth.ts; same pattern for client forms |
+| Tailwind CSS | ^4 (in package.json) | Styling — utility classes, CSS variables via `@theme` | Already configured; brand variables defined in globals.css |
+| lucide-react | ^0.577.0 (in package.json) | Icons (UserPlus, Users, LayoutDashboard, etc.) | Already installed in Phase 2 |
+| postgres | ^3.4.8 (in package.json) | PostgreSQL driver | Already used |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| tsx | ^4.21.0 (devDep) | Run seed scripts | Already used for `scripts/seed.ts` |
+| drizzle-kit | ^0.31.10 (devDep) | Generate and run migrations | Already used |
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| Plain Tailwind modals | shadcn/ui Dialog | shadcn is not installed; adding it adds complexity; raw Tailwind modals are 20–30 lines and fully controlled |
+| Server Action mutations | Route Handlers (API routes) | Server Actions are simpler — no fetch, no JSON, same validation pipeline; already used in Phase 2 (contact form) |
+| Separate "create" page | Route-intercepting modal | Intercepting routes add parallel route complexity; client-side state modal is simpler for a single-agent app |
+
+**Installation:** No new packages needed. All dependencies are already installed.
+
+---
+
+## Architecture Patterns
+
+### Recommended Project Structure
+
+```
+src/app/
+├── agent/ # Existing — keep intact
+│ ├── login/ # Existing login page
+│ └── (protected)/ # Existing minimal layout — CAN BE MIGRATED
+│ └── dashboard/ # Phase 1 stub — REPLACE with redirect to /portal/dashboard
+├── portal/ # NEW — Phase 3
+│ ├── (protected)/ # Route group — applies portal layout
+│ │ ├── layout.tsx # Top-nav + auth check
+│ │ ├── dashboard/
+│ │ │ └── page.tsx # DASH-01, DASH-02
+│ │ ├── clients/
+│ │ │ └── page.tsx # CLIENT-01, CLIENT-02 (card grid + create modal)
+│ │ └── clients/
+│ │ └── [id]/
+│ │ └── page.tsx # CLIENT-03 (profile + documents table)
+│ └── _components/ # Portal-scoped components (not shared with marketing)
+│ ├── PortalNav.tsx # Top nav component
+│ ├── ClientCard.tsx # Card for clients grid
+│ ├── ClientModal.tsx # Create/edit modal (shared mode prop)
+│ ├── ConfirmDialog.tsx # Delete confirmation dialog
+│ ├── StatusBadge.tsx # Color-coded status badge
+│ └── DocumentsTable.tsx # Reusable table for dashboard + profile
+src/lib/
+├── db/
+│ ├── schema.ts # ADD: clients + documents tables
+│ └── index.ts # Existing lazy proxy singleton — unchanged
+└── actions/
+ └── clients.ts # NEW: createClient, updateClient, deleteClient server actions
+scripts/
+└── seed.ts # EXTEND: add client + placeholder document rows
+drizzle/
+└── 0001_....sql # NEW migration for clients + documents tables
+```
+
+### Pattern 1: Portal Layout with Top Nav and Auth Check
+
+**What:** A route-group layout that checks session and renders top nav.
+**When to use:** Every portal page.
+
+```typescript
+// src/app/portal/(protected)/layout.tsx
+// Source: /teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/layout.md
+import { auth } from "@/lib/auth";
+import { redirect } from "next/navigation";
+import { PortalNav } from "../_components/PortalNav";
+
+export default async function PortalLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const session = await auth();
+ if (!session) redirect("/agent/login");
+
+ return (
+
+ );
+}
+```
+
+### Pattern 2: Dynamic Route — params is a Promise (Next.js 16)
+
+**What:** In Next.js 16, `params` prop is a Promise, not a plain object.
+**When to use:** Every page with `[id]` segment.
+
+```typescript
+// src/app/portal/(protected)/clients/[id]/page.tsx
+// Source: /teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/page.md
+export default async function ClientProfilePage({
+ params,
+}: {
+ params: Promise<{ id: string }>;
+}) {
+ const { id } = await params;
+ // db query with id...
+}
+```
+
+### Pattern 3: Server Action with Zod Validation + revalidatePath
+
+**What:** Validated server mutation that refreshes the relevant page cache.
+**When to use:** createClient, updateClient, deleteClient.
+
+```typescript
+// src/lib/actions/clients.ts
+// Source: /teressa-copeland-homes/node_modules/next/dist/docs/01-app/02-guides/forms.md
+"use server";
+
+import { z } from "zod";
+import { db } from "@/lib/db";
+import { clients } from "@/lib/db/schema";
+import { auth } from "@/lib/auth";
+import { revalidatePath } from "next/cache";
+
+const clientSchema = z.object({
+ name: z.string().min(1, "Name is required"),
+ email: z.string().email("Valid email required"),
+});
+
+export async function createClient(
+ _prevState: { error?: string } | null,
+ formData: FormData
+) {
+ const session = await auth();
+ if (!session) return { error: "Unauthorized" };
+
+ const parsed = clientSchema.safeParse({
+ name: formData.get("name"),
+ email: formData.get("email"),
+ });
+
+ if (!parsed.success) {
+ return { error: parsed.error.flatten().fieldErrors.name?.[0] ?? "Invalid input" };
+ }
+
+ await db.insert(clients).values(parsed.data);
+ revalidatePath("/portal/clients");
+ return { success: true };
+}
+```
+
+### Pattern 4: Client Component Modal with useActionState
+
+**What:** Modal opened by client-side state, form submits Server Action, closes on success.
+**When to use:** Create client, edit client dialogs.
+
+```typescript
+// src/app/portal/_components/ClientModal.tsx
+// Source: /teressa-copeland-homes/node_modules/next/dist/docs/01-app/02-guides/forms.md (useActionState)
+"use client";
+
+import { useActionState, useEffect } from "react";
+import { createClient } from "@/lib/actions/clients";
+
+type Props = {
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+export function ClientModal({ isOpen, onClose }: Props) {
+ const [state, formAction, pending] = useActionState(createClient, null);
+
+ useEffect(() => {
+ if (state?.success) onClose();
+ }, [state, onClose]);
+
+ if (!isOpen) return null;
+
+ return (
+
+ );
+}
+```
+
+### Pattern 5: Status Badge Component
+
+**What:** Reusable color-coded pill for document status.
+**When to use:** Dashboard table + client profile documents table.
+
+```typescript
+// src/app/portal/_components/StatusBadge.tsx
+type DocumentStatus = "Draft" | "Sent" | "Viewed" | "Signed";
+
+const STATUS_STYLES: Record = {
+ Draft: "bg-gray-100 text-gray-600",
+ Sent: "bg-blue-100 text-blue-700",
+ Viewed: "bg-amber-100 text-amber-700",
+ Signed: "bg-green-100 text-green-700",
+};
+
+export function StatusBadge({ status }: { status: DocumentStatus }) {
+ return (
+
+ {status}
+
+ );
+}
+```
+
+### Pattern 6: Drizzle Schema with Relations
+
+**What:** New `clients` and `documents` (stub) tables with foreign key.
+**When to use:** Phase 3 schema migration.
+
+```typescript
+// src/lib/db/schema.ts — additions
+// Source: Drizzle ORM docs (verified against node_modules/drizzle-orm)
+import { pgTable, text, timestamp, pgEnum } from "drizzle-orm/pg-core";
+
+export const users = pgTable("users", { /* existing */ });
+
+export const clients = pgTable("clients", {
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
+ name: text("name").notNull(),
+ email: text("email").notNull(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
+});
+
+export const documentStatusEnum = pgEnum("document_status", [
+ "Draft", "Sent", "Viewed", "Signed"
+]);
+
+export const documents = pgTable("documents", {
+ id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
+ name: text("name").notNull(),
+ clientId: text("client_id").notNull().references(() => clients.id, { onDelete: "cascade" }),
+ status: documentStatusEnum("status").notNull().default("Draft"),
+ sentAt: timestamp("sent_at"),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+});
+```
+
+### Pattern 7: Dashboard Query with JOIN
+
+**What:** Fetch all documents with client name in one query.
+**When to use:** Dashboard page server component data fetch.
+
+```typescript
+// Inside dashboard/page.tsx (server component)
+import { db } from "@/lib/db";
+import { documents, clients } from "@/lib/db/schema";
+import { eq } from "drizzle-orm";
+
+const rows = await db
+ .select({
+ id: documents.id,
+ name: documents.name,
+ status: documents.status,
+ sentAt: documents.sentAt,
+ clientName: clients.name,
+ clientId: documents.clientId,
+ })
+ .from(documents)
+ .leftJoin(clients, eq(documents.clientId, clients.id))
+ .orderBy(/* desc(documents.sentAt) or desc(documents.createdAt) */);
+```
+
+### Anti-Patterns to Avoid
+
+- **Synchronous params access on Next.js 16:** `params.id` crashes. Always `const { id } = await params` first.
+- **Fetching data in Client Components with useEffect:** Portal pages are server components — query DB directly, pass data as props to client child components.
+- **Inline editing of client fields:** CONTEXT.md locked this to modal/dialog only — no `contentEditable` or inline input trick.
+- **Separate page for "create client":** The decision is modal-on-client-list — do not create `/portal/clients/new/page.tsx`.
+- **Calling `revalidatePath` without 'use server' boundary:** It only works in Server Functions and Route Handlers, not in Client Components.
+- **Hover event handlers on Server Components:** Already a known project pitfall from Phase 2 — use Tailwind `hover:` classes, never `onMouseEnter`/`onMouseLeave` on server components.
+
+---
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Form validation | Custom validators | Zod (already installed) | Already established pattern in auth.ts |
+| Dialog/modal | CSS + JS from scratch | 30-line Tailwind modal pattern (see Pattern 4 above) | Simpler than installing shadcn for two dialogs |
+| UUID generation | `uuid` package | `crypto.randomUUID()` (already used in schema.ts) | Built-in, already established |
+| Cache invalidation after mutation | Router.refresh() hack | `revalidatePath` from `next/cache` | Official Next.js 16 pattern for server actions |
+| Filter/sort state | URL query string manually | `useSearchParams` + `useRouter` | Filter dropdown state can be URL-driven for shareability |
+
+**Key insight:** The project already has all necessary packages. Adding shadcn, headlessui, or any other UI component library is unnecessary overhead for the two modals this phase requires.
+
+---
+
+## Common Pitfalls
+
+### Pitfall 1: Middleware Doesn't Cover `/portal`
+**What goes wrong:** The existing `middleware.ts` matcher is `["/agent/:path*"]` only. Routes under `/portal/` will not be protected — unauthenticated users can access portal pages.
+**Why it happens:** Middleware matcher is explicitly set to `/agent` only.
+**How to avoid:** Update `middleware.ts` matcher to `["/agent/:path*", "/portal/:path*"]` AND update `auth.config.ts` `authorized` callback to include `nextUrl.pathname.startsWith("/portal")` check.
+**Warning signs:** Dashboard page renders without session check passing.
+
+### Pitfall 2: Login Redirect Loops with New `/portal` Prefix
+**What goes wrong:** The `authorized` callback in `auth.config.ts` redirects to `/agent/dashboard` on successful login from login page. If the agent is already on a `/portal` route, double-redirect can occur.
+**Why it happens:** Hard-coded redirect destination in `authorized` callback.
+**How to avoid:** Change the login-success redirect in `auth.config.ts` from `/agent/dashboard` to `/portal/dashboard`. Keep the login page at `/agent/login` (user decision: portal prefix is `/portal/dashboard`, not `/portal/login`).
+**Warning signs:** After login, browser navigates to `/agent/dashboard` (old route) not `/portal/dashboard`.
+
+### Pitfall 3: `params` Is a Promise in Next.js 16
+**What goes wrong:** `const { id } = params` on a client profile page returns `undefined`; the page renders with no data or throws.
+**Why it happens:** Next.js 15+ changed `params` from a synchronous object to a Promise. In Next.js 16, accessing it synchronously may still "work" during local dev (backwards compat shim) but causes issues at build time.
+**How to avoid:** Always `const { id } = await params` in async server components.
+**Warning signs:** `id` is `undefined` on profile page, or TypeScript error about `Promise<{id:string}>` not having `.id`.
+
+### Pitfall 4: `revalidatePath` Import Location
+**What goes wrong:** `import { revalidatePath } from "next/cache"` fails or throws in a Client Component.
+**Why it happens:** `revalidatePath` is server-only. It must be in a `'use server'` file.
+**How to avoid:** Keep all `revalidatePath` calls inside `src/lib/actions/*.ts` files marked `'use server'`.
+**Warning signs:** Runtime error "revalidatePath is not a function" or "Cannot import server-only module."
+
+### Pitfall 5: Drizzle pgEnum Must Be Exported and Used in Migration
+**What goes wrong:** `pgEnum` values in schema don't appear in the migration SQL, causing runtime Drizzle errors when inserting.
+**Why it happens:** If the enum is defined but not exported or referenced by a table column, `drizzle-kit generate` may skip it.
+**How to avoid:** Export the enum, reference it in the table column, then run `npm run db:generate && npm run db:migrate`.
+**Warning signs:** `invalid input value for enum` PostgreSQL error when seeding.
+
+### Pitfall 6: `useActionState` Imported from `react` Not `react-dom`
+**What goes wrong:** `useFormState` / wrong import path causes runtime error.
+**Why it happens:** This was a known project pitfall in Phase 2 — `useActionState` is from `'react'` in React 19, not `'react-dom'`.
+**How to avoid:** `import { useActionState } from 'react'` — the project already has this pattern in the contact form.
+**Warning signs:** "useActionState is not a function" or "useFormState is not exported."
+
+### Pitfall 7: Brand Colors in Tailwind v4 — Use CSS Variables
+**What goes wrong:** One-off hex values like `bg-[#1B2B4B]` get missed by Tailwind v4 JIT and don't appear in output.
+**Why it happens:** Phase 2 pitfall documented in STATE.md: "Brand colors applied via inline style props — Tailwind JIT may miss one-off hex values."
+**How to avoid:** Use CSS variable references: `bg-[var(--navy)]`, `text-[var(--gold)]`. The variables are defined in `globals.css`.
+**Warning signs:** Background color doesn't appear in production build; works in dev but not build.
+
+---
+
+## Code Examples
+
+Verified patterns from official sources:
+
+### Drizzle Insert with onConflictDoNothing (seed pattern)
+```typescript
+// Source: scripts/seed.ts (existing project pattern)
+await db.insert(clients).values([
+ { name: "Sarah Johnson", email: "sarah.j@example.com" },
+ { name: "Mike Torres", email: "m.torres@example.com" },
+]).onConflictDoNothing();
+```
+
+### Drizzle Delete with WHERE
+```typescript
+// Source: drizzle-orm (installed in project, verified in node_modules)
+import { eq } from "drizzle-orm";
+await db.delete(clients).where(eq(clients.id, id));
+```
+
+### revalidatePath After Mutation
+```typescript
+// Source: /teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/04-functions/revalidatePath.md
+import { revalidatePath } from "next/cache";
+revalidatePath("/portal/clients");
+revalidatePath(`/portal/clients/${id}`);
+```
+
+### Filter by Status (URL search params for filter state)
+```typescript
+// src/app/portal/(protected)/dashboard/page.tsx
+export default async function DashboardPage({
+ searchParams,
+}: {
+ searchParams: Promise<{ status?: string; sort?: string }>;
+}) {
+ const { status, sort } = await searchParams;
+ // filter docs in query based on status param
+}
+```
+
+---
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Sync `params` access | `params` is a Promise → must await | Next.js 15+ | All dynamic pages MUST be async and await params |
+| `useFormState` from `react-dom` | `useActionState` from `react` | React 19 | Import from `react`, not `react-dom` |
+| `tailwind.config.js` with content paths | `@import "tailwindcss"` in CSS, no config file | Tailwind v4 | No tailwind.config.ts needed; CSS variables via `@theme inline` |
+| Server Actions imported from page files | Dedicated `actions/` files with `'use server'` at top | Next.js 15+ convention | Cleaner separation; action files can be shared across multiple pages |
+
+**Deprecated/outdated:**
+- `useFormState` (react-dom): Removed in React 19 — project already using `useActionState` from `react`
+- Synchronous `params` access: Works in dev (compat shim) but deprecated and will fail at build
+- `tailwind.config.js` content scanning: Replaced by `@import "tailwindcss"` in Tailwind v4 — already done in this project
+
+---
+
+## Routing Architecture Notes
+
+### Current state (Phase 1/2)
+```
+/agent/login — login page
+/agent/(protected)/ — route group, layout.tsx with basic auth check + logout
+/agent/(protected)/dashboard — stub "coming in Phase 3" page
+middleware.ts matcher: ["/agent/:path*"]
+auth.config.ts: redirect logged-in users on login page → /agent/dashboard
+```
+
+### Required changes for Phase 3
+1. **middleware.ts**: Change matcher to `["/agent/:path*", "/portal/:path*"]`
+2. **auth.config.ts authorized callback**: Add `nextUrl.pathname.startsWith("/portal")` to protected routes check; change post-login redirect from `/agent/dashboard` to `/portal/dashboard`
+3. **`/agent/(protected)/dashboard/page.tsx`**: Replace with redirect to `/portal/dashboard` (or delete and let the middleware redirect handle it)
+4. **New `src/app/portal/(protected)/layout.tsx`**: Full portal nav layout
+
+This migration is clean — no destructive changes to auth. Login page stays at `/agent/login`.
+
+---
+
+## Open Questions
+
+1. **Should `/agent/(protected)/` be removed or left as redirect shell?**
+ - What we know: The existing `layout.tsx` and `dashboard/page.tsx` under `/agent/(protected)/` are Phase 1 stubs meant to be replaced here
+ - What's unclear: Whether any external link or test points to `/agent/dashboard`
+ - Recommendation: Replace `/agent/(protected)/dashboard/page.tsx` with `redirect("/portal/dashboard")` and update `auth.config.ts` post-login redirect. Keep `/agent/(protected)/layout.tsx` as a minimal pass-through or delete it — the portal layout replaces it.
+
+2. **Dashboard filter state: URL params vs. client state**
+ - What we know: CONTEXT.md specifies filter dropdown and sort controls on dashboard
+ - What's unclear: Whether filter should survive page refresh (URL params) or be ephemeral (client state)
+ - Recommendation: URL search params (`?status=Signed`) — enables bookmarking and is the Next.js idiomatic approach for filter state in server components. The `searchParams` prop on the dashboard page handles this without any client JS.
+
+3. **`lastActivityDate` on client cards**
+ - What we know: CONTEXT.md says client cards show "last activity date"
+ - What's unclear: Whether this is `clients.updatedAt` or the `MAX(documents.sentAt)` for that client
+ - Recommendation: Use `MAX(documents.sentAt)` via a Drizzle subquery, falling back to `clients.createdAt` if no documents. This is more meaningful than update timestamp.
+
+---
+
+## Sources
+
+### Primary (HIGH confidence)
+- `/teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/page.md` — params as Promise, page conventions
+- `/teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/layout.md` — layout conventions, params
+- `/teressa-copeland-homes/node_modules/next/dist/docs/01-app/02-guides/forms.md` — Server Actions, useActionState, form validation with Zod
+- `/teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/04-functions/revalidatePath.md` — cache invalidation after mutations
+- `/teressa-copeland-homes/node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/intercepting-routes.md` — modal routing options (considered, not used)
+- `/teressa-copeland-homes/src/lib/db/schema.ts` — existing schema pattern
+- `/teressa-copeland-homes/src/lib/auth.config.ts` — existing middleware/auth config
+- `/teressa-copeland-homes/middleware.ts` — existing matcher config
+- `/teressa-copeland-homes/src/app/globals.css` — brand CSS variables
+- `/teressa-copeland-homes/package.json` — exact installed versions
+
+### Secondary (MEDIUM confidence)
+- drizzle-orm installed in `node_modules/drizzle-orm` — pgEnum, relations, query patterns verified by inspecting existing schema.ts usage and drizzle docs folder (not exhaustively read, but schema pattern consistent with existing code)
+
+### Tertiary (LOW confidence)
+- None
+
+---
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — all packages already in project, verified in package.json
+- Architecture: HIGH — routing docs read directly from installed Next.js 16 dist; existing project structure inspected
+- Pitfalls: HIGH — derived from official docs + STATE.md accumulated decisions from Phases 1–2
+- Schema patterns: HIGH — matches existing schema.ts conventions exactly
+
+**Research date:** 2026-03-19
+**Valid until:** 2026-04-19 (next-auth beta and Next.js 16 move fast; recheck if upgrading either)