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

335 lines
17 KiB
Markdown

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