`. Verify on mobile viewport that FAB is gone.
+**Warning signs:** FAB overlapping tab bar on narrow screens.
+
+### Pitfall 4: CatalogSearchOverlay z-index Fighting Bottom Tab Bar
+
+**What goes wrong:** The `CatalogSearchOverlay` renders below the bottom tab bar, making the tab bar visible on top of the search overlay.
+**Why it happens:** `CatalogSearchOverlay` and `BottomTabBar` both use high z-index. Current overlay z-index needs checking.
+**How to avoid:** Ensure `BottomTabBar` uses `z-20` and `CatalogSearchOverlay` uses `z-30` or higher. The overlay already uses `fixed inset-0` — verify its z-index is above the tab bar.
+**Warning signs:** Bottom tab bar visible when search overlay is open.
+
+### Pitfall 5: Collection URL with ?tab=setups Breaks After Tab Removal
+
+**What goes wrong:** Existing links, bookmarks, or tests that reference `/collection?tab=setups` stop working after the tab is removed.
+**Why it happens:** The Zod `catch("gear")` will handle it gracefully in the router (redirects to gear tab), but E2E tests may assert on the old three-tab structure.
+**How to avoid:** Update `e2e/dashboard.spec.ts` and `e2e/collection.spec.ts` — the existing tests assert on "Collection, Planning, and Setups card headings" and the old tab structure. Update or remove those assertions.
+**Warning signs:** E2E test failures asserting Setups tab inside Collection.
+
+### Pitfall 6: useAuth() During SSR / Hydration Flash
+
+**What goes wrong:** Nav renders "Sign in" on first paint even for authenticated users, causing a flash.
+**Why it happens:** `useAuth()` is async (React Query). `auth.isLoading` is true on first render. The existing `TotalsBar` has this same behavior — it's acceptable in this app.
+**How to avoid:** Match existing TotalsBar behavior. Don't add special hydration handling unless this becomes a visible problem. The flash is consistent with current UX.
+**Warning signs:** Nav flickers from "Sign in" to avatar on page load. Acceptable if it matches current behavior.
+
+## Code Examples
+
+### TopNav.tsx skeleton (desktop + mobile top bar)
+
+```typescript
+// src/client/components/TopNav.tsx
+// Source: derived from TotalsBar.tsx pattern + __root.tsx matchRoute pattern
+import { Link, useMatchRoute } from "@tanstack/react-router";
+import { useAuth } from "../hooks/useAuth";
+import { LucideIcon } from "../lib/iconData";
+import { useUIStore } from "../stores/uiStore";
+import { UserMenu } from "./UserMenu";
+
+export function TopNav() {
+ const { data: auth } = useAuth();
+ const isAuthenticated = !!auth?.user;
+ const openAuthPrompt = useUIStore((s) => s.openAuthPrompt);
+ const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);
+ const matchRoute = useMatchRoute();
+
+ const isHome = !!matchRoute({ to: "/" });
+ const isCollection = !!matchRoute({ to: "/collection", fuzzy: true });
+ const isSetups = !!matchRoute({ to: "/setups", fuzzy: true });
+
+ function navLinkClass(active: boolean) {
+ return `text-sm font-medium transition-colors ${
+ active ? "text-gray-900" : "text-gray-500 hover:text-gray-700"
+ }`;
+ }
+
+ function NavLinkOrButton({
+ label,
+ to,
+ active,
+ isProtected,
+ }: {
+ label: string;
+ to: string;
+ active: boolean;
+ isProtected: boolean;
+ }) {
+ if (isProtected && !isAuthenticated) {
+ return (
+
+ {label}
+
+ );
+ }
+ return (
+
+ {label}
+
+ );
+ }
+
+ return (
+
+
+
+ {/* Logo */}
+
+
+
GearBox
+
+
+ {/* Desktop nav links (hidden on mobile) */}
+
+ Home
+
+
+
+
+ {/* Desktop search + user (hidden on mobile, user avatar shown on mobile) */}
+
+ {/* Search bar — desktop only */}
+
openCatalogSearch("collection")}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => e.key === "Enter" && openCatalogSearch("collection")}
+ className="hidden md:flex items-center gap-2 px-3 py-1.5 bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:border-gray-300 transition-all"
+ >
+
+ Search catalog...
+
+ {/* User menu / sign-in */}
+ {isAuthenticated ? (
+
+ ) : (
+
+ Sign in
+
+ )}
+
+
+
+
+ );
+}
+```
+
+### BottomTabBar.tsx skeleton
+
+```typescript
+// src/client/components/BottomTabBar.tsx
+// Source: FabMenu.tsx framer-motion pattern + uiStore
+import { Link, useMatchRoute } from "@tanstack/react-router";
+import { motion } from "framer-motion";
+import { useAuth } from "../hooks/useAuth";
+import { LucideIcon } from "../lib/iconData";
+import { useUIStore } from "../stores/uiStore";
+
+export function BottomTabBar() {
+ const { data: auth } = useAuth();
+ const isAuthenticated = !!auth?.user;
+ const openAuthPrompt = useUIStore((s) => s.openAuthPrompt);
+ const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);
+ const matchRoute = useMatchRoute();
+
+ const isHome = !!matchRoute({ to: "/" });
+ const isCollection = !!matchRoute({ to: "/collection", fuzzy: true });
+ const isSetups = !!matchRoute({ to: "/setups", fuzzy: true });
+
+ const tabClass = (active: boolean) =>
+ `flex flex-col items-center gap-0.5 py-2 px-3 text-xs font-medium transition-colors ${
+ active ? "text-gray-900" : "text-gray-400"
+ }`;
+
+ return (
+
+
+
+
+ Home
+
+ {isAuthenticated ? (
+
+
+ Collection
+
+ ) : (
+
+
+ Collection
+
+ )}
+ {isAuthenticated ? (
+
+
+ Setups
+
+ ) : (
+
+
+ Setups
+
+ )}
+ openCatalogSearch("collection")}
+ className={tabClass(false)}
+ >
+
+ Search
+
+
+
+ );
+}
+```
+
+### __root.tsx changes
+
+```typescript
+// Replace:
+import { TotalsBar } from "../components/TotalsBar";
+// With:
+import { TopNav } from "../components/TopNav";
+import { BottomTabBar } from "../components/BottomTabBar";
+
+// In RootLayout return:
+// Replace:
+// With:
+
+// Wrap FabMenu to hide on mobile:
+{showFab && (
+
+
+
+)}
+
+// Add after FabMenu:
+
+```
+
+### collection/index.tsx tab removal
+
+```typescript
+// Remove "setups" from TAB_ORDER, TAB_LABELS, and Zod schema
+// Remove SetupsView import
+// Remove tab === "setups" conditional render
+const TAB_ORDER = ["gear", "planning"] as const;
+const TAB_LABELS: Record<(typeof TAB_ORDER)[number], string> = {
+ gear: "Gear",
+ planning: "Planning",
+};
+const searchSchema = z.object({
+ tab: z.enum(["gear", "planning"]).catch("gear"),
+});
+```
+
+### routes/index.tsx hero removal
+
+```typescript
+// Remove HeroSection function entirely
+// Remove HeroSection from LandingPage render
+// Remove Search import from lucide-react
+function LandingPage() {
+ return (
+
+ );
+}
+// Remove: openCatalogSearch from useUIStore (no longer needed in this file)
+// Remove: useAuth import (no longer needed in this file)
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| Hero-based catalog entry | Nav-persistent search bar | This phase | Catalog accessible from any page, not just landing page |
+| Setups as Collection tab | Setups as top-level route | This phase | Setups gets own URL, bookmarkable, mobile tab bar includes it |
+| FAB for all mobile actions | Bottom tab bar for nav, FAB only on desktop | This phase | Standard mobile pattern — iOS/Android tab bar convention |
+| TotalsBar (logo + user) | TopNav (logo + links + search + user) | This phase | Full navigation affordance for multi-section app |
+
+**No deprecated patterns:** The transition follows standard React + TanStack Router conventions throughout.
+
+## Open Questions
+
+1. **`layers` icon availability in the curated LucideIcon set**
+ - What we know: `iconData.tsx` exports a curated subset of 119 Lucide icons. The `EMOJI_TO_ICON_MAP` doesn't include `layers`.
+ - What's unclear: Whether `layers` is in the exported set. The full `icons` object from `lucide-react` is imported, so any icon name should work via `LucideIcon` (it passes the name to the `icons` lookup) — but the comment says "119 curated" icons.
+ - Recommendation: Check `iconData.tsx` for the full export or simply try `layers` — if it fails silently, use `briefcase` or `grid-2x2` as fallback. The planner should note this as a quick verify step.
+
+2. **Body padding-bottom for bottom tab bar**
+ - What we know: The bottom tab bar is `fixed bottom-0` so it overlays page content. On mobile, the last content may be obscured by the tab bar.
+ - What's unclear: The exact height of the tab bar (approximately 60-64px with icons + labels + padding).
+ - Recommendation: Add `pb-20 md:pb-0` to the root `
` in `__root.tsx` to prevent content being hidden behind the tab bar.
+
+3. **`openCatalogSearch` mode parameter from TopNav**
+ - What we know: `openCatalogSearch` takes `"collection" | "thread"`. From the nav, it should always be `"collection"`.
+ - What's unclear: Whether calling it in "collection" mode when on a thread detail page is correct behavior (D-07 says it's always catalog-global).
+ - Recommendation: Always pass `"collection"` from the nav. The mode only affects what happens after the user selects a catalog item (add to collection vs add to thread). A user on a thread page who opens search from the nav would get the "add to collection" flow, not "add to thread" — this is a reasonable simplification per D-07 and D-08.
+
+## Environment Availability
+
+Step 2.6: SKIPPED (no external dependencies — purely client-side React component restructuring, no new CLI tools, services, or runtimes required).
+
+## Validation Architecture
+
+### Test Framework
+
+| Property | Value |
+|----------|-------|
+| Framework | Playwright (E2E) + Bun test runner (unit) |
+| Config file | `playwright.config.ts` (E2E), built-in Bun test runner |
+| Quick run command | `bun test tests/` |
+| Full suite command | `bun run test:e2e` |
+
+### Phase Requirements → Test Map
+
+| Behavior | Test Type | Automated Command | File Exists? |
+|----------|-----------|-------------------|-------------|
+| Top nav renders logo, Home/Collection/Setups links, search | E2E smoke | `bunx playwright test e2e/dashboard.spec.ts` | Partial — needs update |
+| Clicking Collection while anon triggers AuthPromptModal | E2E | `bunx playwright test e2e/dashboard.spec.ts` | ❌ Wave 0 |
+| Mobile bottom tab bar shows 4 items | E2E (mobile viewport) | `bunx playwright test e2e/dashboard.spec.ts` | ❌ Wave 0 |
+| Landing page has no hero section | E2E | `bunx playwright test e2e/dashboard.spec.ts` | Partial — existing test checks for heading, needs update |
+| /setups route renders SetupsView | E2E | `bunx playwright test e2e/collection.spec.ts` | ❌ Wave 0 |
+| Collection page has only Gear and Planning tabs | E2E | `bunx playwright test e2e/collection.spec.ts` | ❌ needs update |
+
+### Sampling Rate
+
+- **Per task commit:** `bun test tests/` (unit only — fast)
+- **Per wave merge:** `bun run test:e2e` (full E2E suite)
+- **Phase gate:** Full E2E suite green before `/gsd:verify-work`
+
+### Wave 0 Gaps
+
+- [ ] `e2e/dashboard.spec.ts` — update existing tests: remove assertions about hero heading "Discover Gear", add assertion for top nav presence, update "GearBox heading" to check nav bar (not h1)
+- [ ] `e2e/dashboard.spec.ts` — add: anon user clicking Collection nav triggers auth modal
+- [ ] `e2e/dashboard.spec.ts` — add: mobile viewport bottom tab bar test (use Playwright `page.setViewportSize`)
+- [ ] `e2e/collection.spec.ts` — update: remove Setups tab assertions, add /setups route navigation test
+
+*(Note: `tests/` unit tests cover service-level logic — no unit tests needed for this pure UI restructuring phase. E2E tests are the validation layer.)*
+
+## Sources
+
+### Primary (HIGH confidence)
+- Existing codebase: `TotalsBar.tsx`, `__root.tsx`, `FabMenu.tsx`, `uiStore.ts`, `AuthPromptModal.tsx`, `UserMenu.tsx`, `collection/index.tsx`, `routes/index.tsx` — direct source inspection
+- Existing codebase: `SetupsView.tsx`, `setups/$setupId.tsx` — route structure verified
+
+### Secondary (MEDIUM confidence)
+- TanStack Router file-based routing conventions — inferred from existing route structure (collection/index.tsx, setups/$setupId.tsx)
+- Framer Motion v12 entry animation pattern — inferred from FabMenu.tsx usage
+
+### Tertiary (LOW confidence)
+- None — all findings backed by direct codebase inspection
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — all libraries already in use, versions from package.json
+- Architecture: HIGH — patterns derived directly from existing code
+- Pitfalls: HIGH — derived from direct code analysis (isPublicRoute, z-index, tab removal implications)
+
+**Research date:** 2026-04-10
+**Valid until:** 2026-05-10 (stable codebase; no external API dependencies)