|
6 | 6 | - Drizzle ORM |
7 | 7 | - Neon PostgreSQL |
8 | 8 |
|
9 | | -## Equipment architecture |
| 9 | +## Core project invariants |
10 | 10 |
|
11 | | -### Data model |
| 11 | +- Equipment properties use an EAV model: `category_properties` defines available fields, `item_property_values` stores per-item values. |
| 12 | +- Groups and categories are separate reference entities with no FK between them. Do not invent one unless the product plan explicitly changes. |
| 13 | +- Items are flat. Size and similar distinctions are category properties, not a variant subsystem. |
| 14 | +- User-facing catalog entities use UUID v7 where the product already depends on stable public IDs. Reference data uses serial IDs. |
| 15 | +- Reference-data slugs are canonical lowercase URL tokens using only `a-z`, `0-9`, and single hyphens. |
| 16 | +- Reference-data `name` values stay English display strings. Slugs are the durable keys for URLs and future i18n lookups. |
| 17 | +- `equipment_items` brand/category foreign keys must stay non-cascading (`restrict`), so deleting reference data cannot silently remove catalog or inventory records. |
| 18 | +- Public reference-data read routes use `slug`; admin mutations use stable `id` params. |
| 19 | +- All tables live in `server/database/schema.ts`, organized by Auth, Equipment catalog, and User data sections. |
12 | 20 |
|
13 | | -- **EAV (Entity-Attribute-Value)** for item properties: `category_properties` defines what properties a category has, `item_property_values` stores actual values per item. This enables dynamic filters and item comparison without schema migrations. |
14 | | -- **Two-level categorization**: Groups (functional area: Sleep, Shelter, Cooking) and Categories (specific type: Sleeping Bags, Sleeping Pads) are independent entities with no FK relationship. Connection can be added later when navigation patterns become clear. |
15 | | -- **Items are flat** — each size/variant is a separate item. Size is a category property, not a separate variant system. |
| 21 | +## Local skills |
16 | 22 |
|
17 | | -### Database conventions |
| 23 | +Before any task, check whether a local skill matches the domain and follow it. |
18 | 24 |
|
19 | | -- **UUID v7** as PK for all user-facing entities (items, user_equipment, contributions). |
20 | | -- **Serial** PK for reference data (groups, categories, properties, brands). |
21 | | -- **Slugs** on reference data only (groups, categories, brands) — used for URLs and as future i18n translation keys. Items use UUID in URLs. |
22 | | -- **Reference data slugs are canonical lowercase URL tokens** using only `a-z`, `0-9`, and single hyphens between segments. |
23 | | -- **`name`** columns on reference data are English display values. When i18n is added, `slug` maps to a translation key, `name` becomes the fallback. |
24 | | -- **Brand/category FKs from `equipment_items` must not cascade on delete**: use `restrict` so deleting reference data cannot silently remove catalog items, item property values, or user inventories. |
| 25 | +- `vue-components` for `.vue` component structure, styling, props/emits, and SSR-safe frontend patterns. |
| 26 | +- `equipment-backend` for equipment/catalog backend work in `server/api`, validation schemas, Drizzle write paths, schema changes, and equipment API tests. |
| 27 | +- `planning-docs` for roadmap files in `plan/`, completed work notes, architecture docs, and iteration planning tasks. |
25 | 28 |
|
26 | | -### API conventions |
| 29 | +## Workflow rules |
27 | 30 |
|
28 | | -- **Public read routes for reference data use `slug`** in detail URLs (`/api/equipment/brands/[slug]`, `/api/equipment/categories/[slug]`). |
29 | | -- **Admin mutation routes for reference data use `id`** in route params (`PATCH`/`DELETE`), because `slug` is editable content and must not be the stable mutation key. |
30 | | -- **Admin category property mutations stay nested under category `id` routes** (`/api/equipment/categories/[categoryId]/properties/...`) so parent ownership is explicit and property/enum-option handlers can verify the full route chain. |
31 | | -- **All API request inputs use Valibot schemas through h3 validated helpers**: `readValidatedBody` for request bodies, `getValidatedRouterParams` for route params, and `getValidatedQuery` for query strings. Schemas and validator functions live in `server/utils/validation/schemas.ts`; handlers should consume parsed values instead of manually validating raw input. |
32 | | -- **Catalog admin writes that both mutate reference data and log `contributions` must be atomic**: run them through a transaction-capable write path, not separate `dbHttp` calls. |
33 | | -- **Single-result query arrays should be destructured directly from the awaited query** when only the first row is needed once (`const [row] = await ...`); keep an intermediate array only when the full result set, row count, or repeated reuse matters. |
34 | | -- **Public read detail endpoints stay narrow**: return the entity needed for that route, and fetch related collections with separate read endpoints when a page needs them. Do not expand detail payloads just to save a future frontend request. |
35 | | -- **Shared catalog `returning(...)` shapes use reusable base records, not global endpoint models**: extract common `id`/`name`/`slug` selections into server-only helpers when reused, but keep each endpoint free to return a different response shape later if needed. |
36 | | -- **Deleting an enum option that is already used by existing item property values must return `409`**: enum item values are currently stored by slug text, so option deletes need an application-level guard against orphaned enum values. |
37 | | -- **Protected `/api/*` routes keep mixed auth behavior by caller type**: unauthenticated browser document navigations redirect to `/login?redirectTo=...`, while programmatic API requests (`fetch`/XHR) still receive `401`. |
| 31 | +- Files imported by standalone Node or `tsx` scripts, including `tools/*.ts`, migrations, seeds, and their transitive dependencies, must not rely on Nuxt-only aliases like `~/` or `@@/`. If they use `#shared/*` or `#server/*`, keep those aliases backed by `package.json#imports`. |
| 32 | +- After any architecture, route-convention, data-model, or test-strategy change, update the relevant docs in the same change. This can include `AGENTS.md`, `plan/PLAN.md`, `plan/completed.md`, or the detailed roadmap file that changed. |
38 | 33 |
|
39 | | -### Testing conventions |
| 34 | +## Verification matrix |
40 | 35 |
|
41 | | -- **API handlers are tested in Vitest with mocks** for `event`, `dbHttp`, and auth/body helpers. Required API coverage must not depend on shared database state. |
42 | | -- **Playwright is reserved for browser/UI smoke** and must not be the primary layer for API contract coverage. |
43 | | -- **Deterministic tests over seeded assumptions**: avoid checks that depend on fixed counts or specific catalog rows in a real database. |
44 | | -- **Shared test helpers live at the repo root** (for example `test-utils/`), not inside `server/` or `app/`, so runtime source trees stay free of test-only utilities. Import them through root aliases like `~~/` or `@@/`, not deep relative paths. |
45 | | -- **Avoid file-level lint disables in tests** when a clean test helper, typed wrapper, or test-specific lint override can solve the problem. Use inline/file disables only as a last resort for a documented tool mismatch. |
46 | | -- **Prefer `vi.mock(import(...))` in Vitest** so test mocks stay aligned with the project skill and `vitest/prefer-import-in-mock`. If a test needs leniency, prefer narrow `oxlint` test overrides like `max-lines` or `import/no-relative-parent-imports`, not disabling mock-style rules. |
| 36 | +After any code modification, run the checks that match the files you touched. |
47 | 37 |
|
48 | | -### Content management |
| 38 | +- If Markdown files changed: `pnpm run lint:markdown` |
| 39 | +- If TypeScript or Vue code changed: `pnpm run test:typecheck` |
| 40 | +- If TypeScript or Vue code changed: `pnpm run test:unit:agent` |
| 41 | +- If TypeScript or Vue code changed: `pnpm run lint:oxlint` |
| 42 | +- If TypeScript or Vue code changed: `pnpm run build` |
| 43 | +- If TypeScript or Vue code changed: `pnpm run test:e2e:ci` |
49 | 44 |
|
50 | | -- **Contributions table** logs every write operation (create/update/delete) with userId, action, targetId, and optional metadata. `targetId` stores the changed entity's primary key as a string, so it can hold either a serial ID or a UUID. Used for future gamification. |
51 | | -- **Item status**: `approved` by default for admin-created items. Future user submissions will default to `pending`. |
52 | | -- MVP: only admins can manage the equipment catalog. Users browse and add items to their inventory. |
53 | | - |
54 | | -### Schema file |
55 | | - |
56 | | -All tables live in a single `server/database/schema.ts` file, organized by sections: Auth, Equipment catalog, User data. |
57 | | - |
58 | | -## Workflow |
59 | | - |
60 | | -Before performing any task, action, or code modification, event small one, check if there are existing skills that cover the domain of your task and follow their instructions. |
61 | | - |
62 | | -- Files imported by standalone Node/`tsx` scripts (for example migration, seed, and other `tools/*.ts` entry points, plus their transitive dependencies) must not rely on Nuxt-only aliases like `~/` or `@@/`. If such files use `#shared/*` or `#server/*`, keep those aliases backed by `package.json#imports`, because plain script execution does not get Nuxt alias resolution automatically. |
63 | | - |
64 | | -After any code modification, always run: |
65 | | - |
66 | | -- If Markdown files are modified: |
67 | | - - `pnpm run lint:markdown` for markdown linting |
68 | | -- If TypeScript or Vue code is modified: |
69 | | - - `pnpm run test:typecheck` - to ensure type safety |
70 | | - - `pnpm run test:unit:agent` for unit tests |
71 | | - - `pnpm run lint:oxlint` for linting |
72 | | - - `pnpm run build` to ensure the project builds successfully |
73 | | - - `pnpm run test:e2e:ci` for E2E tests (auto-starts dev server) |
74 | | - |
75 | | -All tasks above can be run in parallel. |
76 | | - |
77 | | -If any architectural decisions were made during the task (new patterns, conventions, data model changes), update relevant sections of this file. Remove or revise entries that are no longer accurate. |
78 | | -If a task changes architecture, route conventions, or test strategy, update `AGENTS.md` and relevant roadmap/completed docs in the same change automatically. |
79 | | - |
80 | | -## Planning conventions |
81 | | - |
82 | | -The `plan/` directory is the **global product roadmap**, not a per-sprint task list. It describes the full scope of planned product features and their implementation order. |
83 | | - |
84 | | -- Prefer the **smallest possible completed iteration** that removes one blocker or delivers one coherent slice. Smaller iterations reduce implementation mistakes and make progress easier to verify. |
85 | | -- For new API iterations, every join must be justified by a field returned in the current response or by a filter applied in the current endpoint. Do not preload relations "just in case". |
86 | | -- Do not add extra detail payload or extra detail endpoints unless the current iteration already has a concrete consumer for them. |
87 | | -- Response examples in plan files are the **upper bound** for payload in that iteration. Do not silently extend them without updating the plan and the consumer need. |
88 | | -- `plan/PLAN.md` is the roadmap index — keep it short, link to detailed plan files. |
89 | | -- Large iterations must be split into sequential task files (e.g. `plan/admin-management/01-foundations.md`). |
90 | | -- Completed work goes to `plan/completed.md` as short summaries, not detailed specs. |
91 | | -- When plan files are renamed or moved, update links in `plan/PLAN.md` and overview files immediately. |
| 45 | +All applicable commands may be run in parallel. |
0 commit comments