Files
GearBox/.planning/phases/20-fab-full-screen-catalog-search/20-01-PLAN.md

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
20-fab-full-screen-catalog-search 01 execute 1
src/server/services/tag.service.ts
src/server/routes/tags.ts
src/server/index.ts
src/client/stores/uiStore.ts
src/client/hooks/useTags.ts
src/client/hooks/useGlobalItems.ts
tests/services/tag.service.test.ts
tests/routes/tags.test.ts
true
CATFLOW-01
CATFLOW-02
truths artifacts key_links
GET /api/tags returns all tags from the database
GET /api/global-items endpoint is reachable (route registered)
UIStore exposes fabMenu and catalogSearch state slices
useGlobalItems hook supports optional tags parameter
useTags hook fetches and caches tag data
path provides exports
src/server/services/tag.service.ts getAllTags function
getAllTags
path provides exports
src/server/routes/tags.ts GET /api/tags endpoint
tagRoutes
path provides contains
src/client/stores/uiStore.ts FAB menu + catalog search state fabMenuOpen
path provides exports
src/client/hooks/useTags.ts Tag fetching hook
useTags
path provides contains
src/client/hooks/useGlobalItems.ts Updated hook with tag support tags
from to via pattern
src/server/routes/tags.ts src/server/services/tag.service.ts getAllTags import getAllTags
from to via pattern
src/server/index.ts src/server/routes/tags.ts app.route registration app.route.*tags
from to via pattern
src/server/index.ts src/server/routes/global-items.ts app.route registration app.route.*global-items
Create the server-side tags endpoint, register the global-items route, extend UIStore with FAB/catalog-search state, and update client hooks for tag-aware searching.

Purpose: Provides the data layer (tags API), state management (UIStore), and hooks that Plan 02's UI components will consume. Output: Working GET /api/tags endpoint, registered GET /api/global-items, extended UIStore, useTags hook, updated useGlobalItems hook with tag support.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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

@src/server/index.ts @src/server/routes/global-items.ts @src/server/services/global-item.service.ts @src/db/schema.ts @src/client/stores/uiStore.ts @src/client/hooks/useGlobalItems.ts @src/client/lib/api.ts @tests/routes/global-items.test.ts @tests/helpers/db.ts

From src/db/schema.ts:

export const tags = pgTable("tags", {
  id: serial("id").primaryKey(),
  name: text("name").notNull().unique(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

From src/client/lib/api.ts:

export async function apiGet<T>(path: string): Promise<T>;

From src/client/stores/uiStore.ts (existing pattern):

export const useUIStore = create<UIState>((set) => ({ ... }));
Task 1: Tag service, route, route registrations, and tests src/server/services/tag.service.ts, src/server/routes/tags.ts, src/server/index.ts, tests/services/tag.service.test.ts, tests/routes/tags.test.ts src/db/schema.ts, src/server/index.ts, src/server/routes/global-items.ts, src/server/services/global-item.service.ts, tests/routes/global-items.test.ts, tests/helpers/db.ts - Test: getAllTags returns all tags from the database as { id, name }[] (no createdAt) - Test: getAllTags returns empty array when no tags exist - Test: GET /api/tags returns 200 with array of tag objects - Test: GET /api/global-items still works after route registration (existing tests pass) 1. Create `src/server/services/tag.service.ts`: - Export `getAllTags(db)` function that selects `{ id: tags.id, name: tags.name }` from the `tags` table, ordered alphabetically by name (per D-17, alphabetical is a reasonable default). - Import `tags` from `../../db/schema.ts`. - Use the same `type Db = typeof prodDb` pattern as other services.
2. Create `src/server/routes/tags.ts`:
   - Single GET "/" handler that calls `getAllTags(db)` and returns `c.json(allTags)`.
   - Export as `tagRoutes`.
   - Follow the exact pattern from `src/server/routes/global-items.ts` (Hono + Env type).

3. Update `src/server/index.ts`:
   - Add import: `import { globalItemRoutes } from "./routes/global-items.ts";`
   - Add import: `import { tagRoutes } from "./routes/tags.ts";`
   - Register both routes AFTER existing route registrations (before MCP block):
     `app.route("/api/global-items", globalItemRoutes);`
     `app.route("/api/tags", tagRoutes);`
   - CRITICAL: The global-items route file EXISTS but is NOT registered (research pitfall #1). Must add it here.
   - Add public route skip in auth middleware for tags: `if (c.req.path.startsWith("/api/tags") && c.req.method === "GET") return next();`
   - Add public route skip for global-items: `if (c.req.path.startsWith("/api/global-items") && c.req.method === "GET") return next();`

4. Create `tests/services/tag.service.test.ts`:
   - Use `createTestDb()` from `tests/helpers/db.ts`.
   - Test: returns empty array when no tags.
   - Test: returns all tags as `{ id, name }` after inserting test data.
   - Seed tags using `db.insert(tags).values(...)`.

5. Create `tests/routes/tags.test.ts`:
   - Follow pattern from `tests/routes/global-items.test.ts`.
   - Use the app with test db injection.
   - Test: GET /api/tags returns 200 with JSON array.
   - Test: response contains expected tags after seeding.
bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts tests/routes/global-items.test.ts - File `src/server/services/tag.service.ts` exists and exports `getAllTags` - File `src/server/routes/tags.ts` exists and exports `tagRoutes` - `src/server/index.ts` contains `app.route("/api/tags", tagRoutes)` - `src/server/index.ts` contains `app.route("/api/global-items", globalItemRoutes)` - `src/server/index.ts` contains auth skip for `/api/tags` and `/api/global-items` GET requests - All tag tests pass: `bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts` - Existing global-items tests still pass: `bun test tests/routes/global-items.test.ts` GET /api/tags returns all tags. GET /api/global-items is registered and reachable. All tests green. Task 2: UIStore extension + useTags hook + useGlobalItems tag support src/client/stores/uiStore.ts, src/client/hooks/useTags.ts, src/client/hooks/useGlobalItems.ts src/client/stores/uiStore.ts, src/client/hooks/useGlobalItems.ts, src/client/lib/api.ts, .planning/phases/20-fab-full-screen-catalog-search/20-CONTEXT.md 1. Extend `src/client/stores/uiStore.ts` per D-21, D-22, D-23: - Add to UIState interface: ``` fabMenuOpen: boolean; openFabMenu: () => void; closeFabMenu: () => void; catalogSearchOpen: boolean; catalogSearchMode: "collection" | "thread" | null; openCatalogSearch: (mode: "collection" | "thread") => void; closeCatalogSearch: () => void; ``` - Add to store implementation: ``` fabMenuOpen: false, openFabMenu: () => set({ fabMenuOpen: true }), closeFabMenu: () => set({ fabMenuOpen: false }), catalogSearchOpen: false, catalogSearchMode: null, openCatalogSearch: (mode) => set({ catalogSearchOpen: true, catalogSearchMode: mode, fabMenuOpen: false }), closeCatalogSearch: () => set({ catalogSearchOpen: false, catalogSearchMode: null }), ``` - Note: `openCatalogSearch` also closes the FAB menu (natural flow: tap FAB -> tap option -> menu closes, overlay opens).
2. Create `src/client/hooks/useTags.ts`:
   - Export `useTags()` hook using `useQuery` from `@tanstack/react-query`.
   - Query key: `["tags"]`.
   - Query fn: `apiGet<Tag[]>("/api/tags")` where `Tag = { id: number; name: string }`.
   - Set `staleTime: 5 * 60 * 1000` (tags change rarely, cache 5 min).
   - Export the `Tag` interface as well.

3. Update `src/client/hooks/useGlobalItems.ts`:
   - Modify `useGlobalItems` signature to accept optional `tags?: string[]` parameter.
   - Build query string using `URLSearchParams`:
     ```
     const params = new URLSearchParams();
     if (query) params.set("q", query);
     if (tags && tags.length > 0) params.set("tags", tags.join(","));
     const qs = params.toString();
     ```
   - Update query key to include tags: `["global-items", query ?? "", tags ?? []]`.
   - Update query fn URL: `` `/api/global-items${qs ? `?${qs}` : ""}` ``.
   - Keep all other exports (`useGlobalItem`, `useLinkItem`, `useUnlinkItem`) unchanged.
bun run lint && bun run build - `src/client/stores/uiStore.ts` contains `fabMenuOpen` state field - `src/client/stores/uiStore.ts` contains `catalogSearchOpen` state field - `src/client/stores/uiStore.ts` contains `catalogSearchMode` state field - `src/client/stores/uiStore.ts` contains `openCatalogSearch` action - `src/client/stores/uiStore.ts` contains `closeCatalogSearch` action - `src/client/hooks/useTags.ts` exists and exports `useTags` - `src/client/hooks/useGlobalItems.ts` accepts `tags?: string[]` parameter - `bun run lint` passes with no errors - `bun run build` succeeds UIStore has FAB menu + catalog search state. useTags hook fetches tags. useGlobalItems supports tag filtering. Lint and build pass. - `bun test tests/services/tag.service.test.ts tests/routes/tags.test.ts tests/routes/global-items.test.ts` -- all green - `bun run lint` -- no errors - `bun run build` -- succeeds - `bun test` -- full suite green (no regressions)

<success_criteria>

  1. GET /api/tags returns all tags as JSON array
  2. GET /api/global-items is registered and reachable (was missing from index.ts)
  3. UIStore has fabMenuOpen, catalogSearchOpen, catalogSearchMode state
  4. useTags hook works with staleTime caching
  5. useGlobalItems accepts optional tags parameter for filtering
  6. All existing tests pass (no regressions) </success_criteria>
After completion, create `.planning/phases/20-fab-full-screen-catalog-search/20-01-SUMMARY.md`