Files
ToDoList/AGENTS.md

6.6 KiB

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 (4 commits total), no CI/CD

Quick commands

# 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

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. set_default_password() auto-initializes password to "elysia" on first access if password_hash is empty.
  • Habit checkins for the same day accumulate count (not new rows), enforced by a (habit_id, checkin_date) unique constraint. Cancelling reduces count; deleting when count ≤ 0 removes the row.
  • Anniversaries have computed fields (next_date, days_until, year_count) calculated at request time, not stored in DB. The calculation functions live in the router layer (not models), because they depend on date.today().
  • task_tags M2M table is defined in models/tag.py (not models/task.py). Tags only support create/delete (no update).
  • Update schemas use clearable_fields + exclude_unset=True to distinguish "field not sent" from "field sent as null". For non-clearable fields, None means "don't change"; for clearable fields, None means "clear it". See schemas/task.py:TaskUpdate, schemas/habit.py:HabitUpdate, schemas/anniversary.py:AnniversaryUpdate.
  • JWT authenticationutils/auth.py handles JWT (HS256, key: "elysia-todo-secret-key-change-in-production", 24h expiry). Middleware in main.py validates tokens 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. Middleware only validates the token; it does NOT inject user info into request.state. Routes needing user data must call get_current_user(request) manually.
  • Different cascade delete strategies:
    • Category: refuses deletion if tasks are linked (400)
    • HabitGroup: sets linked habits' group_id to NULL
    • AnniversaryCategory: sets linked anniversaries' category_id to NULL

Router registration quirks

  • /health MUST be registered before /{full_path:path} in main.py:114 — otherwise SPA fallback intercepts health checks and returns index.html.
  • habits router (routers/habits.py) is an empty-shell router that combines 3 sub-routers via include_router: habit-groups (/api/habit-groups), habits (/api/habits), and checkins (/api/habits/{habit_id}/checkins).
  • anniversaries router uses prefix="/api" (not /api/anniversaries). Its internal paths are /anniversaries, /anniversary-categories, etc.

Frontend (WebUI/)

  • Vue Router uses createWebHistory() (HTML5 history mode) — requires the backend SPA fallback (/{full_path:path}index.html).
  • Vite dev proxy forwards /apihttp://localhost:23994.
  • @ alias maps to src/.
  • 8 Pinia stores: auth, task, category, tag, habit, anniversary, userSettings, ui. The ui store manages dialog visibility, editing state, sidebar collapse, and global loading (no API calls).
  • Element Plus icons registered globally in main.ts — use <Edit />, <Delete /> etc. in templates without imports.
  • Element Plus uses Chinese locale (zh-cn).
  • Vite 7.x + TypeScript 5.9 with erasableSyntaxOnly: true and project references.
  • Frontend task filtering is entirely client-side. fetchTasks() loads all tasks once; filtering, sorting, and pinyin search run in computed getters. No server-side filtering for tasks.
  • Pinyin search via pinyin-pro (utils/pinyin.ts) — supports Chinese character search by pinyin initials or full pinyin.
  • Token key elysia_auth_token is hardcoded in 3 separate files (router/index.ts, api/request.ts, stores/useAuthStore.ts). If you rename it, update all 3.
  • 401 response triggers a hard page redirect (window.location.href = '/login') in the axios interceptor, which reloads the SPA entirely. This differs from the router guard's in-app redirect (return { path: '/login' }).
  • sass is a runtime dependency (not devDependency) in package.json — intentional.

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

  • No test framework or test files currently in the repo.
  • 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.
  • python-multipart in requirements.txt is required for FastAPI to parse form data (not optional).
  • pydantic-settings is installed but unused — config is hardcoded.