Files
FabledScribe/docs/development.md
T
bvandeusen 3f1bcc3360 ci: consume shared ci-python:3.14 image; bump runtime to 3.14
Migrate to the FabledRulebook CI-Runner contract:

- .forgejo/workflows/ci.yml: all four jobs (typecheck/lint/test/build)
  now schedule on the `python-ci` runner label and run inside
  container.image: git.fabledsword.com/bvandeusen/ci-python:3.14
  (Python 3.14 + Node 24 + ruff + uv + Docker CLI). Dropped the inline
  uv install in the test job — uv is now baked into the image.
- Dockerfile: production runtime bumped to python:3.14-slim so test
  results stay representative against what we ship.
- ci-requirements.md: new file at repo root declaring image deps and
  per-job installs (per FabledRulebook ci-runners.md).
- infra/Dockerfile.runner-base: deleted. The in-repo runner base
  (Ubuntu 24.04 + Python 3.12 + Node 22) is superseded by the shared
  ci-python image. The runner-host deployment files
  (runner-compose.yml + act-runner-config.yml) stay as deployment-shape
  documentation; source of truth is the deployed config.
- docs/development.md: CI/CD + Runner sections refreshed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:16:26 -04:00

5.9 KiB

Development

Workflow

All development is Docker-based. Do not install Python or Node dependencies locally.

# 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:

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:

# 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 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, :<sha>
Tag v* on main typecheck + lint + test → build :latest, :<version>, :<sha>
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 devmain 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 + :<version> Docker images
  6. After merging to main, sync dev back:
    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 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 <noreply@anthropic.com>

Types: feat, fix, refactor, docs, chore, test Scopes: feature area (e.g. chat, briefing, fable-mcp, notes)

Testing

# 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.