Works through every numbered item in NextSteps260913.md section 3, plus the three known verification issues in section 4.1. Surface what the backend already computed: - 3.1 render surplus_advice in PlanResults, mirroring the shortfall remedies - 3.2 surface VacationBalance.is_assumed so assumed statutory years are visible - 3.3 age-4 expiry given the proposed plan, via a new Age4Projection reusing the existing _age4_projections arithmetic (Dashboard stat verified, not rebuilt) Calendar cluster (one pass over the holiday data): - new GET /api/calendar/days exposing roda dagar, klamdagar and day overrides, paginated four years at a time - 3.4 shade roda dagar/klamdagar, with R/K badges so colour is not the only signal - 3.6 click-to-override behind an explicit mode toggle, leaving drag multi-select intact - 3.7 click month/year to advance - 3.8 per-year red-days list in Settings, statutory days plus overrides i18n: - 3.5 five backend-generated Swedish surfaces converted to message_key + data, following the tips_service pattern; old string fields replaced, not kept Flexible wishes (3.9): - PLANNING_ENGINE_SPEC section 10: window_mode, candidate search, cost function, deterministic tie-break, two-pass ordering, test matrix rows 15-24 - backend: entity, migration 006, _FULL_SCHEMA sync, repository, allocator - frontend: season-preset toggle in WishEditor, unplaced outcome rendering Fixes (4.1): - pin typecheck to the local tsc rather than npx resolving a newer TypeScript - ignore generated coverage/ and dist/ in the ESLint flat config - fix the flaky usePlanApply test properly, with waitFor rather than a retry Tests: backend 361 -> 404, frontend 540 -> 704. Coverage 89.47% -> 96.52% lines. Not yet verified in a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
14 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
findwithout-maxdepth: Unboundedfindcrawls dependency trees just likels -R. Always add-maxdepth 3or 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
treewith 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 ofgrep -rto 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>"whengraphify-out/graph.jsonexists. Usegraphify path "<A>" "<B>"for relationships andgraphify explain "<concept>"for focused concepts. These return a scoped subgraph, usually much smaller thanGRAPH_REPORT.mdor 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.mdexists, use it for broad navigation instead of raw source browsing. - Read
graphify-out/GRAPH_REPORT.mdonly 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 inapp/repositories_impl/{sqlite,postgres}/. There is no ORM (no SQLModel, no SQLAlchemy models). SQLite via asyncaiosqliteis the default; Postgres viaasyncpgis an optional extra. - Migrations: Alembic (
backend/migrations/, configbackend/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 withPyJWT. Rate limiting viaslowapi. - Python:
>=3.14required. Managed viauvwith the Hatchling build backend. Lint/format withruff(line length 100, targetpy314). Tests withpytest+pytest-asyncio(asyncio_mode = "auto", so no@pytest.mark.asyncioneeded). - 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 insrc/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-anyis an error. CI runsnpm run lintwith--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 throughpackage.json. - CI/CD: Deployment/CI is managed via GitHub Actions (
.github/workflows/). Do not introduce.gitea/,.gitlab-ci.yml, orMakefiles — 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 (current total ~52%), climbing toward 90 as tests land. Don't lower it — only raise when coverage grows.
Frontend (from frontend/):
npx tsc --noEmit # type check
npm run lint # ESLint, zero warnings
npm run coverage # Vitest with coverage
CI gates (measured baseline after src/pages/** inclusion):
- lines: 80% | functions: 85% | branches: 85% | statements: 80%
- These act as a regression ratchet, not a target. Current state (82.36% lines, 88.5% functions) shows pages are now visible and poorly covered (Auth, Tips, DevFetch at 0%); the intent is to raise thresholds over time, not to churn tests today.
WHO RUNS THE VERIFICATION COMMANDS (cost rule)
Running the checks above is mechanical work: the commands are fixed and the only judgement
needed is reading pass/fail and the failure output. Paying a large model to sit through
pytest, vitest, tsc, ruff or a coverage run is waste.
Rule, by who you are:
- Haiku subagent — run the commands yourself, immediately. No delegation. You are already the cheap tier; spawning another agent just adds latency.
- Sonnet (or any larger model) subagent — do not run the verification commands yourself. Finish the code change, then spawn a Haiku subagent to run them and report back the raw output. Act on what it reports.
- The orchestrating/reviewing agent — same rule: delegate verification runs to Haiku rather than burning a large model on them.
This applies to the full-suite verification runs: uv run pytest, uv run ruff check .,
uv run ruff format --check ., npx tsc --noEmit, npm run lint, npm run coverage,
npx vitest run, and uv run alembic upgrade head.
It does not apply to quick diagnostics while you are mid-task — reading a single file, a
git grep, or running one focused test to debug what you are actively writing. Delegating those
costs more than it saves. The rule targets the long, whole-suite verification passes.
What the Haiku verifier must be told: the exact commands, the directory to run them from, the expected baseline (e.g. "currently 361 passed / 0 failed — must not regress"), and an instruction to paste the raw output verbatim, including failures, rather than summarising. A verifier that reports "all green" without the output is useless — several agents have reported passing gates that were in fact failing.
ANTI-PATTERNS (CRITICAL GUARDRAILS)
- Type Safety (TS): No type suppression — no
as any,@ts-ignore, or@ts-expect-error.no-explicit-anyis an ESLint error and CI fails on warnings. - Type Safety (Python): No bare
# type: ignoreto silence the type checker; fix the underlying type or model instead. Prefer precise Pydantic models overdict[str, Any]at API boundaries. - Error Handling: No empty
catch/exceptblocks. Explicit handling/logging is required. Backend errors should flow throughapp/utils/errors.py; frontend errors throughsrc/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/.jsxfiles in the frontend. Use only TypeScript (.ts,.tsx). - Respect the layering: routers don't talk to
repositories_impldirectly; components don't callfetchdirectly. 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/modelsandsrc/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.mdfiles 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.