diff --git a/ai/backend-context.md b/ai/backend-context.md index 8ab6a97..9214acd 100644 --- a/ai/backend-context.md +++ b/ai/backend-context.md @@ -8,57 +8,76 @@ Read `ai/shared.md` first. This file covers backend-specific rules. **Location:** `backend/`. -The application is still close to the Laravel starter structure. Do not -introduce a domain architecture, repository layer, service layer, or other -abstraction before the codebase and requested behavior justify it. Match -existing Laravel conventions and inspect similar code before adding a new -pattern. +The backend follows a domain-oriented structure. Existing areas use entities, +DTOs, repository interfaces, Eloquent implementations, use cases, and test +fakes. Extend those patterns when adding behavior to an established area. Do +not add speculative layers or interfaces that the requested behavior does not +need. -## Laravel patterns +The Vue application remains a separate project under `frontend/website/`. +Keep frontend source, dependencies, builds, and delivery out of the backend +unless the user explicitly asks to integrate them. The backend root route is +intentionally unclaimed; the built-in health endpoint is `/up`. -- Keep controllers thin. Put reusable business behavior in an appropriately - named application or domain class once the behavior warrants extraction. -- Use dedicated request validation rather than validating substantial payloads - inline in controllers. -- Let unexpected exceptions reach Laravel's exception handler. Catch only - exceptions that can be handled meaningfully at the current boundary. +## Code patterns + +- Inspect similar domain code before adding a new entity, DTO, repository, + use case, controller action, or fake. +- Entities own domain state and behavior and expose descriptive methods. +- DTOs are explicit data containers for creation or transfer between seams. +- Repository interfaces define domain-facing persistence operations. Keep + Eloquent details in their implementations and register bindings in the + application provider. +- Put reusable business behavior in a use case. Give a use case a request DTO + when it accepts a payload; call `execute()` directly when it has no input. +- Document use-case exceptions with `@throws` when callers are expected to + handle them. +- Keep controllers thin. Translate HTTP input to use-case input and domain + output to the response contract. +- Catch only documented, expected exceptions at the controller boundary. Let + unexpected exceptions reach Laravel's exception handler. +- Fake repositories are in-memory implementations used by unit tests. Return + a new entity instance from create and lookup methods rather than exposing a + stored reference. - Use Eloquent relationships and query scopes consistently rather than duplicating query fragments. -- Avoid speculative interfaces and abstractions with only one trivial - implementation. -- The Vue application is a separate project under `frontend/website/`. -- Keep frontend source, dependencies, builds, and delivery out of the backend - unless the user explicitly asks to integrate them. -- The backend root route is intentionally unclaimed. The built-in health - endpoint is `/up`. -## Tests +## Unit tests -- Follow the existing PHPUnit organization under `tests/Unit/` and - `tests/Feature/`. -- Prefer `PHPUnit\Framework\TestCase` when a test only exercises plain PHP. -- Extend `Tests\TestCase` only when the test needs Laravel's container, - facades, database, routing, or HTTP kernel. -- HTTP feature tests extend `Tests\TestCase`. -- Use `RefreshDatabase` when a test reads or writes database state. -- Assert behavior at the appropriate seam: - - Unit tests cover isolated business behavior and edge cases. - - Feature tests cover routing, middleware, validation, persistence, and - response shape. -- Do not duplicate every business branch through the HTTP layer when unit - coverage already proves it. Feature tests should focus on wiring and the - public contract. +- Follow the existing organization under `tests/Unit//`. +- Use fake repositories and fake collaborators for the behavior under test. + Construct unrelated dependency entities directly instead of routing them + through additional repositories. +- Test use-case branches at the use-case seam. Do not repeat every branch in + controller or HTTP tests. +- Plain entities, value objects, use cases, middleware, and controller units + should extend `PHPUnit\Framework\TestCase` when they do not need Laravel. +- Extend `Tests\TestCase` only when a test needs Laravel's container, facades, + database, routing, or HTTP kernel. +- Reserve direct Eloquent repository feature tests for persistence mapping, + query behavior, or database constraints that cannot be proven through a + plain unit test. -## Test database +## Feature tests +- Feature tests exercise the HTTP seam: routing, middleware, real Eloquent + bindings, cookies, persistence, validation, and response shape. +- Feature tests are additive to unit tests. Prefer a happy path and the + relevant authorization guard instead of duplicating all business branches. +- Place endpoint tests under `tests/Feature//`, extend + `Tests\TestCase`, and use `RefreshDatabase` when database state is involved. - Development and runtime use PostgreSQL through the local Unix socket. -- PHPUnit intentionally uses SQLite `:memory:` as configured in - `phpunit.xml`. -- Feature tests are self-contained and do not require the process-compose - PostgreSQL service. + PHPUnit uses SQLite `:memory:` as configured in `phpunit.xml`, so feature + tests are self-contained and do not need the worktree stack. - Never point `RefreshDatabase` tests at the development PostgreSQL database. -- Keep mail set to the PHPUnit `array` transport unless a test explicitly - exercises a real mail integration. +- Keep mail on PHPUnit's `array` transport unless a test explicitly exercises + a real mail integration. +- Authentication uses a custom database-session cookie, not a Laravel guard. + `actingAs()` does not apply. Credentialed requests use + `withCredentials()` and `withUnencryptedCookie()` because API middleware + reads the raw cookie. +- Read an unencrypted response cookie with + `$response->getCookie($name, false)`. ## PHP rules @@ -80,7 +99,7 @@ pattern. - Do not add production repository or model APIs solely to make seeding convenient. - Use existing lookup methods for cross-seeder relationships. When a group of - records only makes sense together, keep them in one seeder and retain local + records only makes sense together, keep it in one seeder and retain local references. ## Migrations @@ -99,15 +118,12 @@ pattern. - Once a production database exists, replace this policy with additive, forward-only migrations. -## Before completing backend work +## Backend workflow -- Run the focused test during development. -- Run the full test suite before completion: - - ```sh - direnv exec "$(git rev-parse --show-toplevel)" php artisan test - ``` - -- Run the Composer static-analysis scripts. +- Run the focused PHPUnit test while developing. +- Use `just backend-types-check` for Larastan and `just backend-test` for the + full PHPUnit suite during iteration. - Fix failures caused by the change. Report unrelated baseline failures - precisely rather than hiding them or expanding scope without authorization. + precisely rather than expanding scope silently. +- The shared `just test-all` command is the required completion gate. Focused + backend recipes never replace it. diff --git a/ai/frontend-context.md b/ai/frontend-context.md index 213d164..a2acb1e 100644 --- a/ai/frontend-context.md +++ b/ai/frontend-context.md @@ -4,7 +4,8 @@ Read `ai/shared.md` first. This file covers frontend-specific rules. ## Project context -**Stack:** Vue 3.5, TypeScript 6, Vite 8, Vue Router 5, Pinia 4, npm. +**Stack:** Vue 3.5, TypeScript 6, Vite 8, Vue Router 5, Pinia 4, Zod 4, +Cypress 15, npm. **Location:** `frontend/website/`. @@ -12,18 +13,16 @@ The frontend is a standalone application. Keep its source, dependencies, development server, and production build independent from the backend unless the user explicitly requests integration. -The scaffold uses: +The application currently has route-level views, reusable authentication +components, a Pinia authentication store, Zod schemas, API URL handling, and +Cypress end-to-end specs. The main entry points are: -- `src/App.vue` as the root component. -- `src/main.ts` to create the app and install the router and Pinia. -- `src/router/index.ts` for routes. -- `src/stores/` for Pinia stores. -- `@` as an alias for `src/` in both Vite and TypeScript. - -There are no views, shared components, API layer, or configured test suite -yet. Cypress is installed as a frontend development dependency, but there is -no Cypress configuration or npm test script. Do not invent an architecture -before requested behavior establishes one. +- `src/App.vue` for the root component. +- `src/main.ts` for app creation, Pinia, the router, and global styles. +- `src/router/index.ts` for routes and authentication guards. +- `src/stores/` for Pinia stores and API boundaries. +- `src/views/` and `src/components/` for route and reusable UI. +- `@` as the `src/` alias in Vite and TypeScript. ## Package management and commands @@ -36,32 +35,14 @@ Install dependencies in a fresh checkout or worktree: direnv exec "$(git rev-parse --show-toplevel)" npm install ``` -To start only the development server on the port assigned by the shell hook: +`npm run format` and `npm run lint` rewrite files. Their `format:check` and +`lint:check` counterparts are non-mutating completion checks. `npm run build` +runs type checking and the production build; generated `dist/` output is +ignored. -```sh -direnv exec "$(git rev-parse --show-toplevel)" \ - npm run dev -- \ - --host 127.0.0.1 \ - --port "$VITE_PORT" \ - --strictPort -``` - -`process-compose` starts the frontend as part of the full development stack. -The frontend remains directly accessible on `VITE_PORT`; Caddy does not proxy -it. - -The available validation commands are: - -```sh -direnv exec "$(git rev-parse --show-toplevel)" npm run format -direnv exec "$(git rev-parse --show-toplevel)" npm run lint -direnv exec "$(git rev-parse --show-toplevel)" npm run type-check -direnv exec "$(git rev-parse --show-toplevel)" npm run build -``` - -`npm run format` and `npm run lint` rewrite files. Review the resulting diff. -`npm run build` runs type checking and the production build. Build output -under `dist/` is generated and ignored. +`process-compose` starts the frontend as part of the complete development +stack. The frontend is directly accessible on `VITE_PORT`; Caddy serves the +backend and does not proxy the frontend. ## Vue conventions @@ -72,58 +53,69 @@ under `dist/` is generated and ignored. `src/router/index.ts`. - Keep page, component, composable, and store responsibilities distinct. - Prefer small components with explicit props and emitted events. -- Keep component styles scoped until the project adopts a deliberate global - styling system. - Use setup-style Pinia stores named `useXxxStore`. -- Inspect similar files before introducing a new component, composable, - store, or data-access pattern. +- Keep application-wide resets and base styles in `src/styles/main.css`. + Keep component and view styles scoped and match established Attainly visual + patterns. +- Inspect similar files before introducing a component, composable, store, + route, or data-access pattern. -## TypeScript +## TypeScript and runtime validation -- Preserve the strict TypeScript configuration and - `noUncheckedIndexedAccess`. -- Do not use `any`. Model unknown external values as `unknown`, then narrow or - validate them. -- Derive types from runtime schemas if the project adopts a schema library. - Do not maintain a hand-written type that can drift from its schema. -- Validate payloads at trust boundaries, especially backend responses and - user-submitted forms. -- Keep request and response types close to the API or store boundary that - owns them. -- Keep the `@` alias aligned across Vite, TypeScript, and any future test - configuration. +- Preserve strict TypeScript and `noUncheckedIndexedAccess`. +- Do not use `any`. Model unknown external values as `unknown`, then parse or + narrow them. +- Zod schemas are the source of truth for runtime payloads. Define a schema + and derive its TypeScript type with `z.infer` instead of maintaining a + parallel hand-written interface. +- Parse every backend response at the store or API boundary before placing it + in application state. +- Keep read and write schemas separate when their shapes differ. +- Validate user-submitted forms with a Zod object schema when the form has + meaningful validation rules. Surface field errors from the schema rather + than maintaining parallel regular expressions and error logic. +- Keep request and response schemas and types near the API or store boundary + that owns them. +- Keep the `@` alias aligned across Vite, TypeScript, and future test tooling. ## State and API access - Use Pinia for shared client state. Keep component-local state in components. -- Keep server requests and response transformation at an API or store - boundary, not scattered through presentation components. +- Keep requests, response parsing, and response transformation at an API or + store boundary, not in presentation components. - Represent loading, empty, success, validation-error, and unexpected-error states explicitly. -- Do not cast unchecked JSON directly to an application interface. -- Keep read and write payload types separate when their shapes differ. +- Do not cast unchecked JSON to an application interface. +- Send cookie-backed API requests with the established credentials behavior. ## Testing -Cypress is installed, but no frontend test configuration or test script -exists yet. +- Cypress is configured under `cypress/` and runs through `npm run test:e2e` + or `just frontend-cypress-run`. +- Prefer the cheapest test seam that proves the behavior. Cypress covers + routing, browser forms, authentication flows, request wiring, and responsive + behavior. +- Mock backend calls in frontend-focused Cypress tests. Use the worktree + backend only for a deliberately end-to-end integration scenario. +- Assert both the request contract and the rendered response behavior when a + spec intercepts an API call. +- Keep mock payloads synchronized with exported store types and Zod schemas. + Add typed builders when payloads repeat across specs, and parse builder + output through the exported schema when practical. +- Authentication requests are mockable like every other frontend boundary. + Backend persistence, cookie creation, middleware, and mail behavior belong + in PHPUnit tests. +- No Vitest unit or component suite is configured. Do not claim that coverage. + If new pure logic or component behavior cannot be proved economically with + Cypress, establish the smallest appropriate Vitest setup test-first. -- Do not invent test commands or claim frontend tests passed. -- New frontend behavior must still follow the shared test-first workflow. - Establish the smallest appropriate test setup before implementing behavior - that needs it. -- Unit tests should cover pure transformations, composables, and store logic. -- Component tests should cover rendering, events, form behavior, and - conditional UI. -- End-to-end tests should cover routing, multi-page flows, and request wiring. -- Prefer the cheapest layer that proves the behavior. -- Mock backend requests in frontend tests. Do not retest backend persistence, - validation, authentication, or mail behavior through the frontend. +## Frontend workflow -## Before completing frontend work - -- Run the focused test while developing once test tooling exists. -- Run the formatter, linter, type checker, production build, and every - configured test script affected by the change. -- Do not claim a green gate when a command fails. Report a baseline or - environmental failure precisely. +- Run the focused Cypress spec while developing when browser behavior changes. +- Run `npm run format` and `npm run lint` before committing frontend changes, + then review every rewrite. +- Use the focused `just frontend-*` recipes for development feedback. +- The shared `just test-all` command is the required completion gate. Focused + frontend checks never replace it. +- Do not claim a green gate when a command fails. Report baseline or + environmental failures precisely. diff --git a/ai/shared.md b/ai/shared.md index 00a1273..bf52fe5 100644 --- a/ai/shared.md +++ b/ai/shared.md @@ -39,6 +39,24 @@ Use judgment for changes that cannot meaningfully be test-driven, such as documentation-only edits or declarative environment configuration. Validate those changes with the most relevant parser, formatter, dry run, or check. +## Maintaining these instructions + +- Treat user steering as durable when it corrects or establishes a reusable + project rule for workflow, architecture, conventions, validation, safety, + or scope. +- When durable steering is received during work, update the appropriate + `ai/*.md` file in the active worktree before completing the task. Do not + wait for a separate request to maintain the instructions. +- Put repository-wide rules in `shared.md` and stack-specific rules in the + matching backend or frontend guide. +- Merge new guidance into the existing rule set. Keep it concise, remove + duplication, and resolve conflicts between shared and stack-specific text. +- Do not persist task-specific scope, temporary directions, secrets, + environment incidents, or instructions that conflict with higher-priority + guidance. +- Commit a durable instruction update separately from implementation unless + the task itself is solely an instruction change. + ## Approval discipline - Treat dependency installation, tests, static analysis, formatting, linting, @@ -64,58 +82,42 @@ those changes with the most relevant parser, formatter, dry run, or check. - A worktree owns its own isolated stack. The flake shell hook assigns a deterministic port offset and creates worktree-local PostgreSQL state. - Start a worktree stack only when runtime or integration validation requires - it. Start it once from the worktree root, reuse it throughout validation, - and stop it once when finished. -- Do not start any service for PHPUnit, frontend formatting, linting, type - checking, or production builds. -- When authorized worktree stack control is necessary, operate it directly. - Do not ask the user to start or stop worktree services. -- For non-interactive use, start the stack detached and stop it when finished, - as shown below. -- Do not use `process-compose -t=false` for a detached stack. It can leave an + it. Start it once, reuse it throughout validation, and stop it once when + finished. +- PHPUnit, frontend formatting, linting, type checking, and production builds + do not require services. The Cypress completion suite does require the + worktree stack. +- When worktree stack control is necessary, operate it directly. Do not ask + the user to start or stop worktree services. +- Never use `process-compose -t=false` for a detached stack. It can leave an orphaned PostgreSQL process holding the data directory. - Non-interactive agent shells do not automatically load direnv. Bare project commands can use missing tools, default ports, or paths from the main checkout. -- Run project tooling that depends on the repository development environment - through direnv. This includes PHP, Composer, Artisan, npm, tests, builds, - database clients, and services. -- Resolve the direnv target from the worktree containing the agent's current - working directory. Never target the main checkout or a different worktree: +- Run PHP, Composer, Artisan, npm, tests, builds, database clients, and + services through the development environment. +- Resolve the direnv target from the worktree containing the current working + directory. Never target the main checkout or a different worktree: ```sh direnv exec "$(git rev-parse --show-toplevel)" ``` -- Git and environment-neutral read-only file inspection do not need the - direnv wrapper. -- Worktree stack examples: +- Git and environment-neutral read-only inspection do not need direnv. +- Start and stop a worktree stack from its root: ```sh direnv exec "$(git rev-parse --show-toplevel)" process-compose up -D direnv exec "$(git rev-parse --show-toplevel)" process-compose down ``` -- Run backend commands from `backend/`, or explicitly change into it in the - command. The direnv target remains the worktree root. -- Run frontend commands from `frontend/website/`. -- `process-compose` starts the frontend on the worktree's assigned port. To - start only the frontend: - - ```sh - direnv exec "$(git rev-parse --show-toplevel)" \ - npm run dev -- \ - --host 127.0.0.1 \ - --port "$VITE_PORT" \ - --strictPort - ``` - +- Run backend commands from `backend/` and frontend commands from + `frontend/website/`. The direnv target remains the worktree root. - When a normally valid check fails because a required service is down, - surface the environmental failure. Do not skip the check or silently switch - to a different database or service. + surface the environmental failure. Do not skip the check, change databases, + or claim the work is complete. - PHPUnit is self-contained and uses in-memory SQLite. It does not require the - PostgreSQL stack unless a future test is explicitly designed as a real - PostgreSQL integration test. + PostgreSQL stack unless a future test explicitly targets real PostgreSQL. ## Code style @@ -155,14 +157,13 @@ those changes with the most relevant parser, formatter, dry run, or check. - Do not include drive-by formatter or linter changes. Restore them or land them as a separate formatting commit. - If a check fails on untouched code, do not bundle an unrelated fix. Report - the pre-existing failure or handle it as its own explicitly scoped change. + the baseline failure or handle it as its own explicitly scoped change. ## Branching - Never implement features directly in the main checkout or on `master`/`main`. -- Use a dedicated worktree under - `/.worktrees/`. +- Use a dedicated worktree under `/.worktrees/`. - Create it with: ```sh @@ -183,10 +184,10 @@ those changes with the most relevant parser, formatter, dry run, or check. ``` - Never symlink `backend/vendor` or `frontend/website/node_modules` from - another checkout. Dependency paths and generated files must remain + another checkout. Dependencies and generated files must remain worktree-local. -- The shell hook installs backend dependencies but does not install frontend - dependencies. From `frontend/website/`, provision them with: +- The shell hook installs backend dependencies but not frontend dependencies. + Install frontend dependencies from `frontend/website/`: ```sh direnv exec "$(git rev-parse --show-toplevel)" npm install @@ -196,59 +197,35 @@ Do not push anything. Make commits as the TDD workflow requires. ## Before completing a change -Run the smallest relevant checks while iterating, then run every repository -gate affected by the change. +A change is not complete until the worktree stack is ready and the unified +gate passes against that worktree: -### Backend +1. Start the stack detached from the worktree root: -- Run tests from `backend/`: + ```sh + direnv exec "$(git rev-parse --show-toplevel)" \ + process-compose up -D + ``` - ```sh - direnv exec "$(git rev-parse --show-toplevel)" php artisan test - ``` +2. Poll `process-compose process list` until every process is running and + ready. +3. Run the complete gate from the worktree root: -- Run the Composer checks defined by `backend/composer.json`: + ```sh + direnv exec "$(git rev-parse --show-toplevel)" just test-all + ``` - ```sh - direnv exec "$(git rev-parse --show-toplevel)" composer types:check - direnv exec "$(git rev-parse --show-toplevel)" composer test - ``` +4. Do not hand-assemble a substitute from focused commands. `test-all` runs + frontend format and lint checks, frontend type checking, Larastan, the + production build, PHPUnit, and Cypress in fail-fast order. +5. Everything must pass before completion. Report exact baseline or + environmental failures rather than hiding them. +6. If the stack was started only for validation, stop it when finished: -- Do not claim a green gate when a command fails. If the failure predates the - change, report the precise baseline failure. + ```sh + direnv exec "$(git rev-parse --show-toplevel)" process-compose down + ``` -### Frontend - -- Run these commands from `frontend/website/`: - - ```sh - direnv exec "$(git rev-parse --show-toplevel)" npm run format - direnv exec "$(git rev-parse --show-toplevel)" npm run lint - direnv exec "$(git rev-parse --show-toplevel)" npm run type-check - direnv exec "$(git rev-parse --show-toplevel)" npm run build - ``` - -- The formatter and linters rewrite files. Review their changes before - committing. -- No frontend test runner is configured yet. Do not claim unit, component, or - end-to-end test coverage until the relevant scripts exist and pass. - -### Environment and integration - -- For Nix or shell-hook changes, run: - - ```sh - direnv exec "$(git rev-parse --show-toplevel)" nix fmt - direnv exec "$(git rev-parse --show-toplevel)" nix flake check - ``` - -- For service configuration changes, run: - - ```sh - direnv exec "$(git rev-parse --show-toplevel)" \ - process-compose --dry-run - ``` - -- When a change affects runtime wiring, start the worktree's stack and verify - the relevant endpoint or service against that worktree. -- If you started the stack only for validation, stop it before finishing. +Focused `just` recipes are for iteration only. For Nix or shell-hook changes, +also run `nix fmt` and `nix flake check`. For service configuration changes, +also run `process-compose --dry-run`.