Commit graph

64 commits

Author SHA1 Message Date
9a8ef04230 Hydration: water intake from the food log, drop the "+вода" button
Intake is what you already log (liquids count via water_pct), so there is
nothing to tap mid-ride and no guessing an amount. At session start JS seeds
the native deficit from the food log — need-so-far for the time of day minus
logged intake — via set_hydration_config(start_deficit_ml); the service then
accrues sweat on top. Removes the +вода button, hydration_drink and water_drink.txt.
2026-10-24 01:29:30 +03:00
6183f0c529 Hydration reminder: level-based drink alerts during a ride or VR
A shared Hydration singleton (ticked by the GPS ride service and the HR/VR
service, deduped in one process) accrues the body's water need over an active
session and subtracts what you logged via the "+вода" button. It stays silent
while you keep up — can be quiet for hours — and only beeps LOUD on the alarm
stream (cuts through media volume) once the running deficit passes a threshold,
then re-beeps on a cooldown. Not a fixed timer.

Rust set_hydration_config / hydration_drink bridge JS settings and the "+вода"
button to hydration.json / water_drink.txt that the native service reads.
2026-10-24 01:13:43 +03:00
4637846839 Roads overlay: one polyline per segment + viewport culling (fix jank)
The per-pass thickness split fragmented each segment into a run per
weight change, turning a ~1.4k-segment network into ~84k polylines and
janking the map. Draw one polyline per segment at its median-pass width
instead (verified on device: 1430 vs 84083). Add buildRoadSegments
(segment + bbox) and cullRoadsInto (draw only segments meeting the padded
view, redraw on moveend/zoomend); the recording overlay now renders on a
canvas pane instead of SVG, and showRoadsMap culls too. Thickness is now
per-segment (median) rather than per-point — a deliberate simplification.
2026-10-23 01:11:15 +03:00
c7dd588f37 TEF in daily balance + separate HR-only "vr" (hands/VR) workout type
TEF (thermic effect of food): optional profile toggle, 10% of intake,
introduced so maintenance is preserved - base = BMR*actLevel*(1-tef),
then +tef*intake. On a deficit day expenditure is slightly lower (eat
less, digest less), which keeps the existing calibration rather than
inflating the deficit. Day summary shows "расход: база + переваривание
+ активность"; the 7-day trend uses it too.

New activity_type "vr" (Руки/VR): HR-only like "workout" (no GPS, kcal
from HR), tracked separately. GPS.isHrOnly() replaces the hardcoded
"workout" checks so both kinds share the no-GPS record/detail path.
2026-10-22 00:51:31 +03:00
85d5e47972 Bump version (build.sh output) 2026-10-19 23:16:39 +03:00
802b2cc6e9 Bump version (build.sh output) 2026-10-19 14:12:04 +03:00
0123d3562b Bump version (build.sh output) 2026-09-27 00:04:52 +03:00
749064ce59 Bump version (build.sh output) 2026-09-22 21:09:13 +03:00
862b1ffc96 Bump version (build.sh output) 2026-09-22 11:32:19 +03:00
bb9f03131d Bump version (build.sh output) 2026-09-22 11:19:37 +03:00
3ba8751664 Bump version (build.sh output) 2026-09-21 20:31:34 +03:00
b379160ff5 Bump version (build.sh output) 2026-09-21 13:49:27 +03:00
ea7c6e68a8 Bump version (build.sh output) 2026-09-21 13:45:36 +03:00
ae90b4368b Bump version (build.sh output) 2026-09-21 13:36:43 +03:00
d4faed200d Bump version to 0.1.467 (build.sh output) 2026-09-21 13:22:50 +03:00
5b3239258f Bump version to 0.1.466 (build.sh output) 2026-09-21 13:20:10 +03:00
263163cf52 Bump version to 0.1.465 (build.sh output) 2026-09-21 13:05:34 +03:00
db62d52650 Bump version to 0.1.464 (build.sh output) 2026-09-21 12:36:51 +03:00
44bb08e81e Bump version to 0.1.463 (build.sh output) 2026-09-21 11:28:37 +03:00
c5386ae22b Bump version to 0.1.462 (build.sh output) 2026-09-18 00:09:14 +03:00
4895cbb042 Bump version to 0.1.461 (build.sh output) 2026-09-17 23:49:12 +03:00
de93f77c3a Bump version to 0.1.460 (build.sh output) 2026-09-17 23:20:37 +03:00
1a57f36621 Bump version to 0.1.459 (build.sh output) 2026-09-17 22:51:10 +03:00
b2defb4564 Bump version to 0.1.458 (build.sh output) 2026-09-17 22:12:18 +03:00
d79a9f217a Bump version to 0.1.457 (build.sh output) 2026-09-14 13:28:11 +03:00
0be980849c Bump version to 0.1.456 (build.sh output) 2026-09-14 13:08:17 +03:00
999361a2e9 Bump version to 0.1.455 (build.sh output) 2026-09-14 12:58:33 +03:00
a2cc150298 Bump version to 0.1.454 (build.sh output) 2026-09-14 12:47:04 +03:00
abdcb94f55 Bump version to 0.1.453 (build.sh output) 2026-09-14 11:53:11 +03:00
7bb764d2ca Bump version to 0.1.452 (build.sh output) 2026-09-12 23:15:52 +03:00
5a80469bba Bump version to 0.1.451 (build.sh output) 2026-09-11 01:30:52 +03:00
80e35aa096 Bump version to 0.1.450 (build.sh output) 2026-09-10 16:23:14 +03:00
2551394cd3 Bump version to 0.1.449 (build.sh output) 2026-09-10 00:55:45 +03:00
9fc7cd5a49 Bump version to 0.1.448 (build.sh output) 2026-09-09 20:30:08 +03:00
7031e2690c build 434 + docs: scanner zoom/torch, OS back-gesture fix 2026-08-27 23:14:54 +03:00
3357b55ba6 build 427 2026-08-27 11:30:55 +03:00
8ae2838320 build 423 2026-08-27 04:42:14 +03:00
493c9b92df Recording map: navigation puck (position+direction), z-order panes, compass-only mini-compass
- One direction indicator instead of two geo-arrows: an SVG divIcon L.marker
  (rotateWithView) — a track-colored circle (20% darker) with white + 1px black
  rings, plus a triangle pointing the ACTIVE source heading (compass in compass
  mode, GPS course in GPS mode). Rotation = geo heading; rotateWithView keeps it
  correct as the map turns. Replaces posMarker + compass/course arrows.
- Z-order via panes: roads -> roadsPane(240), route routePane(250), track
  overlay(400), puck markerPane — tiles<roads<route<track<puck (track no longer
  hidden under roads).
- Mini-compass shown only in compass mode; tap-to-calibrate kept.
- darkenHex helper; .rec-puck css (no default div-icon box).
2026-08-24 23:06:26 +03:00
c1f063ded5 Fix: elevation chart empty after 'Уточнить высоты' (read srtm, not just gps)
showSessionDetail's elevation profile, min/max, x-labels and point popup read
only altitude_gps_m, but 'Уточнить высоты (онлайн)' writes altitude_srtm_m
(computeStats already prefers srtm — hence stats updated but the chart stayed
empty). Read altitude_srtm_m ?? altitude_gps_m everywhere the detail shows
elevation, matching computeStats. No regression: no srtm -> falls back to gps.
2026-08-22 14:23:06 +03:00
d252440204 Fix: single-point track hung showSessionDetail (Delete did nothing)
Diagnosed live over the WebView DevTools protocol: a 0 km track had exactly one
usable point. The map block runs (points.length is truthy), but with a single
coordinate fitBounds is skipped, so the Leaflet map never gets a view and never
fires 'load'. With a saved route active, the overlay code then does
`await whenMapReady(map)`, which waits on that 'load' event forever — hanging the
rest of showSessionDetail, so the Delete/Recalc/Export handlers past it never
bind. No exception, just a silent stall (hence "nothing happens on Delete").

Fix: when there are < 2 coords, setView to the single point so the map loads and
whenMapReady resolves. Verified end-to-end via DevTools: the button now binds and
the 0 km track deletes.

(The prior commit moved the handlers out of the `points.length` guard, which
covers a truly 0-point track; this covers the 1-point-with-route hang.)
2026-08-16 21:06:50 +03:00
ee2880a435 Fix: can't delete a 0-point (0 km) track
The Delete / Recalculate / Refine / Compare handlers were bound inside
`if (mapEl && points.length)` in showSessionDetail, so a track with no points
(started recording, no GPS fix, stopped → 0 km) skipped the whole block and its
"Удалить" button did nothing. Moved the session-action button handlers outside
the map/points guard so they bind regardless. (Also covers a track whose points
all get dropped by the new accuracy filter.)

DB.deleteGpsSession already handled zero points fine (DELETE of 0 rows is a
no-op); the bug was purely the unbound click handler.
2026-08-16 20:35:08 +03:00
bdec4b7bc0 Recompute tracks with an accuracy threshold (non-destructive)
Exclude real GPS fixes coarser than the "Макс. погрешность GPS" setting
(minAccuracyM) from a track's stats and its drawn line. Non-destructive: the
points stay in the DB, so raising the threshold brings them back.

- GPS.filterByAccuracy(points, threshold?): drops points with a measured
  accuracy above the threshold; always keeps points without accuracy (imported
  GPX/FTB, older recordings) and estimated dead-reckoning fills (sentinel >=999).
- Wired into GPS.computeStats (top) so every recompute path funnels through it —
  the "Пересчитать" button, "Пересчитать всё", and the post-de-jitter recompute.
  Also into GPS.filterNoise so the detail-screen line matches the stats.
- Removes mild jitter detours that the spike filter misses (verified: 342 -> 334m
  when a poor-accuracy nudge is dropped). Lower the threshold + Пересчитать to
  apply; raise it to undo.
2026-08-16 20:31:12 +03:00
fd6cf62fc0 Route editor + 1% slope bands + debug-only diagnostics + rotate-map route fix
- Route editor (showRouteEditor): create ("Новый маршрут") or edit an existing
  route's geometry in-app — tap to add, drag a point to move, tap a point to
  delete. Save: new prompts a name (addRoute), edit rewrites points_json
  (updateRoutePoints; drops stale elevations, re-fetch from the preview).
- Slope colouring in discrete 1% bands (green→yellow→red→brown→black, ≥10% black)
  instead of degree buckets; colour-strip legend (slopeLegendHtml).
- Diagnostic logs compiled-in but silenced in release: get_app_info now returns
  debug = cfg!(debug_assertions); JS installs a console.log filter that drops
  lines starting with a diagnostic tag ([poll]/[recover]/[stop]/[GPS]/[HR]/[SD])
  in release only. No more hand-adding/removing logs.
- Fix: saved-route overlay was invisible on the recording (rotating) map — it
  flashed on pinch-zoom then vanished. leaflet-rotate keeps tile/overlay panes
  inside _rotatePane, but the commented-out createPane override meant our custom
  routePane attached to mapPane (outside rotation). Now parent routePane to
  map._rotatePane when present; non-rotating maps unchanged.
2026-08-16 17:15:42 +03:00
f376e0944d Route elevation preview: profile, topo/slope casing, GPXZ + fallback chain
See how hilly a hand-drawn route is BEFORE riding it.

- parseRouteGPX reads <ele>; GPS.routeElevStats computes cumulative distance,
  grade-to-next, ascent/descent (1m hysteresis) and max grade over smoothed
  elevations (filterAndSmoothAlt kills SRTM vertical noise).
- Route preview screen (tap a route name): map with a wide colored casing +
  tappable altitude/grade points, elevation profile chart (steep >8% in red),
  length/ascent/descent/max-grade, fullscreen expand/collapse.
- Casing colored by slope (deg: lime <2, yellow 2-4, red 4-6, black >6) or by
  elevation (green->yellow->brown), toggled with an on-map ∡/⛰ button. Casing
  drawn in a dedicated pane below the overlay pane, so a recorded track stays on
  top; the same toggle is on the track-detail map too.
- fetchElevations tries, in order: GPXZ (Copernicus GLO-30, key from GPS
  settings — stored in localStorage, never in git) -> open-elevation (SRTM) ->
  open-meteo -> OpenTopoData (keyless). Import auto-fetches elevations.
- db.js: updateRoutePoints persists fetched elevations into points_json;
  getRoutes reports hasEle. Route points are now {lat, lon, ele?}.

Native prompt at route import replaced with in-app appPrompt.
2026-08-03 19:29:51 +03:00
bf3c0e2534 Track detail de-jitter: fix overlay stacking, native dialogs, slow save
Fixes to the smoothing/routes UI reported on device:
- Blank detail after save/revert: showSessionDetail was called from inside the
  open detail modal, stacking a second overlay; querySelector("#sd-map") then
  hit the stale, already-initialized Leaflet map and threw, aborting the rest
  of the render (map + speed chart). New reopenDetail() closes the current
  detail (and its fullscreen-map child) and reopens fresh, like recalc/refine.
- Native confirm()/prompt() replaced with in-app appConfirm/appPrompt dialogs
  (winStack + history integrated, hardware back cancels). Used for smoothed
  save, route delete, route rename.
- Slow smoothed save: one UPDATE per point (4000+ IPC round-trips) replaced
  with a chunked CASE update — 18 statements for 4366 points, params kept
  under SQLite's 999 limit. Verified value-for-value on a copy DB.
- Closing the fullscreen map now also closes the smoothing panel (it is a
  fixed element that otherwise lingered over the collapsed map).
- "≈" button active state now reflects panel-open only, not session.smoothed
  (it looked permanently pressed after a save).

Docs: README + PROJECT_MAP updated for saved routes, de-jitter, accuracy_m,
routes table and new gps_points/gps_sessions columns. Version 0.1.393.
2026-07-31 23:51:20 +03:00
cd975b5640 GPS distance: estimate blind-GPS gaps by speed*time; savable gap setting
On stretches where the fix rate drops (dt > gapThresholdSec, incl. dead-
reckoning) the straight chord mis-measures the path, so computeStats now
estimates gap distance by avg(speed)*dt (capped at max speed; a tiny chord
over a long gap means the rider was stopped and the reported speed is stale
-> trust the position). A fast jump WITHOUT a time gap is a teleport glitch
and is dropped. Distance now depends only on ts + speed, not on the
`estimated` flag (which live points carry but DB rows don't), so the live
counter, Stop and recompute finally agree. Verified on a real ride: at
gapThreshold=30 -> 35.7 km / 1024 kcal (map-measured ~35 km); clean tracks
without gaps unchanged.

Settings: read GPS config on input+change (reliable on mobile), add a
"Save GPS settings" button and a "saved" toast so it's clear the value
persisted; changing gapThreshold + recompute now visibly applies.
2026-07-31 12:58:34 +03:00
30a9535793 GPS stop: import only the un-polled tail, not a full file re-scan
The old stop path re-added every native point "not already in the DB",
which raw-inserted points the live poll had intentionally dropped
(stationary / auto-pause / wrong state) with scrambled timestamps. The
detail view draws by seq order, so those late-written points with early
timestamps rendered as spikes across the map (distance stayed correct —
computeStats sorts by ts). Now stop imports only points past
_lastProcessedTs (the poll's processed marker): the genuinely un-polled
tail, including a screen-off stretch, without re-adding filtered points.
2026-07-30 00:23:05 +03:00
56c2ff4ec7 GPS: safe on-device recovery, net-kcal model, poll/watchPosition dedup
- native_root(): pick app root by presence of gps_live/gps_points files.
  app_data_dir() is the root directly on this Tauri; the old .parent().parent()
  read the wrong dir, so background (screen-off) points were never imported.
- recoverFromFiles: match file->session by id/date, plus session id stamped
  into the native file name (gps_points_<ms>_s<id>.jsonl) for exact binding
  (gps_set_session_id -> Kotlin bindSessionIfPending). Proximity dedup +-2.5s,
  recalc with noDelete (never runs the destructive glitch/>24h filter that
  once wiped real rides), delete already-imported files. Progress bar UI.
- bike kcal is NET (daily balance already books BMR*activityLevel): pedaling =
  max(physics power/efficiency, MET-net by speed), coasting (power<=0, the bike
  would roll unpowered) = basal only. Elevation via cumulative hysteresis.
  recomputeAllStats + "recompute elevation/kcal" menu refresh stored tracks
  without touching points.
- live recording: the native file is the DB source of truth; watchPosition
  only drives the live marker, so a fix is not stored twice with two timestamps.
- track list pagination, SAF local-file picker, tile regions from router.
2026-07-29 15:35:18 +03:00
84a2b786ca GPS: course-up map rotation in landscape + robust screen-off import
- applyBearing: use h, not h - rotOffset. The .rec-screen (map + panel) is
  CSS-rotated by rotOffset in landscape and setBearing adds another rotate on
  the map pane; subtracting rotOffset cancelled the screen rotation for the
  map only, gluing course to the portrait top edge while the panel turned.
  Using h lets the map turn with the screen -> course points to physical top.
- Poll: guard the requestAnimationFrame yield with !document.hidden. rAF never
  fires while the screen is off, so awaiting it there hung the import loop and
  froze point import mid-walk (regression from the progressive-draw change).
- recoverSession: import missing native points by the SET of existing
  timestamps instead of ts > maxDbTs. Sparse screen-gated points can have a
  late max ts, so the old check imported nothing and left the track full of
  gaps; also pass sensorData so bearing/accel/gyro are stored.
2026-07-08 22:11:17 +03:00
ec5937ea7e GPS: full native re-import on stop + visible progressive import
- stopRecording: replace the dead _lastNativeCount backfill (the variable
  is never assigned → guard is always false → missed points were never
  saved) with a full timestamp-based re-import from the native points
  file, then recompute stats over the complete set. Fixes losing ~90% of a
  track when recording with the screen off — the JS import poll is frozen
  by the WebView while the screen is off, so only the native file had all
  points.
- Poll: draw each imported point via a lightweight onImportPoint and yield
  a frame every ~20 points so the track visibly grows as points transfer
  on screen-wake catch-up (restores pre-batch behaviour, kept cheap).
2026-07-07 23:45:11 +03:00