diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 331b8a6..d6db296 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -200,7 +200,10 @@ Plans: 3. Full-screen catalog search overlay opens from either add option 4. Search results display catalog items with name, weight, price, owner count 5. Tag chips filter search results -**Plans**: TBD +**Plans:** 2 plans +Plans: +- [ ] 20-01-PLAN.md — Tags endpoint, global-items route registration, UIStore extension, useTags hook +- [ ] 20-02-PLAN.md — FabMenu component, CatalogSearchOverlay component, root layout wiring **UI hint**: yes ### Phase 21: Add-from-Catalog & Thread Integration @@ -248,6 +251,6 @@ Plans: | 17. Object Storage | v2.0 | 0/? | Not started | - | | 18. Global Items & Public Profiles | v2.0 | 4/5 | Complete | 2026-04-05 | | 19. Reference Item Model & Tags Schema | v2.0 | 3/3 | Complete | 2026-04-05 | -| 20. FAB & Full-Screen Catalog Search | v2.0 | 0/? | Not started | - | +| 20. FAB & Full-Screen Catalog Search | v2.0 | 0/2 | Not started | - | | 21. Add-from-Catalog & Thread Integration | v2.0 | 0/? | Not started | - | | 22. Manual Entry Fallback | v2.0 | 0/? | Not started | - | diff --git a/.planning/phases/20-fab-full-screen-catalog-search/20-01-PLAN.md b/.planning/phases/20-fab-full-screen-catalog-search/20-01-PLAN.md new file mode 100644 index 0000000..8feb67d --- /dev/null +++ b/.planning/phases/20-fab-full-screen-catalog-search/20-01-PLAN.md @@ -0,0 +1,279 @@ +--- +phase: 20-fab-full-screen-catalog-search +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/server/services/tag.service.ts + - src/server/routes/tags.ts + - src/server/index.ts + - src/client/stores/uiStore.ts + - src/client/hooks/useTags.ts + - src/client/hooks/useGlobalItems.ts + - tests/services/tag.service.test.ts + - tests/routes/tags.test.ts +autonomous: true +requirements: + - CATFLOW-01 + - CATFLOW-02 + +must_haves: + truths: + - "GET /api/tags returns all tags from the database" + - "GET /api/global-items endpoint is reachable (route registered)" + - "UIStore exposes fabMenu and catalogSearch state slices" + - "useGlobalItems hook supports optional tags parameter" + - "useTags hook fetches and caches tag data" + artifacts: + - path: "src/server/services/tag.service.ts" + provides: "getAllTags function" + exports: ["getAllTags"] + - path: "src/server/routes/tags.ts" + provides: "GET /api/tags endpoint" + exports: ["tagRoutes"] + - path: "src/client/stores/uiStore.ts" + provides: "FAB menu + catalog search state" + contains: "fabMenuOpen" + - path: "src/client/hooks/useTags.ts" + provides: "Tag fetching hook" + exports: ["useTags"] + - path: "src/client/hooks/useGlobalItems.ts" + provides: "Updated hook with tag support" + contains: "tags" + key_links: + - from: "src/server/routes/tags.ts" + to: "src/server/services/tag.service.ts" + via: "getAllTags import" + pattern: "getAllTags" + - from: "src/server/index.ts" + to: "src/server/routes/tags.ts" + via: "app.route registration" + pattern: "app\\.route.*tags" + - from: "src/server/index.ts" + to: "src/server/routes/global-items.ts" + via: "app.route registration" + pattern: "app\\.route.*global-items" +--- + + +Create the server-side tags endpoint, register the global-items route, extend UIStore with FAB/catalog-search state, and update client hooks for tag-aware searching. + +Purpose: Provides the data layer (tags API), state management (UIStore), and hooks that Plan 02's UI components will consume. +Output: Working GET /api/tags endpoint, registered GET /api/global-items, extended UIStore, useTags hook, updated useGlobalItems hook with tag support. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.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 + +@src/server/index.ts +@src/server/routes/global-items.ts +@src/server/services/global-item.service.ts +@src/db/schema.ts +@src/client/stores/uiStore.ts +@src/client/hooks/useGlobalItems.ts +@src/client/lib/api.ts +@tests/routes/global-items.test.ts +@tests/helpers/db.ts + + + + +From src/db/schema.ts: +```typescript +export const tags = pgTable("tags", { + id: serial("id").primaryKey(), + name: text("name").notNull().unique(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}); +``` + +From src/client/lib/api.ts: +```typescript +export async function apiGet(path: string): Promise; +``` + +From src/client/stores/uiStore.ts (existing pattern): +```typescript +export const useUIStore = create((set) => ({ ... })); +``` + + + + + + + Task 1: Tag service, route, route registrations, and tests + + src/server/services/tag.service.ts, + src/server/routes/tags.ts, + src/server/index.ts, + tests/services/tag.service.test.ts, + tests/routes/tags.test.ts + + + src/db/schema.ts, + src/server/index.ts, + src/server/routes/global-items.ts, + src/server/services/global-item.service.ts, + tests/routes/global-items.test.ts, + tests/helpers/db.ts + + + - Test: getAllTags returns all tags from the database as { id, name }[] (no createdAt) + - Test: getAllTags returns empty array when no tags exist + - Test: GET /api/tags returns 200 with array of tag objects + - Test: GET /api/global-items still works after route registration (existing tests pass) + + + 1. Create `src/server/services/tag.service.ts`: + - Export `getAllTags(db)` function that selects `{ id: tags.id, name: tags.name }` from the `tags` table, ordered alphabetically by name (per D-17, alphabetical is a reasonable default). + - Import `tags` from `../../db/schema.ts`. + - Use the same `type Db = typeof prodDb` pattern as other services. + + 2. Create `src/server/routes/tags.ts`: + - Single GET "/" handler that calls `getAllTags(db)` and returns `c.json(allTags)`. + - Export as `tagRoutes`. + - Follow the exact pattern from `src/server/routes/global-items.ts` (Hono + Env type). + + 3. Update `src/server/index.ts`: + - Add import: `import { globalItemRoutes } from "./routes/global-items.ts";` + - Add import: `import { tagRoutes } from "./routes/tags.ts";` + - Register both routes AFTER existing route registrations (before MCP block): + `app.route("/api/global-items", globalItemRoutes);` + `app.route("/api/tags", tagRoutes);` + - CRITICAL: The global-items route file EXISTS but is NOT registered (research pitfall #1). Must add it here. + - Add public route skip in auth middleware for tags: `if (c.req.path.startsWith("/api/tags") && c.req.method === "GET") return next();` + - Add public route skip for global-items: `if (c.req.path.startsWith("/api/global-items") && c.req.method === "GET") return next();` + + 4. Create `tests/services/tag.service.test.ts`: + - Use `createTestDb()` from `tests/helpers/db.ts`. + - Test: returns empty array when no tags. + - Test: returns all tags as `{ id, name }` after inserting test data. + - Seed tags using `db.insert(tags).values(...)`. + + 5. Create `tests/routes/tags.test.ts`: + - Follow pattern from `tests/routes/global-items.test.ts`. + - Use the app with test db injection. + - Test: GET /api/tags returns 200 with JSON array. + - Test: response contains expected tags after seeding. + + + bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts tests/routes/global-items.test.ts + + + - File `src/server/services/tag.service.ts` exists and exports `getAllTags` + - File `src/server/routes/tags.ts` exists and exports `tagRoutes` + - `src/server/index.ts` contains `app.route("/api/tags", tagRoutes)` + - `src/server/index.ts` contains `app.route("/api/global-items", globalItemRoutes)` + - `src/server/index.ts` contains auth skip for `/api/tags` and `/api/global-items` GET requests + - All tag tests pass: `bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts` + - Existing global-items tests still pass: `bun test tests/routes/global-items.test.ts` + + GET /api/tags returns all tags. GET /api/global-items is registered and reachable. All tests green. + + + + Task 2: UIStore extension + useTags hook + useGlobalItems tag support + + src/client/stores/uiStore.ts, + src/client/hooks/useTags.ts, + src/client/hooks/useGlobalItems.ts + + + src/client/stores/uiStore.ts, + src/client/hooks/useGlobalItems.ts, + src/client/lib/api.ts, + .planning/phases/20-fab-full-screen-catalog-search/20-CONTEXT.md + + + 1. Extend `src/client/stores/uiStore.ts` per D-21, D-22, D-23: + - Add to UIState interface: + ``` + fabMenuOpen: boolean; + openFabMenu: () => void; + closeFabMenu: () => void; + catalogSearchOpen: boolean; + catalogSearchMode: "collection" | "thread" | null; + openCatalogSearch: (mode: "collection" | "thread") => void; + closeCatalogSearch: () => void; + ``` + - Add to store implementation: + ``` + fabMenuOpen: false, + openFabMenu: () => set({ fabMenuOpen: true }), + closeFabMenu: () => set({ fabMenuOpen: false }), + catalogSearchOpen: false, + catalogSearchMode: null, + openCatalogSearch: (mode) => set({ catalogSearchOpen: true, catalogSearchMode: mode, fabMenuOpen: false }), + closeCatalogSearch: () => set({ catalogSearchOpen: false, catalogSearchMode: null }), + ``` + - Note: `openCatalogSearch` also closes the FAB menu (natural flow: tap FAB -> tap option -> menu closes, overlay opens). + + 2. Create `src/client/hooks/useTags.ts`: + - Export `useTags()` hook using `useQuery` from `@tanstack/react-query`. + - Query key: `["tags"]`. + - Query fn: `apiGet("/api/tags")` where `Tag = { id: number; name: string }`. + - Set `staleTime: 5 * 60 * 1000` (tags change rarely, cache 5 min). + - Export the `Tag` interface as well. + + 3. Update `src/client/hooks/useGlobalItems.ts`: + - Modify `useGlobalItems` signature to accept optional `tags?: string[]` parameter. + - Build query string using `URLSearchParams`: + ``` + const params = new URLSearchParams(); + if (query) params.set("q", query); + if (tags && tags.length > 0) params.set("tags", tags.join(",")); + const qs = params.toString(); + ``` + - Update query key to include tags: `["global-items", query ?? "", tags ?? []]`. + - Update query fn URL: `` `/api/global-items${qs ? `?${qs}` : ""}` ``. + - Keep all other exports (`useGlobalItem`, `useLinkItem`, `useUnlinkItem`) unchanged. + + + bun run lint && bun run build + + + - `src/client/stores/uiStore.ts` contains `fabMenuOpen` state field + - `src/client/stores/uiStore.ts` contains `catalogSearchOpen` state field + - `src/client/stores/uiStore.ts` contains `catalogSearchMode` state field + - `src/client/stores/uiStore.ts` contains `openCatalogSearch` action + - `src/client/stores/uiStore.ts` contains `closeCatalogSearch` action + - `src/client/hooks/useTags.ts` exists and exports `useTags` + - `src/client/hooks/useGlobalItems.ts` accepts `tags?: string[]` parameter + - `bun run lint` passes with no errors + - `bun run build` succeeds + + UIStore has FAB menu + catalog search state. useTags hook fetches tags. useGlobalItems supports tag filtering. Lint and build pass. + + + + + +- `bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts tests/routes/global-items.test.ts` -- all green +- `bun run lint` -- no errors +- `bun run build` -- succeeds +- `bun test` -- full suite green (no regressions) + + + +1. GET /api/tags returns all tags as JSON array +2. GET /api/global-items is registered and reachable (was missing from index.ts) +3. UIStore has fabMenuOpen, catalogSearchOpen, catalogSearchMode state +4. useTags hook works with staleTime caching +5. useGlobalItems accepts optional tags parameter for filtering +6. All existing tests pass (no regressions) + + + +After completion, create `.planning/phases/20-fab-full-screen-catalog-search/20-01-SUMMARY.md` + diff --git a/.planning/phases/20-fab-full-screen-catalog-search/20-02-PLAN.md b/.planning/phases/20-fab-full-screen-catalog-search/20-02-PLAN.md new file mode 100644 index 0000000..c7a6df3 --- /dev/null +++ b/.planning/phases/20-fab-full-screen-catalog-search/20-02-PLAN.md @@ -0,0 +1,334 @@ +--- +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" +--- + + +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. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.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; +``` + +From src/client/hooks/useGlobalItems.ts (after Plan 01): +```typescript +export function useGlobalItems(query?: string, tags?: string[]): UseQueryResult; +``` + +From src/client/hooks/useFormatters.ts: +```typescript +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([])`. 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 `` 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 && } + ``` + + 5. Render CatalogSearchOverlay unconditionally (it manages its own visibility via UIStore): + ``` + + ``` + 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) + + + +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 + + + +After completion, create `.planning/phases/20-fab-full-screen-catalog-search/20-02-SUMMARY.md` +