---
phase: 26-discovery-landing-page
plan: 02
type: execute
wave: 2
depends_on: [26-01]
files_modified:
  - src/server/routes/discovery.ts
  - src/server/index.ts
  - src/client/hooks/useDiscovery.ts
  - tests/routes/discovery.test.ts
autonomous: true
requirements: [DISC-02, DISC-03, DISC-04, INFR-02]

must_haves:
  truths:
    - "GET /api/discovery/setups returns popular setups for anonymous users"
    - "GET /api/discovery/items returns recent catalog items for anonymous users"
    - "GET /api/discovery/categories returns trending categories for anonymous users"
    - "All discovery endpoints accept limit and cursor query params"
    - "Discovery endpoints are rate-limited with browseTier"
  artifacts:
    - path: "src/server/routes/discovery.ts"
      provides: "Hono route handlers for three discovery endpoints"
      exports: ["discoveryRoutes"]
    - path: "src/client/hooks/useDiscovery.ts"
      provides: "React Query hooks for landing page data fetching"
      exports: ["useDiscoverySetups", "useDiscoveryItems", "useDiscoveryCategories"]
    - path: "tests/routes/discovery.test.ts"
      provides: "Route-level integration tests for discovery endpoints"
      min_lines: 50
  key_links:
    - from: "src/server/routes/discovery.ts"
      to: "src/server/services/discovery.service.ts"
      via: "imports getPopularSetups, getRecentGlobalItems, getTrendingCategories"
      pattern: "from.*discovery\\.service"
    - from: "src/server/index.ts"
      to: "src/server/routes/discovery.ts"
      via: "app.route registration and auth skip"
      pattern: "discoveryRoutes|/api/discovery"
    - from: "src/client/hooks/useDiscovery.ts"
      to: "/api/discovery"
      via: "apiGet fetch calls"
      pattern: "apiGet.*api/discovery"
---

<objective>
Wire the discovery service to HTTP endpoints and create client-side React Query hooks. Register routes in server/index.ts with public access (auth skip) and browseTier rate limiting.

Purpose: Makes the discovery feed data accessible to the landing page UI via three REST endpoints and three React Query hooks.
Output: Working endpoints at `/api/discovery/{setups,items,categories}`, matching client hooks, route-level tests.
</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 01: discovery service exports -->
From src/server/services/discovery.service.ts:
```typescript
export async function getPopularSetups(db: Db, limit?: number, cursor?: string): Promise<{ items: PopularSetup[], nextCursor: string | null, hasMore: boolean }>
export async function getRecentGlobalItems(db: Db, limit?: number, cursor?: string): Promise<{ items: GlobalItemRow[], nextCursor: string | null, hasMore: boolean }>
export async function getTrendingCategories(db: Db, limit?: number): Promise<{ name: string, itemCount: number }[]>
```

From src/server/routes/global-items.ts (pattern reference):
```typescript
type Env = { Variables: { db?: any } };
const app = new Hono<Env>();
app.get("/", async (c) => { ... });
export { app as globalItemRoutes };
```

From src/server/index.ts (auth skip pattern, lines 151-170):
```typescript
// Skip public global-items endpoint (GET /api/global-items)
if (c.req.path.startsWith("/api/global-items") && c.req.method === "GET")
  return next();
```

From src/server/index.ts (rate limit pattern, lines 126-134):
```typescript
app.use("/api/global-items", async (c, next) => {
  if (c.req.method === "GET" && !c.req.path.match(/^\/api\/global-items\/\d+$/))
    return browseTier(c, next);
  return next();
});
```

From src/client/hooks/useGlobalItems.ts (hook pattern):
```typescript
import { useQuery } from "@tanstack/react-query";
import { apiGet } from "../lib/api";
export function useGlobalItems(query?: string, tags?: string[]) {
  return useQuery({
    queryKey: ["global-items", query ?? "", tags ?? []],
    queryFn: () => apiGet<GlobalItem[]>(`/api/global-items${qs ? `?${qs}` : ""}`),
  });
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Discovery routes, server registration, and route tests</name>
  <files>src/server/routes/discovery.ts, src/server/index.ts, tests/routes/discovery.test.ts</files>
  <read_first>
    - src/server/routes/global-items.ts (exact Hono route pattern to replicate)
    - src/server/index.ts (auth skip list at lines 151-170, rate limit setup at lines 120-148, route registration at lines 173-183)
    - tests/routes/global-items.test.ts (route test pattern: createTestApp, middleware setup, request format)
    - src/server/services/discovery.service.ts (function signatures from plan 01)
  </read_first>
  <action>
    **Create `src/server/routes/discovery.ts`:**

    Follow the exact pattern of `global-items.ts`:
    ```typescript
    import { Hono } from "hono";
    import { getPopularSetups, getRecentGlobalItems, getTrendingCategories } from "../services/discovery.service.ts";

    type Env = { Variables: { db?: any } };
    const app = new Hono<Env>();
    ```

    Three GET handlers:

    `app.get("/setups", ...)`:
    - Parse query params: `limit` (parseInt, default 6, max 50), `cursor` (string, optional)
    - Call `getPopularSetups(db, limit, cursor)`
    - Return `c.json(result)` — result already has `{ items, nextCursor, hasMore }` shape

    `app.get("/items", ...)`:
    - Parse query params: `limit` (parseInt, default 8, max 50), `cursor` (string, optional)
    - Call `getRecentGlobalItems(db, limit, cursor)`
    - Return `c.json(result)`

    `app.get("/categories", ...)`:
    - Parse query params: `limit` (parseInt, default 12, max 50)
    - Call `getTrendingCategories(db, limit)`
    - Return `c.json(result)` — result is array directly

    Export: `export { app as discoveryRoutes };`

    **Modify `src/server/index.ts`:**

    1. Add import at top (after line 14, near other route imports):
       `import { discoveryRoutes } from "./routes/discovery.ts";`

    2. Add rate limiting for discovery endpoints (after line 134, in the browse tier section):
       ```typescript
       app.use("/api/discovery/*", async (c, next) => {
         if (c.req.method === "GET") return browseTier(c, next);
         return next();
       });
       ```

    3. Add auth skip in the auth middleware block (after line 167, before the requireAuth call):
       ```typescript
       // Skip public discovery endpoints (GET /api/discovery/*)
       if (c.req.path.startsWith("/api/discovery") && c.req.method === "GET")
         return next();
       ```

    4. Add route registration (after line 183, near other app.route calls):
       `app.route("/api/discovery", discoveryRoutes);`

    **Create `tests/routes/discovery.test.ts`:**

    Follow the exact test pattern from `global-items.test.ts`:
    ```typescript
    import { beforeEach, describe, expect, it } from "bun:test";
    import { Hono } from "hono";
    import { globalItems, items, setups, setupItems } from "../../src/db/schema.ts";
    import { discoveryRoutes } from "../../src/server/routes/discovery.ts";
    import { createTestDb } from "../helpers/db.ts";

    async function createTestApp() {
      const { db, userId } = await createTestDb();
      const app = new Hono();
      app.use("*", async (c, next) => {
        c.set("db", db);
        await next();
      });
      // Note: NO userId set — discovery endpoints don't need auth
      app.route("/api/discovery", discoveryRoutes);
      return { app, db, userId };
    }
    ```

    Tests (minimum 6):
    1. `GET /api/discovery/setups` returns 200 with `{ items, nextCursor, hasMore }` shape
    2. `GET /api/discovery/items` returns 200 with `{ items, nextCursor, hasMore }` shape
    3. `GET /api/discovery/categories` returns 200 with array shape
    4. `GET /api/discovery/setups?limit=1` respects limit param
    5. `GET /api/discovery/items?limit=1&cursor=<timestamp>` pagination works
    6. `GET /api/discovery/categories?limit=2` respects limit param

    For each test, seed appropriate data using db.insert() then make fetch requests via `app.request("/api/discovery/...")`.
  </action>
  <verify>
    <automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun test tests/routes/discovery.test.ts</automated>
  </verify>
  <acceptance_criteria>
    - src/server/routes/discovery.ts contains `app.get("/setups"` and `app.get("/items"` and `app.get("/categories"`
    - src/server/routes/discovery.ts contains `export { app as discoveryRoutes }`
    - src/server/index.ts contains `import { discoveryRoutes }` from `"./routes/discovery.ts"`
    - src/server/index.ts contains `app.route("/api/discovery", discoveryRoutes)`
    - src/server/index.ts contains `c.req.path.startsWith("/api/discovery")` in auth skip section
    - src/server/index.ts contains `"/api/discovery/*"` in rate limit section with `browseTier`
    - tests/routes/discovery.test.ts contains at least 6 `it(` calls
    - `bun test tests/routes/discovery.test.ts` exits 0
  </acceptance_criteria>
  <done>Three discovery endpoints respond to GET requests with correct JSON shapes, anonymous access works (no auth required), rate limiting is applied, and route tests pass.</done>
</task>

<task type="auto">
  <name>Task 2: Client-side React Query hooks for discovery data</name>
  <files>src/client/hooks/useDiscovery.ts</files>
  <read_first>
    - src/client/hooks/useGlobalItems.ts (hook pattern: useQuery, apiGet, interface definitions, queryKey structure)
    - src/client/lib/api.ts (apiGet signature)
  </read_first>
  <action>
    Create `src/client/hooks/useDiscovery.ts` with three named exports.

    **Type definitions** at top of file:
    ```typescript
    export interface DiscoverySetup {
      id: number;
      name: string;
      createdAt: string;
      itemCount: number;
      creatorName: string | null;
    }

    export interface DiscoveryCategory {
      name: string;
      itemCount: number;
    }

    interface CursorPage<T> {
      items: T[];
      nextCursor: string | null;
      hasMore: boolean;
    }
    ```

    For GlobalItem type, import from useGlobalItems or re-define inline matching the existing `GlobalItem` interface in `useGlobalItems.ts` (id, brand, model, category, weightGrams, priceCents, imageUrl, description, sourceUrl, imageCredit, imageSourceUrl, createdAt — all as they appear in that file).

    **Three hooks:**

    `useDiscoverySetups(limit = 6)`:
    - queryKey: `["discovery", "setups", limit]`
    - queryFn: `apiGet<CursorPage<DiscoverySetup>>(`/api/discovery/setups?limit=${limit}`)`
    - staleTime: `2 * 60 * 1000` (2 minutes)

    `useDiscoveryItems(limit = 8)`:
    - queryKey: `["discovery", "items", limit]`
    - queryFn: `apiGet<CursorPage<GlobalItem>>(`/api/discovery/items?limit=${limit}`)`
    - staleTime: `2 * 60 * 1000` (2 minutes)

    `useDiscoveryCategories(limit = 12)`:
    - queryKey: `["discovery", "categories", limit]`
    - queryFn: `apiGet<DiscoveryCategory[]>(`/api/discovery/categories?limit=${limit}`)`
    - staleTime: `5 * 60 * 1000` (5 minutes — categories change rarely)

    Import `useQuery` from `@tanstack/react-query` and `apiGet` from `../lib/api`.
  </action>
  <verify>
    <automated>cd /home/jean-luc-makiola/Development/projects/GearBox && bun run build 2>&1 | tail -20</automated>
  </verify>
  <acceptance_criteria>
    - src/client/hooks/useDiscovery.ts contains `export function useDiscoverySetups(`
    - src/client/hooks/useDiscovery.ts contains `export function useDiscoveryItems(`
    - src/client/hooks/useDiscovery.ts contains `export function useDiscoveryCategories(`
    - src/client/hooks/useDiscovery.ts contains `export interface DiscoverySetup`
    - src/client/hooks/useDiscovery.ts contains `export interface DiscoveryCategory`
    - src/client/hooks/useDiscovery.ts contains `staleTime: 2 * 60 * 1000` (for setups and items)
    - src/client/hooks/useDiscovery.ts contains `staleTime: 5 * 60 * 1000` (for categories)
    - src/client/hooks/useDiscovery.ts contains `queryKey: ["discovery",` for all three hooks
    - src/client/hooks/useDiscovery.ts contains `apiGet` import from `../lib/api`
  </acceptance_criteria>
  <done>Three React Query hooks export correctly with proper types, query keys, stale times, and API endpoint URLs matching the server routes.</done>
</task>

</tasks>

<verification>
- `bun test tests/routes/discovery.test.ts` — route tests pass
- `bun test` — full suite green
- `bun run build` — client builds without TypeScript errors
</verification>

<success_criteria>
- Three GET endpoints at /api/discovery/{setups,items,categories} respond to anonymous requests
- Endpoints are rate-limited with browseTier
- Three React Query hooks ready for consumption by the landing page
- Route-level tests verify response shapes and status codes
</success_criteria>

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