d07a083 changed the /api/energy/prices docstring (sell now deducts sell_fee)
and refreshed openapi/openapi.json, but frontend/src/api/schema.d.ts was not
regenerated, so CI's "check codegen is in sync" step failed. Comment-only diff.
The price chart keyed its X axis on formatLocalTime() "HH:mm" labels, which
repeat across a today+tomorrow range. Recharts resolves axis tooltips by value
(combineTooltipPayload -> findEntryInArray), so hovering a slot after midnight
matched today's identically labelled point: the tooltip showed today's prices
and the active dot jumped back to today's position instead of following the
cursor.
Key the axis on the ISO instant instead (buildChartRows, sorted by slot start)
and format down to HH:mm in the tick formatter; the tooltip label now carries
the date so today and tomorrow are distinguishable.
Also mark the price slot currently in effect by default: findActiveSlotIndex()
locates the slot containing now, rendered as a ReferenceDot on the buy and sell
lines plus a caption, re-evaluated every 30s.
Regression test drives a real mousemove over a sized chart in jsdom and asserts
the resolved slot and active-dot position.
- MQTT import_cost_total / export_revenue_total now anchor fixed-fee / tax-credit
accrual at max(contract start, first recorded period), so standing charges for
untracked pre-recording days are no longer folded into the running total.
- Add energy.import_cost_today / energy.export_revenue_today (monetary,
total_increasing) that reset at server-local midnight, for per-day cost
aggregation in Home Assistant alongside the running totals.
- ContractForm add-version mode prefills the contract's open version values, so
only the fields that actually change need editing.
- Store UTC, compute day boundaries in server local timezone (new app/services/timezone.py).
- summarize: fixed fee / tax credit accrue only for elapsed whole local days,
integrated across contract versions — no future-day inflation, no version-switch collapse.
- MQTT import_cost_total / export_revenue_total getters delegate to summarize (no inline
apportionment), anchored at the active contract's earliest version effective_from so the
total sensors stay monotonic and never jump backward.
- effective_from: naive datetimes interpreted as server-local wall-clock, then stored UTC.
- frontend ContractForm sends local-midnight naive datetime (was UTC midnight).
- get_costs_summary default window uses server-local "today".
A disabled device with historical readings still cannot be deleted by default
(409 guard stays, to prevent accidental data loss). A new explicit opt-in lets
the user remove it for real:
- DELETE /api/modbus/devices/{uuid}?cascade=true removes the device's readings
and exposed_entity_toggle rows, then the device, in one transaction (FK order:
readings before device). Best-effort clears the HA discovery configs first
(MQTT not connected -> no-op, never blocks the delete). Default (no cascade)
unchanged. Returns 200 with deletion counts.
- Frontend: a 'Force Delete (all data)' button appears only after the 409, with
a red irreversible warning; cascade is sent only on that explicit action.
- Also corrected a stale comment: FK RESTRICT IS enforced at runtime now.
Backend serializes SQLite-read (naive) UTC datetimes without a Z, so the browser
was parsing them as local time -> the UI effectively showed UTC values.
- New src/utils/datetime.ts: treats a tz-less timestamp as UTC, then formats in
the browser's local timezone with hour12:false (no AM/PM).
- Applied to Energy tabs (contracts/prices/costs/DSMR), per-device readings &
trends, and the meter readings table on the Records page.
- CostView Today/This month now use local day boundaries (queries stay UTC ISO).
- Untouched: location/poo (raw Zulu strings), the map, and query-window params.
- Tests are timezone-independent (verified under TZ=America/Los_Angeles).
- New 'DSMR' tab showing the latest parsed telegram (GET /api/energy/dsmr/latest)
as a key/value table — the source-of-truth view so you can see ingest is
landing data without reading the DB.
- Auto-refresh toggle (~10s); loading/error/empty states (empty explains the
likely cause); null phase values shown as a dash.
- useDsmrLatest extended with optional auto-refresh. Tests added.
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
- real Mantine login form -> POST /api/auth/login; 401 inline error; redirect when already authed
- ProtectedRoute: loading state, preserves intended destination, gates force_password_change
- ChangePasswordPage forced-change gate -> POST /api/auth/password
- logout control in AppLayout nav -> POST /api/auth/logout
- typed client only; vitest tests for the login flow