Files
GearBox/.planning/phases/20-fab-full-screen-catalog-search/20-02-PLAN.md

17 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
20-fab-full-screen-catalog-search 02 execute 2
20-01
src/client/components/FabMenu.tsx
src/client/components/CatalogSearchOverlay.tsx
src/client/routes/__root.tsx
false
CATFLOW-01
CATFLOW-02
truths artifacts key_links
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
path provides min_lines
src/client/components/FabMenu.tsx FAB with mini menu 60
path provides min_lines
src/client/components/CatalogSearchOverlay.tsx Full-screen catalog search 100
from to via pattern
src/client/components/FabMenu.tsx src/client/stores/uiStore.ts useUIStore (fabMenuOpen, openCatalogSearch) useUIStore
from to via pattern
src/client/components/CatalogSearchOverlay.tsx src/client/hooks/useGlobalItems.ts useGlobalItems(query, tags) useGlobalItems
from to via pattern
src/client/components/CatalogSearchOverlay.tsx src/client/hooks/useTags.ts useTags() useTags
from to via pattern
src/client/routes/__root.tsx src/client/components/FabMenu.tsx FabMenu component render FabMenu
from to via pattern
src/client/routes/__root.tsx src/client/components/CatalogSearchOverlay.tsx CatalogSearchOverlay component render CatalogSearchOverlay
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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

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):

export function useGlobalItems(query?: string, tags?: string[]): UseQueryResult<GlobalItem[]>;

From src/client/hooks/useFormatters.ts:

export function useFormatters(): { weight: (grams: number) => string; price: (cents: number) => string; };
Task 1: FabMenu component and CatalogSearchOverlay component src/client/components/FabMenu.tsx, src/client/components/CatalogSearchOverlay.tsx 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 **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.
bun run lint && bun run build - 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 FabMenu renders with animated mini menu. CatalogSearchOverlay renders with search, tag chips, result cards, loading/empty states. Both components compile and lint clean. Task 2: Wire FabMenu and CatalogSearchOverlay into root layout src/client/routes/__root.tsx src/client/routes/__root.tsx, src/client/components/FabMenu.tsx, src/client/components/CatalogSearchOverlay.tsx, src/client/stores/uiStore.ts 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.
bun run lint && bun run build - `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 ` Root layout renders FabMenu on all authenticated routes and CatalogSearchOverlay globally. Old single-action FAB removed. Build passes. Task 3: Visual verification of FAB and catalog search none Human verifies the FAB mini menu and catalog search overlay work correctly across pages and viewports. 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. 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 Human confirms all 12 verification steps pass User approves FAB and catalog search overlay behavior across all pages and viewports. Type "approved" or describe any issues - `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)

<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>
After completion, create `.planning/phases/20-fab-full-screen-catalog-search/20-02-SUMMARY.md`