Post-M2 self-walkthrough polish, batched into one commit.
Map / heat:
- fix heat-layer white-screen crash after login (add layer to map before
setLatLngs; an off-map leaflet.heat layer has a null _map and throws)
- normalize each heat layer to the densest pixel cell visible in the CURRENT
viewport (maxZoom:0 so intensity factor f=1) and recompute on moveend/zoomend,
so sparse poo data reaches red and stays normalized at any zoom level
- dark CARTO basemap tiles when the color scheme is dark
UI:
- dark-mode toggle in the top-right, beside the settings gear
- switch top-right nav (records / theme / settings / logout) to Feather icons
with hover tooltips
- home: Grafana-style quick time-range presets + back/forward shift buttons,
placed between the From/To pickers and Apply; fix Select/tooltip z-index
(Leaflet stacking) and the shift-button height alignment
API client:
- stop flooding GET /api/session with 401s: the session probe and the login
endpoint own their 401s (no global redirect), which fixes the logout hang and
the spinning login page
Compose:
- rename docker-compose.override.yml -> docker-compose.dev.yml as an explicit,
non-auto-layered dev stack (8001, -dev container names, prod-copy ./data DB);
update tests/test_deployment.py (read dev.yml, tolerate the !override tag) and
the README "Docker Compose" section
Tests:
- pixel-grid peak counter, time-range presets, heat-layer ordering regression,
and 401-redirect regression
- Dockerfile: node:22-slim stage runs npm ci + npm run build; python runtime
stage COPY --from copies dist to /app/frontend/dist (matches SPA_DIST_DIR);
runtime image has no node
- .dockerignore: exclude frontend/node_modules and frontend/dist from context
- .github/workflows/frontend.yml: npm ci + codegen-sync + lint/typecheck/test/build
- tests/test_deployment.py: skip COPY --from sources in the context-existence
check; assert the multi-stage frontend build wiring
- verified with a real docker build (image serves SPA, no node at runtime)
M1-T04 deleted the alembic_location / alembic_poo chains and their .ini files,
but the Dockerfile still COPYed those four paths, so the release image build
failed at 'COPY alembic_poo.ini ./' (path not found). Drop the four stale COPY
lines (only alembic_app remains). Add test_dockerfile_copy_sources_exist, which
asserts every Dockerfile COPY source exists in the build context, so this class
of breakage fails pytest instead of only surfacing in the image CI.
Verified with a real local 'docker build' (succeeds) and pytest (98 passed).
run_all_migrations() now adopts/initializes only the app DB and returns
{'app': ...}. app/main.py drops the location/poo readiness checks
(ensure_location_db_ready / ensure_poo_db_ready) and their imports;
ensure_runtime_dirs only provisions the app DB path; lifespan still
fail-closes on a missing/unmanaged app DB. Delete the retired
location/poo adopt scripts and the alembic_location / alembic_poo
chains. Update tests to single-DB expectations and drop the obsolete
location/poo adoption + readiness tests.
pytest 95 passed; ruff clean (pre-existing only); a fresh app DB
initialized via scripts.run_migrations contains location + poo_records.
Collapse the three data layers into one. app/db.py now exposes a single
Base, a cached engine bound to app_database_url with SQLite WAL enabled, and
get_engine/get_session_local/reset_db_caches/get_db_session. Delete
app/auth_db.py, app/poo_db.py and app/models/base.py. All models (auth,
config, public_ip, location, poo) inherit the one Base and register on a
single metadata. Dependencies converge to a single get_db; all routes use it.
Also update the alembic env.py files (app/location/poo) and tests that
imported the removed modules so the suite stays green, and drop the obsolete
test_legacy_style_location_db test whose flow (app reading a separate location
DB) no longer exists. Location/poo Alembic chains, adopt scripts and adoption
tests remain for M1-T04; config fields remain for M1-T05.
pytest 109 passed; ruff clean (pre-existing only); WAL verified; single
Base.metadata holds all seven tables.