# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Essential Commands This is a **pnpm + Turborepo monorepo**. Use `pnpm` instead of `npm`. Root-level convenience scripts follow the `:` pattern (e.g., `webapp:dev`, `docs:build`). When adding new apps that require dev servers or build steps, add matching `:` shortcuts to the root `package.json`. ### Webapp - `pnpm webapp:dev` - Start webapp dev server on port 3000 - `pnpm webapp:build` - Build webapp for production - `pnpm webapp:test -- --run` - Run Vitest unit tests (always use `--run` flag) - `pnpm webapp:validate` - Run all tests, linting, and typecheck (use before commits) - `pnpm webapp:start` - Start webapp production server locally (loads `.env` from monorepo root) **IMPORTANT:** When running tests manually, ALWAYS use the `--run` flag to run tests once and exit. Without `--run`, Vitest runs in watch mode which consumes excessive memory. Never run multiple test processes in parallel as this can freeze the system. ### Companion App (Mobile) - `pnpm companion:dev` - Start Metro dev server (connects to existing build) - `pnpm companion:dev:clear` - Start Metro with cleared cache (after env changes) - `pnpm companion:build:ios` - Build native iOS + run on Simulator - `pnpm companion:build:ios:device` - Build native iOS + run on physical iPhone - `pnpm companion:build:android` - Build native Android + run on device/emulator - `pnpm companion:prebuild:clean` - Regenerate iOS native project from Expo config - `pnpm companion:doctor` - Run [react-doctor](https://www.react.doctor/) against the companion app (React Native diagnostics: deprecated modules, reanimated, FlatList perf, hook misuse) See `apps/companion/README.md` for full setup guide (LAN IPs, HTTP mode, device trust). ### Docs - `pnpm docs:dev` - Start docs dev server on port 5173 - `pnpm docs:build` - Build docs for production - `pnpm docs:preview` - Preview docs production build on port 5174 ### Code Quality - `pnpm webapp:lint` - ESLint checking (webapp only) - `pnpm turbo lint` - ESLint checking (all packages) - `pnpm --filter @shelf/webapp lint:fix` - Fix ESLint issues automatically - `pnpm turbo typecheck` - TypeScript type checking (all packages) - `pnpm run format` - Prettier code formatting (root-level) - `pnpm --filter @shelf/webapp validate` - Complete pre-commit validation - `pnpm webapp:doctor` - Run [react-doctor](https://www.react.doctor/) against the webapp (React health diagnostics: hook misuse, perf, a11y, architecture). Not part of `validate` or the pre-commit hook. It **does** run in CI: the `🩺 React Doctor` GitHub Action scans changed files on every PR (matrix over webapp + companion), posts a per-app sticky comment, and fails the check on newly-introduced errors (warnings stay advisory). See [companion:doctor](#companion-app-mobile) for the React Native equivalent. ### Security Review Agent (pre-commit) A Claude-powered security reviewer runs automatically on `git commit` against security-sensitive diffs (routes, `*.server.ts`, prisma, Supabase wiring, server middleware, new dependencies). It catches the regressions hit most often — cross-org IDORs, missing `requirePermission` gates, open redirects, missing Zod validation, audit-trail gaps — before code reaches review. - Interactive subagent: `.claude/agents/shelf-security-reviewer.md` (full toolset — for manual `claude --agent shelf-security-reviewer ...` use with permission prompts). - Headless subagent: `.claude/agents/shelf-security-reviewer-headless.md` (`Skill`-only — what the pre-commit hook invokes; safe under `bypassPermissions` because there's no Bash/network channel to exfiltrate through). - Pre-commit wrapper: `scripts/security-review-staged.sh` - Wired into `lefthook.yml` at priority 5 (after typecheck) Advisory by default — findings print, the commit proceeds. Opt in to blocking with `SHELF_SEC_REVIEW_BLOCK=1`; skip with `SHELF_SEC_REVIEW=0`. Manual use: `claude --agent shelf-security-reviewer "review PR #N"`. 📖 Full documentation: [apps/docs/security-review-agent.md](./apps/docs/security-review-agent.md). ### Database All database commands run via the `@shelf/database` package (`packages/database/`). This package owns the Prisma schema, migrations, and client generation. The webapp does **not** manage database concerns directly — it consumes `@shelf/database` as a workspace dependency. - `pnpm db:generate` - Generate Prisma client after schema changes - `pnpm db:prepare-migration` - Create new database migration - `pnpm db:deploy-migration` - Apply migrations and regenerate client - `pnpm db:reset` - Reset database (destructive!) - `pnpm webapp:setup` - Generate Prisma client + deploy migrations (for initial setup/onboarding) ### Build & Production - `pnpm turbo build` - Build all packages and apps for **production** - `pnpm webapp:start` - Start production server locally (loads `.env` from monorepo root) - `pnpm run start` (inside `apps/webapp/`) - Used by Docker/Fly (env vars from platform) ## Monorepo Structure This is a **pnpm workspaces + Turborepo** monorepo. All packages are defined in `pnpm-workspace.yaml` and orchestrated by `turbo.json`. ### Apps | Package | Path | Description | | ------------------ | ----------------- | --------------------------------------------------------------------------------------------------------------------- | | `@shelf/webapp` | `apps/webapp/` | Remix web application — the main product. Contains routes, components, modules (business logic), and integrations. | | `@shelf/companion` | `apps/companion/` | Expo/React Native mobile companion app. QR/barcode scanning, asset management, audits, bookings. Uses webapp API. | | `@shelf/docs` | `apps/docs/` | Developer documentation site (VitePress). Contains guides on local development, database triggers, architecture, etc. | ### Packages | Package | Path | Description | | ----------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@shelf/database` | `packages/database/` | **Owns all database concerns**: Prisma schema (`prisma/schema.prisma`), migrations (`prisma/migrations/`), and the `createDatabaseClient()` factory (`src/client.ts`). All `db:*` root scripts delegate to this package. The webapp imports from this package — it does **not** run Prisma commands directly in production. | ### Tooling | Package | Path | Description | | -------------------------- | --------------------- | --------------------------------------------------------------------- | | `@shelf/typescript-config` | `tooling/typescript/` | Shared `tsconfig` base configurations extended by all other packages. | ### How packages connect - **Webapp → Database**: The webapp depends on `@shelf/database` (workspace dependency). Its `app/database/db.server.ts` is a thin wrapper that calls `createDatabaseClient()` from `@shelf/database`. All 135+ `~/database/db.server` imports in the webapp work unchanged. - **Webapp → Prisma types**: The webapp's `build`, `typecheck`, and `validate` scripts run `prisma generate` to ensure types are available. In CI, this is done via `pnpm --filter @shelf/database run db:generate`. - **Vite config**: The webapp's `vite.config.ts` includes `ssr.noExternal: ["@shelf/database"]` so Vite bundles it correctly, and aliases `.prisma/client/index-browser` for browser builds. ## Architecture Overview **Shelf.nu** is an asset management platform built with Remix, React, TypeScript, and PostgreSQL. ### Core Technologies - **Remix** - Full-stack React framework with file-based routing - **Prisma** - Database ORM with PostgreSQL - **Supabase** - Authentication, storage, and database hosting - **Tailwind CSS + Radix UI** - Styling and UI components - **Jotai** - Atomic state management ### Key Directory Structure ``` shelf/ ├── turbo.json # Turborepo pipeline config ├── pnpm-workspace.yaml # Workspace package definitions ├── packages/ │ └── database/ # @shelf/database — Prisma client + types │ ├── prisma/schema.prisma │ ├── prisma/migrations/ │ └── src/client.ts # createDatabaseClient() factory ├── apps/ │ └── webapp/ # @shelf/webapp — Remix app │ ├── app/ │ │ ├── routes/ # File-based routes (remix-flat-routes) │ │ ├── modules/ # Business logic services │ │ ├── components/ # Reusable React components │ │ ├── database/db.server.ts # Thin re-export from @shelf/database │ │ ├── atoms/ # Jotai state atoms │ │ ├── utils/ # Utility functions │ │ └── integrations/ # Third-party service integrations │ └── server/ # Hono server entry + middleware └── tooling/ └── typescript/ # Shared tsconfig bases ``` ### Route Organization - `_layout+/` - Main authenticated application routes - `_auth+/` - Authentication and login routes - `_welcome+/` - User onboarding flow - `api+/` - API endpoints - `qr+/` - QR code handling for assets ## Development Patterns ### State Management - **Server State**: Remix loaders/actions for data fetching and mutations - **Client State**: Jotai atoms for complex UI state - **URL State**: Search params for filters, pagination, and bookmarks - **Optimistic UI & nProgress**: When implementing optimistic UI with fetchers, add the fetcher key to the `excludeFetchers` array in `apps/webapp/app/hooks/use-nprogress.ts` so the global loading bar does not show for operations that already provide instant visual feedback. ### Data Layer - **Prisma Schema**: Located in `packages/database/prisma/schema.prisma` (owned by `@shelf/database`) - **Client Generation**: Always run via `@shelf/database` (`pnpm db:generate`), never from the webapp directly - **DB Client**: `@shelf/database` exports `createDatabaseClient()` factory; the webapp's `app/database/db.server.ts` is a thin wrapper - **Row Level Security (RLS)**: Implemented via Supabase policies - **Full-text Search**: PostgreSQL search across assets and bookings ### Component Architecture - **Modular Services**: Business logic separated into `apps/webapp/app/modules/` - **Reusable Components**: Organized by feature/domain in `apps/webapp/app/components/` - **Form Handling**: Remix Form with client-side validation - **UI Primitives**: Radix UI components with Tailwind styling - **Date Display**: Always use the `DateS` component (`apps/webapp/app/components/shared/date.tsx`) for displaying dates in the UI. Do not use raw `toLocaleDateString()` or other custom date formatting. ### Email Templates All HTML emails must follow the design established in `app/emails/stripe/audit-trial-welcome.tsx`: - **React Email components**: `Html`, `Head`, `Container`, `Text`, `Button`, `Link` - **LogoForEmail** at the top of every email - **Shared styles** from `app/emails/styles.ts` (`styles.p`, `styles.h2`, `styles.button`, `styles.li`) - **Personalized greeting** with user's first name: `Hey {firstName},` - **CTA buttons** using `styles.button` (not bare links) - **Info/warning boxes**: yellow background `#FFF8E1` + border `#FFE082` for important notices - **Both HTML and plain text exports**: HTML via `render()`, plain text as template literal - **Send wrapper function** with `try/catch` + `Logger.error` + `ShelfError` - **Closing**: `The Shelf Team` ### Button Type Prop (Required) Every ` // ✅ Good // Link button, no type needed ``` **Why:** The HTML spec defaults ` ``` ### Deprecated Components - **DropdownMenu** (`apps/webapp/app/components/shared/dropdown.tsx`): Do not use for new features. Instead, use `Popover` from `@radix-ui/react-popover` with custom select behavior. See `apps/webapp/app/components/assets/assets-index/advanced-filters/field-selector.tsx` for a good example implementation. ### Silencing react-doctor findings `react-doctor` runs in CI on every PR for both the webapp (`pnpm webapp:doctor`) and the companion app (`pnpm companion:doctor`): warnings are advisory, but **newly-introduced errors fail the PR check**, so keep both clean. The guidance below applies to both apps. **Important:** `react-doctor` does **not** respect `// eslint-disable-next-line` comments. The only way to silence a finding is to refactor the code so the pattern no longer matches. Standard ESLint disable comments still work for `pnpm webapp:lint` — they just don't help with `pnpm webapp:doctor`. **Refactor strategies for common findings:** - **`react/no-danger` for static CSS injection** — use React's native `` form (safe text child). See `apps/webapp/app/components/shared/mobile-dropdown-styles.tsx` for the reusable mobile-dropdown helper. - **`jsx-a11y/no-autofocus`** — remove the `autoFocus` prop, then focus imperatively with `ref.current?.focus()` inside a `useEffect` when intentional modal/form focus is needed. This satisfies the rule and keeps the UX. - **`react/no-danger` for scripts (e.g., `