bilhej/AGENTS.md
Joakim Mörling 9931061cb6 feat: scaffold Vue 3 + Vite frontend with TypeScript, Router, Pinia, Vitest, ESLint, Prettier
- Scaffold via npm create vite@latest --template vue-ts (create-vue interactive
  prompts require manual selection; create-vite supports non-interactive flags)
- Dependencies: vue-router (SPA routing, createWebHistory for clean URLs),
  pinia (centralised state management), vitest + @vue/test-utils + jsdom
  (unit testing with browser DOM simulation)
- Dev tooling: eslint (v10 flat config) + eslint-plugin-vue + @vue/eslint-config-typescript
  + @vue/eslint-config-prettier (ESLint-Prettier integration via vueTsConfigs),
  prettier (semi: false, singleQuote, trailingComma: all), jiti (bridges ESLint
  with TypeScript config files)
- vite.config.ts: dev server on port 3000, @ alias resolving to src/, vitest
  with jsdom environment
- eslint.config.ts: defineConfigWithVueTs wraps tseslint.config with Vue SFC
  support (vue-eslint-parser, <script setup lang="ts">), vue/multi-word off
- tsconfig.app.json: path alias @/* -> src/* for TypeScript module resolution
- src/router/index.ts: single route mapping / to HomePage
- src/pages/HomePage.vue: minimal <script setup lang="ts"> placeholder
- src/main.ts: bootstraps app with Pinia plugin + Vue Router
- src/App.vue: delegates rendering to <RouterView />
- src/__tests__/HomePage.spec.ts: smoke test verifying component mounts
- Directory structure: src/stores/, src/api/, src/composables/ with .gitkeep
  placeholders matching AGENTS.md convention (PascalCase pages, camelCase stores/composables)
- index.html: lang="sv", title BilHälsning (Swedish UI convention)
- Cleaned up: HelloWorld.vue, style.css, template boilerplate SVGs/PNGs
- Update AGENTS.md + CODING_GUIDELINES.md: .js extensions → .ts across all
  file naming examples (useXxx.ts, authStore.ts, orders.ts, client.ts)
- Verification: npm run dev serves blank page on http://localhost:3000,
  npm run lint passes (0 errors, 0 warnings), npm test passes (1 test, 1 file)
2026-05-01 00:52:38 +02:00

201 lines
6.7 KiB
Markdown

# AGENTS.md — BilHej / Bilhälsning.se
Project-specific instructions for OpenCode. Commit this file.
---
## Project Identity
BilHej is a web platform letting Swedish residents send physical letters to
vehicle owners by entering a registration number. The sender composes a letter,
pays 49 SEK, and BilHej prints+mails it via PostNord. The sender never sees
the recipient's name or address.
**Phase 0 (current):** Manual workflow. No Transportstyrelsen or PostNord API
integration yet. Owner address is obtained manually by a human and entered into
the admin panel.
Tech stack: Vue.js 3 (Vite, Pinia) frontend + Java 21 Spring Boot 3 backend +
PostgreSQL 16. Deployed via Docker Compose.
---
## Build, Lint, Test & Run Commands
Always run these after making changes to verify nothing is broken.
### Quick start (everything)
```bash
cp .env.example .env # first time only, then fill in keys
docker compose up -d # starts postgres, backend, frontend
```
### Frontend (Vue.js 3 + Vite)
```bash
cd frontend
npm install # first time only
npm run dev # dev server on :3000 with HMR
npm run build # production build
npm run lint # ESLint
npm run test # vitest
```
### Backend (Spring Boot 4 + Java 21)
```bash
cd backend
./gradlew bootRun # dev server on :8080
./gradlew test # JUnit 5 + Mockito
./gradlew check # full verification including integration tests
```
### Stripe webhooks (local testing)
```bash
stripe listen --forward-to localhost:8080/api/webhooks/stripe
```
### Database
Flyway migrations run automatically on Spring Boot startup. Migration files
live in `backend/src/main/resources/db/migration/`. Naming: `V<number>__descriptive_name.sql`.
To reset: `docker compose down -v && docker compose up -d`.
---
## Project Structure
```
bilhej/
├── frontend/ # Vue.js 3 SPA (Vite)
│ ├── src/
│ │ ├── pages/ # Route-level page components
│ │ ├── components/ # Reusable UI components
│ │ ├── composables/ # useXxx.ts shared logic
│ │ ├── stores/ # Pinia stores
│ │ ├── api/ # API client modules
│ │ ├── router/ # Vue Router config
│ │ └── assets/ # Static files, CSS
│ └── ...
├── backend/ # Spring Boot 3 (Java 21)
│ ├── src/main/java/se/bilhalsning/
│ │ ├── config/ # @Configuration classes
│ │ ├── controller/ # REST controllers
│ │ ├── dto/ # Request/response DTOs
│ │ ├── entity/ # JPA entities
│ │ ├── repository/ # Spring Data JPA repos
│ │ ├── service/ # Business logic
│ │ ├── security/ # JWT filter, UserDetailsService
│ │ ├── exception/ # Custom exceptions + @ControllerAdvice
│ │ └── mapper/ # Entity ↔ DTO mapping
│ └── src/main/resources/
│ ├── application.yml
│ └── db/migration/ # Flyway migrations
├── docker/ # Dockerfiles
├── docker-compose.yml
├── docker-compose.prod.yml
├── .env.example
├── AGENTS.md # This file
├── README.md
├── REQUIREMENTS.md
└── CODING_GUIDELINES.md
```
---
## Conventions (Summary)
Full details in `@CODING_GUIDELINES.md`. Key rules:
### Both sides
- Code and comments in English. User-facing strings in Swedish.
- No commented-out code. Delete it.
- Functions stay small (<30 lines).
### Git
- Create `feature/*`, `fix/*`, or `chore/*` branches from `develop`.
- Never commit directly to `master` or `develop`.
- Merge strategy: fast-forward or merge either is fine.
### Frontend (Vue.js 3)
- `<script setup>` with Composition API only. Never Options API.
- File naming: PascalCase for pages/components, camelCase (`useXxx`) for composables.
- API calls live in `api/` modules, never in components.
- Component styles are scoped.
### Backend (Spring Boot 3)
- Constructor injection with `@RequiredArgsConstructor`. No `@Autowired`.
- DTOs: prefer Java records. No bare entities in responses.
- Controllers stay thin. All logic in services.
- Use `@ControllerAdvice` for consistent error responses (`{ "message": "..." }`).
- No Lombok beyond `@RequiredArgsConstructor`.
### Database
- Table names: snake_case, plural. PKs: UUID, generated in code.
- Timestamps: `created_at`, `updated_at`. Use `Instant` in Java.
- Enums: stored as VARCHAR.
- Index every FK and every column used in WHERE.
---
## Critical Gotchas
### Phase 0: Address lookup is MANUAL
There is no Transportstyrelsen API integration yet. When an order is paid, a
human must manually request the owner address via the "Fråga om fordonsägare"
form and update the admin panel. Do NOT write code that calls an API that
doesn't exist yet. The backend should store the order and wait for an admin
to mark it as processed.
### Never store recipient addresses
After the address is used to mail the letter, it must be deleted. The Order
entity must NOT have an address field. The address lookup and mailing are
external/human processes in Phase 0.
### Stripe webhook signature verification
Always verify `stripe-signature` header using `STRIPE_WEBHOOK_SECRET`.
Webhook endpoint is public (no auth). Without signature verification an
attacker could mark orders as paid.
### Swedish UI strings
All text visible to end users must be in Swedish. Button labels, error
messages, validation text, email content, template bodies. Only developer
facing content is in English.
### JWT in Authorization header
Backend expects `Authorization: Bearer <token>`. Frontend interceptor must
attach this to all API calls. Unauthorized APIs (register, login, webhook,
public vehicle info) must be excluded from the Spring Security filter chain.
---
## Testing Approach
### Backend
- JUnit 5 + Mockito for service layer tests.
- `@WebMvcTest` for controller tests.
- Test naming: `shouldXxxWhenYyy`.
- Use test profile with H2 or Testcontainers for DB-dependent tests.
- Flyway migrations must run in test profile too.
### Frontend
- Vitest for composables and utility functions.
- Component tests with Vue Test Utils where needed.
- E2E tests deferred to Phase 1.
### CI (future)
- `./gradlew check` and `npm run test && npm run lint` must pass before merge.
---
## External References
For detailed conventions, load `@CODING_GUIDELINES.md`.
For product requirements and business logic, load `@REQUIREMENTS.md`.
For setup and quick start, load `@README.md`.
These are lazy-loaded by OpenCode only read them when the task at hand
needs the detail.