# AGENTS.md ## Project overview - Full-stack todo app: **Vue 3 + Element Plus** (WebUI/) + **FastAPI + SQLAlchemy** (api/) - **Python 3.10+, Node 18+**; SQLite database - Single `master` branch, no CI/CD ## Quick commands ```powershell # Install Python deps (required before first run) pip install -r requirements.txt # Full-stack one-shot (builds frontend, then starts backend on :23994) python main.py # Frontend dev only (hot-reload on :5173, proxies /api → :23994) cd WebUI; npm run dev # Backend only cd api; uvicorn app.main:app --host 0.0.0.0 --port 23994 # Type-check frontend (noEmit; uses project references tsconfig) cd WebUI; npm run typecheck # Run the one hand-written test (backend must be running on :23994) python tests/test_accounts.py ``` **Build order matters:** `python main.py` automatically compiles WebUI (`npm install` + `npm run build`), copies `WebUI/dist/` → `api/webui/`, then starts uvicorn. It checks timestamps to skip rebuilds when frontend is unchanged. **Import path:** `main.py` injects `api/` into `sys.path` (no `os.chdir`). Backend imports resolve relative to `api/` — e.g. `from app.config import ...`, not `from api.app.config import ...`. **Port conflict:** `main.py` will auto-kill any process already listening on port 23994 before starting. If you have another instance running, it will be terminated without warning. ## Architecture ### Backend (`api/app/`) - **Config is hardcoded** in `api/app/config.py` — no `.env`, no `os.getenv()`. Port `23994`, CORS origins `["http://localhost:5173", "http://localhost:23994"]`. Note: `pydantic-settings` is in `requirements.txt` but unused. - **SQLite path** is computed relative to `api/` via `__file__` — safe regardless of cwd. `connect_args={"check_same_thread": False}` for FastAPI async compatibility. - **`database.py:init_db()`** auto-creates tables on startup (`create_all`) and auto-adds missing columns via `ALTER TABLE ADD COLUMN` (no Alembic). Columns that are non-nullable with no default are skipped. - **UserSettings is a singleton**: always id=1, auto-created on first `GET`. - **Account balance changes** auto-create `AccountHistory` records in `update_balance()`. - **Habit checkins for the same day** accumulate count (not new rows), enforced by a `(habit_id, checkin_date)` unique constraint. - **Anniversaries / DebtInstallments** have computed fields (`next_date`, `days_until`, `year_count` / `remaining_periods`) calculated at request time, not stored in DB. - **`task_tags` M2M table** is defined in `models/tag.py` (not `models/task.py`). - **Update schemas** use `clearable_fields` + `exclude_unset=True` to distinguish "field not sent" from "field sent as null". - **JWT authentication** — mandatory for all `/api/*` routes except `/api/auth/*` and `/health`. Default password: `elysia`. Login via `POST /api/auth/login`. Token key in localStorage: `elysia_auth_token`. Frontend axios interceptor auto-attaches `Authorization: Bearer `. ### Frontend (`WebUI/`) - Vue Router uses `createWebHistory()` (HTML5 history mode) — **requires the backend SPA fallback** (`/{full_path:path}` → `index.html`). - Vite dev proxy forwards `/api` → `http://localhost:23994`. - `@` alias maps to `src/`. - Global styles in SCSS (`src/styles/`). - 8 Pinia stores; Element Plus icons registered globally in `main.ts`. - Element Plus uses Chinese locale (`zh-cn`). ### Route registration order matters The `/health` endpoint must be registered **before** the `/{full_path:path}` SPA fallback catch-all, otherwise `/health` requests return `index.html`. This is enforced in `api/app/main.py:114` — do not reorder these registrations. ### Docker - `Dockerfile` copies pre-built `api/webui/` — you must build frontend before `docker build`. - Docker CMD is an inline `python -c` one-liner that injects `api/` into `sys.path` and starts uvicorn. No separate entrypoint script. - `docker-compose.yml` mounts `api/data/` and `api/logs/` for persistence; `api/webui/` is read-only. ## Testing quirks - Only one test file: `tests/test_accounts.py` — **hand-written, no framework** (not pytest). It counts pass/fail manually and uses `requests` directly against `localhost:23994`. - Backend must be running before executing tests. - **The test permanently mutates the database** — Section 15 deliberately leaves test data in place for UI display. Run it on a disposable database copy or reset manually if you need a clean state. - **The test sends no auth headers** and will fail with 401 if JWT auth is enforced. You must first `POST /api/auth/login` with `{"password": "elysia"}` to get a token, then include `Authorization: Bearer ` in requests, or temporarily comment out the auth middleware for testing. - No test coverage for tasks, habits, anniversaries, or tags. ## What's missing (agents should not assume) - No linter, formatter, pre-commit hooks, or CI/CD - No `.env` or environment variable loading - No database migrations framework (Alembic) ## Additional notes - Swagger UI at `/docs` when backend is running — the live, auto-generated API reference. - `python-multipart` in `requirements.txt` is required for FastAPI to parse form data (not optional). - `sass` is a runtime `dependency` (not `devDependency`) in `package.json` — unusual, but intentional.