---
phase: 20-fab-full-screen-catalog-search
plan: 02
type: execute
wave: 2
depends_on: ["20-01"]
files_modified:
  - src/client/components/FabMenu.tsx
  - src/client/components/CatalogSearchOverlay.tsx
  - src/client/routes/__root.tsx
autonomous: false
requirements:
  - CATFLOW-01
  - CATFLOW-02

must_haves:
  truths:
    - "FAB is visible on all authenticated pages (collection, threads, setups, dashboard, settings, global-items)"
    - "FAB is NOT visible on login page or public profile/setup pages"
    - "Tapping FAB opens a mini menu with 'Add to Collection' and 'Start New Thread' options"
    - "On setups page, FAB menu also shows 'New Setup' option"
    - "Tapping 'Add to Collection' opens full-screen catalog search overlay in 'collection' mode"
    - "Tapping 'Start New Thread' opens full-screen catalog search overlay in 'thread' mode"
    - "Catalog search overlay has search input with debounce, tag chips, and result cards"
    - "Tag chips toggle on/off and filter search results via AND logic"
    - "Result cards show brand, model, weight, price, category, and an Add button (stub)"
    - "Back arrow closes the catalog search overlay"
  artifacts:
    - path: "src/client/components/FabMenu.tsx"
      provides: "FAB with mini menu"
      min_lines: 60
    - path: "src/client/components/CatalogSearchOverlay.tsx"
      provides: "Full-screen catalog search"
      min_lines: 100
  key_links:
    - from: "src/client/components/FabMenu.tsx"
      to: "src/client/stores/uiStore.ts"
      via: "useUIStore (fabMenuOpen, openCatalogSearch)"
      pattern: "useUIStore"
    - from: "src/client/components/CatalogSearchOverlay.tsx"
      to: "src/client/hooks/useGlobalItems.ts"
      via: "useGlobalItems(query, tags)"
      pattern: "useGlobalItems"
    - from: "src/client/components/CatalogSearchOverlay.tsx"
      to: "src/client/hooks/useTags.ts"
      via: "useTags()"
      pattern: "useTags"
    - from: "src/client/routes/__root.tsx"
      to: "src/client/components/FabMenu.tsx"
      via: "FabMenu component render"
      pattern: "FabMenu"
    - from: "src/client/routes/__root.tsx"
      to: "src/client/components/CatalogSearchOverlay.tsx"
      via: "CatalogSearchOverlay component render"
      pattern: "CatalogSearchOverlay"
---

<objective>
Build the FAB mini menu component and full-screen catalog search overlay, then wire them into the root layout.

Purpose: Delivers the primary user-facing UI for Phase 20 -- the global FAB with action menu and the catalog discovery experience with search + tag filtering.
Output: FabMenu.tsx, CatalogSearchOverlay.tsx, updated __root.tsx with new FAB and overlay rendering.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/20-fab-full-screen-catalog-search/20-CONTEXT.md
@.planning/phases/20-fab-full-screen-catalog-search/20-RESEARCH.md
@.planning/phases/20-fab-full-screen-catalog-search/20-01-SUMMARY.md

@src/client/routes/__root.tsx
@src/client/stores/uiStore.ts
@src/client/components/GlobalItemCard.tsx
@src/client/components/CreateThreadModal.tsx
@src/client/hooks/useTags.ts
@src/client/hooks/useGlobalItems.ts
@src/client/hooks/useFormatters.ts
@src/client/routes/global-items/index.tsx

<interfaces>
<!-- From Plan 01 outputs (UIStore extensions) -->
From src/client/stores/uiStore.ts (after Plan 01):
```typescript
// FAB menu state
fabMenuOpen: boolean;
openFabMenu: () => void;
closeFabMenu: () => void;

// Catalog search state
catalogSearchOpen: boolean;
catalogSearchMode: "collection" | "thread" | null;
openCatalogSearch: (mode: "collection" | "thread") => void;
closeCatalogSearch: () => void;
```

From src/client/hooks/useTags.ts (after Plan 01):
```typescript
export interface Tag { id: number; name: string; }
export function useTags(): UseQueryResult<Tag[]>;
```

From src/client/hooks/useGlobalItems.ts (after Plan 01):
```typescript
export function useGlobalItems(query?: string, tags?: string[]): UseQueryResult<GlobalItem[]>;
```

From src/client/hooks/useFormatters.ts:
```typescript
export function useFormatters(): { weight: (grams: number) => string; price: (cents: number) => string; };
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: FabMenu component and CatalogSearchOverlay component</name>
  <files>
    src/client/components/FabMenu.tsx,
    src/client/components/CatalogSearchOverlay.tsx
  </files>
  <read_first>
    src/client/stores/uiStore.ts,
    src/client/components/GlobalItemCard.tsx,
    src/client/components/CreateThreadModal.tsx,
    src/client/routes/global-items/index.tsx,
    src/client/hooks/useTags.ts,
    src/client/hooks/useGlobalItems.ts,
    src/client/hooks/useFormatters.ts,
    .planning/phases/20-fab-full-screen-catalog-search/20-CONTEXT.md
  </read_first>
  <action>
    **FabMenu.tsx** (per D-01 through D-06, D-23):

    1. Create `src/client/components/FabMenu.tsx` that accepts props: `{ isSetupsPage: boolean }`.
    2. Read state from UIStore: `fabMenuOpen`, `openFabMenu`, `closeFabMenu`, `openCatalogSearch`, `catalogSearchOpen`.
    3. The main FAB button: `fixed bottom-6 right-6 z-20 w-14 h-14 bg-gray-700 hover:bg-gray-800 text-white rounded-full shadow-lg`. Shows "+" icon normally, rotates to "x" when menu is open.
    4. When `fabMenuOpen` is true, render:
       - A subtle backdrop (`fixed inset-0 z-10 bg-black/20`) that closes menu on click (per D-02).
       - Menu items stack vertically above the FAB (per D-02), each with an icon + label:
         - "Add to Collection" with Package icon -- calls `openCatalogSearch("collection")` (per D-04)
         - "Start New Thread" with Search icon -- calls `openCatalogSearch("thread")` (per D-04)
         - If `isSetupsPage`, also show "New Setup" with Plus icon -- triggers existing setup creation flow (per D-05). For now, navigate to `/setups` with a query param or call the existing pattern. Read how setups page handles creation and replicate. If unclear, use `navigate({ to: "/setups", search: { action: "create" } })` as a stub.
       - Menu items positioned `fixed bottom-24 right-6 z-20` (above FAB), each item spaced ~56px apart vertically.
    5. Use Framer Motion `AnimatePresence` + `motion.div` for menu entrance/exit:
       - Items appear with `initial={{ opacity: 0, y: 10, scale: 0.9 }}`, `animate={{ opacity: 1, y: 0, scale: 1 }}`, `exit={{ opacity: 0, y: 10, scale: 0.9 }}`.
       - Transition: `{ type: "spring", stiffness: 400, damping: 25 }` (per research Pattern 5).
       - Stagger children slightly (50ms delay between items).
    6. Each menu item: white background with shadow, `rounded-full px-4 py-3 flex items-center gap-3`, icon (20x20) + text label (`text-sm font-medium text-gray-700`).
    7. Hide FAB entirely when `catalogSearchOpen` is true (per research Pitfall #2 -- overlay covers everything, FAB shouldn't peek through).
    8. FAB should NOT appear on login page or public routes (per D-06). This is handled by the parent (__root.tsx), not FabMenu itself.

    **CatalogSearchOverlay.tsx** (per D-07 through D-13, D-14 through D-20):

    1. Create `src/client/components/CatalogSearchOverlay.tsx`.
    2. Read state from UIStore: `catalogSearchOpen`, `catalogSearchMode`, `closeCatalogSearch`.
    3. Only render when `catalogSearchOpen` is true. Wrap in `AnimatePresence` for enter/exit.
    4. Overlay container: `fixed inset-0 z-50 bg-white flex flex-col` (per D-07). Full white background, not a backdrop modal.
    5. Add `overflow-hidden` to document body when overlay is open (per research Pitfall #4). Use `useEffect` to toggle `document.body.style.overflow`.

    **Header section** (per D-08):
    - Top bar with: back arrow button (left, calls `closeCatalogSearch`), context text ("Adding to Collection" when mode is "collection", "Starting a Thread" when mode is "thread").
    - Below: large search input (`text-lg px-4 py-3 border-b border-gray-100 w-full`), autofocused, with placeholder "Search the catalog...".
    - Debounce search input using the established pattern (per research Pattern 4): `useState` for input, `useEffect` with 300ms `setTimeout` for debouncedQuery.

    **Tag chips section** (per D-09, D-14 through D-17):
    - Below search bar: horizontal scrollable row (`flex gap-2 overflow-x-auto px-4 py-3 no-scrollbar`).
    - Fetch tags with `useTags()` hook.
    - Each tag chip: `rounded-full px-3 py-1.5 text-sm font-medium cursor-pointer transition-colors whitespace-nowrap`.
    - Active state (per D-16): `bg-blue-100 text-blue-700`.
    - Inactive state (per D-16): `bg-gray-100 text-gray-500`.
    - Manage selected tags as local state: `useState<string[]>([])`. Toggle tag name on click. Multiple selections = AND filtering (per D-15).
    - Pass selected tag names to `useGlobalItems(debouncedQuery, selectedTags)`.

    **Results grid** (per D-10, D-18 through D-20):
    - Grid layout: `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 p-4 overflow-y-auto flex-1`.
    - For each result, render a card adapted from `GlobalItemCard` pattern:
      - Image area (aspect-[4/3] with bg-gray-50 placeholder if no image).
      - Brand (small, uppercase, gray).
      - Model name (semibold, truncated).
      - Badge row: weight (blue), price (green), category (gray) -- same colors as GlobalItemCard.
      - "Add" button: `bg-gray-700 text-white rounded-lg px-3 py-1.5 text-xs font-medium mt-2`. Per D-19, this is a STUB in Phase 20 -- clicking shows a brief toast or does nothing. Add `onClick` that logs or shows a placeholder message. Do NOT implement actual add-to-collection or add-to-thread flow.
    - Use `useFormatters()` for weight/price formatting.

    **Loading state** (per D-13):
    - When query is loading, show 6 skeleton cards matching the grid pattern.
    - Skeleton card: `animate-pulse` with gray placeholder blocks for image, title, badges.
    - Copy the skeleton pattern from `src/client/routes/global-items/index.tsx` if available, or create matching pattern.

    **Empty state** (per D-12):
    - When query returns empty results and not loading: centered message "No items found" with subtle text.
    - Include a non-functional "Add Manually" text link (`text-blue-600 underline`) that does nothing in Phase 20 (Phase 22 wires it). If Claude judges it premature, omit the link entirely -- this is Claude's discretion per CONTEXT.md.
  </action>
  <verify>
    <automated>bun run lint && bun run build</automated>
  </verify>
  <acceptance_criteria>
    - File `src/client/components/FabMenu.tsx` exists with at least 60 lines
    - File `src/client/components/CatalogSearchOverlay.tsx` exists with at least 100 lines
    - FabMenu.tsx imports from `framer-motion` (AnimatePresence)
    - FabMenu.tsx imports `useUIStore` from stores
    - FabMenu.tsx renders "Add to Collection" and "Start New Thread" menu items
    - CatalogSearchOverlay.tsx imports `useTags` and `useGlobalItems`
    - CatalogSearchOverlay.tsx contains `bg-blue-100 text-blue-700` (active tag chip style per D-16)
    - CatalogSearchOverlay.tsx contains `bg-gray-100 text-gray-500` (inactive tag chip style per D-16)
    - CatalogSearchOverlay.tsx contains debounce pattern (setTimeout with 300)
    - CatalogSearchOverlay.tsx contains skeleton/loading state
    - CatalogSearchOverlay.tsx contains empty state message
    - `bun run lint` passes
    - `bun run build` succeeds
  </acceptance_criteria>
  <done>FabMenu renders with animated mini menu. CatalogSearchOverlay renders with search, tag chips, result cards, loading/empty states. Both components compile and lint clean.</done>
</task>

<task type="auto">
  <name>Task 2: Wire FabMenu and CatalogSearchOverlay into root layout</name>
  <files>
    src/client/routes/__root.tsx
  </files>
  <read_first>
    src/client/routes/__root.tsx,
    src/client/components/FabMenu.tsx,
    src/client/components/CatalogSearchOverlay.tsx,
    src/client/stores/uiStore.ts
  </read_first>
  <action>
    1. In `src/client/routes/__root.tsx`:
       - Import `FabMenu` from `../components/FabMenu`.
       - Import `CatalogSearchOverlay` from `../components/CatalogSearchOverlay`.

    2. Remove the old FAB button block (lines ~257-278, the `button` with "Add new item" title and "+" SVG). Replace with `<FabMenu>` component.

    3. Compute FAB visibility (per D-06):
       - Remove old `showFab` logic (which was collection gear tab only).
       - New logic:
         ```
         const isPublicRoute = location.pathname.startsWith("/users/") || location.pathname === "/login";
         const showFab = isAuthenticated && !isPublicRoute;
         ```
       - Determine if on setups page: `const isSetupsPage = !!matchRoute({ to: "/setups", fuzzy: true });`

    4. Render FabMenu conditionally:
       ```
       {showFab && <FabMenu isSetupsPage={isSetupsPage} />}
       ```

    5. Render CatalogSearchOverlay unconditionally (it manages its own visibility via UIStore):
       ```
       <CatalogSearchOverlay />
       ```
       Place it after the FabMenu render, before the OnboardingWizard.

    6. Read `catalogSearchOpen` from UIStore. When catalog search is open, the old `openAddPanel` call from the FAB is no longer relevant -- the FabMenu component handles its own actions. Verify the old `openAddPanel` reference on the FAB button is fully removed.

    7. Verify the `isSetupDetail` matchRoute and existing `collectionSearch` logic can be cleaned up if they were only used for FAB visibility. Keep any logic still needed for TotalsBar or other features.
  </action>
  <verify>
    <automated>bun run lint && bun run build</automated>
  </verify>
  <acceptance_criteria>
    - `src/client/routes/__root.tsx` imports `FabMenu` from `../components/FabMenu`
    - `src/client/routes/__root.tsx` imports `CatalogSearchOverlay` from `../components/CatalogSearchOverlay`
    - `src/client/routes/__root.tsx` does NOT contain the old single-action FAB button (`title="Add new item"`)
    - `src/client/routes/__root.tsx` contains `<FabMenu` component usage
    - `src/client/routes/__root.tsx` contains `<CatalogSearchOverlay` component usage
    - `src/client/routes/__root.tsx` contains public route check: `location.pathname.startsWith("/users/")`
    - `bun run lint` passes
    - `bun run build` succeeds
  </acceptance_criteria>
  <done>Root layout renders FabMenu on all authenticated routes and CatalogSearchOverlay globally. Old single-action FAB removed. Build passes.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Visual verification of FAB and catalog search</name>
  <files>none</files>
  <action>
    Human verifies the FAB mini menu and catalog search overlay work correctly across pages and viewports.
  </action>
  <what-built>
    Global FAB with mini menu (Add to Collection, Start Thread, and New Setup on setups page) plus full-screen catalog search overlay with debounced search, tag chip filtering, and result cards.
  </what-built>
  <how-to-verify>
    1. Start dev server: `bun run dev`
    2. Navigate to collection page (/collection) -- verify FAB (gray circle with "+") is visible at bottom-right
    3. Click FAB -- verify mini menu appears with "Add to Collection" and "Start New Thread" items, with subtle backdrop
    4. Click backdrop -- verify menu closes
    5. Click FAB again, then click "Add to Collection" -- verify full-screen white overlay opens with "Adding to Collection" text, search input (autofocused), and tag chips
    6. Type a search query -- verify results appear after brief debounce delay (~300ms) as card grid
    7. Click a tag chip -- verify it toggles to blue active state and filters results
    8. Click the back arrow -- verify overlay closes
    9. Navigate to /threads -- verify FAB is still visible
    10. Navigate to /setups -- verify FAB menu includes third "New Setup" option
    11. Navigate to /login -- verify FAB is NOT visible
    12. Check on mobile viewport (use devtools responsive mode) -- verify single column cards, horizontal scrollable tag chips
  </how-to-verify>
  <verify>Human confirms all 12 verification steps pass</verify>
  <done>User approves FAB and catalog search overlay behavior across all pages and viewports.</done>
  <resume-signal>Type "approved" or describe any issues</resume-signal>
</task>

</tasks>

<verification>
- `bun run lint` -- no errors
- `bun run build` -- succeeds
- `bun test` -- full suite green
- Visual: FAB visible on authenticated routes, hidden on public routes
- Visual: Mini menu opens/closes with animation
- Visual: Full-screen overlay with search, tags, results
- Visual: Tag filtering works with AND logic
- Visual: Responsive grid (1 col mobile, 2-3 col desktop)
</verification>

<success_criteria>
1. FAB visible on all authenticated pages with mini menu showing correct options (per D-01 through D-06, CATFLOW-01)
2. "New Setup" appears only on setups page (per D-03, CATFLOW-01)
3. Full-screen catalog search overlay opens from both add options (per D-07, CATFLOW-02)
4. Search with debounce queries existing API (per D-08, D-11, CATFLOW-02)
5. Tag chips filter results with AND logic (per D-14 through D-17, CATFLOW-02)
6. Result cards display brand, model, weight, price, with stub Add button (per D-10, D-18 through D-20)
7. Loading skeletons and empty state present (per D-12, D-13)
8. Human verification approved
</success_criteria>

<output>
After completion, create `.planning/phases/20-fab-full-screen-catalog-search/20-02-SUMMARY.md`
</output>
