Commit graph

154 commits

Author SHA1 Message Date
aa7022ec6b routes: return a reliable id from addRoute (re-read, not lastInsertId)
Found in desktop testing: saving a built route showed 'Маршрут пуст' and dropped
to the list — addRoute returned the INSERT lastInsertId, an i64 the SQL plugin
corrupts by ±1, so showRouteDetail opened a non-existent id. Re-read the id via
CAST(id AS TEXT) (same guard importGpxSession uses). Session log: desktop
verification of all 6 fixes.
2026-09-11 01:25:28 +03:00
e45c92f09a Route editor + gap-fill: 6 fixes (casing/slope, controls, tap-insert, waypoints, precise fill)
- Editor shows the route with casing + slope/elevation colouring like the detail
  view (drawRoutePolyline over the routed geometry); day/night, slope/elev and
  locate controls added. Root cause of the plain thin line: ele was dropped when
  building leg geometry — now kept from BRouter, so casing/slope work here and in
  showRouteDetail. Straight legs (no ele) stay a thin line.
- Insert a point is now a TAP on the line (fat invisible hit target,
  pointer-events:stroke); long-press toggles road/straight.
- Routes store editable waypoints (routes.waypoints_json {waypoints,legMode})
  alongside the dense geometry, so re-editing shows the 5-6 points, not thousands.
- Bottom bar reworked (.re-bottom): hint on top, buttons in a row, cancel below.
- Gap-fill is conservative: only real long dropouts (dt>60s, chord>150m), skips
  big detours (>2x chord) and no-gain fills (road ~ straight).
2026-09-11 01:06:06 +03:00
80e35aa096 Bump version to 0.1.450 (build.sh output) 2026-09-10 16:23:14 +03:00
ceee0a48eb gap-fill: reload detail via reopenDetail (in-place), not a stacked screen
Found in desktop testing: showSessionDetail() from the save/revert handlers
pushed a second detail overlay whose map rendered blank. reopenDetail() collapses
the stack and re-renders in place (the pattern recalc/refine already use). Session
log updated with the desktop verification results.
2026-09-10 15:51:34 +03:00
cfe0212bbc BRouter UX: gap-fill preview+reversible save; per-segment route planner
Gap-fill (track detail): GPS.fillGaps now returns {points,gaps} with per-gap
status; the 🧩 control previews ONLY the filled dropouts (cyan casing; unfilled
= red dashed) with a Save/Cancel panel. Save is a reversible in-place patch
(estimated points added, original untouched; DB.saveGapFill/revertGapFill +
estimated/gap_filled columns), then recalculateSession. Revert removes them.

Route planner: renamed menu 'Нарисовать маршрут' → 'Нарисовать тренировку'
(it makes a workout session, not a route); routes empty-state now points at the
on-phone planner. showRouteEditor rewritten per-leg: each leg road (BRouter) or
straight independently, road by default, tap a leg to toggle it, long-press to
insert, silent straight fallback where no road; the global button sets all legs.
2026-09-10 15:26:05 +03:00
2551394cd3 Bump version to 0.1.449 (build.sh output) 2026-09-10 00:55:45 +03:00
99cd4c8d30 Scrub location/identity: home city name and external mirror IP → placeholders
Remove the home-city name from docs/comments/UI strings and the external
mirror IP (WAN) from code, docs and the Android cleartext allowlist, replacing
the IP with mirror.invalid (set the real host locally, uncommitted). LAN mirror
and routing keep working; the WAN fallback needs the real host set. Generic
big-city map presets are kept (not identifying).
2026-09-10 00:55:45 +03:00
d504315229 brouter-web: white+black casing under the slope-colored route
leaflet-hotline has a single outlineColor, so render the colored core thin
(outlineWidth 0) and add two plain polylines beneath it in a dedicated pane
just under the hotline pane: a wide black one and a narrower white one — black
rim, white rim, colored core, legible on any map background. Map captured via
L.Map addInitHook to build the casing pane.
2026-09-10 00:17:42 +03:00
5fce4197a3 brouter-web: hover box above the route; color route by slope (BalDav palette)
- .height-focus.label (on-map hover box) lifted 42px so it no longer covers
  the route line.
- Repoint the 'Route quality' overlay's incline provider from signed incline
  (degrees, blue->green->red) to |grade %| with BalDav's discrete bands
  (green->yellow->red->brown->black, >=10% black), via addInitHook before the
  control is built; best-effort auto-enable the overlay.
2026-09-09 21:31:40 +03:00
f3b0710790 brouter-web: readable elevation hover tooltip in dark theme
The .focusbox tooltip rect stayed white and our .elevation text rule turned
its text light — light-on-light, unreadable on the dark chart. Give the box a
dark ground with light text (same for the on-line height label), and lighten
the near-black guide lines/markers so they show on the dark chart.
2026-09-09 21:17:21 +03:00
9fc7cd5a49 Bump version to 0.1.448 (build.sh output) 2026-09-09 20:30:08 +03:00
60dd364a9f gitignore: build output (binaries/, test.exe) and local PDFs 2026-09-09 20:30:08 +03:00
1546e947e7 BRouter: fix routed-line re-route loop; verify planning + gap-fill on desktop
doBrouter() ended with changed(), which nulled routeGeom and re-queued
doBrouter — an endless re-route loop where the road line was never drawn
(editor kept the straight line, distance unchanged). Draw directly instead
(updatePolyline/updateStats).

Verified end-to-end in the UI on a virtual display (Xvfb) against the
RouteServer: editor 'По дорогам' turns a 1.69km straight line into a 2.25km
road-following route; the gap-fill '🧩' overlay draws a shortest-road cyan
dashed line across a track's GPS dropouts. Docs (README/PROJECT_MAP/plan/log)
updated to match; router.js/poi.js now listed in PROJECT_MAP.
2026-09-09 18:18:35 +03:00
cd98078682 BRouter: gap-fill along roads + release hardening
Stage 4 — gap fill: GPS.fillGaps(points) routes each GPS dropout (dt>gap
threshold, chord>60m) with the shortest road path and splices the routed points
in (flagged estimated), falling back to the chord when the road is >3x the chord
(graph detoured). A 🧩 button on the track detail draws the road-filled path as
a cyan dashed overlay — non-destructive (stats recompute over filled points is a
follow-up). Also fixes doBrouter: parseRouteGPX returns {name,points}, not an
array (planning would have kept the straight line).

Release hardening: network_security_config.xml permits cleartext only to the
mirror/RouteServer hosts (base off, debug-overrides on) — wired via
android:networkSecurityConfig; proguard keeps btools.** so release minification
doesn't strip the embedded engine.
2026-09-09 17:43:56 +03:00
8e720ecd17 BRouter: offline route planning by waypoints + segment download UI
Stage 3 — planning: showRouteEditor gets a "🚲 По дорогам" toggle. Waypoints
stay editable markers; with it on, the line between them (and the saved
geometry) follows roads via BRouter (brouterMode/routeGeom, 450ms debounce on
edits). BRouter.route (router.js) now: Android -> embedded engine, otherwise
(desktop / at home) -> the PC RouteServer over HTTP (http_request bypasses the
WebView CSP; LAN 192.168.1.18:17777 / WAN mirror.invalid:17777). Lets planning be
debugged on the desktop.

Stage 2 — segment UI: a "Сегменты" button on the roads screen opens
showBrouterSegments — region tiles (E35_N45/E40_N45), "есть" status, a
Download/Update button with percent progress via BRouter.downloadTileAndWait.

Verified: RouteServer returns 83 trkpt for a 3-waypoint trekking route (3594 m),
parseRouteGPX reads trkpt; both files compile into the apk. End-to-end UI (tap
waypoints -> routed line; in-app segment download) pending desktop/phone run.
2026-09-09 17:33:11 +03:00
542bb5c376 rec screen: rename wind button 'Ветер в лицо' -> 'Ветер'
Shorter label on the recording screen; docs (README, PROJECT_MAP) updated to match.
2026-09-09 17:04:04 +03:00
ef36b15ce6 brouter-web: broaden dark theme (sidebar tabs, inputs, dropdowns, elevation chart text/footer)
Cover the elements that stayed light: right vertical sidebar tab strip, search
fields, the profile dropdown and dropdown menus, light-styled buttons, the
elevation chart's SVG axis text (was black on dark) and its background/footer/
stats. Injected as a <style> keyed on body.bw-dark.
2026-09-09 16:51:58 +03:00
d078db7288 POI overlay on maps: circular contrasting icons + category filter (offline)
Points of interest (cafe, shops, water, toilets, bank/ATM, bus, shelter,
viewpoint, ...) as circular contrasting badges (emoji in a white/dark circle,
readable on light and dark tiles) — not pins. A 📍 control toggles the layer
and opens a per-category filter (checkboxes, remembered). Markers are culled to
the visible bounds and shown from zoom 13 (cap 500/view), on the recording,
track-detail and roads maps.

Data is bundled and fully offline: frontend/data/pois.json ([{la,lo,c,n}], 358KB,
5662 POIs) fetched once from Overpass for ~100km around region (the whole
bbox returned in one query; adaptive subdivision was ready if it had truncated).

Verified functionally on device: 124 badges rendered in view at z16 over
region with correct bounds/category culling; the live visual could not be
screenshotted (the WebView surface is black under CDP). frontend/data + poi.js.
2026-09-09 16:51:58 +03:00
9c7f41c7ef brouter-web: robust dark toggle (floating button + injected style)
The navbar toggle was invisible/zero-width; replaced with a fixed floating
button (bottom-right). Dark mode now injects a <style> keyed on body.bw-dark
covering the tile layer, right sidebar, left toolbar (zoom/draw controls) and
the bottom elevation profile panel (incl. the chart drawn later on route).
2026-09-09 14:57:12 +03:00
be7ee76a4c tools/brouter-web: preserve mirror customization (config + dark toggle + default overlays)
Static brouter-web on the Keenetic mirror pointed at the PC RouteServer.
config.js (host/layers/centre), baldav-custom.{css,js} (day/night toggle that
inverts only the tile layer + dark sidebar, one-time default POI overlays),
and a README with the deploy steps. Kept in-repo so the mirror is reproducible.
2026-09-09 13:50:42 +03:00
375fb0a7ab Map: dark backdrop + reliable tile caching
Two fixes to the dark map + offline tiles:
- Dark backdrop: body.dark-map also darkens .leaflet-container (was #ddd), so
  the light Leaflet background no longer flashes bright between/behind tiles
  while zooming or where a tile is missing.
- Tile caching: createTile fetched the display <img> from the URL but cached
  via a SEPARATE fetch — on a flaky mobile link that second request often
  failed, so tiles showed but were never cached and re-zooming re-fetched them.
  Now one fetch caches and displays from the same blob, so every shown tile is
  cached. CSP already allows connect-src to the OSM tile host.

Verified on device: container bg rgb(27,31,36) under dark map; IndexedDB tile
cache populated.
2026-09-09 13:46:22 +03:00
5c23c4dda4 Map: dark-map toggle (invert tiles only), no repacking
A moon button on the recording, track-detail and roads maps inverts ONLY the
tile layer: body.dark-map applies `invert(1) hue-rotate(180deg) brightness(1.5)`
to .leaflet-tile-pane, so the track/route/roads/puck overlays keep their colours.
Works the same on offline (tiles_*.db) and online tiles — no keys, quotas or
tile repacking. The choice persists in localStorage (baldav_dark_map).

Chosen after ruling out dark tile providers (CARTO/Thunderforest/2GIS/Yandex —
keys/quotas/ToS/projection/RF-reachability) and the transport style (inverts
badly in dense city); plain OSM inverted looks good city and countryside.

Verified on device: filter on the tile pane only, overlay pane filter none,
button active and remembered.
2026-09-09 13:33:05 +03:00
b40775c9d0 BRouter: download segments from own mirror (LAN->WAN->upstream)
Per the user's decision, BalDav pulls .rd5 tiles from their own mirror instead
of brouter.de directly. downloadTile now tries, in order: LAN
http://192.168.1.1:88/brouter/segments4, WAN http://mirror.invalid:8088/... (via
the router port-forward), then brouter.de as a last resort; a base that 4xx's
or fails is skipped. The mirror is the Keenetic's existing uhttpd (:88, docroot
/opt/share/www) serving /opt/share/www/brouter/ off USB — always on.

Verified on device: the phone downloads a tile from the LAN mirror in <2s and
routes on it. Routing stays local (embedded engine); the PC RouteServer +
systemd unit are the user's separate remote endpoint, not used by BalDav.

Debug builds allow cleartext; release needs a network-security-config for the
mirror hosts. WAN port-forwards on the router are outward-facing (added
separately with confirmation).
2026-09-09 11:48:50 +03:00
d807e4ec3b BRouter Stage 2: resilient segment downloader (Range resume), verified on device
In-app download initially stalled at 7922 bytes — not the network: the phone
reaches brouter.de fully (confirmed via Termux). BalDav was not in the device's
split tunnel, so its traffic took the wrong path; fixed device-side. Over the
now-working but bursty link the fixed 30s readTimeout tripped, so the downloader
is rewritten to resume: HTTP Range (append to the .tmp, 206=resume / 200=restart),
readTimeout 60s, up to 30 retries.

Verified on device: E40_N45 (14.1 MB) downloaded by the app (done ~55s), a
cross-tile trekking route on the app-downloaded tile (44.8 km / 316 m, 1.66s)
matches the PC-pushed tile, and listTiles returns both. Remaining Stage 2 work:
the tile-selection UI.
2026-09-09 11:10:24 +03:00
b4046841be BRouter Stage 2 (primitive): download .rd5 segments inside the app
Kotlin owns the download so tiles land in the exact dir the engine reads
(files/brouter/segments4) — no Rust path guessing. processBrouterCommand now
dispatches: dltile:<tile> streams brouter.de/brouter/segments4/<tile>.rd5 to a
.tmp then renames, writing progress/final state to brouter_dl_<tile>.json;
listtiles writes brouter_tiles.json. Tile names are sanitized ([A-Za-z0-9_]).

Rust relays: brouter_download_tile / brouter_download_status / brouter_list_tiles.
JS: BRouter.downloadTile/downloadStatus/listTiles/downloadTileAndWait.

Compiles into the apk. Not yet exercised on device (phone offline) — the tile
selection UI and the end-to-end download->offline-route test are the remaining
Stage 2 work. IMPLEMENTATION_PLAN updated.
2026-09-09 01:11:20 +03:00
bc63b34cb9 BRouter Stage 1: verified on device; ship v11 lookups/profiles to match segments
End-to-end offline routing confirmed on the phone: shortest ~5.4 km in Rostov
(3.2s cold), trekking cross-tile Rostov->region 44.8 km / 316 m ascend
(1.2s), 617 ms on the apk-shipped data. JS -> Rust brouter_route -> Kotlin
engine -> GPX works through the whole chain.

The 1.6.3 distribution ships lookups.dat v10, but the live brouter.de/segments4
tiles are v11 ("lookup version mismatch"). Replaced the vendored lookups.dat +
trekking/shortest profiles with the v11 set from abrensch/brouter master
(misc/profiles2); the 1.6.3 engine accepts them. Profiles and lookups MUST match
the segments' version.
2026-09-09 01:07:24 +03:00
a52e998fb6 BRouter: analysis + embed the offline routing engine (Stage 1)
Analysis in docs/brouter-findings.md: how BRouter knows roads (.rd5
segments), routes and reaches low elevation gain (profile uphill/downhill
cost + kinematics), the three call surfaces, and an evaluation of two ideas
— fill GPS gaps along roads, and offline waypoint route planning. License is
MIT; the engine has no desktop-only deps, so it embeds on Android ART.

Stage 1 (embed + bridge, compiles into the apk; on-device route not yet run):
- vendored brouter-1.6.3-all.jar into the Android module (dexes cleanly) and
  bundled the trekking/shortest profiles + lookups.dat as assets, copied to
  files/brouter/profiles2 on launch
- MainActivity: a brouter_command.txt handler on the existing FileObserver
  runs btools.router.RoutingEngine off the UI thread (recipe taken via javap
  from ServerHandler) and returns the track as GPX
- Rust brouter_route command: same file bridge as GPS (request + command,
  poll for the result), registered in the invoke handler
- frontend/js/router.js: BRouter.route(points, profile) -> GPX (parseRouteGPX)
- LICENSE-brouter (MIT notice) kept alongside the vendored engine

Segments (.rd5, ~13-23 MB per 5x5 tile) are delivered later (Stage 2, wired
download inside BalDav). IMPLEMENTATION_PLAN.md tracks the 5 stages.
2026-09-09 00:56:13 +03:00
34927e3f52 rec screen: status-bar backdrop so the system bar stays visible over the map
The recording map is edge-to-edge and drew behind the system status bar,
leaving its icons unreadable over the tiles (the map controls were already
offset by --safe-area-top, so the design intended a visible bar). Paint a
solid strip the height of the top inset behind the bar — like the header on
every other screen. Disabled in the rotated landscape modes.
2026-09-07 17:15:19 +03:00
1b10aa67de Wind: "headwind in the face" label button on the recording screen
Variant 5 from the findings doc, as GROUND TRUTH for validating the
from-track wind estimate — it does NOT feed kcal/power. Chosen scope
(per user): a toggle button now, binary "in the face" only, voice later.

- rec screen button "Ветер в лицо": tap opens a headwind interval, tap
  closes it; the open state is highlighted and survives a screen re-render
- GPS.toggleWindMark()/_closeOpenWindMark(); recording.windMarks =
  [{startTs,endTs}] ms, persisted to localStorage, written to the DB on
  finish and restored on crash recovery (a dangling interval auto-closes)
- gps_sessions.wind_marks (JSON) + DB.setSessionWindMarks (separate from
  finishGpsSession so recompute paths preserve marks); parsed in getGpsSession
- rec-bottom wraps now so the third button never overflows a narrow screen

Analysis (marks vs estimate) stays in tools/track once enough km accumulate.
2026-09-07 17:10:16 +03:00
dd9ec1bda7 GPS model: accel-based Crr scale, auto walk-the-bike split; document climb/elevation limits
Acted on docs/gps-model-findings.md (independent analysis of 156 rides).

Implemented:
- crrFromAccel extended past the old 0.008 ceiling: 7-11 -> 0.008,
  11-15 -> 0.012 (packed dirt), >15 -> 0.020 (grass/rut/gravel). Rough
  surfaces were undercounted ~3x. Applied to session median and per-point Crr.
- Auto walk-the-bike detection (single-speed only, gear_count 1/unset):
  a normal-fix segment slower than the 30-rpm-cadence speed is the rider
  pushing, not riding. Excluded from ride distance/speed/elevation (elevRef
  reset so a pushed climb doesn't merge with the ridden one); effort counted
  as walking, not bike physics. Every sub-threshold point excluded regardless
  of duration; only runs >=30s reported as walked (walkBikeSec/walkBikeMeters).
  gearCount threaded through every computeStats/finish caller.

Documented as known limitations (left as-is by decision, need calibration/DEM):
- kcal max(physics, MET) never sums the biological overhead, so climbs are
  slightly undercounted (comment + README + PROJECT_MAP).
- absolute elevation gain is bounded by the raw GPS altimeter (tens of
  percent; needs a terrain model / wired DEM preload).
2026-09-07 15:50:19 +03:00
ec77f67cab GPS stats: exclude manual pauses; reject unrealistic altitude steps
Manual pause (hand-paused at foot of a climb, walked up, resumed) was
counted as riding: computeStats bridged the GPS gap for distance and
elevRef survived the gap, leaking the on-foot climb into elevation gain.

- gps_sessions.pauses (JSON) now persists manual-pause intervals
- computeStats(opts.pauses): a segment crossing a pause is excluded
  entirely (no distance/speed/elevation/kcal), elevRef reset, paused
  time subtracted from duration; automatic GPS gaps still interpolated
- altitude glitch guard: an adjacent smoothed step steeper than
  maxRealGradePct (100% = 45deg) is not banked and does not move elevRef
  (covers Android no-fix 0.0); a smooth change to any value (a real 0
  near a river) still passes
- recorder writes "alt":null when hasAltitude() is false (real fixed 0 kept)
- pauses threaded through live-stop, recalculateSession, recovery and bulk recompute
- new-tracks only (old tracks never stored pauses; no manual segment-cut UI)

Also: track-detail header shows a click-to-copy track ID.
2026-09-07 13:53:40 +03:00
872e20a5b0 tools: steep_segments.js — extract steep bike-track sections to GPX + brouter
Two-pass extractor for the steepest sections of recorded bike tracks:
- pulls gps_points/gps_sessions from baldav.db (adb run-as), bike only;
- refines elevation online (--gpxz Copernicus GLO-30 with the user's key, deduped
  to ~33m cells + cached; falls back to open-elevation / on-device altitude_srtm_m);
- distance-window smoothing, grade over a configurable base (default 120m) with a
  GPS-gap guard, keeps the steepest base-window core per section (so a real steep
  core is not averaged away by a long gentle stretch), spatial de-dup of repeats;
- outputs steep_segments.gpx (named by grade/length/drop) + a brouter lonlats string.
Verified against two ground-truth spots the rider confirmed; open-elevation/SRTM30
flatten short steep features that GLO-30 resolves. Key + data stay gitignored.
2026-08-29 10:05:39 +03:00
7031e2690c build 434 + docs: scanner zoom/torch, OS back-gesture fix 2026-08-27 23:14:54 +03:00
296d23a008 Android: fix OS back gesture for SPA modals/screens (drive JS history.back)
WryActivity's generated back handler calls WebView.goBack(), which does NOT
dispatch popstate for same-document history.pushState() entries — so the OS back
gesture never reached the app's popstate handler and couldn't dismiss modals
(new product, add-to-eaten, ЧЗ scanner) or navigate screens; only the on-screen
back arrow worked. WryActivity.kt is auto-regenerated each build, so override in
the app-owned MainActivity instead: set handleBackNavigation=false and register
our own OnBackPressedCallback that runs window.history.back() via evaluateJavascript,
which fires popstate. Verified on-device: swipe-back now closes modals and the ЧЗ
scanner. (adb `input keyevent BACK` does NOT go through OnBackPressedDispatcher on
Android 13+, so it can't be used to test this — only the real gesture does.)
2026-08-27 23:12:34 +03:00
bda0cdcf21 ЧЗ scanner: digital-zoom fallback + fix back-stack desync on scan->add
- Zoom slider: many Android WebViews don't report a `zoom` track capability
  (torch works, zoom doesn't), so the slider never showed. Add a digital-zoom
  fallback (always-visible slider 1-5x): CSS-scale the preview + crop the decode
  ROI tighter to center. Hardware zoom still used when available.
- Back gesture did nothing after scanning: onDecoded closed the scan modal with
  the async history.go(-1) and then synchronously opened the add-product modal,
  so history.go unwound the NEW modal's entry and desynced the winStack/history.
  Add closeThen(): await the close's popstate(s) before opening the next modal.
2026-08-27 18:08:25 +03:00
27e7693bd3 docs: recognition — zxing-wasm switch, CSP wasm fix, zoom/torch, on-device verify 2026-08-27 11:31:33 +03:00
3357b55ba6 build 427 2026-08-27 11:30:55 +03:00
0e44dc07e2 ЧЗ scanner: zoom + torch, looser ROI, CSP wasm-unsafe-eval (wasm verified on device)
- The tight guide box forced the phone closer than it can focus. Drop it: decode
  the whole visible center square (WYSIWYG under object-fit:cover) at up to 768px;
  zxing-wasm locates the code anywhere in it.
- Add a zoom slider and a torch toggle driven by MediaStreamTrack capabilities
  (getCapabilities/applyConstraints), plus continuous focus and a 1920x1080 request
  — the user enlarges a small code optically instead of moving too close.
- CSP: add "script-src ... 'wasm-unsafe-eval'". Tauri's generated hash-based
  script-src dropped unsafe-eval, so WebAssembly.instantiate was blocked
  ("violates CSP") — verified fixed on-device via CDP (zxReady ok, code_33 decodes).
2026-08-27 11:28:00 +03:00
5f30af1cf7 ЧЗ scanner: switch decoder to zxing-wasm (ZXing-C++) for reliable DataMatrix
The pure-JS @zxing/library locator is weak on real ЧЗ codes (decoded 1/4 of the
test photos with correct grayscale input; libdmtx and zxing-wasm read 3/4 — the
4th is physically undecodable). Symptom: scanner takes very long to even find a
code, 2 of 4 never read.

- Bundle zxing-wasm reader as an IIFE (frontend/lib/zxing-wasm.js, global
  ZXingWASM) + zxing_reader.wasm; drop frontend/lib/zxing.js. Init fetches the
  .wasm as an arrayBuffer and hands it over as wasmBinary (no streaming/MIME/CDN).
- ChZ.zxReady()/ChZ.decodeImageData() (chzn.js): decode an ImageData -> raw GS1
  bytes (result.bytes keeps the GS separators) with tryHarder/tryRotate/tryInvert.
- Scanner (main.js) rewritten to a canvas loop: crop the central ROI, upscale to
  540px, decode each frame — full-frame search was the slow/unreliable part.
  Photo path decodes the picked image the same way.

Not yet verified on-device (WebView WASM instantiation); decoder logic validated
in a Node harness against the 4 sample photos.
2026-08-27 09:42:05 +03:00
8ae2838320 build 423 2026-08-27 04:42:14 +03:00
a2a2fd4a00 docs: ЧЗ scanner — offline ZXing decoder (no GMS), ЧЗ badge, session auto-push 2026-08-27 04:37:24 +03:00
3ca2bc00c0 Chestny Znak scanner: bundle offline ZXing decoder, ЧЗ badge, session auto-push
- Native BarcodeDetector needs Google Play Services (fails "Barcode detection
  service unavailable" on de-Googled ROMs). Replace it with a bundled, fully
  offline ZXing decoder (frontend/lib/zxing.js, @zxing/library UMD): scanner uses
  BrowserDatamatrixCodeReader (TRY_HARDER) for live camera and decodeFromImageUrl
  for the photo fallback. No Google, codes never leave the device.
- Source badge "ЧЗ" (src-chzn) for products added via the scanner.
- tools/chzn_export.sh: adb push the exported session to the device Downloads so
  BalDav's file picker can import it directly.
2026-08-27 04:31:58 +03:00
c3ec590da5 Chestny Znak scanner: DataMatrix -> National Catalog КБЖУ prefill
- ChZ module (frontend/js/chzn.js): import an anonymous session (exported from
  the CRPT app with tools/chzn_export.sh) and keep it alive via /authorize/refresh
  — /authorize/anonymous is Play Integrity-gated so BalDav can't mint its own.
  GS1 code normalization (reconstruct the GS separator before AI 93), check ->
  catalogData good_attrs КБЖУ parser (match by attr group + name prefix, since
  producers name the fields differently), optional nutrition/info vitamins.
- chzn_http Rust command: browser UA + returns {status,body} so 204/401/427 drive
  the session/refresh logic.
- Scanner modal (BarcodeDetector camera + photo/manual fallbacks) -> prefill the
  new-product form; CAMERA permission; settings section to import/refresh session.
- tools/chzn_export.sh: pull + validate the anonymous session from a rooted device.
2026-08-27 03:27:15 +03:00
fe20a4d590 Recording puck: point direction triangle UP (no jitter)
The direction triangle jumped around in GPS mode: rotation was set to the geo
heading with rotateWithView adding map._bearing, and the two desynced (they
should cancel in course-up but updated at different times). Since the map already
rotates the active heading to the top, travel direction is always screen-up —
so just point the triangle up (no rotateWithView, no per-frame rotation), shown
whenever a heading is known. Compass mode still to be tested.
2026-08-25 20:48:27 +03:00
f7b45d98f0 Product catalog cleanup (no-badge unused) + order-independent search
- searchProducts: order-independent token AND (every whitespace word must appear
  in name, any order), ranked full-substring > prefix > length. Fixes needing to
  type words in the name's order.
- Cleanup of unlabeled junk (health-diet/ashan/fast-food, null source): keep-list
  = magnit,5ka,ion,kdv,manual,recipe_import,gridnev. DB.cleanupUnlabeledProducts
  deletes non-keep AND unused (food_log/recipe_ingredients protected); Settings
  button 'Очистить продукты без магазина' with a counted appConfirm.
- Seed products_seed.db cleaned: 24443 -> 13008 (non-keep removed), seed_meta
  version 10 -> 11, VACUUM (13.5MB -> 8.9MB). So future imports don't re-add junk.
  On-device 11207 unused non-keep will be removed via the button (24 eaten stay).
2026-08-25 18:23:51 +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
b5275da61e docs: route-editor mid-insert, track color setting, app dialogs, orient-dbg debug-gate 2026-08-22 22:18:44 +03:00
de10a02746 Route editor mid-insert (tap line), track color setting, app dialogs, debug-only orient overlay
- Route editor: tap the route line to insert a point at that spot (nearest
  segment, splice); addMarker -> makeMarker so end-append and mid-insert share
  the drag/delete handlers. Add/drag/delete unchanged.
- Настройка 'Цвет текущего трека' (GPS.config.trackColor, default #bb86fc):
  recording line, detail track + gap dashes, manual-draw line/markers use it —
  so the track is distinguishable from the (blue) roads overlay.
- Replace remaining native dialogs with app ones: new appAlert; tile-path prompt
  -> appPrompt; seed/modal/product-in-use alerts -> appAlert. Selects untouched.
- orient-dbg sensor overlay now only renders when window.__BALDAV_DEBUG (debug
  builds); silent in release.
2026-08-22 22:08:58 +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
3f2100cdd1 build: embed exact/fresh git hash (+ -dirty for uncommitted code)
build.rs now re-runs when the commit changes (cargo:rerun-if-changed on
.git/HEAD + the branch ref) so GIT_HASH never goes stale, and appends -dirty
when tracked code (frontend, src-tauri/src, build.rs) is uncommitted. Lets a
distributed APK be pinned to an exact source commit for regression tracking.
README documents versionName 0.1.NNN + hash traceability. Decision at b4fdc824144.
2026-08-20 19:55:08 +03:00