335 lines
17 KiB
Markdown
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>
|