Files
FabledScribe/docs/development.md
T
bvandeusen bf292e6019 docs: remove extraneous content — pipeline internals moved to architecture, changelog removed
- features.md: remove SQL impl detail from tasks section, sw.js reference from PWA section,
  and entire "LLM Chat — Internal Pipeline" section (moved to architecture.md)
- architecture.md: add "LLM Pipeline Internals" section (intent routing, tool loop, duplicate
  guards, context window, research pipeline, image cache)
- development.md: remove site-specific NFS path from custom runner instructions
- Remove changelog.md (duplicates git history)
- README.md: remove changelog link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:38:44 -04:00

5.6 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 with a custom runner base image (py3.12-node22):

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
    

Custom Runner

Runner base image: infra/Dockerfile.runner-base (Ubuntu 24.04 + Python 3.12 + Node 22 LTS). Runner config: infra/act-runner-config.yml (label: py3.12-node22). Runner compose: infra/runner-compose.yml.

To activate a new runner registration, copy infra/act-runner-config.yml to the runner's config directory, delete the .runner registration file in the runner container, and restart the stack.

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_note_parent_and_project_fields
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

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.