Files
GearBox/CLAUDE.md
Jean-Luc Makiola 076616cd1b ci: switch release workflow to tag-driven
Trigger on `push: tags` instead of `workflow_dispatch`. The pushed tag
name is the version — removes the bump-input + compute-and-create-tag
dance, avoiding accidental major bumps from the manual dispatch form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:44:09 +02:00

9.7 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

GearBox is a single-user web app for managing gear collections (bikepacking, sim racing, etc.), tracking weight/price, and planning purchases through research threads. Full-stack TypeScript monolith running on Bun.

Commands

# Development
bun run dev                 # Starts both Vite client (:5173) and Hono server (:3000) concurrently
bun run dev:client          # Vite dev server on :5173 (proxies /api to :3000)
bun run dev:server          # Hono server on :3000 with hot reload

# Database
bun run db:generate         # Generate Drizzle migration from schema changes
bun run db:push             # Apply migrations to gearbox.db

# Testing
bun test                    # Run all unit/integration tests
bun test tests/services/item.service.test.ts  # Run single test file
bun run test:e2e            # Run Playwright E2E tests
bun run test:e2e:ui         # Playwright UI mode for debugging

# Lint & Format
bun run lint                # Biome check (tabs, double quotes, organized imports)

# Build
bun run build               # Vite build → dist/client/

Architecture

Stack: React 19 + Hono + Drizzle ORM + SQLite, all running on Bun.

Client (src/client/)

  • Routing: TanStack Router with file-based routes in src/client/routes/. Route tree auto-generated to routeTree.gen.ts — never edit manually.
  • Data fetching: TanStack React Query via custom hooks in src/client/hooks/ (e.g., useItems, useThreads, useSetups). Mutations invalidate related query keys.
  • UI state: Zustand store (stores/uiStore.ts) for panel/dialog state only — server data lives in React Query.
  • API calls: Thin fetch wrapper in lib/api.ts (apiGet, apiPost, apiPut, apiDelete, apiUpload).
  • Styling: Tailwind CSS v4.

Server (src/server/)

  • Routes (routes/): Hono handlers with Zod validation via @hono/zod-validator. Delegate to services.
  • Services (services/): Pure business logic functions that take a db instance. No HTTP awareness — testable without mocking.
  • Route registration in src/server/index.ts via app.route("/api/...", routes).

Shared (src/shared/)

  • schemas.ts: Zod schemas for API request validation (source of truth for types).
  • types.ts: Types inferred from Zod schemas + Drizzle table definitions. No manual type duplication.

Database (src/db/)

  • Schema: schema.ts — Drizzle table definitions for SQLite.
  • Prices stored as cents (priceCents: integer) to avoid float rounding.
  • Timestamps: stored as integers (unix epoch) with { mode: "timestamp" }.
  • Tables: categories, items, threads, threadCandidates, setups, setupItems, settings, users, sessions, apiKeys.

Testing (tests/ and e2e/)

  • Unit/integration: Bun test runner (bun test). Tests at service level and route level.
  • tests/helpers/db.ts: createTestDb() creates in-memory SQLite via Drizzle migrations and seeds an "Uncategorized" category.
  • E2E: Playwright (bun run test:e2e). Tests in e2e/ run against a seeded SQLite database with the server in production mode. Seed script: e2e/seed.ts.

Releasing

Releases are tag-driven. The Gitea Actions workflow (.gitea/workflows/release.yml) triggers on any pushed tag matching v* — it runs CI (lint, test, build), generates a changelog from the previous tag, builds and pushes a Docker image, and creates a Gitea release. The version comes from the tag name.

To release, tag the desired commit on Develop and push:

git tag v2.3.0
git push origin v2.3.0

Branching

  • Develop is the main branch. Keep it clean — don't commit large feature work directly.
  • For each new brainstorming/implementation session, create a feature branch off Develop (e.g., feature/setup-impact-preview, fix/error-handling).
  • Merge back to Develop via PR or fast-forward merge when the work is complete and verified.

Path Alias

@/* maps to ./src/* (configured in tsconfig.json).

Reusable Components

Always use existing components instead of rebuilding with plain HTML. Check src/client/components/ before creating new form elements or UI patterns.

Need Use Not
Category selection CategoryPicker (icons, search, inline create) Plain <select>
Icon selection IconPicker (119 curated Lucide icons, grouped) Manual icon input
Image upload ImageUpload (preview, click-to-upload) Raw file input
Lucide icon rendering LucideIcon from lib/iconData Direct SVG or lucide-react imports
Weight/price formatting useFormatters() hook Manual formatting

Key Patterns

  • Thread resolution: Resolving a thread copies the winning candidate's data into a new item in the collection, sets resolvedCandidateId, and changes status to "resolved".
  • Setup item sync: PUT /api/setups/:id/items replaces all setup_items atomically (delete all, re-insert).
  • Image uploads: POST /api/images saves to S3-compatible storage (Garage/R2), returned as imageFilename on item/candidate records.
  • Image URL fetching: POST /api/images/from-url fetches an image from a URL, saves to S3, returns { filename, sourceUrl }.
  • Aggregates (weight/cost totals): Computed via SQL on read, not stored on records.
  • Authentication: Public-read, authenticated-write. Cookie sessions for web UI, API keys (X-API-Key header) for programmatic access. POST /api/auth/setup for first-time account creation. Auth middleware protects all POST/PUT/DELETE on /api/* except /api/auth/*.

Authentication

  • OIDC via Logto: Authentication is handled by an external Logto instance via @hono/oidc-auth. Users are redirected to Logto for login, and sessions are managed via OIDC cookies.
  • Programmatic access: API keys created in Settings > API Keys. Pass via X-API-Key header.
  • Public read: All GET endpoints work without auth. POST/PUT/DELETE require auth.
  • Auth routes: /api/auth/me, /api/auth/keys, /api/auth/profile.
  • MCP OAuth: OAuth 2.1 + PKCE for Claude mobile/web. Endpoints at /oauth/*. Uses existing GearBox credentials.

Logto Setup

The Logto application must be configured with the correct scopes. In the Logto admin console, go to the application settings and ensure the following User Scopes are granted: openid, profile, email (matching the OIDC_SCOPES env var).

Required env vars:

OIDC_ISSUER=https://your-logto-domain/oidc    # Logto OIDC issuer URL
OIDC_CLIENT_ID=<client-id>                     # From Logto app settings
OIDC_CLIENT_SECRET=<client-secret>             # From Logto app settings
OIDC_AUTH_SECRET=<random-32-char-hex>          # Session encryption key
OIDC_SCOPES="openid profile email"             # Must match Logto app scopes
OIDC_REDIRECT_URI=https://your-app/callback    # Must match Logto redirect URI

MCP Server

GearBox includes a built-in MCP server for integration with Claude Code and Claude Desktop. Enabled by default, disable with GEARBOX_MCP=false. Authenticated via API key or OAuth 2.1 Bearer token.

Tools (19 total)

Tool Description
list_items List all items, optionally filter by category
get_item Get item details by ID
create_item Add item to collection (for decided items; use create_thread for research)
update_item Update item details
delete_item Remove item from collection
list_categories List all categories
create_category Create a new category
list_threads List research threads (recommended workflow for gear purchases)
get_thread Get thread with candidates
create_thread Start a research thread to compare gear options
resolve_thread Pick winning candidate, adds it to collection
add_candidate Add candidate to a research thread
update_candidate Update candidate details
remove_candidate Remove candidate from thread
list_setups List gear setups
get_setup Get setup with items and totals
create_setup Create a new setup
update_setup Update setup name or items
upload_image_from_url Fetch image from URL, save locally

Resources

  • gearbox://collection/summary — Overview of collection: totals, items per category, active threads.

Configuration

Claude Code (.claude/settings.json):

{
  "mcpServers": {
    "gearbox": {
      "type": "streamable-http",
      "url": "http://localhost:3000/mcp",
      "headers": {
        "X-API-Key": "<your-api-key>"
      }
    }
  }
}

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "gearbox": {
      "type": "streamable-http",
      "url": "http://localhost:3000/mcp",
      "headers": {
        "X-API-Key": "<your-api-key>"
      }
    }
  }
}

Generate an API key from Settings > API Keys after logging in.

OAuth Authentication (Claude Mobile / claude.ai)

GearBox supports OAuth 2.1 Authorization Code + PKCE for MCP connections from Claude mobile app and claude.ai. The OAuth flow is automatic — Claude handles discovery and token exchange.

Required environment variable for production:

GEARBOX_URL=https://your-gearbox-domain.com  # Used as OAuth issuer URL

OAuth endpoints:

  • GET /.well-known/oauth-authorization-server — Discovery metadata
  • POST /oauth/register — Dynamic Client Registration
  • GET/POST /oauth/authorize — Authorization with login form
  • POST /oauth/token — Token exchange and refresh

Both auth methods work simultaneously:

  • API key (X-API-Key header) — Claude Code, scripts, programmatic access
  • OAuth Bearer token (Authorization: Bearer header) — Claude mobile, claude.ai