# QA Audit Inventory — 2026-06-25

> Generated for [Loop 010 — full product evaluation](https://signals.forwardfuture.com/loop-library/loops/full-product-evaluation-loop/).
> Scope: Admin shell (all 19 modules) + public/auth pages, tested as `platform_admin` via `dev_session` cookie.
> Source: 5 parallel `explore` subagents enumerated every UI control, server action, state, and workflow.

## Inventory organization

- `R1`–`R4`: Role/identity assumptions
- `A1`–`A11`: Admin shell, layout, dashboard
- `M1`–`M11`: Admin modules (orders, stops, products, pickup, shipping, communications, wholesale, settings, analytics, users, import)
- `P1`–`P19`: Public + auth pages
- `T1`–`T6`: Cross-cutting patterns, design system, server-action catalog

Each item: route → purpose → buttons → forms → modals → states → workflows → server actions.

---

## R — Roles & identity

### R1 — `dev_session` cookie impersonation
**File:** `src/lib/admin-permissions.ts` lines 36-39.
Gated by `NODE_ENV !== "production"`. Values: `platform_admin`, `brand_admin`, `store_employee`. Returns admin user via `buildDevAdmin(role)` bypassing Neon Auth.

### R2 — Real Neon Auth (Better Auth)
**File:** `src/lib/auth.ts`. Email/password sign-in, password reset, OAuth via `signInWithGoogleAction`. Session via `getSession()`. **Not exercised** in this audit (auth is stubbed).

### R3 — RBAC permission flags
**File:** `admin_users` table; flags: `can_manage_orders`, `can_manage_products`, `can_manage_stops`, `can_manage_customers`, `can_manage_wholesale`, `can_manage_billing`, `can_manage_settings`, `can_manage_water_log`, `can_manage_time_tracking`, `can_manage_route_trace`, `can_manage_reports`, `can_manage_communications`, `can_manage_pickup`, `can_manage_messages`, `can_manage_refunds`, `can_manage_users`, `active`. Default brand_admin = orders/products/stops/customers/pickup/messages/reports = `true`, rest `false`.

### R4 — Tenant scoping (brand isolation)
- App-layer: `effectiveBrandId = brandId ?? adminUser.brand_id ?? null`; threaded through page → client.
- DB: `current_brand_id()` GUC + RLS policies on most tables (`brand_id = current_brand_id() OR is_platform_admin()`).
- Platform admin: `brand_id IS NULL`, sees all brands via `withPlatformAdmin()`.

---

## A — Admin shell, layout, dashboard

### A1 — `/admin/layout.tsx`
Server root layout. Gates access via `getAdminUser()`, redirects on `must_change_password`. Mounts: `AdminSidebar` (desktop), `MobileTabBar` (mobile via v2 layout), `CommandPalette`, `PWAInstallPrompt`, `ToastContainer`, `RouteAnnouncer`, `SmoothViewTransition`. Brand/brand-list/addons fetched with empty fallbacks on error.

### A2 — `/admin/page.tsx` (legacy dashboard)
Redirects to `/admin/v2`. Two paths: `role === "store_employee"` → 2-card landing (Pickup Lookup + Wholesale); otherwise fetches `getDashboardStats()` + `getBillingOverview()` server-side, renders `DashboardClient`.

### A3 — `/admin/v2/layout.tsx` + `/admin/v2/page.tsx` (mobile-first dashboard)
Mobile "Today" dashboard. Fetches summary/stops/orders in parallel. `Promise.all` failure → silent empty state. Renders `EmptyState` ("No brand selected" / "Nothing to show yet"). `pending_fulfillment > 0` → amber callout link `/admin/v2/orders?status=placed`. Top 5 today stops + top 5 pending orders. Pull-to-refresh wrapper.

### A4 — `AdminShell.tsx`
Responsive shell: `useMediaQuery("(min-width: 1024px)")` swaps desktop sidebar vs mobile (`OfflineBanner` + `MobileTabBar`). Mounts `OfflineBanner` only on mobile. `pb-20` mobile bottom padding for tab bar.

### A5 — `AdminSidebar.tsx`
Desktop nav + mobile slide-in. Nav groups: Workspace / Operations / Communications / Growth / Tracking / Insights / Settings. Brand selector pill. Sign-out button (`signOutAction`). Hamburger on `<lg`. Focus trap + arrow-key navigation + body scroll lock + ESC closes. `requiresPlatformAdmin` filter; `enabledAddons` hides Water Log/Route Trace when disabled.

### A6 — `MobileTabBar.tsx`
Bottom nav for `/admin/v2/*`. 5 tabs: Home / Orders / Stops / Products / More (opens `MoreSheet`). Active highlight via `pathname` match.

### A7 — `MoreSheet.tsx`
Right-anchored native `<dialog>`. Sections: Operations (Pickup, Shipping, Reports, Analytics), Marketing (Communications, Sales), Settings (Settings, Advanced). Saves focus on open, restores on close.

### A8 — `CommandPalette.tsx`
Cmd/Ctrl+K modal. Fuzzy search over static entry list (`./command-palette-data.ts`). Up to 8 results. ↑↓ to navigate, Enter to select, ESC closes. `body.style.overflow = "hidden"` while open. Honors `prefers-reduced-motion`.

### A9 — `BrandSelector.tsx`
Pill dropdown for active brand. Calls `setActiveBrand(brandId)` server action, then `router.refresh()`. Outside-click + ESC close. "All brands" option for platform_admin only. Multi-brand badge.

### A10 — `AdminAccessDenied.tsx`
Static card with "Go to Login" + "Return to homepage" links.

### A11 — `OfflineBanner.tsx`
Sticky top banner: warning-soft when syncing, danger-soft when offline. Polls pendingCount every 1 s. Hidden pre-mount to avoid hydration mismatch.

---

## M — Admin modules

### M1 — Orders
- `/admin/orders` (legacy) → `AdminOrdersPanel`: KPI tiles, status tabs (all/pending/picked_up), search (name/phone/id), stop multi-filter dropdown, table with checkbox + bulk pickup, pagination (PAGE_SIZE 20), `+ New Order` modal. Modal fields: customer name (required), email (optional), phone (optional), stop (optional), items[product+qty+price+fulfillment]. Workflows: create→full-page-reload, single pickup (optimistic), bulk pickup (loop).
- `/admin/orders/[id]` → `OrderEditForm` + `OrderPaymentSection` + `OrderPickupAction`. Edit fields: items qty/price, discount (must be ≥ 0), customer name (required), email/phone, status (pending/confirmed/cancelled — **fulfilled not exposed**), pickup toggle, internal notes. Payment fields: processor (none/manual/stripe/square/cash/venmo/other), payment status, transaction id, refund amount (0 < x ≤ remaining balance) + reason.
- `/admin/orders/new` → redirect to `/admin/orders?new=true` (opens modal).
- `/admin/v2/orders` → card-style list, `?status=` filter (placed/ready/picked-up/cancelled), LIMIT 50, no pagination.
- `/admin/v2/orders/[id]` → mobile detail with `FulfillmentTimeline` + sticky action bar (`OrderActionButtons`).

### M2 — Stops
- `/admin/stops` → `AdminStopsPanel`: status tabs (all/active/draft/inactive), search city/location, `Add Stop` modal, `Upload Schedule` modal, per-row Edit/Publish/Duplicate/Delete. Delete uses inline popover confirm. PAGE_SIZE 50.
- `/admin/stops/[id]` → product assignment + edit form + `MessageCustomersSection`.
- `/admin/stops/new` → inline `NewStopForm` (not modal). Fields: city/state/location/date/time (required), brand select, active, address/zip/cutoff (optional). Also supports `?duplicate={id}`.
- `/admin/v2/stops` → card list bucketed by Morning/Afternoon/Evening/Anytime, `?date=` filter, LIMIT 200.
- Modals: `AddStopModal`, `EditStopModal`, `AddLocationModal`, `EditLocationModal`, `LocationsTab`.

### M3 — Products
- `/admin/products` → `ProductsClient` (legacy): list with brand scoping (platform_admin sees all). No client pagination.
- `/admin/products/new` → `NewProductForm`: name (required), description, price, type (pickup/shipping/both), brand (locked for non-platform_admin), taxable, pickup_type, active, image upload (drag/drop, 5MB cap, client-side resize to 1200px).
- `/admin/products/import` → CSV upload → `parseProductCSV` → preview → `importProductsBatch`.
- `/admin/v2/products` → mobile card list with `?filter=` (all/in-stock/low/out/hidden) + `StockAdjustButton` per card.

### M4 — Pickup
- `/admin/pickup` → `DriverPickupPanel`. Pending orders (filter by stop + search), per-order `Pick Up` button (calls `markPickupComplete`). Picked-up section collapsible (last 72h). Toast `✓ Order picked up` 3s.

### M5 — Shipping
- `/admin/shipping` → `ShippingFulfillmentPanel`. Pre-loaded via `getShippingOrders()`. (Details in deeper inspection.)
- `/admin/settings/shipping` (advanced subtab) → `AdvancedShipping`: static UI mock — no server action calls. Carrier select (fedex/ups/usps/dhl), account/api fields, eye toggle, **Test Connection is simulated (1s setTimeout), Save Settings is simulated (500ms setTimeout)**.

### M6 — Communications (Harvest Reach)
Unified hub at `/admin/communications` with 8 tabs:
- **Campaigns**: list with type/status filters. `New Campaign` modal (name + campaignType). Edit panel: name, type, template, audience target (stop/zip_code/customer_history/all_customers), stop_id/dates, subject, body, schedule (now/later + datetime-local). Save Draft / Schedule Campaign / Send Campaign buttons. `upsertCampaign`, `deleteCampaign`, `getCampaignTemplates`, `getCommunicationSegments`, `previewCampaignAudience`, `sendCampaign` server actions.
- **Compose**: legacy redirect target.
- **Templates**: list + edit (template_type, subject, body_text, body_html, campaign_type). Server: `getCommunicationTemplates`, `getTemplateById`.
- **Contacts**: search + source filter, pagination (50/page, offset), per-row delete (confirm), export CSV (`exportContacts`). Bulk: none. Card layout on `<sm`.
- **Segments**: redirect → `?tab=segments`.
- **Logs**: `MessageLogPanel` — search by email/subject/status, status filter, pagination (20/page, numeric 1-5). Stats cards: total/delivered/failed/pending. `getMessageLogs` (server-side limit 100).
- **Analytics**: redirect → `?tab=analytics`.
- **Settings** (`/admin/communications/settings`): `CommunicationSettingsForm` — sender email/name, reply-to, footerHtml (supports `{unsubscribe_url}` placeholder).
- **Abandoned Carts** (`/admin/communications/abandoned-carts`): 3-step sequence (1h/24h/48h). Stats (total/active/recovered/expired), filter, pagination (25/page, 7×7 prev/next), View/Close/Resend actions per cart. Modal: cart detail with items + timestamps.
- **Welcome Sequence** (`/admin/communications/welcome-sequence`): 4-email dashboard.

Server actions: `getCommunicationCampaigns`, `getCommunicationTemplates`, `getCommunicationContacts`, `getMessageLogs`, `getCommunicationSettings`, `upsertCommunicationSettings`, `getAbandonedCarts`, `manuallyCloseAbandonedCart`, `resendAbandonedCartEmail`, `sendStopBlast`, `previewContactImport`, `importContactsBatch`, `uploadContactsToBucket`, `processBucketImport`, `listImportHistory`, `exportContacts`.

### M7 — Wholesale
- `/admin/wholesale` → `WholesaleClient` (mounted by `getActiveBrandId`). Dashboard tabs: Customers / Orders / Pricing / Settings.
- Customers: list with search, status filter (active/inactive/suspended/pending), pagination, bulk "Send Price Sheet" (counts).
- Orders: list with status filter, bulk Fulfill / bulk Deposit (with confirms).
- Pricing: per-customer price overrides.
- Settings: `WholesaleSettingsForm` — require_approval, min_order_amount, online_payment_enabled, pickup_location, fob_location, from_email, invoice_business_name.

Server actions: `getWholesaleCustomers`, `getWholesaleOrders`, `updateWholesaleCustomer`, `sendPriceSheetToCustomer`, `bulkFulfillOrders`, `recordWholesaleDeposit`, `getWholesaleSettings`, `updateWholesaleSettings`.

### M8 — Settings (`/admin/settings`)
Main hub. Tabs (URL anchor): brand / addons / users / billing.
- **Brand** (`/admin/settings` `#brand`): `BrandSettingsForm` — company info, address, 4 logo uploads, email/invoice branding, storefront customization (tagline, hero image, about, footer), brand colors (4 pickers), tax settings (collect toggle + nexus states), feature toggles (wholesale/zip/schedule-pdf/text-alerts), schedule PDF footer.
- **Add-ons** (`/admin/settings` `#addons`): `BrandFeatureCards` — grid of cards from `ADDON_CATALOG`. Enable/Disable toggle (with GlassModal confirm) + optimistic with rollback on failure.
- **Users** (`/admin/settings` `#users`): list of admin users + `CreateUserModal`. Create fields: email (required, must include @), password (≥6), display name, phone, role (3-way radio gated by caller), brand (required when brand_admin/store_employee), 9 permission toggles. Surfaces temp password on success.
- **Billing**: tier display + Stripe portal link (if customer_id set).
- **Payments** (`/admin/settings/payments`): `PaymentSettingsForm` — provider radio (None/Stripe/Square), Stripe OAuth button (`/api/stripe/oauth`), Square OAuth (`/api/stripe/oauth` → Square). Square Location ID validated `L` prefix. Inventory Mode radio (None/RC→Square/Square→RC/Bidirectional). Sync Products/Orders/All Now buttons. Wholesale Webhook Log.
- **Square Sync** (`/admin/settings/square-sync`): `SquareSyncSettingsClient` — provider/account/last sync/status grid + sync settings + manual sync + last 50 sync log entries.
- **AI** (`/admin/settings/ai`): link cards only.
- **Advanced** (`/admin/advanced`): aggregator cards linking to sub-pages.
  - **Integrations** (`AdvancedIntegrations`): Resend + Twilio credential cards (Test Connection + Save).
  - **AI Tools** (`AdvancedAIPanel`): provider cards (OpenAI/Anthropic/Google/xAI/Custom), API key (eye toggle), org ID (OpenAI only), custom endpoint (Custom only), model chips with cost display, Test + Save.
  - **Square Sync** (`AdvancedSquareSync`): static stub — App ID/Access Token/Location ID, sync toggles hardcoded ON, **Test/Save purely client-side setTimeout**.
  - **Shipping** (`AdvancedShipping`): static mock — same as M5.
  - **Webhooks**: "Coming Soon" placeholder.
  - **Payments** (`AdvancedPayments`): Stripe Connect onboarding — Connect with Stripe / Complete Setup / Stripe Dashboard / Disconnect. `createStripeConnectLink`, `refreshStripeConnectLink`, `disconnectStripeConnect`, `createStripeDashboardLink`, `getStripeConnectStatus`.

### M9 — Analytics (`/admin/analytics`)
`AnalyticsDashboard`. KPI cards (revenue, orders, customers, AOV), revenue chart, top products, recent orders, customer growth ring, conversion funnel. Period selector (7D/30D/90D/1Y) — **purely visual, no server refetch**. Refresh + Export Report (no-op). Server: `getAnalyticsMetrics`, `getRevenueChart`, `getTopProducts`, `getRecentOrders`, `getCustomerGrowth`, `getConversionFunnel`.

### M10 — Users (`/admin/users`)
Redirect → `/admin/settings#users`.

### M11 — Other admin modules
- **Import Center** (`/admin/import`): `ImportCenterClient` — 4-step wizard (Upload → Analyze → Preview → Import). Accepts CSV/XLSX/XLS/TXT (≤5,000 rows / 10 MB). AI-powered type detection (products/orders/contacts/stops). Per-column mapping dropdowns. Brand select (platform_admin only). Server: `analyzeImport`, `executeImport`.
- **Sales import** (`/admin/sales/import`): CSV → `parseOrderCSV` → `importOrdersBatch`. Brand ID (UUID text input) required.
- **Time Tracking** (`/admin/time-tracking`): `TimeTrackingAdminPanel`. Tabs: Summary / Workers / Tasks / Logs / Settings. Workers modal (name, role, language, active). Tasks modal (name, name_es, unit, active). Logs: date range + worker + task filters, pagination (50/page offset). Export CSV → `/api/time-tracking/export`. Tuxedo-only.
- **Water Log** (`/admin/water-log`): `WaterLogAdminPanel`. Tabs inline: Headgates / Users / Entries. Add Headgate (name, unit, notes) inline. Add User (name, role=irrigator/water_admin, language=en/es, phone). 4-digit PIN auto-generated + shown in `PinBanner`. Per-headgate: Edit (link), Rotate token, Delete. Per-entry: date range + headgate + user + method filters. Export CSV (5,000 entries). Preview Report = `window.alert` of text. Settings: `/admin/water-log/settings` — admin portal toggle, session duration slider (1-168h), coarse permission flags (edit/delete/export), alert phone. Tuxedo + `can_manage_water_log`.
- **Headgates** (`/admin/water-log/headgates`): `HeadgatesManager`. Add inline + Edit modal + QR modal (Preview/Print/Download tabs). Bulk QR print via `POST /api/water-qr-sheet` → new window. Per-headgate QR endpoints: `/api/water-qr-label`, `/api/water-qr?token=...`.
- **Route Trace** (`/admin/route-trace`, `/admin/route-trace/lots`, `/admin/route-trace/lookup`, `/admin/route-trace/settings`): all render the same `RouteTracePage` shell. Server: `getRouteTraceStats`, `getRouteTraceLots`, `getHarvestLotsReadyToHaul`, `getFieldYieldSummary`, `getInventoryByCrop`, `getRecentLotEvents`. Lot detail at `/admin/route-trace/lots/[id]` with timeline + order fulfillment. New lot → modal in Lots tab.
- **Launch Checklist** (`/admin/launch-checklist`): 8 categories × 4 tasks each (32 tasks). Progress is **purely cosmetic** (hardcoded 20% / 7 of 32). Checkbox buttons have no handlers. Export PDF / Mark All Complete / Mark Launch Complete all no-ops.

---

## P — Public + auth pages

### P1 — `/login`
Email/password + Google OAuth + dev-mode shortcuts (Platform/Brand/Store buttons).
- Form: email (required, autoComplete=username), password (required, autoComplete=current-password).
- Buttons: Sign in (submit), Continue with Google (OAuth), 3 dev shortcuts.
- Links: `/forgot-password` (Forgot?).
- States: loading (sign-in/Google), error (inline role=alert), success → redirect `/admin`.
- Dev shortcuts only when `NODE_ENV !== "production"`.

### P2 — `/change-password`
Force password update.
- Form: new password (required, minLength 8), confirm password (required).
- Buttons: Update Password (submit), "Sign out instead" link → `/logout`.
- Server: `POST /api/auth/change-password`.

### P3 — `/reset-password`
New password from reset link.
- Form: new password + confirm (both required, ≥ 8 chars, must match).
- Server: `POST /api/auth/reset-password`.

### P4 — `/logout`
Effect-only: spinner + "Signing out..." → calls `/api/auth/sign-out` → `/login`.

### P5 — `/maintenance`
Static splash. No controls. mailto + `/contact` links.

### P6 — `/` (homepage)
Marketing landing (`LandingPageClient` → `HeroSection`). No forms. CTA links: `/login`, `/brands`, `/contact`. Scroll indicator.

### P7 — `/pricing`
Pricing page. BillingToggle (Monthly/Annual, aria-pressed, "-25%" badge). 3 plan CTA buttons → `/admin`. Compare table expand/collapse. 8 FAQ accordion toggles. No form/select/textarea.

### P8 — `/contact`
Contact form (simulated setTimeout submit).
- Fields: name (required), email (required), topic select (General/Orders/Wholesale/Partner/Tech), message textarea (required, rows 5).
- Buttons: Send Message (submit, spinner), Send another message (success state).
- tel + mailto links.
- States: idle, isSubmitting, success.

### P9 — `/blog`
Static. Newsletter form (decorative — no submit handler). "Read More" links to `/blog/getting-started-with-route-commerce` (slug not implemented). 3 "Download →" buttons (no handlers).

### P10 — `/changelog`
Static timeline. Links: `/admin`, `/`, RSS feed `/api/feed/changelog.xml`, `/waitlist`.

### P11 — `/roadmap`
Static 3-column roadmap.
- Suggestion form: feature title (text), description (textarea), category (select). **No submit handler.**
- 6 Upvote buttons — **no handlers.**
- Anchor jump `#suggest`.

### P12 — `/security`
Static. mailto security@routecommerce.com for vulnerability reports.

### P13 — `/privacy-policy` + `/terms-and-conditions`
Pure static legal text.

### P14 — `/brands`
Partner brands showcase. Per-brand `<Link href="/${slug}">` "Visit Store" + "View Stops". 3D-tilt parallax.

### P15 — `/waitlist`
`<WaitlistForm>`.
- Fields: name (optional text), email (required), referral select (Google/Social/Friend/Event/Podcast/Blog/Other).
- Button: Join the Waitlist (spinner → success card).
- Server: `POST /api/waitlist`.
- States: idle, isSubmitting, error, success.

### P16 — `/cart`
`<CartClient>`.
- Per-item buttons: − (decrease qty), + (increase qty), Remove.
- Modal: "Choose Pickup Stop" (stop buttons + Cancel).
- Buttons: Continue to Checkout (state-gated: Cart is Empty / Select Pickup Stop First / Remove Incompatible Items First / Choose Correct Stop / Continue Shopping).
- States: empty, stop mismatch alert, incompatible items, availability error, loading stops.

### P17 — `/checkout`
`<CheckoutClient>` + Stripe Express iframe.
- Fields: customer_name (text), customer_email (required), customer_phone (tel optional), stop_id (select required when no stop), shipping_address/city/state (maxLength 2 upper)/postal_code (when ship items).
- Buttons: Change pickup stop, "Use secure hosted checkout →" (`POST createRetailStripeCheckoutSession`), `<Link href="/">` Back to storefront.
- States: empty cart, hostedLoading, hostedError, hasPickup/hasShed/hasStop/hasShip branching.

### P18 — Auth API routes
| Route | Method | Behavior |
|---|---|---|
| `/api/auth/sign-in` | POST | `{success}` or 400/401/500 with `{error,message}`. |
| `/api/auth/sign-out` | POST | Server sign-out + redirect `/login`. |
| `/api/auth/forgot-password` | POST | Always `{success:true}` (anti-enumeration). Calls `requestPasswordReset`. |
| `/api/auth/reset-password` | POST | Min 8 chars. |
| `/api/auth/change-password` | POST | Auth-required; uses `setUserPassword`. |
| `/api/auth/[...nextauth]` | GET/POST | Neon Auth handler proxy. |

### P19 — Other public pages
- `/water` — likely the field portal; brief enumeration.
- `/test-simple.html` — diagnostic HTML.
- `/api/feed/changelog.xml` — referenced but not in scope.

---

## T — Cross-cutting patterns

### T1 — Server-action catalog (top-level actions touched by the audited routes)
`getAdminUser`, `getAdminOrders`, `getAdminOrderDetail`, `markPickupComplete`, `createAdminOrder`, `updateOrder`, `updateOrderItem`, `deleteOrderItem`, `createRefund`, `getShippingOrders`, `createStop`, `updateStop`, `deleteStop`, `publishStop`, `assignProductToStop`, `unassignProductFromStop`, `createLocation`, `updateLocation`, `deleteLocation`, `createProduct`, `uploadProductImage`, `importProductsBatch`, `parseProductCSV`, `analyzeImport`, `executeImport`, `getAnalyticsMetrics`, `getRevenueChart`, `getTopProducts`, `getRecentOrders`, `getCustomerGrowth`, `getConversionFunnel`, `getCommunicationCampaigns`, `getCommunicationTemplates`, `getCommunicationContacts`, `getMessageLogs`, `getCommunicationSettings`, `upsertCommunicationSettings`, `getAbandonedCarts`, `manuallyCloseAbandonedCart`, `resendAbandonedCartEmail`, `sendStopBlast`, `previewContactImport`, `importContactsBatch`, `exportContacts`, `getWholesaleCustomers`, `getWholesaleOrders`, `getWholesaleSettings`, `updateWholesaleSettings`, `sendPriceSheetToCustomer`, `bulkFulfillOrders`, `recordWholesaleDeposit`, `createAdminUser`, `getAdminUsers`, `getBrands`, `getPaymentSettings`, `savePaymentSettings`, `syncSquareNow`, `getSyncLog`, `getResendCredentials`, `saveResendCredentials`, `testResendConnection`, `getTwilioCredentials`, `saveTwilioCredentials`, `testTwilioConnection`, `getStripeConnectStatus`, `createStripeConnectLink`, `refreshStripeConnectLink`, `disconnectStripeConnect`, `createStripeDashboardLink`, `getWaterIrrigators`, `getWaterHeadgatesAdmin`, `getWaterEntries`, `createWaterHeadgate`, `updateWaterHeadgate`, `deleteWaterHeadgate`, `regenerateHeadgateToken`, `createWaterUser`, `resetWaterIrrigatorPin`, `deleteWaterUser`, `getWaterAdminSettings`, `saveWaterAdminSettings`, `regenerateAdminPin`, `getTimeTrackingWorkers`, `getTimeTrackingTasks`, `getTimeTrackingSummary`, `getWorkerTimeLogs`, `createTimeWorker`, `updateTimeWorker`, `deleteTimeWorker`, `resetTimeWorkerPin`, `createTimeTask`, `updateTimeTask`, `deleteTimeTask`, `getRouteTraceStats`, `getRouteTraceLots`, `getRouteTraceLotDetail`, `getLotOrders`, `getHarvestLotsReadyToHaul`, `getFieldYieldSummary`, `getInventoryByCrop`, `getRecentLotEvents`, `signOutAction`, `signInWithGoogleAction`, `toggleBrandFeature`.

### T2 — Server-action categories
- **CRUD**: orders, stops, products, customers, campaigns, templates, contacts, wholesale_*, time_tracking_*, water_log_*, harvest_lots.
- **Bulk**: `bulkFulfillOrders`, `sendPriceSheetToCustomer` (wholesale); `markPickupComplete` loop (orders).
- **Auth**: sign-in, sign-out, password reset/change, OAuth.
- **Integration side-effects**: `syncSquareNow`, `sendCampaign`, `sendStopBlast`, `savePaymentSettings` → Stripe OAuth URL generation.
- **Reads**: `get*` actions returning typed rows for page/server-component hydration.

### T3 — Server-component vs client-component split
- Every route's `page.tsx` is `"use server"` and resolves `getAdminUser()` + brand + initial datasets in parallel.
- Heavy interactive panels (orders, stops, products, communications, settings, water-log) are `"use client"` components mounted by their page.
- v2 dashboard surfaces (`/admin/v2/*`) lean more on server components with `pool.query` directly.

### T4 — Auth gating patterns
- Page-level: `if (!adminUser) redirect("/login")` or `<AdminAccessDenied />`.
- Permission check: `if (!adminUser.can_manage_X) redirect("/admin/pickup")`.
- Brand-scope: `effectiveBrandId = brandId ?? adminUser.brand_id ?? firstBrandId`.
- Direct DB queries in pages use `getActiveBrandId()` helper; admin queries use `pool.query` with explicit `WHERE brand_id = $1` or `1=1` for platform_admin.

### T5 — Modal inventory
| Modal | Triggered from | Server action |
|---|---|---|
| `GlassModal` shell | many | n/a (UI only) |
| `ElegantModal` shell | some | n/a |
| `AdminOrdersPanel` New Order modal | inline fixed overlay | `createAdminOrder` |
| `AddStopModal` | `AdminStopsPanel` Add | `createStop` |
| `EditStopModal` | inline | `createStop` / `updateStop` |
| `NewStopForm` (inline, not modal) | `/admin/stops/new` | `createStop` |
| `AddLocationModal` / `EditLocationModal` | `LocationsTab` | `createLocation` / `updateLocation` |
| `CreateUserModal` | settings Users | `createAdminUser` |
| `CampaignListPanel` New Campaign | `/admin/communications` | `upsertCampaign` |
| `AbandonedCartDashboard` detail | abandoned carts page | read-only |
| `BrandFeatureCards` Enable confirm | settings Add-ons | `toggleBrandFeature` |
| `HeadgatesManager` Edit/QR | headgates page | `updateWaterHeadgate`, `regenerateHeadgateToken` |
| Cart stop picker | `/cart` | n/a |
| `UpgradePlanModal` (lazy) | `DashboardClient` / `DashboardUpgradeButton` | n/a |
| `CommandPalette` | global Cmd+K | n/a |

### T6 — Edge cases inventory (extracted for Phase 3)
- **Auth/role**: dev_session values, Neon Auth failures, must_change_password redirect, role-mismatched user without brand links.
- **Empty/zero states**: 0 brands, 0 stops, 0 orders, 0 contacts, 0 campaigns, 0 templates, 0 wholesale customers.
- **Long data**: very long product names, unicode (αβγ δεζ 🌿🍊 谢谢), special chars (`<script>`-style), URL-encoded emails.
- **Pagination boundaries**: page 0, last page, beyond last page, single result, exactly PAGE_SIZE, 0 results.
- **Brand switching**: platform_admin with no active brand, brand with no admin links, multi-brand admin.
- **Date/time**: past stops, future stops, today, before cutoff, after cutoff, DST boundaries, leap years (2024 just passed).
- **Status enums**: every allowed value + one disallowed value per field for boundary tests.
- **Stop states**: active/paused/closed, public/private, with/without cutoff, with/without address.
- **Order states**: all 4 statuses × 3 fulfillment types × with/without stop × with/without customer.
- **Order items**: zero items (free order), many items, mixed pickup/ship within one order.
- **Customers**: only email, only phone, both, no source, no metadata.
- **Bulk actions**: 0 selected, 1 selected, all selected, partial success.
- **Network failures**: timeouts, partial responses (not testable here — would need test API endpoints).
- **Image upload**: missing file, oversized file, wrong type, network failure mid-upload.
- **CSV import**: malformed CSV, missing columns, duplicate rows, very large file (>5 MB → bucket path).
- **Modal stacking**: open modal over modal.
- **Browser back/forward**: `/admin/orders?new=true` modal state preserved?
- **Concurrent edits**: two admin tabs editing same order.
- **Offline mode**: `OfflineBanner` state transitions + queued mutations.
- **Permission transitions**: admin user disabled mid-session.

---

## Items intentionally not enumerated in this run

- Server actions in `src/actions/` (catalogued by name in T1; full parameter signatures to be reviewed during acceptance-criteria phase).
- JSON API route handlers (`src/app/api/**/route.ts` beyond auth) — 62 routes. Out of scope for UI inventory; they will be exercised indirectly via the admin UI but not unit-tested separately unless a specific bug surfaces.
- Wholesale portal (`/wholesale/*`) — out of scope per user.
- Brand storefronts (`/tuxedo/*`, `/indian-river-direct/*`) — out of scope per user.
- Database RPCs (PL/pgSQL functions) — out of scope for inventory; exercised indirectly via server actions.
- Background jobs (cron, email automation) — exercised via curl on their API endpoints only if a bug is suspected.