diff --git a/.claude/skills/frontend-v2-components/SKILL.md b/.claude/skills/frontend-v2-components/SKILL.md index 0f4a570b9..df50bd827 100644 --- a/.claude/skills/frontend-v2-components/SKILL.md +++ b/.claude/skills/frontend-v2-components/SKILL.md @@ -17,7 +17,7 @@ Related skills: `frontend-v2-theming` (tokens/colors), `frontend-v2-input` (focu 1. **v1 is frozen.** Don't touch `src/views/`, `src/components/`, `src/console/`, `src/layouts/`. When coexistence forces a v2 fork of a store/composable/util, annotate the v1 export with `@deprecated` pointing at the v2 replacement. 2. **Three component tiers** (below). -3. **Shared resources are canonical.** Pinia stores, API services, OpenAPI types (`src/__generated__/`), locales, utils — v2 *imports* them, never forks them. Additive changes to shared resources are allowed; changing a shared store API to work around a v2 call-site issue is not. +3. **Shared resources are canonical.** Pinia stores, API services, OpenAPI types (`src/__generated__/`), locales, utils — v2 _imports_ them, never forks them. Additive changes to shared resources are allowed; changing a shared store API to work around a v2 call-site issue is not. 4. **TypeScript strict.** Zero `any` (justify with a comment if unavoidable). No `as unknown as ...`; fix the source or define an intermediate type. 5. **Universal substitution.** When an `R*` primitive exists, use it. If it doesn't, create or extend it. Never drop to raw HTML or raw Vuetify when a primitive applies. 6. **Wrapper contract.** Wrappers around Vuetify use `defineOptions({ inheritAttrs: false })` + `v-bind="$attrs"` + slot passthrough, and accept every prop/slot of the wrapped component. @@ -66,16 +66,16 @@ If any fails: **shared composite** if generic across features, **feature composi ```ts // 1. External -import { computed, ref } from "vue"; // 2. v2 primitives import { RBtn, RDialog } from "@v2/lib"; -// 3. v2 composables / shared -import { useCan } from "@/v2/composables/useCan"; -// 4. v2 feature siblings -import GameCard from "@/v2/components/GameCard.vue"; +import { computed, ref } from "vue"; +import type { SimpleRom } from "@/__generated__"; // 5. Canonical shared resources import storeAuth from "@/stores/auth"; -import type { SimpleRom } from "@/__generated__"; +// 4. v2 feature siblings +import GameCard from "@/v2/components/GameCard.vue"; +// 3. v2 composables / shared +import { useCan } from "@/v2/composables/useCan"; ``` - `@v2/lib` — primitives barrel. `@/v2/...` — anything else under v2. `@/...` — canonical shared resources. Never relative paths (`../../foo`) when an alias exists. @@ -109,7 +109,7 @@ import type { SimpleRom } from "@/__generated__"; 5. Don't add backwards-compat shims inside v2: delete removed code; no `// removed`, no renamed-but-unused exports, no deprecated wrappers that just call the new function. 6. Don't write redundant tests; don't touch v1; never `--no-verify` on commits. -**Allowed (often misread):** modifying shared stores/services/utils *additively*; creating v2-only composables; importing from `src/__generated__/`. +**Allowed (often misread):** modifying shared stores/services/utils _additively_; creating v2-only composables; importing from `src/__generated__/`. --- diff --git a/.claude/skills/frontend-v2-patterns/SKILL.md b/.claude/skills/frontend-v2-patterns/SKILL.md index b113d396f..59bfd1078 100644 --- a/.claude/skills/frontend-v2-patterns/SKILL.md +++ b/.claude/skills/frontend-v2-patterns/SKILL.md @@ -54,7 +54,7 @@ Don't push state into `useUISettings` "so it persists" — follow the rule above - Use the **`RForm` primitive** (wraps `v-form`: Enter-to-submit when valid, scroll-to-first-error after a failed `validate()`). **Never use `v-form` directly.** - **Native Vuetify rules** — no Zod/Yup. Rules are arrays of `(v) => true | string`. -- **Reusable rules** in `src/v2/utils/validation.ts` (`required(msg?)`, `email`, `asciiOnly`, `lengthBetween`, `usernameLength/Chars`, `passwordLength`). Utility code *may* call `i18n.global.t(...)` (the no-i18n rule covers lib primitives, not utils). +- **Reusable rules** in `src/v2/utils/validation.ts` (`required(msg?)`, `email`, `asciiOnly`, `lengthBetween`, `usernameLength/Chars`, `passwordLength`). Utility code _may_ call `i18n.global.t(...)` (the no-i18n rule covers lib primitives, not utils). - **Submit pattern:** `await formRef.value?.validate()` before the API call; submit button uses `:loading="submitting"`; errors → snackbar; field errors stay in-place via `:error-messages`. ## G. Permissions diff --git a/.claude/skills/frontend-v2-theming/SKILL.md b/.claude/skills/frontend-v2-theming/SKILL.md index 7375d4c29..dd002e7ca 100644 --- a/.claude/skills/frontend-v2-theming/SKILL.md +++ b/.claude/skills/frontend-v2-theming/SKILL.md @@ -13,12 +13,12 @@ description: Theming, design tokens, colors, and visual language in the RomM v2 `src/v2/tokens/index.ts` is the source. It feeds two consumers: -- **`src/v2/styles/tokens.css`** — *generated* by `scripts/build-tokens.ts` (`npm run build:tokens`, hooked into `predev`/`prebuild`). **Do not hand-edit.** This is how the vast majority of tokens are consumed: `var(--r-color-...)` in CSS. +- **`src/v2/styles/tokens.css`** — _generated_ by `scripts/build-tokens.ts` (`npm run build:tokens`, hooked into `predev`/`prebuild`). **Do not hand-edit.** This is how the vast majority of tokens are consumed: `var(--r-color-...)` in CSS. - **Direct JS/TS imports** of named exports (`colorCanvas`, `colorCoverArt`, `layout`, …) for the few cases needing a token value in JavaScript — baking colors into an SVG string (`utils/covers`), canvas/QR backgrounds (`Player/Ruffle.vue`, `ShowQRCodeDialog`), and the virtualiser's pixel math (`Gallery/listColumns` reading `layout`). v2 has **no Vuetify theme of its own.** `tokens.css` emits a palette block per theme under `.r-v2.r-v2-dark` / `.r-v2.r-v2-light`; `RomM.vue` toggles those classes on ``. v2 surfaces never read Vuetify's runtime theme. -> Caveat: a wrapped Vuetify component still resolves `color="primary"` against Vuetify's *own* registered themes (`src/plugins/vuetify.ts`, sourced from v1's `@/styles/themes`). They mirror the brand tokens by hand (both `#8B74E8`), so they line up, but it's a parallel source. **Prefer `var(--r-...)` over the `color` prop.** +> Caveat: a wrapped Vuetify component still resolves `color="primary"` against Vuetify's _own_ registered themes (`src/plugins/vuetify.ts`, sourced from v1's `@/styles/themes`). They mirror the brand tokens by hand (both `#8B74E8`), so they line up, but it's a parallel source. **Prefer `var(--r-...)` over the `color` prop.** ### Adding a new token diff --git a/.claude/skills/pre-pr-verification/SKILL.md b/.claude/skills/pre-pr-verification/SKILL.md index cadbca054..94d9e38c4 100644 --- a/.claude/skills/pre-pr-verification/SKILL.md +++ b/.claude/skills/pre-pr-verification/SKILL.md @@ -14,7 +14,7 @@ Run the checks that match what you touched. **Static checks don't prove a featur Run from `frontend/`: 1. `npm run typecheck` — zero errors (`vue-tsc --noEmit`). -2. `npm run lint` *(if present)* / ESLint clean. Trunk also runs ESLint + Prettier in CI. +2. `npm run lint` _(if present)_ / ESLint clean. Trunk also runs ESLint + Prettier in CI. 3. `npm run test` — zero failures (Vitest + happy-dom; runs unit tests **and** every `/lib` story's `play()` via `composeStories`). 4. `npm run build` — zero failures (CI sanity check). diff --git a/CLAUDE.md b/CLAUDE.md index cb1c8aa34..ce7901ff2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,21 +1,23 @@ # RomM — Repository Guide for Contributors & Agents -RomM is a self-hosted ROM manager and player: scan a game library off disk, enrich it with metadata from 10+ providers, browse it in a web UI, and play in the browser. This file orients anyone working in the repo — humans and AI agents alike. **Many features here are written by outside contributors using coding agents**, so this guide is written to be actioned without prior context. +RomM is a self-hosted ROM manager and player: scan a game library off disk, enrich it with metadata from 10+ providers, browse it in a web UI, and play in the browser. This file orients anyone working in the repo — humans and AI agents alike. -> **Official language:** all code, comments, identifiers, `.md` files, and commit/PR messages are in **English**. Always. +> **Official language:** outside of language files, all code, comments, identifiers, `.md` files, and commit/PR messages are in **English**. Always. +> +> **No em-dashes:** never use em-dashes (—) when writing comments or text. Use commas, parentheses, or separate sentences instead. --- ## The stack at a glance -| | Backend | Frontend | -| --- | --- | --- | -| Path | `backend/` | `frontend/` | -| Language | Python 3.13+ | TypeScript 5.7 (Vue 3) | -| Framework | FastAPI, SQLAlchemy 2.0, Alembic | Vue 3 + Vite, Vuetify, Pinia, Vue Router | -| Infra | Redis + RQ (jobs/cache/sessions), Socket.IO | vue-i18n, Socket.IO client | -| DB | MariaDB (default), MySQL, PostgreSQL | — | -| Tooling | `uv`, pytest, Trunk (ruff/black/isort/mypy) | `npm`, vue-tsc, ESLint, Vitest, Storybook | +| | Backend | Frontend | +| --------- | ------------------------------------------- | ----------------------------------------- | +| Path | `backend/` | `frontend/` | +| Language | Python 3.13+ | TypeScript 5.7 (Vue 3) | +| Framework | FastAPI, SQLAlchemy 2.0, Alembic | Vue 3 + Vite, Vuetify, Pinia, Vue Router | +| Infra | Redis + RQ (jobs/cache/sessions), Socket.IO | vue-i18n, Socket.IO client | +| DB | MariaDB (default), MySQL, PostgreSQL | — | +| Tooling | `uv`, pytest, Trunk (ruff/black/isort/mypy) | `npm`, vue-tsc, ESLint, Vitest, Storybook | The frontend talks to the backend over `/api/*` (REST) and `/ws` (Socket.IO). TypeScript types are **generated** from the backend's OpenAPI schema into `frontend/src/__generated__/` — the backend is the single source of truth for API shapes. @@ -41,15 +43,15 @@ v2 has a detailed constitution, split across focused skills (below). **Read the These live in `.claude/skills/` and carry the detailed rules. Invoke the one that matches what you're doing: -| Skill | When | -| --- | --- | -| `frontend-v2-components` | Building/editing any v2 component — tiers (lib/shared/feature), file & SFC conventions, barrels, anti-patterns. | -| `frontend-v2-theming` | Colors, tokens, light/dark themes, visual language — and the **zero-hex-literal** policy. | -| `frontend-v2-input` | Interactive components, focus/spatial navigation, gamepad/keyboard, breakpoints & responsive layout. | -| `frontend-v2-patterns` | Feature behavior — errors/snackbars, loading, sockets, state persistence, pagination, forms, permissions, destructive confirmations. | -| `frontend-i18n` | Any user-visible string or change under `frontend/src/locales/**`. | -| `backend-development` | Endpoints, handlers, models, schemas, metadata adapters, tasks, migrations under `backend/`. | -| `pre-pr-verification` | Before committing / opening a PR / declaring done — the checks that keep CI green. | +| Skill | When | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | +| `frontend-v2-components` | Building/editing any v2 component — tiers (lib/shared/feature), file & SFC conventions, barrels, anti-patterns. | +| `frontend-v2-theming` | Colors, tokens, light/dark themes, visual language — and the **zero-hex-literal** policy. | +| `frontend-v2-input` | Interactive components, focus/spatial navigation, gamepad/keyboard, breakpoints & responsive layout. | +| `frontend-v2-patterns` | Feature behavior — errors/snackbars, loading, sockets, state persistence, pagination, forms, permissions, destructive confirmations. | +| `frontend-i18n` | Any user-visible string or change under `frontend/src/locales/**`. | +| `backend-development` | Endpoints, handlers, models, schemas, metadata adapters, tasks, migrations under `backend/`. | +| `pre-pr-verification` | Before committing / opening a PR / declaring done — the checks that keep CI green. | --- @@ -69,15 +71,17 @@ These live in `.claude/skills/` and carry the detailed rules. Invoke the one tha **Setup:** see `DEVELOPER_SETUP.md`. Docker path is `cp env.template .env` → `docker compose build` → `docker compose up -d` (app at `http://localhost:3000`). **Backend** (`cd backend`): + ```bash uv sync --all-extras --dev # install -uv run python3 main.py # run (migrations auto-apply) +uv run main.py # run (migrations auto-apply) uv run pytest [path/file] # test (subset) — or -vv for all uv run alembic revision --autogenerate -m "msg" # new migration (then HAND-REVIEW) uv run alembic upgrade head # apply migrations ``` **Frontend** (`cd frontend`): + ```bash npm install # install (Node 24) npm run dev # dev server :3000