# Development ## Workflow All development is Docker-based. Do not install Python or Node dependencies locally. ```bash # Start the full stack (app + PostgreSQL + Ollama) docker compose up --build # Rebuild after backend changes (frontend changes require rebuild too) docker compose up --build app # Reset everything (wipes database) docker compose down -v && docker compose up --build # Run checks (lint, format, typecheck, tests) make check # Individual checks make lint # ruff check src/ make fmt # ruff format src/ make typecheck # vue-tsc --noEmit make test # pytest tests/ ``` ## Frontend Hot Reload The Docker setup does not include Vite's hot-reload dev server. After frontend changes, rebuild the image. For faster iteration during active frontend work, you can run Vite locally: ```bash cd frontend npm install npm run dev # Vite dev server at http://localhost:5173 ``` Point the Vite dev server at the backend by setting `VITE_API_BASE_URL=http://localhost:5000` (or configure `vite.config.ts` proxy). ## Database Migrations Alembic migrations run automatically on container startup (`alembic upgrade head` in the `CMD`). To create a new migration: ```bash # Inside the running app container docker compose exec app alembic revision -m "description_of_change" # Edit the generated file in alembic/versions/ ``` Migration conventions: - Use raw SQL with `IF NOT EXISTS` guards for idempotency - Use `DO $$ BEGIN CREATE TYPE … EXCEPTION WHEN duplicate_object THEN NULL; END $$` for enum types - Number migrations sequentially (e.g. `0027_add_something.py`) - Always provide both `upgrade()` and `downgrade()` ## CI/CD ### Pipeline CI runs on Forgejo Actions, consuming the shared [`ci-python:3.14`](https://git.fabledsword.com/bvandeusen/CI-runner) image via `container.image` (Python 3.14 + Node 24 + ruff + uv + Docker CLI): | Trigger | Jobs | Docker tags pushed | |---------|------|--------------------| | Push to `dev` | typecheck + lint + test → build | `:dev`, `:` | | Tag `v*` on `main` | typecheck + lint + test → build | `:latest`, `:`, `:` | | Push to `main` | typecheck + lint + test | (no build) | ### Release Process 1. Work on `dev` branch — CI validates on every push 2. When ready, open a PR from `dev` → `main` in Forgejo 3. Merge the PR 4. Create a release via the Forgejo UI on `main` with a `v*` tag (e.g. `v26.03.23.1` — CalVer: `YY.MM.DD.N`) 5. The tag push triggers CI → build job pushes `:latest` + `:` Docker images 6. After merging to main, sync dev back: ```bash git checkout dev && git merge main && git push origin dev ``` ### Runner CI jobs schedule against the `python-ci` runner label and run inside the shared `git.fabledsword.com/bvandeusen/ci-python:3.14` image (see `ci-requirements.md` for what this project relies on from the image). The runner deployment lives outside this repo; image bumps happen in [CI-Runner](https://git.fabledsword.com/bvandeusen/CI-runner) via Renovate. `infra/runner-compose.yml` + `infra/act-runner-config.yml` document the runner-host deployment shape; the source of truth is the deployed config on the runner host. ### Docker Registry Images pushed to: `git.fabledsword.com/bvandeusen/fabledassistant` Cache tag: `:cache` (reduces build time ~80%) Required secrets (repo → Settings → Secrets → Actions): - `REGISTRY_USER` — Forgejo username - `REGISTRY_TOKEN` — Forgejo PAT with `write:packages` scope ## Migration Chain Current migration sequence (all idempotent raw SQL): ``` 0001 create_notes_table 0002 create_tasks_table 0003 task_note_companion (data migration) 0004 merge_tasks_into_notes 0005 add_chat_tables 0006 add_settings_table 0007 add_title_and_updated_at_indexes 0008 add_users_and_user_id 0009 add_message_status 0010 add_app_logs_table 0011 add_password_reset_tokens 0012 add_invitation_tokens 0013 add_tool_calls_to_messages 0014 add_note_embeddings 0015 add_oauth_fields 0016 add_image_cache 0017 add_projects 0018 add_push_subscriptions 0019 add_events (dead code — internal CalDAV/Radicale table; Radicale was removed) 0020 add_milestones 0021 add_task_logs 0022 add_note_versions_and_drafts 0023 add_tags_to_note_versions 0024 add_session_version 0025 add_sharing_and_notifications 0026 add_briefing_tables 0027 add_api_keys ``` **Important:** Do NOT use `op.create_table()` or `sa.Enum()` — SQLAlchemy's event system can fire `CREATE TYPE` even with `create_type=False`, causing failures on re-run. Always use raw SQL with `IF NOT EXISTS` / `DO $$ BEGIN ... EXCEPTION WHEN duplicate_object` guards. ## Project Conventions ### Backend - Services: `async with async_session() as session:` — import from `fabledassistant.models` - No `fabledassistant.database` module - Blueprint per resource: `routes/notes.py`, `routes/tasks.py`, etc. - All business logic in `services/`; routes are thin wrappers - Permission checks via `services/access.py` — never inline ownership checks in routes ### Frontend - API calls via `frontend/src/api/client.ts` typed helpers (`apiGet`, `apiPost`, `apiPatch`, `apiDelete`) - Pinia stores for shared state; local `ref()` for component-only state - Composables in `composables/` for reusable behaviour (autosave, keyboard nav, tag suggestions, …) - Views are page-level components in `views/`; reusable UI in `components/` ### Commit Style ``` type(scope): short description Longer body if needed. Co-Authored-By: Claude Sonnet 4.6 ``` Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test` Scopes: feature area (e.g. `chat`, `briefing`, `fable-mcp`, `notes`) ## Testing ```bash # Run all tests make test # Run specific test file docker compose exec app /opt/venv/bin/pytest tests/test_auth.py -v ``` Tests are in `tests/`. They run against a real PostgreSQL instance in CI (not mocked). Keep tests integration-style where possible — mock failures have historically masked real migration bugs.