This repository uses scoped instruction files under .github/instructions/.
Follow those files when their applyTo patterns match the files being changed.
- Read
docs/ARCHITECTURE.mdbefore changing service boundaries, session handling, route patterns, or model relationships. - Read
docs/TESTING.mdbefore adding or changing tests. - Read
docs/SECURITY.mdbefore touching CSRF configuration, file uploads, input validation, logging, or secret key handling. - Read
docs/DEVELOPMENT.mdfor local setup, migration commands, Docker workflow, and environment variables. - Read
docs/DESIGN.mdbefore changing UI layout, templates, flash messages, or reusable components.
Flask web app for managing BattleTech miniature inventories and organizing forces. Three main entities: Miniatures (individual models), Forces (collections of lances), and Lance Templates (reusable lance configurations).
- Backend: Flask 3.1+, SQLAlchemy 2.0+, SQLite, Waitress (production)
- Frontend: Bootstrap 5.3, Font Awesome 6.4, SortableJS — all via CDN; server-rendered Jinja2
- Package manager:
uv— use PowerShell for all commands - Python: 3.13+ required
app/
__init__.py # Application factory: create_app()
config.py # Config class; reads from environment / .env
extensions.py # SQLAlchemy setup and session_scope()
blueprints/ # Thin route controllers (one file per area)
models/ # SQLAlchemy models (one file per model)
services/ # Business logic (one file per domain)
templates/ # Jinja2 templates; base.html + per-area folders
static/ # Custom CSS and JS; libraries via CDN
tests/ # pytest; conftest.py has all shared fixtures
docs/ # Architecture, development, design, security docs
main.py # Dev entry point (debug=True, port 5001)
server.py # Production entry point (Waitress)
Dockerfile # Docker image
| Blueprint | Prefix | File |
|---|---|---|
miniatures |
/miniatures |
app/blueprints/miniatures.py |
forces |
/forces |
app/blueprints/forces.py |
lance_templates |
/lance-templates |
app/blueprints/lance_templates.py |
| (root) | / |
registered in app/__init__.py |
Always use session_scope() from app/extensions.py. Always session.expunge() objects before returning from service functions.
from ..extensions import session_scope
def get_force_by_id(force_id: int) -> Force | None:
with session_scope() as session:
force = session.get(Force, force_id)
if force:
for lance in force.lances:
_ = lance.miniatures
session.expunge(force) # Critical: prevents DetachedInstanceError
return forceRoutes support both JSON (AJAX) and form submissions. Standard JSON envelope:
{"success": bool, "error": str | None, "data": dict | None}HTTP status codes: 200 success · 400 bad input · 404 not found · 409 conflict
Rules:
- Validate
int()conversions intry/exceptat route level — return 400 on failure - Set flash messages before the
if is_jsoncheck — JS usessetTimeout(() => location.reload(), 100) - Use
request.get_json(silent=True) or request.form—silent=Trueprevents Content-Type errors - Always include
"success"key in every JSON response
# ❌ Fails with joins
session.query(ForceMiniature).join(Lance).filter(...).delete()
# ✅ Correct
records = session.query(ForceMiniature).join(Lance).filter(...).all()
for record in records:
session.delete(record)Force→ manyLance(cascade delete)Lance→ manyForceMiniature(join table with position ordering)ForceMiniature→ oneMiniature(reference, not cascade)LanceTemplate→ manyLanceTemplateMiniature(chassis patterns for auto-matching)
Active Force: Only one force can be is_active=True at a time.
Miniature naming: Chassis ("Warhammer"), Prefix ("WHM"), Type ("WHM-6R"). Template matching uses chassis name substring.
uv sync # Install dependencies
uv run python .\main.py # Start dev server (port 5001)
uv run python -m app.migrations # Create/update schema
uv run python -m app.seed # Load demo data
uv run pytest -q # Run tests
uv run ruff check . # LintTests use in-memory SQLite via create_app({"DATABASE_URL": "sqlite+pysqlite:///:memory:"}).
See docs/DEVELOPMENT.md for the full table.
SECRET_KEY— Flask session/CSRF signing key (random ephemeral default; set in production)DATABASE_URL— defaults to%APPDATA%\MechBay\mechbay.db(Windows) or/data/mechbay.db(Docker)DEBUG— enables debug mode and colourized logsTRUST_PROXY_HEADERS— settrueonly behind a trusted reverse proxyAPPLICATION_ROOT— path prefix for reverse-proxy deployments
Export files: {EntityType}_YYYYMMDD_HHMMSS.json
Generate with: datetime.now().strftime("%Y%m%d_%H%M%S")