Files
F-r-ldraledigplannerare/CLAUDE.md
T
2026-06-14 09:58:30 +02:00

12 KiB

AGENTS.md

Operating guide for humans and AI agents working in Föräldraledigplannerare — a web app for planning Swedish parental leave (alone or with a partner), folding in vacation days to optimize leave over time until the child turns 4 (after which only 96 days may be saved).

This is a full-stack monorepo: a TypeScript/React/Vite frontend and a Python/FastAPI backend.


🛑 DIRECTORY NAVIGATION & FILE DISCOVERY

Context: This repo contains massive generated/dependency folders (node_modules, dist, build, .venv/venv, __pycache__, .mypy_cache, .ruff_cache, graphify-out, etc.). Unrestricted file listings will bloat your context window, crash your session, and cause severe token waste.

  • NEVER use ls -R: This command is strictly forbidden in this repository.

  • NEVER use find without -maxdepth: Unbounded find crawls dependency trees just like ls -R. Always add -maxdepth 3 or tighter.

  • Primary Discovery: To map out files or explore the project tree, ALWAYS use git ls-files. This is your default command and ensures you only interact with human-written source code.

  • Structural Context: If you explicitly need a visual directory layout to understand architecture, use tree with the mandatory ignore flags:

    tree -I 'node_modules|.git|dist|build|__pycache__|venv|.venv|env|.pytest_cache|.mypy_cache|.ruff_cache|graphify-out|*.egg-info'
    
  • Searching Code: To search for text or code across the repo, ALWAYS use git grep "search_term" instead of grep -r to avoid crawling ignored or minified assets.

  • NEVER go outside the current directory; always use relative paths based on "here".


graphify

This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.

When the user types /graphify, invoke the skill tool with skill: "graphify" before doing anything else.

Rules:

  • For codebase questions, first run graphify query "<question>" when graphify-out/graph.json exists. Use graphify path "<A>" "<B>" for relationships and graphify explain "<concept>" for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
  • Dirty graphify-out/ files are expected after hooks or incremental updates; dirty graph files are not a reason to skip graphify. Only skip graphify if the task is about stale or incorrect graph output, or the user explicitly says not to use it.
  • If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
  • Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
  • After modifying code, run graphify update . to keep the graph current (AST-only, no API cost).

QUICK START

The frontend package.json orchestrates both services. From frontend/:

npm install              # install frontend deps
npm run dev              # runs BOTH: FastAPI (:8000) + Vite dev server concurrently

Backend can also be run on its own. From backend/:

uv sync                                              # install deps incl. dev group
uv run uvicorn app.main:app --reload --port 8000     # serve API

The scraper (FK login flow via Playwright) is off by default. To exercise it locally, use npm run login from frontend/, which sets APP_SCRAPER_ENABLED=true APP_SCRAPER_HEADLESS=true. Chromium must be installed separately: uv run playwright install chromium.


REPOSITORY LAYOUT

backend/                 Python 3.14 · FastAPI · uv (Hatchling build backend)
  app/
    entities/            Persistence-layer row shapes (DB-facing)
    models/              Pydantic v2 request/response schemas (API-facing)
    repositories/        Abstract repository interfaces
    repositories_impl/
      sqlite/            Default aiosqlite implementations
      postgres/          Optional asyncpg implementations (extra: `postgres`)
    routers/             FastAPI route handlers (thin)
    services/            Business logic (planning, tips, analytics, auth, …)
    scrapers/            Försäkringskassan (FK) scraper — Playwright
    utils/               dates, holidays, errors, tip predicates
    db/                  Connection management
  migrations/            Alembic migrations (NOT auto-generated from ORM models)
  tests/                 pytest + pytest-asyncio (asyncio_mode = auto)

frontend/                TypeScript · React 18.3 · Vite 5 · Vitest
  src/
    pages/               Route-level screens (Dashboard, Calendar, Settings, …)
    components/          Presentational + composite components (BEM CSS classes)
    hooks/               Data-fetching/state hooks (the data layer — no TanStack Query)
    services/            api.ts (HTTP client) + errors.ts
    context/             Auth + Workspace React context
    types/               Shared TS types (api.ts, entities.ts)
    utils/               dates, formatting
  tests/                 Vitest setup + fixtures; co-located *.test.tsx also used

.github/workflows/       CI (GitHub Actions) — backend + frontend gates

Layering rule (backend): routers call services; services call repositories (the abstract interface). Concrete DB code lives only in repositories_impl/. Keep routers thin and put business logic in services. Don't reach across layers (e.g. a router talking directly to repositories_impl).

Data layer (frontend): components consume src/hooks/*, which call src/services/api.ts. Components should not call fetch/the API client directly.


CONVENTIONS

  • Backend stack: FastAPI + Pydantic v2 (request/response models) with a hand-rolled repository pattern — abstract interfaces in app/repositories/, concrete implementations in app/repositories_impl/{sqlite,postgres}/. There is no ORM (no SQLModel, no SQLAlchemy models). SQLite via async aiosqlite is the default; Postgres via asyncpg is an optional extra.
  • Migrations: Alembic (backend/migrations/, config backend/alembic.ini). Migrations are written by hand against the schema — they are not generated from ORM metadata. Add a new revision when you change the schema.
  • Auth: password hashing with argon2-cffi, sessions/tokens with PyJWT. Rate limiting via slowapi.
  • Python: >=3.14 required. Managed via uv with the Hatchling build backend. Lint/format with ruff (line length 100, target py314). Tests with pytest + pytest-asyncio (asyncio_mode = "auto", so no @pytest.mark.asyncio needed).
  • Frontend stack: React 18.3, react-router-dom v6 (BrowserRouter/Routes/Route), TypeScript 5.6, Vite 5, Vitest + Testing Library. State/data fetching is done with custom hooks in src/hooks/ (no TanStack Query, no Redux).
  • Styling: plain CSS using BEM-style class names (e.g. calendar-day__number, calendar-day--disabled). There is no CSS framework (no Tailwind) and no CSS-in-JS. Match the conventions in existing components before inventing new ones.
  • Linting: ESLint is configured (flat config, frontend/eslint.config.js, ESLint 9). @typescript-eslint/no-explicit-any is an error. CI runs npm run lint with --max-warnings 0 — zero warnings allowed.
  • Scripts: any backend helper scripts must be placed explicitly in backend/scripts/ (create the folder if needed). Frontend scripts go through package.json.
  • CI/CD: Deployment/CI is managed via GitHub Actions (.github/workflows/). Do not introduce .gitea/, .gitlab-ci.yml, or Makefiles — extend the existing GitHub workflow instead.

TESTS, LINT & CI GATES

Run the same checks CI runs before pushing.

Backend (from backend/):

uv run ruff check .              # lint
uv run ruff format --check .     # format check
uv run pytest                    # tests with coverage (config in pyproject.toml)

CI gate: --cov-fail-under=50 today, climbing toward 90 as tests land. Don't lower it.

Frontend (from frontend/):

npx tsc --noEmit                 # type check
npm run lint                     # ESLint, zero warnings
npm run coverage                 # Vitest with coverage

CI gate: 90% coverage on the frontend.


ANTI-PATTERNS (CRITICAL GUARDRAILS)

  • Type Safety (TS): No type suppression — no as any, @ts-ignore, or @ts-expect-error. no-explicit-any is an ESLint error and CI fails on warnings.
  • Type Safety (Python): No bare # type: ignore to silence the type checker; fix the underlying type or model instead. Prefer precise Pydantic models over dict[str, Any] at API boundaries.
  • Error Handling: No empty catch/except blocks. Explicit handling/logging is required. Backend errors should flow through app/utils/errors.py; frontend errors through src/services/errors.ts.
  • Testing Integrity: NEVER delete or comment out failing tests to make a suite "pass". Fix the code, or update the test properly with a clear reason.
  • No JS Slop (frontend): NEVER create .js/.jsx files in the frontend. Use only TypeScript (.ts, .tsx).
  • Respect the layering: routers don't talk to repositories_impl directly; components don't call fetch directly. Go through services / hooks.
  • No ORM creep: don't add SQLModel/SQLAlchemy models — extend the existing repository pattern.
  • Assumption Error: NEVER assume the contents of ANY file. ALWAYS read the target file to confirm its structure and implementation before modifying or referencing it.

MANDATORY EXECUTION PROTOCOLS

  • Read Before Edit: ALWAYS read the entire target file before performing any edit.
  • Post-Edit Verification: Immediately after an edit, verify that no lines were duplicated and imports are intact.
  • Stability Check: Following any code change, run the relevant side's checks above (lint + type check + tests). For cross-cutting changes (e.g. API contract edits in app/models and src/types), run both backend and frontend suites — they must agree on the wire format.
  • Keep the graph current: after modifying code, run graphify update ..

DOMAIN RULES (Swedish parental leave)

Useful context so calculations and UI copy stay correct:

  • 390 + 90 model: of the parental-benefit days, 390 are sickness-benefit-level and 90 are at the minimum (lägstanivå) rate. Track day types, not just a single total.
  • Reserved days: a portion of days is reserved per parent and cannot be transferred to the partner — surface this in shared/partner planning.
  • Dubbeldagar (double days): both parents can take leave on the same day for a limited number of days early in the child's life; model these as consuming each parent's balance.
  • No partial days: the planner works in whole days — do not introduce fractional/percentage days in the model.
  • The age-4 cutoff: the core purpose is optimizing leave over time. After the child turns 4, only 96 days may be saved; the planner must show how many days remain at that boundary.
  • Money/SGI is abstracted: the app treats benefit amounts/SGI abstractly rather than computing exact kronor payouts — keep money handling at the agreed abstraction level rather than hardcoding rates.

When in doubt about a rule, ask before encoding a number — Swedish parental-leave rules change and incorrect figures are worse than a TODO.


WHERE TO LOOK FIRST

  • ARCHITECTURE.md — system design and rationale.
  • graphify-out/wiki/index.md — generated navigation over the codebase.
  • backend/README.md — backend-specific setup notes.
  • The various *_REPORT.md / *_TODO.md / *_ONBOARDING.md files at the root are working handoff notes between contributors; treat them as historical context, not authoritative spec.

OUTPUTS

Be concise: skip preamble, caveats, and restating the request. Use sentence fragments and short bullet points where clarity isn't lost. Explain reasoning only when it aids debugging or isn't obvious from the code/diff itself.