docs(27): create phase plan
This commit is contained in:
@@ -129,14 +129,19 @@ Plans:
|
||||
### Phase 27: Top Nav Restructure & Search Bar Rethink
|
||||
**Goal**: Replace the minimal TotalsBar with a persistent top navigation bar (logo, section links, catalog search, user avatar) and move mobile navigation to a bottom tab bar — elevating Setups to top-level and removing the landing page hero
|
||||
**Depends on**: Phase 26
|
||||
**Requirements**: TBD
|
||||
**Requirements**: NAV-01, NAV-02, NAV-03, NAV-04, NAV-05
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. A persistent top nav bar shows logo, Home/Collection/Setups links, catalog search, and user avatar on desktop
|
||||
2. Clicking Collection or Setups while anonymous triggers AuthPromptModal instead of navigating
|
||||
3. On mobile, navigation appears as a fixed bottom tab bar with Home, Collection, Setups, and Search icons
|
||||
4. The landing page no longer has a hero section — content starts with Popular Setups
|
||||
5. Setups has its own top-level route accessible from the nav bar, not nested in Collection tabs
|
||||
**Plans**: TBD
|
||||
**Plans**: 3 plans
|
||||
|
||||
Plans:
|
||||
- [ ] 27-01-PLAN.md — TopNav and BottomTabBar components
|
||||
- [ ] 27-02-PLAN.md — Setups top-level route and Collection tab simplification
|
||||
- [ ] 27-03-PLAN.md — Root layout wiring, hero removal, and visual verification
|
||||
**UI hint**: yes
|
||||
|
||||
## Progress
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
---
|
||||
phase: 27-top-nav-restructure-and-search-bar-rethink
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/client/components/TopNav.tsx
|
||||
- src/client/components/BottomTabBar.tsx
|
||||
autonomous: true
|
||||
requirements:
|
||||
- NAV-01
|
||||
- NAV-02
|
||||
- NAV-03
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "TopNav renders logo, Home/Collection/Setups links, search bar, and user avatar on desktop"
|
||||
- "Clicking Collection or Setups while anonymous calls openAuthPrompt instead of navigating"
|
||||
- "Active section is visually highlighted in the nav"
|
||||
- "BottomTabBar renders 4 tabs (Home, Collection, Setups, Search) with Lucide icons on mobile"
|
||||
- "Tapping Search tab calls openCatalogSearch"
|
||||
artifacts:
|
||||
- path: "src/client/components/TopNav.tsx"
|
||||
provides: "Persistent top navigation bar replacing TotalsBar"
|
||||
exports: ["TopNav"]
|
||||
- path: "src/client/components/BottomTabBar.tsx"
|
||||
provides: "Mobile bottom tab bar with 4 navigation items"
|
||||
exports: ["BottomTabBar"]
|
||||
key_links:
|
||||
- from: "src/client/components/TopNav.tsx"
|
||||
to: "src/client/stores/uiStore.ts"
|
||||
via: "openCatalogSearch, openAuthPrompt"
|
||||
pattern: "useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt"
|
||||
- from: "src/client/components/BottomTabBar.tsx"
|
||||
to: "src/client/stores/uiStore.ts"
|
||||
via: "openCatalogSearch, openAuthPrompt"
|
||||
pattern: "useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Create the two new navigation components: TopNav (desktop persistent nav bar) and BottomTabBar (mobile fixed bottom tab bar).
|
||||
|
||||
Purpose: These components are the core UI deliverables of Phase 27, replacing the minimal TotalsBar with full navigation. They implement D-01 through D-03 (top nav structure), D-07 (nav search bar), D-12 through D-14 (mobile bottom tab bar), and D-17 (search trigger from nav).
|
||||
|
||||
Output: Two new component files ready to be wired into __root.tsx in Plan 03.
|
||||
</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/27-top-nav-restructure-and-search-bar-rethink/27-CONTEXT.md
|
||||
@.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. -->
|
||||
|
||||
From src/client/stores/uiStore.ts:
|
||||
```typescript
|
||||
openCatalogSearch: (mode: "collection" | "thread") => void;
|
||||
closeCatalogSearch: () => void;
|
||||
openAuthPrompt: () => void;
|
||||
closeAuthPrompt: () => void;
|
||||
```
|
||||
|
||||
From src/client/hooks/useAuth.ts:
|
||||
```typescript
|
||||
// Returns { data: { user: User | null }, isLoading: boolean }
|
||||
export function useAuth(): UseQueryResult<{ user: User | null }>;
|
||||
```
|
||||
|
||||
From src/client/lib/iconData.tsx:
|
||||
```typescript
|
||||
// Renders a Lucide icon by name string. Available icons include: package, home, layers, search
|
||||
export function LucideIcon({ name, size, className }: { name: string; size?: number; className?: string }): JSX.Element;
|
||||
```
|
||||
|
||||
From src/client/components/UserMenu.tsx:
|
||||
```typescript
|
||||
export function UserMenu(): JSX.Element;
|
||||
```
|
||||
|
||||
From src/client/components/TotalsBar.tsx (pattern reference — being replaced):
|
||||
```typescript
|
||||
// Sticky positioning pattern: "sticky top-0 z-10 bg-white border-b border-gray-100"
|
||||
// Container: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
|
||||
// Height: "h-14"
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create TopNav component</name>
|
||||
<files>src/client/components/TopNav.tsx</files>
|
||||
<read_first>
|
||||
src/client/components/TotalsBar.tsx
|
||||
src/client/stores/uiStore.ts
|
||||
src/client/components/UserMenu.tsx
|
||||
src/client/hooks/useAuth.ts
|
||||
</read_first>
|
||||
<action>
|
||||
Create `src/client/components/TopNav.tsx` that replaces TotalsBar with a full navigation bar.
|
||||
|
||||
**Structure (per D-01):**
|
||||
- Sticky top bar: `sticky top-0 z-10 bg-white border-b border-gray-100` (same as TotalsBar)
|
||||
- Container: `mx-auto max-w-7xl px-4 sm:px-6 lg:px-8`, flex row, `h-14`
|
||||
- Left: Logo — `<Link to="/">` with `<LucideIcon name="package" size={20} />` and "GearBox" text. Same styling as TotalsBar logo.
|
||||
- Center: Desktop nav links (hidden on mobile with `hidden md:flex`) — Home, Collection, Setups
|
||||
- Right: Search bar (desktop only) + UserMenu or "Sign in" link
|
||||
|
||||
**Active route detection (per D-03):**
|
||||
```typescript
|
||||
const matchRoute = useMatchRoute();
|
||||
const isHome = !!matchRoute({ to: "/" });
|
||||
const isCollection = !!matchRoute({ to: "/collection", fuzzy: true });
|
||||
const isSetups = !!matchRoute({ to: "/setups", fuzzy: true });
|
||||
```
|
||||
Active link gets `text-gray-900`, inactive gets `text-gray-500 hover:text-gray-700`.
|
||||
|
||||
**Auth-gated nav links (per D-02):**
|
||||
Create a `NavLinkOrButton` internal component. When `isAuthenticated` is false and the link is protected (Collection, Setups), render a `<button type="button" onClick={openAuthPrompt}>` styled identically to the link. When authenticated, render `<Link to={to}>`. Home is always a `<Link>` (not protected).
|
||||
|
||||
Do NOT use `<Link>` with `e.preventDefault()` — TanStack Router fires navigation before React event handlers can intercept reliably.
|
||||
|
||||
**Search bar (per D-07, D-17):**
|
||||
Desktop only (`hidden md:flex`). Clickable div that calls `openCatalogSearch("collection")`. Include keyboard handler for Enter. Style: `bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:border-gray-300`. Contains `<LucideIcon name="search" size={16} />` and placeholder text "Search catalog..." (text hidden below lg: `hidden lg:inline`).
|
||||
|
||||
**User section:**
|
||||
Same as TotalsBar: `isAuthenticated ? <UserMenu /> : <Link to="/login">Sign in</Link>`. Avatar always visible (both mobile and desktop).
|
||||
|
||||
**Imports:**
|
||||
- `Link, useMatchRoute` from `@tanstack/react-router`
|
||||
- `useAuth` from `../hooks/useAuth`
|
||||
- `LucideIcon` from `../lib/iconData`
|
||||
- `useUIStore` from `../stores/uiStore`
|
||||
- `UserMenu` from `./UserMenu`
|
||||
|
||||
Export: `export function TopNav()`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "export function TopNav" src/client/components/TopNav.tsx && grep -c "useMatchRoute" src/client/components/TopNav.tsx && grep -c "openAuthPrompt" src/client/components/TopNav.tsx && grep -c "openCatalogSearch" src/client/components/TopNav.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/client/components/TopNav.tsx exists and exports `export function TopNav()`
|
||||
- File imports `useMatchRoute` from `@tanstack/react-router`
|
||||
- File contains `openAuthPrompt` call for anonymous nav interception
|
||||
- File contains `openCatalogSearch("collection")` for search bar click
|
||||
- File contains `hidden md:flex` for desktop-only nav links
|
||||
- File contains `<UserMenu />` for authenticated users
|
||||
- File contains `<Link to="/login"` for anonymous "Sign in"
|
||||
- File uses `LucideIcon` (not direct lucide-react imports)
|
||||
- File does NOT contain `e.preventDefault` on Link elements
|
||||
</acceptance_criteria>
|
||||
<done>TopNav component renders logo, nav links (Home/Collection/Setups), search bar, and user section. Anonymous clicks on Collection/Setups fire AuthPromptModal. Active route is highlighted. Desktop nav links hidden on mobile.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create BottomTabBar component</name>
|
||||
<files>src/client/components/BottomTabBar.tsx</files>
|
||||
<read_first>
|
||||
src/client/components/FabMenu.tsx
|
||||
src/client/stores/uiStore.ts
|
||||
src/client/hooks/useAuth.ts
|
||||
src/client/lib/iconData.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Create `src/client/components/BottomTabBar.tsx` for mobile navigation.
|
||||
|
||||
**Structure (per D-13):**
|
||||
- Fixed bottom: `fixed bottom-0 left-0 right-0 md:hidden z-20 bg-white border-t border-gray-100`
|
||||
- z-20 so CatalogSearchOverlay (which uses higher z-index) renders above it
|
||||
- 4 tab items in a flex row with `justify-around`
|
||||
- Each tab: icon (LucideIcon, size 20) + label (text-xs) stacked vertically
|
||||
|
||||
**Tab items (per D-13, D-14):**
|
||||
1. Home — icon: `home`, label: "Home", always `<Link to="/">`
|
||||
2. Collection — icon: `package`, label: "Collection", `<Link to="/collection">` if authenticated, `<button onClick={openAuthPrompt}>` if anonymous (per D-02)
|
||||
3. Setups — icon: `layers`, label: "Setups", `<Link to="/setups">` if authenticated, `<button onClick={openAuthPrompt}>` if anonymous (per D-02)
|
||||
4. Search — icon: `search`, label: "Search", always `<button onClick={() => openCatalogSearch("collection")}>` (per D-14, D-17)
|
||||
|
||||
**Active state:**
|
||||
Use same `useMatchRoute` pattern as TopNav. Active tab: `text-gray-900`, inactive: `text-gray-400`. Search tab is never "active" (it opens an overlay, not a route).
|
||||
|
||||
**Entry animation (Framer Motion):**
|
||||
```typescript
|
||||
<motion.div
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{ duration: 0.2, ease: "easeOut" }}
|
||||
className="fixed bottom-0 ..."
|
||||
>
|
||||
```
|
||||
|
||||
**Safe area:**
|
||||
Add `pb-[env(safe-area-inset-bottom)]` to the container for iOS devices with home indicator.
|
||||
|
||||
**Imports:**
|
||||
- `Link, useMatchRoute` from `@tanstack/react-router`
|
||||
- `motion` from `framer-motion`
|
||||
- `useAuth` from `../hooks/useAuth`
|
||||
- `LucideIcon` from `../lib/iconData`
|
||||
- `useUIStore` from `../stores/uiStore`
|
||||
|
||||
Export: `export function BottomTabBar()`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "export function BottomTabBar" src/client/components/BottomTabBar.tsx && grep -c "md:hidden" src/client/components/BottomTabBar.tsx && grep -c "openCatalogSearch" src/client/components/BottomTabBar.tsx && grep -c "openAuthPrompt" src/client/components/BottomTabBar.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/client/components/BottomTabBar.tsx exists and exports `export function BottomTabBar()`
|
||||
- File contains `md:hidden` to only show on mobile
|
||||
- File contains `fixed bottom-0` for fixed positioning
|
||||
- File contains `z-20` for z-index
|
||||
- File contains 4 tab items: Home, Collection, Setups, Search (grep for all 4 labels)
|
||||
- File contains `openCatalogSearch("collection")` for Search tab
|
||||
- File contains `openAuthPrompt` for anonymous Collection/Setups taps
|
||||
- File uses `LucideIcon` with names: `home`, `package`, `layers`, `search`
|
||||
- File imports `motion` from `framer-motion` for entry animation
|
||||
</acceptance_criteria>
|
||||
<done>BottomTabBar renders 4 tabs with Lucide icons on mobile viewports. Search tab opens CatalogSearchOverlay. Collection/Setups tabs fire AuthPromptModal for anonymous users. Active tab is highlighted. Component hidden on md+ screens.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Both component files exist and export their named functions
|
||||
- Both use `useMatchRoute` for active route detection
|
||||
- Both use `openAuthPrompt` for anonymous user interception on protected links
|
||||
- Both use `openCatalogSearch("collection")` for search trigger
|
||||
- TopNav uses `hidden md:flex` for desktop nav; BottomTabBar uses `md:hidden` for mobile only
|
||||
- Neither imports directly from `lucide-react` — both use `LucideIcon` wrapper
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- TopNav.tsx is a complete, self-contained component ready to replace TotalsBar in __root.tsx
|
||||
- BottomTabBar.tsx is a complete, self-contained component ready to add to __root.tsx
|
||||
- Both handle authenticated and anonymous states correctly
|
||||
- Both follow existing codebase patterns (Tailwind, LucideIcon, uiStore, useAuth)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,211 @@
|
||||
---
|
||||
phase: 27-top-nav-restructure-and-search-bar-rethink
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/client/routes/setups/index.tsx
|
||||
- src/client/routes/collection/index.tsx
|
||||
autonomous: true
|
||||
requirements:
|
||||
- NAV-05
|
||||
- NAV-04
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Visiting /setups renders the SetupsView component"
|
||||
- "Collection page shows only Gear and Planning tabs (no Setups tab)"
|
||||
- "Existing /collection?tab=setups URLs gracefully fall back to Gear tab"
|
||||
artifacts:
|
||||
- path: "src/client/routes/setups/index.tsx"
|
||||
provides: "Top-level Setups route page"
|
||||
exports: ["Route"]
|
||||
- path: "src/client/routes/collection/index.tsx"
|
||||
provides: "Collection page with Gear and Planning tabs only"
|
||||
contains: "TAB_ORDER = [\"gear\", \"planning\"]"
|
||||
key_links:
|
||||
- from: "src/client/routes/setups/index.tsx"
|
||||
to: "src/client/components/SetupsView.tsx"
|
||||
via: "import and render"
|
||||
pattern: "import.*SetupsView"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Elevate Setups to a top-level route and simplify Collection tabs to Gear and Planning only.
|
||||
|
||||
Purpose: Per D-04, Setups becomes a standalone top-level section with its own route at `/setups`. Per D-05, the Collection page drops its Setups tab, keeping only Gear and Planning. This is the route restructuring that the new nav bar (Plan 01) links to.
|
||||
|
||||
Output: New `setups/index.tsx` route file and updated `collection/index.tsx` with two tabs.
|
||||
</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/27-top-nav-restructure-and-search-bar-rethink/27-CONTEXT.md
|
||||
@.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- SetupsView component that will be rendered in the new route -->
|
||||
From src/client/components/SetupsView.tsx:
|
||||
```typescript
|
||||
export function SetupsView(): JSX.Element;
|
||||
// Renders list of user setups with search, create, delete
|
||||
// Already handles auth-gated mutations internally
|
||||
```
|
||||
|
||||
From src/client/routes/collection/index.tsx (current state — being modified):
|
||||
```typescript
|
||||
const TAB_ORDER = ["gear", "planning", "setups"] as const;
|
||||
const TAB_LABELS: Record<(typeof TAB_ORDER)[number], string> = {
|
||||
gear: "Gear",
|
||||
planning: "Planning",
|
||||
setups: "Setups",
|
||||
};
|
||||
const searchSchema = z.object({
|
||||
tab: z.enum(["gear", "planning", "setups"]).catch("gear"),
|
||||
});
|
||||
// Import: import { SetupsView } from "../../components/SetupsView";
|
||||
// Render: tab === "setups" renders <SetupsView />
|
||||
```
|
||||
|
||||
From src/client/routes/setups/$setupId.tsx (existing sibling route — for pattern reference):
|
||||
```typescript
|
||||
export const Route = createFileRoute("/setups/$setupId")({ ... });
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create setups index route</name>
|
||||
<files>src/client/routes/setups/index.tsx</files>
|
||||
<read_first>
|
||||
src/client/routes/collection/index.tsx
|
||||
src/client/components/SetupsView.tsx
|
||||
src/client/routes/setups/$setupId.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Create `src/client/routes/setups/index.tsx` to make `/setups` a top-level route (per D-04).
|
||||
|
||||
**File contents:**
|
||||
```typescript
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { SetupsView } from "../../components/SetupsView";
|
||||
|
||||
export const Route = createFileRoute("/setups/")({
|
||||
component: SetupsPage,
|
||||
});
|
||||
|
||||
function SetupsPage() {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<SetupsView />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
The container matches the Collection page layout pattern (`max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6`).
|
||||
|
||||
SetupsView already handles all setup CRUD logic, auth gating for mutations, search/filter, and delete confirmation. No additional logic needed in the route component.
|
||||
|
||||
Note: After creating this file, the TanStack Router dev server will auto-regenerate `routeTree.gen.ts` to include the new route. Do NOT edit `routeTree.gen.ts` manually.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "createFileRoute.*setups" src/client/routes/setups/index.tsx && grep -c "SetupsView" src/client/routes/setups/index.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/client/routes/setups/index.tsx exists
|
||||
- File contains `createFileRoute("/setups/")`
|
||||
- File imports `SetupsView` from `../../components/SetupsView`
|
||||
- File renders `<SetupsView />` inside a container div
|
||||
- Container div has class `max-w-7xl mx-auto`
|
||||
</acceptance_criteria>
|
||||
<done>/setups route exists and renders SetupsView in the standard page container layout</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Remove Setups tab from Collection page</name>
|
||||
<files>src/client/routes/collection/index.tsx</files>
|
||||
<read_first>
|
||||
src/client/routes/collection/index.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Modify `src/client/routes/collection/index.tsx` to remove the Setups tab (per D-05, D-06).
|
||||
|
||||
**Changes:**
|
||||
|
||||
1. **Remove SetupsView import:** Delete `import { SetupsView } from "../../components/SetupsView";`
|
||||
|
||||
2. **Update TAB_ORDER:** Change from `["gear", "planning", "setups"]` to `["gear", "planning"]`:
|
||||
```typescript
|
||||
const TAB_ORDER = ["gear", "planning"] as const;
|
||||
```
|
||||
|
||||
3. **Update TAB_LABELS:** Remove the `setups` entry:
|
||||
```typescript
|
||||
const TAB_LABELS: Record<(typeof TAB_ORDER)[number], string> = {
|
||||
gear: "Gear",
|
||||
planning: "Planning",
|
||||
};
|
||||
```
|
||||
|
||||
4. **Update searchSchema:** Remove `"setups"` from the Zod enum. The `.catch("gear")` ensures old `/collection?tab=setups` URLs gracefully fall back:
|
||||
```typescript
|
||||
const searchSchema = z.object({
|
||||
tab: z.enum(["gear", "planning"]).catch("gear"),
|
||||
});
|
||||
```
|
||||
|
||||
5. **Remove setups conditional render:** In the `CollectionPage` component, remove the `tab === "setups"` branch from the ternary. The render should be:
|
||||
```typescript
|
||||
{tab === "gear" ? (
|
||||
<CollectionView />
|
||||
) : (
|
||||
<PlanningView />
|
||||
)}
|
||||
```
|
||||
|
||||
Keep all other code unchanged: the pill tab navigation, AnimatePresence animation, slide variants, and the Collection/Planning views remain exactly as they are.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && ! grep -q "setups" src/client/routes/collection/index.tsx && echo "no-setups-found" && grep -c "gear.*planning" src/client/routes/collection/index.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/client/routes/collection/index.tsx does NOT contain the string "setups" (case-sensitive)
|
||||
- src/client/routes/collection/index.tsx does NOT import SetupsView
|
||||
- TAB_ORDER contains exactly `["gear", "planning"]`
|
||||
- searchSchema z.enum contains exactly `["gear", "planning"]`
|
||||
- TAB_LABELS has only `gear` and `planning` entries
|
||||
- AnimatePresence and slide animation remain unchanged
|
||||
- CollectionView and PlanningView renders remain unchanged
|
||||
</acceptance_criteria>
|
||||
<done>Collection page shows only Gear and Planning tabs. Setups tab is completely removed. Old ?tab=setups URLs fall back to Gear tab via Zod catch.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `/setups` route file exists and renders SetupsView
|
||||
- Collection page has exactly 2 tabs: Gear and Planning
|
||||
- No reference to "setups" remains in collection/index.tsx
|
||||
- Both files follow existing codebase patterns (createFileRoute, Zod validation, Tailwind layout)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Visiting `/setups` renders the SetupsView component in a standard page layout
|
||||
- Collection page shows 2 pill tabs (Gear, Planning) instead of 3
|
||||
- Existing `/collection?tab=setups` URLs gracefully default to Gear tab
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
phase: 27-top-nav-restructure-and-search-bar-rethink
|
||||
plan: 03
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on:
|
||||
- 27-01
|
||||
- 27-02
|
||||
files_modified:
|
||||
- src/client/routes/__root.tsx
|
||||
- src/client/routes/index.tsx
|
||||
autonomous: false
|
||||
requirements:
|
||||
- NAV-01
|
||||
- NAV-02
|
||||
- NAV-03
|
||||
- NAV-04
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "The app shows TopNav instead of TotalsBar on every page"
|
||||
- "BottomTabBar is visible on mobile viewports"
|
||||
- "FAB is hidden on mobile (only visible on md+ screens)"
|
||||
- "The landing page has no hero section — starts with Popular Setups"
|
||||
- "/setups is in isPublicRoute so direct navigation works for anonymous users"
|
||||
- "Page content is not obscured by the bottom tab bar on mobile"
|
||||
artifacts:
|
||||
- path: "src/client/routes/__root.tsx"
|
||||
provides: "Root layout with TopNav, BottomTabBar, and updated FAB visibility"
|
||||
contains: "TopNav"
|
||||
- path: "src/client/routes/index.tsx"
|
||||
provides: "Landing page without hero section"
|
||||
key_links:
|
||||
- from: "src/client/routes/__root.tsx"
|
||||
to: "src/client/components/TopNav.tsx"
|
||||
via: "import and render"
|
||||
pattern: "import.*TopNav"
|
||||
- from: "src/client/routes/__root.tsx"
|
||||
to: "src/client/components/BottomTabBar.tsx"
|
||||
via: "import and render"
|
||||
pattern: "import.*BottomTabBar"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wire TopNav and BottomTabBar into the root layout, hide FAB on mobile, remove the landing page hero, and update public route checks.
|
||||
|
||||
Purpose: This is the integration plan that connects everything built in Plans 01 and 02. The TotalsBar is swapped for TopNav, BottomTabBar is added for mobile, the FAB gets hidden on mobile (per D-15), the landing page hero is removed (per D-09/D-10), and `/setups` is added to isPublicRoute so anonymous direct navigation works.
|
||||
|
||||
Output: Fully wired navigation system visible across the app.
|
||||
</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/27-top-nav-restructure-and-search-bar-rethink/27-CONTEXT.md
|
||||
@.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-RESEARCH.md
|
||||
@.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-01-SUMMARY.md
|
||||
@.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-02-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Components created in Plan 01 -->
|
||||
From src/client/components/TopNav.tsx:
|
||||
```typescript
|
||||
export function TopNav(): JSX.Element;
|
||||
// Persistent top nav bar with logo, nav links, search, user menu
|
||||
// Handles auth interception internally
|
||||
```
|
||||
|
||||
From src/client/components/BottomTabBar.tsx:
|
||||
```typescript
|
||||
export function BottomTabBar(): JSX.Element;
|
||||
// Mobile bottom tab bar, renders only on md:hidden
|
||||
// Handles auth interception internally
|
||||
```
|
||||
|
||||
<!-- Current __root.tsx structure (being modified) -->
|
||||
From src/client/routes/__root.tsx:
|
||||
```typescript
|
||||
// Current imports to change:
|
||||
import { TotalsBar } from "../components/TotalsBar"; // REMOVE
|
||||
|
||||
// Current isPublicRoute check:
|
||||
const isPublicRoute =
|
||||
location.pathname === "/" ||
|
||||
location.pathname.startsWith("/users/") ||
|
||||
location.pathname.startsWith("/global-items") ||
|
||||
location.pathname.startsWith("/setups/") ||
|
||||
location.pathname === "/login";
|
||||
|
||||
// Current FAB render:
|
||||
{showFab && <FabMenu isSetupsPage={isSetupsPage} />}
|
||||
|
||||
// Current TotalsBar render:
|
||||
<TotalsBar {...totalsBarProps} />
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Wire TopNav, BottomTabBar, and FAB changes into __root.tsx</name>
|
||||
<files>src/client/routes/__root.tsx</files>
|
||||
<read_first>
|
||||
src/client/routes/__root.tsx
|
||||
src/client/components/TopNav.tsx
|
||||
src/client/components/BottomTabBar.tsx
|
||||
src/client/components/FabMenu.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Modify `src/client/routes/__root.tsx` with these specific changes:
|
||||
|
||||
**1. Update imports:**
|
||||
- Remove: `import { TotalsBar } from "../components/TotalsBar";`
|
||||
- Add: `import { TopNav } from "../components/TopNav";`
|
||||
- Add: `import { BottomTabBar } from "../components/BottomTabBar";`
|
||||
|
||||
**2. Remove TotalsBar-related code in RootLayout:**
|
||||
- Delete the `isDashboard` variable: `const isDashboard = !!matchRoute({ to: "/" });`
|
||||
- Delete the `totalsBarProps` variable: `const totalsBarProps = isDashboard ? {} : { linkTo: "/" };`
|
||||
|
||||
**3. Replace TotalsBar render with TopNav:**
|
||||
Change `<TotalsBar {...totalsBarProps} />` to `<TopNav />`
|
||||
|
||||
**4. Add `/setups` to isPublicRoute (pitfall 2 from research):**
|
||||
The current check has `location.pathname.startsWith("/setups/")` which only covers `/setups/123` detail pages. Add `location.pathname === "/setups"` so the index route is also public:
|
||||
```typescript
|
||||
const isPublicRoute =
|
||||
location.pathname === "/" ||
|
||||
location.pathname.startsWith("/users/") ||
|
||||
location.pathname.startsWith("/global-items") ||
|
||||
location.pathname === "/setups" ||
|
||||
location.pathname.startsWith("/setups/") ||
|
||||
location.pathname === "/login";
|
||||
```
|
||||
|
||||
**5. Hide FAB on mobile (per D-15):**
|
||||
Wrap the FabMenu in a div with `hidden md:block`:
|
||||
```typescript
|
||||
{showFab && (
|
||||
<div className="hidden md:block">
|
||||
<FabMenu isSetupsPage={isSetupsPage} />
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**6. Add BottomTabBar:**
|
||||
Add `<BottomTabBar />` after the FabMenu block (before CatalogSearchOverlay). It renders itself only on mobile via `md:hidden`.
|
||||
|
||||
**7. Add bottom padding for mobile tab bar (pitfall from research):**
|
||||
On the root div `<div className="min-h-screen bg-gray-50">`, add mobile-only bottom padding so content isn't obscured by the fixed bottom tab bar:
|
||||
```typescript
|
||||
<div className="min-h-screen bg-gray-50 pb-16 md:pb-0">
|
||||
```
|
||||
`pb-16` (64px) accounts for the bottom tab bar height on mobile. `md:pb-0` removes it on desktop.
|
||||
|
||||
**Do NOT change:** CandidateDeleteDialog, ResolveDialog, CatalogSearchOverlay, AddToCollectionModal, AddToThreadModal, Toaster, AuthPromptModal, OnboardingWizard, ConfirmDialog, ExternalLinkDialog, or any other existing modal/dialog code.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "TopNav" src/client/routes/__root.tsx && grep -c "BottomTabBar" src/client/routes/__root.tsx && ! grep -q "TotalsBar" src/client/routes/__root.tsx && echo "no-totalsbar" && grep -c 'hidden md:block' src/client/routes/__root.tsx && grep 'isPublicRoute' src/client/routes/__root.tsx | grep -c '/setups"'</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- __root.tsx imports `TopNav` from `../components/TopNav` (not TotalsBar)
|
||||
- __root.tsx imports `BottomTabBar` from `../components/BottomTabBar`
|
||||
- __root.tsx does NOT contain `TotalsBar` anywhere
|
||||
- __root.tsx does NOT contain `isDashboard` or `totalsBarProps` variables
|
||||
- __root.tsx renders `<TopNav />` (no props)
|
||||
- __root.tsx renders `<BottomTabBar />`
|
||||
- FabMenu is wrapped in `<div className="hidden md:block">`
|
||||
- isPublicRoute includes `location.pathname === "/setups"`
|
||||
- Root div has `pb-16 md:pb-0` classes
|
||||
- All existing modals/dialogs remain unchanged
|
||||
</acceptance_criteria>
|
||||
<done>Root layout uses TopNav instead of TotalsBar, BottomTabBar is rendered for mobile, FAB is hidden on mobile, /setups is a public route, and mobile bottom padding prevents content occlusion.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Remove hero section from landing page</name>
|
||||
<files>src/client/routes/index.tsx</files>
|
||||
<read_first>
|
||||
src/client/routes/index.tsx
|
||||
</read_first>
|
||||
<action>
|
||||
Modify `src/client/routes/index.tsx` to remove the hero section (per D-09, D-10, D-11).
|
||||
|
||||
**Changes:**
|
||||
|
||||
1. **Delete the `HeroSection` function entirely** (lines ~35-72 in current file). This includes the heading "Discover Gear", subtitle, search bar div, and "Go to Collection" link.
|
||||
|
||||
2. **Remove unused imports:**
|
||||
- Remove `{ Search } from "lucide-react"` — only used by HeroSection
|
||||
- Remove `{ Link } from "@tanstack/react-router"` — only used by HeroSection's "Go to Collection" link (check if Link is used elsewhere in the file first; it is not)
|
||||
- Remove `useAuth` import — only used to pass `isAuthenticated` to HeroSection
|
||||
- Remove `useUIStore` import — only used to get `openCatalogSearch` for HeroSection
|
||||
|
||||
3. **Update LandingPage function** to remove HeroSection render and unused variables:
|
||||
```typescript
|
||||
function LandingPage() {
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<PopularSetupsSection />
|
||||
<RecentItemsSection />
|
||||
<TrendingCategoriesSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
4. **Keep unchanged:** `PopularSetupsSection`, `RecentItemsSection`, `TrendingCategoriesSection`, `SectionSkeleton`, all discovery hooks imports, `GlobalItemCard` import, `PublicSetupCard` import, and the `createFileRoute` route definition.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/jean-luc-makiola/Development/projects/GearBox && ! grep -q "HeroSection" src/client/routes/index.tsx && echo "no-hero" && ! grep -q "Discover Gear" src/client/routes/index.tsx && echo "no-heading" && ! grep -q "lucide-react" src/client/routes/index.tsx && echo "no-lucide-direct" && grep -c "PopularSetupsSection" src/client/routes/index.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- src/client/routes/index.tsx does NOT contain `HeroSection` (function definition or usage)
|
||||
- src/client/routes/index.tsx does NOT contain `Discover Gear` heading text
|
||||
- src/client/routes/index.tsx does NOT contain `Go to Collection` link text
|
||||
- src/client/routes/index.tsx does NOT import from `lucide-react`
|
||||
- src/client/routes/index.tsx does NOT import `useAuth` or `useUIStore`
|
||||
- LandingPage function renders PopularSetupsSection as first child
|
||||
- PopularSetupsSection, RecentItemsSection, TrendingCategoriesSection all remain
|
||||
- SectionSkeleton helper function remains
|
||||
</acceptance_criteria>
|
||||
<done>Landing page starts directly with Popular Setups section. No hero section, no heading, no search bar, no "Go to Collection" link. Search is now exclusively in the TopNav bar.</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking">
|
||||
<name>Task 3: Verify full navigation flow</name>
|
||||
<what-built>Complete navigation restructure: TopNav on desktop, BottomTabBar on mobile, Setups as top-level route, Collection with 2 tabs, landing page without hero.</what-built>
|
||||
<how-to-verify>
|
||||
1. Run `bun run dev` and open http://localhost:5173 in your browser
|
||||
2. **Desktop (wide viewport):**
|
||||
- Verify top nav shows: logo (GearBox with package icon), Home/Collection/Setups links, search bar, and user avatar or "Sign in"
|
||||
- Click the search bar — CatalogSearchOverlay should open
|
||||
- If signed out: click Collection — AuthPromptModal should appear (not navigation)
|
||||
- If signed in: click Collection — navigates to /collection, Collection link is highlighted
|
||||
- Navigate to /setups — SetupsView renders, Setups link is highlighted
|
||||
- Navigate to /collection — only Gear and Planning tabs (no Setups tab)
|
||||
- Visit the landing page (/) — no hero section, starts with Popular Setups
|
||||
3. **Mobile (resize browser to ~375px width or use DevTools mobile):**
|
||||
- Top bar shows only logo and user avatar/sign-in (no nav links, no search bar)
|
||||
- Bottom tab bar shows 4 items: Home, Collection, Setups, Search
|
||||
- Tap Search — CatalogSearchOverlay opens
|
||||
- FAB is NOT visible (hidden on mobile)
|
||||
- Content is not cut off at the bottom (padding accounts for tab bar)
|
||||
4. Verify no console errors
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "approved" or describe any issues</resume-signal>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `bun run dev` starts without errors
|
||||
- TopNav replaces TotalsBar across all pages
|
||||
- BottomTabBar appears only on mobile viewports
|
||||
- FAB hidden on mobile, visible on desktop
|
||||
- Landing page has no hero — starts with content sections
|
||||
- /setups renders SetupsView
|
||||
- /collection has 2 tabs (Gear, Planning)
|
||||
- Anonymous nav clicks trigger AuthPromptModal
|
||||
- CatalogSearchOverlay opens from nav search bar and bottom tab bar Search
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Complete navigation flow works on both desktop and mobile viewports
|
||||
- All 17 locked decisions (D-01 through D-17) are satisfied
|
||||
- No visual regressions on existing pages
|
||||
- No console errors
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-03-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user