When the beta image runs with --workers > 1 (default: 2), every worker
calls lifespan() in parallel. Each worker sees an empty DB, then
seed_if_empty() runs concurrently, producing 2x (or Nx) demo entries.
Fix: wrap the seed operation in a process-wide FileLock. Even if a
worker loses the race it re-checks the table count inside the critical
section and returns without re-seeding.
Verified by cold-starting a 4-worker container: 8 items (was: 32).
- FastAPI + SQLite backend with REST API
- Polymorphic Media model (book + series)
- Cover lookup via Open Library
- Vanilla HTML/CSS/JS frontend (MAL-inspired)
- Dark theme, status tabs, drawer detail, stats modal
- 8 seed demo entries