msg_metadata maps to the 'metadata' DB column ('metadata' is reserved
by SQLAlchemy Declarative API). to_dict() exposes the key as 'metadata'
for frontend compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .forgejo/workflows/ci.yml: fast checks on every push (TS typecheck,
Python lint via ruff, pytest) — no Docker build, ~30s with cache
- .forgejo/workflows/build.yml: Docker build with registry-side layer
caching + SSH deploy on main branch pushes only
- pyproject.toml: add ruff to dev deps, configure pytest and ruff rules
- tests/: conftest.py + first unit tests for _safe_filename (no DB needed)
- Makefile: add lint, fmt, typecheck, test, check targets for local use
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>