---
phase: 26-discovery-landing-page
plan: 03
type: execute
wave: 3
depends_on: [26-01, 26-02]
files_modified:
  - src/client/routes/index.tsx
  - src/client/components/PublicSetupCard.tsx
  - src/client/routes/users/$userId.tsx
autonomous: false
requirements: [DISC-01, DISC-02, DISC-03, DISC-04, DISC-05]

must_haves:
  truths:
    - "Root URL shows a hero section with a catalog search bar"
    - "Clicking the search bar opens CatalogSearchOverlay"
    - "Popular setups section shows setup cards with item counts"
    - "Recently added items section shows GlobalItemCard components"
    - "Trending categories section shows category names with item counts"
    - "Authenticated users see Go to Collection link in hero"
    - "Anonymous users see the page without login redirect"
  artifacts:
    - path: "src/client/routes/index.tsx"
      provides: "Landing page with hero, popular setups, recent items, trending categories"
      min_lines: 80
    - path: "src/client/components/PublicSetupCard.tsx"
      provides: "Enhanced setup card with optional itemCount and creatorName"
      contains: "itemCount"
  key_links:
    - from: "src/client/routes/index.tsx"
      to: "src/client/hooks/useDiscovery.ts"
      via: "imports useDiscoverySetups, useDiscoveryItems, useDiscoveryCategories"
      pattern: "from.*useDiscovery"
    - from: "src/client/routes/index.tsx"
      to: "src/client/stores/uiStore.ts"
      via: "openCatalogSearch trigger from hero"
      pattern: "openCatalogSearch"
    - from: "src/client/routes/index.tsx"
      to: "src/client/hooks/useAuth.ts"
      via: "auth state for conditional Go to Collection CTA"
      pattern: "useAuth"
    - from: "src/client/routes/index.tsx"
      to: "src/client/components/GlobalItemCard.tsx"
      via: "renders catalog items in recent items section"
      pattern: "GlobalItemCard"
    - from: "src/client/routes/index.tsx"
      to: "src/client/components/PublicSetupCard.tsx"
      via: "renders setup cards in popular setups section"
      pattern: "PublicSetupCard"
---

<objective>
Rewrite the landing page at `/` from a personal dashboard to a public discovery page. Enhance PublicSetupCard with itemCount and creatorName. Build hero section with search trigger, three content feed sections, and conditional auth CTA.

Purpose: This is the user-facing deliverable — the page visitors see first. Composes existing components with new discovery hooks into the layout specified by D-01 through D-11.
Output: Complete landing page at `/`, enhanced PublicSetupCard.
</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/26-discovery-landing-page/26-CONTEXT.md
@.planning/phases/26-discovery-landing-page/26-RESEARCH.md
@.planning/phases/26-discovery-landing-page/26-01-SUMMARY.md

<interfaces>
<!-- From plan 02: client hooks -->
From src/client/hooks/useDiscovery.ts:
```typescript
export interface DiscoverySetup {
  id: number;
  name: string;
  createdAt: string;
  itemCount: number;
  creatorName: string | null;
}
export interface DiscoveryCategory {
  name: string;
  itemCount: number;
}
export function useDiscoverySetups(limit?: number): UseQueryResult
export function useDiscoveryItems(limit?: number): UseQueryResult
export function useDiscoveryCategories(limit?: number): UseQueryResult
```

From src/client/hooks/useAuth.ts:
```typescript
interface AuthState {
  user: { id: string; email?: string } | null;
  authenticated: boolean;
}
export function useAuth(): UseQueryResult<AuthState>
```

From src/client/stores/uiStore.ts:
```typescript
openCatalogSearch: (mode: "collection" | "thread") => void;
// Access via: const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);
```

From src/client/components/GlobalItemCard.tsx:
```typescript
interface GlobalItemCardProps {
  id: number; brand: string; model: string; category: string | null;
  weightGrams: number | null; priceCents: number | null; imageUrl: string | null;
}
export function GlobalItemCard(props: GlobalItemCardProps): JSX.Element
```

From src/client/components/PublicSetupCard.tsx (current — will be enhanced):
```typescript
interface PublicSetupCardProps {
  setup: { id: number; name: string; createdAt: string; };
}
export function PublicSetupCard({ setup }: PublicSetupCardProps): JSX.Element
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Enhance PublicSetupCard with itemCount and creatorName</name>
  <files>src/client/components/PublicSetupCard.tsx, src/client/routes/users/$userId.tsx</files>
  <read_first>
    - src/client/components/PublicSetupCard.tsx (current component — full content)
    - src/client/routes/users/$userId.tsx (existing usage of PublicSetupCard — check what shape is passed)
  </read_first>
  <action>
    **Modify `src/client/components/PublicSetupCard.tsx`:**

    Update the `PublicSetupCardProps` interface to add optional fields (per Pitfall 3 — keep optional to avoid breaking existing usages):
    ```typescript
    interface PublicSetupCardProps {
      setup: {
        id: number;
        name: string;
        createdAt: string;
        itemCount?: number;      // NEW — optional for backward compat
        creatorName?: string | null;  // NEW — optional
      };
    }
    ```

    Update the component JSX to display the new fields when present:

    After the existing `<h3>` (setup name) and before/replacing the `<p>` date line, render:
    - If `setup.creatorName` is truthy: `<p className="text-xs text-gray-400 mt-1">by {setup.creatorName}</p>`
    - If `setup.itemCount` is defined and > 0: `<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-400">{setup.itemCount} items</span>`
    - Keep the existing date display

    Layout for the bottom area of the card (below the name):
    ```tsx
    <div className="flex items-center justify-between mt-2">
      <div className="flex items-center gap-2">
        {setup.itemCount != null && setup.itemCount > 0 && (
          <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-400">
            {setup.itemCount} {setup.itemCount === 1 ? "item" : "items"}
          </span>
        )}
      </div>
      <p className="text-xs text-gray-400">{formattedDate}</p>
    </div>
    {setup.creatorName && (
      <p className="text-xs text-gray-400 mt-1">by {setup.creatorName}</p>
    )}
    ```

    Add `cursor-pointer` to the Link className (folded todo from CONTEXT.md).

    **Verify `src/client/routes/users/$userId.tsx`:**
    Read the file to confirm the existing `PublicSetupCard` usage still compiles. Since `itemCount` and `creatorName` are optional, the existing usage passing `{ id, name, createdAt }` will continue to work without changes. No modification needed to this file unless it already passes extra props.
  </action>
  <verify>
    <automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run build 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - src/client/components/PublicSetupCard.tsx contains `itemCount?: number`
    - src/client/components/PublicSetupCard.tsx contains `creatorName?: string | null`
    - src/client/components/PublicSetupCard.tsx contains `setup.itemCount` (conditional rendering)
    - src/client/components/PublicSetupCard.tsx contains `setup.creatorName` (conditional rendering)
    - src/client/components/PublicSetupCard.tsx contains `cursor-pointer`
    - `bun run build` succeeds without TypeScript errors
  </acceptance_criteria>
  <done>PublicSetupCard renders item count and creator name when provided, existing usages compile without changes, cursor-pointer applied.</done>
</task>

<task type="auto">
  <name>Task 2: Rewrite index.tsx as discovery landing page</name>
  <files>src/client/routes/index.tsx</files>
  <read_first>
    - src/client/routes/index.tsx (current dashboard — will be completely rewritten)
    - src/client/components/GlobalItemCard.tsx (props interface for rendering items)
    - src/client/components/PublicSetupCard.tsx (enhanced props from Task 1)
    - src/client/hooks/useDiscovery.ts (hook signatures from plan 02)
    - src/client/hooks/useAuth.ts (useAuth hook pattern)
    - src/client/stores/uiStore.ts (openCatalogSearch usage pattern)
    - src/client/routes/__root.tsx (isDashboard detection — do NOT modify per Pitfall 2)
  </read_first>
  <action>
    **Completely rewrite `src/client/routes/index.tsx`** (per D-03, retire DashboardPage entirely):

    Remove ALL existing imports (DashboardCard, useFormatters, useSetups, useThreads, useTotals).

    New imports:
    ```typescript
    import { createFileRoute, Link } from "@tanstack/react-router";
    import { Search } from "lucide-react";
    import { GlobalItemCard } from "../components/GlobalItemCard";
    import { PublicSetupCard } from "../components/PublicSetupCard";
    import { useAuth } from "../hooks/useAuth";
    import { useDiscoverySetups, useDiscoveryItems, useDiscoveryCategories } from "../hooks/useDiscovery";
    import { useUIStore } from "../stores/uiStore";
    ```

    Note: Use `lucide-react` for the Search icon. The project already uses lucide-react (check existing imports). If the project uses the custom `LucideIcon` component instead, use `<LucideIcon name="search" className="w-5 h-5 text-gray-400" />` from `../lib/iconData`.

    **Route export** (same file route, new component):
    ```typescript
    export const Route = createFileRoute("/")({
      component: LandingPage,
    });
    ```

    **LandingPage function** (per D-01, D-02):
    ```typescript
    function LandingPage() {
      const { data: auth } = useAuth();
      const isAuthenticated = !!auth?.user;
      const openCatalogSearch = useUIStore((s) => s.openCatalogSearch);

      return (
        <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
          <HeroSection isAuthenticated={isAuthenticated} onSearchFocus={() => openCatalogSearch("collection")} />
          <PopularSetupsSection />
          <RecentItemsSection />
          <TrendingCategoriesSection />
        </div>
      );
    }
    ```

    **HeroSection** (per D-01, D-04, D-10, D-11):
    ```typescript
    function HeroSection({ isAuthenticated, onSearchFocus }: { isAuthenticated: boolean; onSearchFocus: () => void }) {
      return (
        <div className="text-center mb-16">
          <h1 className="text-3xl sm:text-4xl font-bold text-gray-900 mb-2">Discover Gear</h1>
          <p className="text-gray-500 mb-8">Browse what other people carry</p>
          <div
            onClick={onSearchFocus}
            onKeyDown={(e) => e.key === "Enter" && onSearchFocus()}
            role="button"
            tabIndex={0}
            className="max-w-xl mx-auto flex items-center gap-3 px-4 py-3 bg-white rounded-xl border border-gray-200 hover:border-gray-300 cursor-pointer shadow-sm transition-all"
          >
            <Search className="w-5 h-5 text-gray-400 shrink-0" />
            <span className="text-sm text-gray-400 flex-1 text-left">Search the catalog...</span>
          </div>
          {isAuthenticated && (
            <div className="mt-4">
              <Link to="/collection" className="text-sm text-gray-600 hover:text-gray-900 underline underline-offset-2 cursor-pointer">
                Go to Collection
              </Link>
            </div>
          )}
        </div>
      );
    }
    ```

    **PopularSetupsSection** (per D-02, D-05):
    ```typescript
    function PopularSetupsSection() {
      const { data, isLoading } = useDiscoverySetups(6);
      const setups = data?.items ?? [];

      if (!isLoading && setups.length === 0) return null;

      return (
        <section className="mb-12">
          <div className="flex items-center justify-between mb-4">
            <h2 className="text-lg font-semibold text-gray-900">Popular Setups</h2>
          </div>
          {isLoading ? (
            <SectionSkeleton count={6} aspect="none" />
          ) : (
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
              {setups.map((setup) => (
                <PublicSetupCard key={setup.id} setup={setup} />
              ))}
            </div>
          )}
        </section>
      );
    }
    ```

    **RecentItemsSection** (per D-02, D-06):
    ```typescript
    function RecentItemsSection() {
      const { data, isLoading } = useDiscoveryItems(8);
      const items = data?.items ?? [];

      if (!isLoading && items.length === 0) return null;

      return (
        <section className="mb-12">
          <div className="flex items-center justify-between mb-4">
            <h2 className="text-lg font-semibold text-gray-900">Recently Added</h2>
          </div>
          {isLoading ? (
            <SectionSkeleton count={8} aspect="[4/3]" />
          ) : (
            <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
              {items.map((item) => (
                <GlobalItemCard
                  key={item.id}
                  id={item.id}
                  brand={item.brand}
                  model={item.model}
                  category={item.category}
                  weightGrams={item.weightGrams}
                  priceCents={item.priceCents}
                  imageUrl={item.imageUrl}
                />
              ))}
            </div>
          )}
        </section>
      );
    }
    ```

    **TrendingCategoriesSection** (per D-02, D-07):
    ```typescript
    function TrendingCategoriesSection() {
      const { data, isLoading } = useDiscoveryCategories(12);
      const categories = data ?? [];

      if (!isLoading && categories.length === 0) return null;

      return (
        <section className="mb-12">
          <div className="flex items-center justify-between mb-4">
            <h2 className="text-lg font-semibold text-gray-900">Trending Categories</h2>
          </div>
          {isLoading ? (
            <div className="flex flex-wrap gap-2">
              {Array.from({ length: 8 }).map((_, i) => (
                <div key={i} className="h-8 w-24 bg-gray-100 rounded-full animate-pulse" />
              ))}
            </div>
          ) : (
            <div className="flex flex-wrap gap-2">
              {categories.map((cat) => (
                <span
                  key={cat.name}
                  className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium bg-gray-50 text-gray-700 border border-gray-100 hover:border-gray-200 hover:bg-gray-100 transition-all cursor-pointer"
                >
                  {cat.name}
                  <span className="text-xs text-gray-400">{cat.itemCount}</span>
                </span>
              ))}
            </div>
          )}
        </section>
      );
    }
    ```

    **SectionSkeleton** helper (matches CatalogSearchOverlay animate-pulse pattern):
    ```typescript
    function SectionSkeleton({ count, aspect }: { count: number; aspect: string }) {
      return (
        <div className={`grid ${aspect === "none" ? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3" : "grid-cols-2 sm:grid-cols-3 lg:grid-cols-4"} gap-4`}>
          {Array.from({ length: count }).map((_, i) => (
            <div key={i} className="bg-white rounded-xl border border-gray-100 overflow-hidden animate-pulse">
              {aspect !== "none" && <div className={`aspect-${aspect} bg-gray-100`} />}
              <div className="p-4 space-y-2">
                <div className="h-3 bg-gray-100 rounded w-16" />
                <div className="h-4 bg-gray-100 rounded w-32" />
              </div>
            </div>
          ))}
        </div>
      );
    }
    ```

    Ensure all clickable elements have `cursor-pointer` (folded todo from CONTEXT.md).
  </action>
  <verify>
    <automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run build 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - src/client/routes/index.tsx does NOT contain `DashboardPage` or `DashboardCard` or `useTotals` (per D-03)
    - src/client/routes/index.tsx contains `function LandingPage()`
    - src/client/routes/index.tsx contains `function HeroSection(`
    - src/client/routes/index.tsx contains `openCatalogSearch("collection")` (per D-04)
    - src/client/routes/index.tsx contains `useDiscoverySetups` and `useDiscoveryItems` and `useDiscoveryCategories`
    - src/client/routes/index.tsx contains `"Go to Collection"` (per D-10)
    - src/client/routes/index.tsx contains `!!auth?.user` or `auth?.user` for auth check (per anti-pattern: do not use auth?.authenticated)
    - src/client/routes/index.tsx contains `GlobalItemCard` import
    - src/client/routes/index.tsx contains `PublicSetupCard` import
    - src/client/routes/index.tsx contains `cursor-pointer` on the search bar div
    - src/client/routes/index.tsx contains `animate-pulse` (loading skeletons)
    - `bun run build` succeeds without errors
  </acceptance_criteria>
  <done>Landing page renders hero with search trigger, three feed sections with loading skeletons and empty-state handling, authenticated CTA, and all clickable elements have cursor-pointer. Build succeeds.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 3: Visual verification of discovery landing page</name>
  <files>src/client/routes/index.tsx</files>
  <action>
    No code changes — this is a visual verification checkpoint. The executor should start the dev server with `bun run dev` and present the verification steps to the user.
  </action>
  <what-built>
    Complete discovery landing page replacing the personal dashboard at /. Features:
    - Hero section with "Discover Gear" heading and catalog search bar trigger
    - Popular Setups section with enhanced cards (item count, creator name)
    - Recently Added Items section with GlobalItemCard components
    - Trending Categories section with category pills
    - Conditional "Go to Collection" link for authenticated users
    - Loading skeletons for all sections
    - Empty state handling (sections hide when no data)
  </what-built>
  <how-to-verify>
    1. Run `bun run dev` and open http://localhost:5173/ in a browser
    2. Verify the hero section shows "Discover Gear" heading with search bar
    3. Click the search bar — it should open the full CatalogSearchOverlay
    4. Verify sections appear below: Popular Setups, Recently Added, Trending Categories
    5. If there is seed data, verify cards show correct information (item counts, creator names, images)
    6. If no data exists, verify empty sections are hidden gracefully (no broken/empty grids)
    7. Log in and verify "Go to Collection" link appears in hero area
    8. Click "Go to Collection" — should navigate to /collection
    9. Check responsive behavior: resize browser to mobile width, verify single-column layout
    10. Verify all clickable elements show pointer cursor on hover
  </how-to-verify>
  <verify>
    <automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run build 2>&1 | tail -5</automated>
  </verify>
  <done>User has visually verified the landing page renders correctly with all sections, search trigger works, auth CTA appears for logged-in users, and responsive layout is correct.</done>
  <resume-signal>Type "approved" or describe issues to fix</resume-signal>
</task>

</tasks>

<verification>
- `bun run build` — builds without errors
- Visual inspection of landing page at localhost:5173
- CatalogSearchOverlay opens from hero search bar
- Authenticated user sees "Go to Collection" link
- Anonymous user sees content immediately
</verification>

<success_criteria>
- Root URL shows discovery landing page (not personal dashboard)
- Hero search bar triggers CatalogSearchOverlay on click
- Three content sections render with real data or hide when empty
- PublicSetupCard displays item count and creator name
- Authenticated users see "Go to Collection" CTA in hero
- All clickable elements have cursor-pointer
- Build succeeds
</success_criteria>

<output>
After completion, create `.planning/phases/26-discovery-landing-page/26-03-SUMMARY.md`
</output>
