beetask/CLAUDE.md
Gabriel Santana de Menezes 39ed29c0c4 docs: add PR size limits to CLAUDE.md
Enforce maximum 5 files and 30 lines changed per PR to keep reviews
focused and maintainable. Split by layer (frontend, backend, config)
when exceeding limits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 19:10:35 -04:00

144 lines
7.6 KiB
Markdown

# CLAUDE.md
Guidance for Claude Code when working in this repository. Keep edits aligned with the conventions below.
## Project
**bee-task-v3 / TimeTrack** — task & time-tracking SaaS. Monorepo with React (Vite) frontend and Quarkus backend, PostgreSQL + Keycloak via Docker. Backlog and user stories live in `backlog.md`.
## Layout
```
backend/ Quarkus 3.34, Java 17, Hibernate ORM + Panache, Flyway, JWT (smallrye)
frontend/ Vite + React 19 + TypeScript + Tailwind v4
docker/ Postgres init scripts
docker-compose.yml Postgres + Keycloak
specification/ Domain specs
backlog.md User stories (US-001…)
```
Java root package: `net.beetechgroup.beetask`.
## Commands
Infra: `docker-compose up` (Postgres :5432, Keycloak :8081).
Backend (`cd backend`):
- Dev: `./mvnw quarkus:dev` (port 8080, uses H2 in dev profile, swagger at `/swagger`, OpenAPI at `/openapi`)
- Test: `./mvnw test`
- Build: `./mvnw package`
Frontend (`cd frontend`):
- Dev: `npm run dev` (port 5173, calls backend directly at `http://localhost:8080` via `VITE_API_URL`)
- Build: `npm run build` (runs `tsc -b` then `vite build`)
- Lint: `npm run lint`
## Backend Architecture (Clean Architecture, adapted)
Four layers, dependencies point inward. **Never** import outward.
| Package | Role | May depend on |
|---|---|---|
| `entities/` | Pure domain POJOs and enums (`Task`, `Project`, `User`, `Organization`, `TaskStatus`, …). Plain Java, no annotations. | nothing |
| `usecase/<feature>/<action>/` | Business logic. Plain classes with constructor DI. Records for `XxxInput`/`XxxOutput`. Static `XxxMapper` for domain→Output. | `entities`, `usecase.repository` |
| `usecase/repository/` | Repository **ports** (interfaces) returning domain types. | `entities` |
| `interfaceadapters/controllers/<feature>/` | JAX-RS controllers, `XxxRequest`/`XxxResponse` records, `XxxControllerMapper`. Translates HTTP ↔ use case I/O. | `usecase`, `entities` |
| `frameworks/persistence/` | Panache `XxxEntity`, `XxxRepositoryImpl` (implements port + `PanacheRepository<XxxEntity>`), `XxxEntityMapper` (entity ↔ domain). | everything |
| `frameworks/config/` | `@ApplicationScoped` classes with `@Produces` methods that build use cases from injected repository ports. | everything |
### Use case anatomy
A new use case `usecase/<feature>/<action>/` contains exactly:
- `XxxInput.java` — `record` with primitives/IDs/email (no domain entities in the signature unless intentional)
- `XxxOutput.java` — `record` with the response shape
- `XxxUseCase.java` — plain class, constructor-injected repository ports, single `execute(XxxInput)` method
- `XxxMapper.java` — static `toXxxOutput(domain)` only
Then **wire it** via `@Produces` in the matching `frameworks/config/<Feature>UseCaseConfig.java`. Use cases are NOT annotated with CDI scopes themselves — the producer is what makes them injectable.
### Query parameter DTOs
When a controller endpoint receives **3 or more query/request parameters**, create a dedicated DTO class in the controller package and a mapping method in the `XxxControllerMapper`:
- For **query parameters** (GET): use a plain Java class with public fields annotated with `@QueryParam`, and receive it via `@BeanParam XxxRequest` in the controller method.
- For **request body** parameters (POST/PUT): use a `record XxxRequest(...)` as usual.
- Add `toXxxInput(XxxRequest request, ...)` to `XxxControllerMapper` — never inline the mapping in the controller.
### Controller anatomy
`interfaceadapters/controllers/<feature>/`:
- `XxxController.java` — `@Path`, JAX-RS verbs, `@Inject` use cases + `SecurityIdentity`, OpenAPI annotations (`@Tag`, `@Operation`, `@APIResponse`)
- `XxxRequest.java` / `XxxResponse.java` — records mirroring the use case Input/Output (don't reuse use case records in HTTP layer)
- `XxxControllerMapper.java` — static `toXxxInput(request, email)` / `toXxxResponse(output)`
The authenticated email comes from `securityIdentity.getPrincipal().getName()` and is passed into the Input.
### Repository pattern
- Port: `usecase/repository/XxxRepository.java`, methods return **domain** types.
- Impl: `frameworks/persistence/repository/XxxRepositoryImpl.java`:
- `@ApplicationScoped`
- `implements XxxRepository, PanacheRepository<XxxEntity>`
- `@Transactional` on writes
- Uses `XxxEntityMapper.toEntity` / `toDomain` at the boundary
### DB / migrations
- Postgres in prod, **H2 in dev** (`%dev` profile, file at `backend/data/beetask`).
- `quarkus.hibernate-orm.database.generation=validate` (prod) / `update` (dev).
- Flyway migrations live at `backend/src/main/resources/db/migration/V{N}__{snake_name}.sql`.
- `quarkus.flyway.migrate-at-start=false` — migrations are not auto-applied; run them deliberately.
### Auth
- JWT signed with `privateKey.pem`, verified with `publicKey.pem`. Issuer `https://beetech.net`.
- Keycloak is provisioned in docker-compose but **not yet integrated** into the app — JWTs are minted by `usecase/auth/login`.
## Frontend Conventions
- **React 19**, **Tailwind v4** (`@tailwindcss/vite`, no Tailwind config file required), **react-router-dom v7**.
- Folder layout: `pages/`, `components/`, `services/`, `contexts/`, `types/`, `lib/`.
- HTTP through `apiFetch` in `src/lib/api.ts` — it auto-injects the JWT from `localStorage.user.jwt` and prefixes `VITE_API_URL` (defaults to `http://localhost:8080` em dev; configurar para a URL real do backend em prod).
- Each backend feature gets a service in `services/<feature>Service.ts` exporting an object of methods.
- Class merging uses `cn(...inputs)` from `src/lib/utils.ts` (clsx + tailwind-merge).
- Icons: `lucide-react`. Animations: `framer-motion`. DnD: `@hello-pangea/dnd`.
- Auth state: `AuthContext` (`src/contexts/AuthContext.tsx`) — `user`, `activeOrg`, `login`, `logout`, `refreshUser`. Persisted in `localStorage` (`user`, `activeOrgId`).
- Authenticated routes nest under `<AppShell />` in `src/router.tsx`. Role-restricted routes wrap with `<RoleGate allowedRoles={["OWNER", "ADMIN"]}>`.
- UI strings are in pt-BR. Keep that consistent.
## Conventions & Constraints
- Don't put business logic in controllers — they only translate HTTP ↔ use case I/O.
- **Never return a use case Output record directly from a controller.** Controllers must define their own `XxxResponse` records and use `XxxControllerMapper.toResponse(output)` to convert. Use case I/O types must not leak into the HTTP layer.
- Don't import `jakarta.*`, `io.quarkus.*`, or Panache from `entities/` or `usecase/` (except `usecase.repository` ports which are pure interfaces).
- Don't reach for a UI library on the frontend — Tailwind only.
- Don't create files outside the conventional folders.
- Vertical slices first; refactor when a pattern repeats. The backlog is in `backlog.md`.
## Git Workflow
**Never commit directly to `main`.** Always:
1. Create a feature branch: `git checkout -b feature/short-description`
2. Make commits to the branch
3. Create a pull request (GitHub) for review
4. Merge via PR after approval
This ensures code review, clear history, and traceability.
### PR Size Limits
To keep PRs focused and reviewable, enforce these limits **per PR**:
- **Maximum 5 files changed**
- **Maximum 30 lines changed** (added, modified, or deleted combined)
**If exceeding these limits:** Split into multiple PRs. **Prefer splitting by layer:**
- One PR for backend changes (`backend/`)
- One PR for frontend changes (`frontend/`)
- One PR for config/infra (`docker-compose.yml`, `.env`, etc.)
This keeps code review focused, reduces merge conflicts, and makes history easier to bisect.
## Skills
Skills do projeto em `.claude/skills/`:
- `new-task` — workflow completo: ler issue → branch → código → commit → PR.