From 5a9eda9eeb9b1d01a9603bda111eaf1636ed0b92 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 01:49:08 +0000 Subject: [PATCH] docs: split v2 constitution into skills, make CLAUDE.md holistic Reorient the repo for agentic development by outside contributors. - Rewrite CLAUDE.md from a frontend-v2-only constitution into a lean, holistic guide covering both stacks, the v1/v2 split, repo-wide rules (English, AI disclosure, branch/PR flow, Trunk, verification), a skills index, and a quick command reference. - Extract the v2 constitution and add backend guidance as seven focused skills under .claude/skills/: - frontend-v2-components (tiers, file/SFC conventions, anti-patterns) - frontend-v2-theming (tokens, dual theme, zero-hex-literal policy) - frontend-v2-input (universal input + responsive viewport) - frontend-v2-patterns (errors, loading, sockets, persistence, forms, permissions, confirmations) - frontend-i18n (locale parity rule + check script) - backend-development (FastAPI/SQLAlchemy layering, scopes, migrations, OpenAPI -> frontend type pipeline, uv/pytest/trunk workflow) - pre-pr-verification (per-stack pre-PR gate mirroring CI) - Track shared skills in git while keeping personal/local Claude config ignored. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_012EigwEL43km8mskHjT6sjr --- .claude/skills/backend-development/SKILL.md | 93 ++++ .claude/skills/frontend-i18n/SKILL.md | 32 ++ .../skills/frontend-v2-components/SKILL.md | 124 +++++ .claude/skills/frontend-v2-input/SKILL.md | 60 ++ .claude/skills/frontend-v2-patterns/SKILL.md | 89 +++ .claude/skills/frontend-v2-theming/SKILL.md | 79 +++ .claude/skills/pre-pr-verification/SKILL.md | 65 +++ .gitignore | 4 +- CLAUDE.md | 518 +++--------------- 9 files changed, 610 insertions(+), 454 deletions(-) create mode 100644 .claude/skills/backend-development/SKILL.md create mode 100644 .claude/skills/frontend-i18n/SKILL.md create mode 100644 .claude/skills/frontend-v2-components/SKILL.md create mode 100644 .claude/skills/frontend-v2-input/SKILL.md create mode 100644 .claude/skills/frontend-v2-patterns/SKILL.md create mode 100644 .claude/skills/frontend-v2-theming/SKILL.md create mode 100644 .claude/skills/pre-pr-verification/SKILL.md diff --git a/.claude/skills/backend-development/SKILL.md b/.claude/skills/backend-development/SKILL.md new file mode 100644 index 000000000..decaa1945 --- /dev/null +++ b/.claude/skills/backend-development/SKILL.md @@ -0,0 +1,93 @@ +--- +name: backend-development +description: Working on the RomM Python backend (backend/) — a FastAPI app with SQLAlchemy 2.0, Alembic, RQ/Redis, and Socket.IO. Use when adding or changing API endpoints, handlers, ORM models, response schemas, metadata-provider adapters, background tasks, database migrations, or backend tests. Covers the layered architecture, conventions, auth/scopes, the OpenAPI→frontend type pipeline, and the uv/pytest/alembic/trunk workflow. Trigger on any work under backend/. +--- + +# RomM Backend — FastAPI / SQLAlchemy + +Python 3.13+, FastAPI, SQLAlchemy 2.0 (MariaDB default; MySQL/PostgreSQL supported), Alembic, Redis + RQ for jobs/cache/sessions, Socket.IO for real-time. Managed with **uv**. + +Full reference: **`docs/BACKEND_ARCHITECTURE.md`** (directory map, ER diagram, every endpoint, auth flows). Read it before non-trivial changes. + +--- + +## Layered architecture — where code goes + +``` +endpoints/ FastAPI routers: request validation, response schemas, @protected_route scopes +endpoints/responses/ Pydantic response schemas (these shape the OpenAPI → frontend types) +endpoints/sockets/ Socket.IO event handlers +handler/ Business logic, decoupled from HTTP + ├ auth/ HybridAuthBackend (session/basic/bearer/OIDC/client-token), scopes, CSRF/session middleware + ├ database/ Per-entity CRUD handlers (db_rom_handler, db_user_handler, …), engine/session factory + ├ metadata/ One handler per provider; normalizes + ranks by priority + └ filesystem/ ROM/asset/firmware file I/O, hashing, archive extraction +adapters/services/ Typed external API clients (igdb.py + igdb_types.py, screenscraper.py, …) +models/ SQLAlchemy ORM models (BaseModel adds created_at/updated_at) +tasks/ RQ jobs — scheduled/ (cron) and manual/ (on-demand); base classes in tasks.py +config/ Env-var loading (__init__.py) + YAML config manager (singleton) +decorators/ @begin_session (DB session), @protected_route (auth + scopes) +exceptions/ Custom exception hierarchy +utils/ logger/ Shared helpers, structured logging +alembic/ Migrations (env.py + versions/) +``` + +**Endpoint → handler → (database | metadata | filesystem) → models/adapters.** Endpoints stay thin: validate, enforce scopes, call handlers, serialize via a response schema. Don't put business logic or raw queries in endpoints. + +## Conventions + +- **Naming:** Classes `PascalCase`; functions/vars `snake_case`; constants `UPPER_SNAKE_CASE`; private `_prefixed`. +- **DB sessions:** decorate handler methods with `@begin_session`; it injects and manages the SQLAlchemy session/transaction. Don't open sessions ad hoc. +- **Async:** I/O-bound endpoints and tasks use `async/await`. Per-request `httpx`/`aiohttp` clients come from context vars (`utils/context.py`), not new clients per call. +- **Imports:** stdlib → third-party → local; explicit (no wildcards); `TYPE_CHECKING` blocks to break circular imports. +- **Errors:** raise the custom exceptions in `exceptions/` (e.g. `RomNotFoundInDatabaseException`), not bare `HTTPException`, where a typed one exists. +- **Validation/SSRF:** sanitize filenames/paths before filesystem use (`utils/`); paths are rooted at `LIBRARY_BASE_PATH`/`RESOURCES_BASE_PATH`/`ASSETS_BASE_PATH` from config. + +## Auth & scopes + +- Roles: `VIEWER` (read), `EDITOR` (+write roms/platforms/assets), `ADMIN` (+users/tasks/logs). Defined on `models/user.py`; scope tiers in `handler/auth/constants.py`. +- Granular scopes: `me.read/write`, `roms.read/write`, `platforms.*`, `assets.*`, `devices.*`, `firmware.*`, `collections.*`, `users.*`, `tasks.run`, `logs.read`. +- Protect routes with `@protected_route(router., "", [Scope.X])`. The frontend mirrors these scopes — keep them aligned. + +## Adding things + +- **Endpoint:** add the route in the right `endpoints/*` router, a response schema in `endpoints/responses/`, enforce scopes, delegate to a handler. If the response shape changes, the frontend must regenerate types (below). +- **Model / schema change:** edit `models/`, then create a migration (below). Update the matching response schema so OpenAPI stays accurate. +- **Metadata provider:** add a typed client in `adapters/services/.py` (+ `_types.py`) and a `handler/metadata/_handler.py` that normalizes into the common shape and slots into the priority order. +- **Background job:** subclass `Task`/`PeriodicTask` in `tasks/scheduled/` or `tasks/manual/`; register scheduled jobs in `startup.py`. + +## Database migrations (Alembic) + +Migrations must work on **MariaDB, MySQL, and PostgreSQL** (CI runs `alembic upgrade head` on Postgres and MariaDB — `.github/workflows/migrations.yml`). Use batch mode / DB-specific SQL where needed; mirror existing migrations in `alembic/versions/`. + +```bash +cd backend +uv run alembic revision --autogenerate -m "short description" # generate, then HAND-REVIEW the file +uv run alembic upgrade head # apply +uv run alembic downgrade -1 # verify the downgrade works +``` + +Always review autogenerated migrations — they miss server-default/enum/index nuances and cross-dialect differences. The `virtual_collections` DB view is excluded from migrations. + +## OpenAPI → frontend types + +FastAPI serves the schema at `GET /openapi.json`. The frontend regenerates its TypeScript types from it: + +```bash +# backend running on :3000, then in frontend/ +npm run generate # writes src/__generated__/ via openapi-typescript-codegen +``` + +**Any change to a response schema or route signature should be followed by `npm run generate` + a frontend typecheck.** + +## Run, test, lint + +```bash +cd backend +uv run python3 main.py # run (migrations auto-apply on startup) +uv run pytest [path/file] # tests (subset by path); uv run pytest -vv for all +``` + +- Tests: pytest + pytest-asyncio, isolated per `pytest-xdist` worker (per-worker DBs); `fakeredis`; `pytest-recording` VCR cassettes mock external APIs; Hypothesis for property tests. Mirror the `backend//` layout under `backend/tests/`. First-time test DB setup: `docker exec -i romm-db-dev mariadb -uroot -p < backend/romm_test/setup.sql`. +- **Lint / format / type-check run through Trunk** (ruff, black, isort, mypy, bandit): `trunk fmt && trunk check`. CI enforces Trunk on every PR. Never bypass with `--no-verify`. +- New/changed logic needs a test; new endpoints need endpoint tests. diff --git a/.claude/skills/frontend-i18n/SKILL.md b/.claude/skills/frontend-i18n/SKILL.md new file mode 100644 index 000000000..e04bbec8c --- /dev/null +++ b/.claude/skills/frontend-i18n/SKILL.md @@ -0,0 +1,32 @@ +--- +name: frontend-i18n +description: Internationalization for the RomM frontend (both v1 and v2). Use whenever adding, renaming, or removing any user-visible string / translation key under frontend/src/locales/. Covers the en_US-is-source rule, the requirement to add every new key to ALL locale directories in the same change, namespace layout, and the check_i18n_locales.py validator enforced in CI. Trigger on any change to frontend/src/locales/**. +--- + +# RomM Frontend — i18n / Localization + +User-visible strings are **never hard-coded** in components — they come from locale files via `vue-i18n` (`$t(...)` in templates/composites; utils may call `i18n.global.t(...)`; **v2 lib primitives must not call `$t` at all** — text passes via props/slots). + +## Structure + +- Locales live in `frontend/src/locales//.json`, loaded by dynamic glob import in `src/locales/index.ts`. +- **18 locales**: `en_US` (default + fallback), `en_GB`, `bg_BG`, `cs_CZ`, `de_DE`, `es_ES`, `fr_FR`, `hu_HU`, `it_IT`, `ja_JP`, `ko_KR`, `pl_PL`, `pt_BR`, `ro_RO`, `ru_RU`, `zh_CN`, `zh_TW`. +- Namespaces are per-feature files (e.g. `collection`, `common`, `console`, `detail`, `emulator`, `gallery`, `home`, `library`, `login`, `navigation`, `patcher`, `platform`, `scan`, `settings`, `task`). + +## The rule (enforced in CI) + +- **`en_US` is the source of truth**, but **every key added to `en_US` must be added to all other locale directories in the same change.** Never leave a key English-only. +- Translate where you can; otherwise copy the English value as a placeholder so the key exists. +- Removing or renaming a key means doing it across **every** locale. + +## Verify before handoff + +```bash +python3 frontend/src/locales/check_i18n_locales.py +``` + +It compares every non-English locale against `en_US` and **fails on any missing file, missing key, or extra key**. CI runs the same script (`.github/workflows/i18n.yml`) on any change under `frontend/src/locales/**`. It must pass with zero missing/extra keys. + +## Adding a new language + +Create a new folder under `frontend/src/locales/` mirroring `en_US/`'s files, then translate. Open the PR against `master` (see the docs/Contributing flow). This is the one i18n change where a new locale directory is expected. diff --git a/.claude/skills/frontend-v2-components/SKILL.md b/.claude/skills/frontend-v2-components/SKILL.md new file mode 100644 index 000000000..0f4a570b9 --- /dev/null +++ b/.claude/skills/frontend-v2-components/SKILL.md @@ -0,0 +1,124 @@ +--- +name: frontend-v2-components +description: Building or modifying components in the RomM v2 frontend (frontend/src/v2/). Use when creating/editing v2 primitives (R* components in src/v2/lib/), shared composites, or feature composites — covers the three-tier model, file/folder conventions, SFC structure, import order, barrels, Storybook requirements, and v2 anti-patterns. Trigger on any work under frontend/src/v2/. +--- + +# RomM Frontend v2 — Component Constitution + +This governs work inside `frontend/src/v2/`. **v1 is frozen** (`src/views/`, `src/components/`, `src/console/`, `src/layouts/`) — never refactor it; it will be deleted wholesale in a final wave. v2 is gated by `user.ui_settings.uiVersion`. + +> Official language for all code, comments, identifiers, `.md`, and commit/PR messages: **English**. + +Related skills: `frontend-v2-theming` (tokens/colors), `frontend-v2-input` (focus/gamepad/responsive), `frontend-v2-patterns` (errors/loading/forms/permissions/confirmations), `frontend-i18n`, `pre-pr-verification`. + +--- + +## Premises (stable) + +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. +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. +7. **Layout via Vuetify utility classes + our wrappers, not plain CSS.** Use `d-flex`, `pa-4`, `align-center` directly. Vuetify layout components (`v-row`, `v-col`, `v-container`, `v-spacer`, `v-app`, `v-main`) get wrapped as `R*` on first use (lazy). +8. **Accessibility & performance** are requirements: semantic HTML, focus management, contrast, ARIA on icon-only controls; lazy-load heavy views, virtualize large lists, stable `:key` on every `v-for`. + +--- + +## The three tiers + +| Tier | Path | Prefix | Stores/services/router/emitter/i18n | Story | Domain knowledge | +| --------------------- | ------------------------------ | -------------- | ----------------------------------- | --------- | --------------------------------- | +| **Primitive** | `src/v2/lib/` | `R*` mandatory | **No** | Mandatory | None | +| **Shared composite** | `src/v2/components/shared/` | no prefix | Yes | Optional | Cross-feature, no specific domain | +| **Feature composite** | `src/v2/components//` | no prefix | Yes | Optional | Feature-specific | + +### A component is a primitive only if all three hold + +1. Does not depend on stores, services, router, or emitter. +2. No knowledge of a product domain (ROM, Platform, Collection, User…). `RAvatar` yes, `UserAvatar` no. +3. Its API can be described without naming features — generic props/slots/events. + +If any fails: **shared composite** if generic across features, **feature composite** if owned by one feature. Edge cases get raised to the user, not auto-decided. Consumer count never demotes a primitive. + +### Primitive boundaries + +- **Can use**: tokens, other primitives, Vue/Vuetify, generic composables (`useInput*`, `useFocus*`). +- **Cannot use**: Pinia stores, API services, `emitter`, `router` (a `RouterLink` may be accepted as a prop), `i18n` directly. **No `$t()` in primitives** — text comes via props or slots. + +--- + +## File & folder conventions + +- **Primitive**: one per folder — `RFoo/RFoo.vue`, `RFoo/RFoo.stories.ts`, `RFoo/index.ts`, optional `RFoo/types.ts`. +- **Composite**: flat `.vue` if one file suffices; a folder with the same internal structure (no story required) if it has sub-pieces. +- **Barrel**: `src/v2/lib/index.ts` re-exports every primitive — update it when a new primitive ships. Composites are imported directly by path; no barrel. No single-file `index.ts` that just re-exports to shorten a path. + +### SFC structure + +- `