diff --git a/docs/superpowers/plans/2026-07-09-customer-search-company-disambiguation.md b/docs/superpowers/plans/2026-07-09-customer-search-company-disambiguation.md
new file mode 100644
index 0000000..529d34f
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-09-customer-search-company-disambiguation.md
@@ -0,0 +1,385 @@
+# CustomerSearch: Company-Name Disambiguation Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make every `CustomerSearch` instance in the app disambiguate customers that share a name but live at different companies, by showing the company name as the primary row in dropdowns and as a prefix on the trigger.
+
+**Architecture:** Single-component change in `src/components/ui/customer-search.tsx`, plus a one-line fetch tweak on the Process Quote page so the dropdown has company data to render. No schema changes, no persisted-data changes.
+
+**Tech Stack:** React 19, shadcn/ui (Command + Popover primitives), Vitest + Testing Library, PocketBase expand.
+
+**Spec:** [docs/superpowers/specs/2026-07-09-customer-search-company-disambiguation-design.md](file:///Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/docs/superpowers/specs/2026-07-09-customer-search-company-disambiguation-design.md)
+
+---
+
+## File Structure
+
+| File | Responsibility | Touched? |
+| --- | --- | --- |
+| `src/components/ui/customer-search.tsx` | Render trigger + dropdown; resolve company names | Modify |
+| `src/components/ui/customer-search.test.tsx` | New component tests | Create |
+| `src/app/(app)/quotes/[id]/process/page.tsx` | Load customers with `expand=company` so the picker has company names | Modify |
+| `src/app/(app)/quotes/[id]/process/page.test.tsx` | Existing process-page tests — assert new expand shape if they assert the old one | Modify if needed |
+
+---
+
+## Task 1: Failing tests for the new picker shape
+
+**Files:**
+- Create: `src/components/ui/customer-search.test.tsx`
+
+- [ ] **Step 1: Write the failing test file**
+
+```tsx
+import { fireEvent, render, screen, within } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { CustomerSearch } from "./customer-search";
+import type { Customer } from "@/lib/types";
+
+const johnAcme: Customer = {
+ id: "john-acme",
+ customer: "John Smith",
+ email: "john@acme.co.za",
+ number: "+27 11 555 0100",
+ company: "co-acme",
+ // customer-search treats `expand` as a deep-optional; only the
+ // fields the component reads need to be populated.
+ expand: { company: { id: "co-acme", company: "Acme (Pty) Ltd" } },
+} as unknown as Customer;
+
+const johnBeta: Customer = {
+ id: "john-beta",
+ customer: "John Smith",
+ email: "john@beta.co.za",
+ number: "+27 11 555 0200",
+ company: "co-beta",
+ expand: { company: { id: "co-beta", company: "Beta Holdings" } },
+} as unknown as Customer;
+
+const orphan: Customer = {
+ id: "orphan-1",
+ customer: "Orphan Person",
+ email: "orphan@nowhere.co.za",
+ number: "",
+ company: "",
+ expand: { company: null },
+} as unknown as Customer;
+
+const customers = [johnAcme, johnBeta, orphan];
+
+describe("CustomerSearch — company disambiguation", () => {
+ beforeEach(() => {
+ class ResizeObserverMock {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+ }
+ vi.stubGlobal("ResizeObserver", ResizeObserverMock);
+ window.HTMLElement.prototype.scrollIntoView = vi.fn();
+ });
+
+ it("renders the trigger as 'Company · Customer' for the selected customer", () => {
+ render(
+ ,
+ );
+ expect(
+ screen.getByRole("combobox", { name: /customer/i }),
+ ).toHaveTextContent("Acme (Pty) Ltd · John Smith");
+ });
+
+ it("renders dropdown rows with company on top and customer name underneath", async () => {
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole("combobox", { name: /customer/i }));
+ const acmeRow = await screen.findByRole("option", { name: /john smith/i });
+ expect(within(acmeRow).getByText("Acme (Pty) Ltd")).toBeInTheDocument();
+ expect(within(acmeRow).getByText("John Smith")).toBeInTheDocument();
+ });
+
+ it("matches by company name in search", async () => {
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole("combobox", { name: /customer/i }));
+ fireEvent.change(screen.getByPlaceholderText(/search customers/i), {
+ target: { value: "Beta" },
+ });
+ expect(
+ await screen.findByRole("option", { name: /beta holdings/i }),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole("option", { name: /acme \(pty\) ltd/i }),
+ ).not.toBeInTheDocument();
+ });
+
+ it("matches by partial customer name across companies", async () => {
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole("combobox", { name: /customer/i }));
+ fireEvent.change(screen.getByPlaceholderText(/search customers/i), {
+ target: { value: "john" },
+ });
+ const options = await screen.findAllByRole("option", { name: /john smith/i });
+ expect(options).toHaveLength(2);
+ });
+
+ it("matches by email and phone", async () => {
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole("combobox", { name: /customer/i }));
+ fireEvent.change(screen.getByPlaceholderText(/search customers/i), {
+ target: { value: "+27 11 555 0100" },
+ });
+ expect(
+ await screen.findByRole("option", { name: /john smith/i }),
+ ).toBeInTheDocument();
+ });
+
+ it("renders '(no company)' for orphan customers", async () => {
+ render(
+ ,
+ );
+ fireEvent.click(screen.getByRole("combobox", { name: /customer/i }));
+ const row = await screen.findByRole("option", { name: /orphan person/i });
+ expect(within(row).getByText("(no company)")).toBeInTheDocument();
+ });
+});
+```
+
+- [ ] **Step 2: Run the new tests to verify they fail for the right reasons**
+
+Run:
+```bash
+npm run test:run -- src/components/ui/customer-search.test.tsx
+```
+
+Expected:
+- `uses trigger as 'Company · Customer'` fails: current trigger shows only `customer.customer`.
+- `renders dropdown rows with company on top` fails: current dropdown rows don't show company.
+- `matches by company name in search` fails: company name is not in the search index.
+- `matches by partial customer name across companies` may pass; update if it does.
+- `matches by email and phone` passes today.
+- `renders '(no company)' for orphan customers` fails: orphan renders without that fallback.
+
+Adjust the expectations as needed so the failure output reflects the missing redesign, not broken test setup. Do not modify `src/components/ui/customer-search.tsx` in this task.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/ui/customer-search.test.tsx
+git commit -m "test(customer-search): add failing tests for company disambiguation"
+```
+
+---
+
+## Task 2: Implement the picker shape
+
+**Files:**
+- Modify: `src/components/ui/customer-search.tsx`
+
+- [ ] **Step 1: Add a small helper for resolving the company name**
+
+In `src/components/ui/customer-search.tsx`, just below `isPlaceholderCustomer`:
+
+```tsx
+/**
+ * Resolve the company name for a customer row. Reads the PB-expanded
+ * relation first; falls back to "(no company)" so the gap is visible
+ * even if a record is mid-migration.
+ */
+function resolveCompanyName(c: Customer): string {
+ const expanded = (c.expand?.company?.company || "").trim();
+ return expanded || "(no company)";
+}
+```
+
+- [ ] **Step 2: Update the search index to include the company**
+
+Replace the `CommandItem value={...}` computation:
+
+```tsx
+value={[
+ c.customer,
+ c.email,
+ c.number,
+ resolveCompanyName(c),
+]
+ .filter(Boolean)
+ .join(" ")
+ .toLowerCase()}
+```
+
+- [ ] **Step 3: Update the dropdown row markup**
+
+Replace the inner `
` block of the `ordered.map` `CommandItem` so it renders:
+
+```tsx
+
+ {resolveCompanyName(c)}
+ {c.customer}
+ {(c.email || c.number) && (
+
+ {[c.email, c.number].filter(Boolean).join(" · ")}
+
+ )}
+
+```
+
+- [ ] **Step 4: Update the trigger label for the selected customer**
+
+Replace the trigger's selected branch so the label reads `Company · Customer`:
+
+```tsx
+{showSelected && selected
+ ? selectedIsPlaceholder
+ ? `${selected.customer || "(blank)"} — pick real customer`
+ : `${resolveCompanyName(selected)} · ${selected.customer || ""}`
+ : placeholder}
+```
+
+- [ ] **Step 5: Run the failing test file**
+
+Run:
+```bash
+npm run test:run -- src/components/ui/customer-search.test.tsx
+```
+
+Expected: all 6 tests pass.
+
+- [ ] **Step 6: Run lint**
+
+Run:
+```bash
+npx eslint src/components/ui/customer-search.tsx src/components/ui/customer-search.test.tsx
+```
+
+Expected: exit 0.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add src/components/ui/customer-search.tsx
+git commit -m "feat(customer-search): show company first to disambiguate shared names"
+```
+
+---
+
+## Task 3: Process Quote page passes company data to the picker
+
+**Files:**
+- Modify: `src/app/(app)/quotes/[id]/process/page.tsx`
+
+- [ ] **Step 1: Find the customers loader**
+
+The Process Quote page calls `getAllCustomers()` to populate the
+picker. Confirm the call site (`src/app/(app)/quotes/[id]/process/page.tsx`
+around line 56, `const [customers, setCustomers] = useState
([])`).
+
+- [ ] **Step 2: Add a companies load so the picker can resolve names**
+
+The cleanest path is to fetch customers with `expand=company`. Check
+`getAllCustomers()` in `src/lib/pb-helpers.ts` to see whether it
+already supports an expand arg. If it does, pass `expand: "company"`.
+If not, add a new helper `getAllCustomersWithCompany()` that calls
+the same PB list with `expand=company`.
+
+Implementation choice:
+- If `getAllCustomers` already has an `expand` option (most likely),
+ pass `expand: "company"` at the call site.
+- Otherwise, copy the body into a new helper `getAllCustomersWithCompany`
+ and use it on the process page only.
+
+- [ ] **Step 3: Verify the `Customer.expand.company` type exists**
+
+In `src/lib/types.ts`, the `Customer` interface should already declare
+`expand?: { company?: Company }`. If it does not, add it. Do not
+introduce new optional fields beyond what the picker reads.
+
+- [ ] **Step 4: Run any existing process-page tests**
+
+Run:
+```bash
+npm run test:run -- src/app/'(app)'/quotes/'[id]'/process
+```
+
+Expected: existing tests pass. If any test asserts the old customer
+load shape (no expand), update it to expect the expanded shape.
+
+- [ ] **Step 5: Run lint**
+
+Run:
+```bash
+npx eslint src/app/'(app)'/quotes/'[id]'/process/page.tsx src/lib/pb-helpers.ts
+```
+
+Expected: exit 0.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/app/'(app)'/quotes/'[id]'/process/page.tsx src/lib/pb-helpers.ts src/lib/types.ts
+git commit -m "feat(quotes/process): load customers with company expanded for picker"
+```
+
+---
+
+## Task 4: Final verification
+
+- [ ] **Step 1: Run targeted tests**
+
+```bash
+npm run test:run -- src/components/ui/customer-search src/app/'(app)'/quotes/'[id]'/process
+```
+
+Expected: all targeted tests pass.
+
+- [ ] **Step 2: Run full suite**
+
+```bash
+npm run test:run
+```
+
+Expected: same passing set as before plus the new tests. Pre-existing
+failures unrelated to this change (the `collection-pool` failures
+we documented earlier) are acceptable.
+
+- [ ] **Step 3: Manual browser check**
+
+1. Visit `/quotes/[id]/process` for any quote. Open the customer
+ picker.
+2. Confirm dropdown rows show `Company` on top, `Customer` underneath.
+3. Confirm the trigger for the selected customer reads
+ `Company · Customer`.
+4. Search by company name — confirm the filter narrows correctly.
+5. Search by partial customer name — confirm both same-name
+ customers at different companies appear.
+6. Visit `/quotes` and `/customers/[id]` to confirm the picker is
+ rendered identically everywhere.
\ No newline at end of file