Commit graph

93 commits

Author SHA1 Message Date
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
2051a604e1 GPS accuracy-gated points + bearing; fix startup UI freeze
- Native: hard-reject fixes coarser than the Min-accuracy setting (drops
  coarse cell ~800m that stuck the track); remove low-accuracy and adaptive
  relaxation branches so only accurate fixes are recorded, honest gaps otherwise
- Store bearing_deg per point (gps_points migration + addGpsPoint)
- Default Min-accuracy 20 -> 50m (one-time migration, respects user override)
- Fix: http_request was synchronous with no timeout; an unreachable host
  (LAN sync on startup) blocked the whole UI ~30s. Now async + spawn_blocking
  + connect(4s)/global(12s) timeouts, fails in background without freezing
- Bundled prior work: course-up map orientation, compass calibration,
  online elevation refine (open-elevation), distance gap-gate fix
2026-07-07 00:28:27 +03:00
5d6025ea9c Desktop: synchronous track-detail render + text session ids
showSessionDetail rendered its map/charts/buttons inside setTimeout blocks
guarded by renderToken. On WebKitGTK the deferred render was voided, so the
track detail showed no map, charts, or fullscreen button (while the
synchronously-rendered roads view worked).

- Remove all setTimeout/requestAnimationFrame/IIFE from showSessionDetail;
  render map, charts and wire buttons synchronously right after the modal
  markup exists (like showRoadsMap). Early `return`s become `if` guards;
  goals/food-cost keep real `await`. Force layout (offsetHeight) + call
  invalidateSize so Leaflet reads the real container size in the modal.

- Fix large i64 session-id corruption from tauri-plugin-sql 2.4.0 (ids come
  back off by 1 as JS numbers). Read/pass session ids as text via
  CAST(id AS TEXT) AS id_str everywhere they are reused: Trek list, Today
  click, compare modal/view, goal-marker prev sessions, map default-center,
  and importGpxSession (whose points were orphaned by the corrupted id).
  SQLite coerces the string to the INTEGER column. Point ids (max ~135k)
  are small and unaffected.
2026-07-03 22:19:58 +03:00
d4ef9a1f9f Fix: background GPS points lost on OS service restart
Regression from per-session points file change (1feb2ef): the service
returns START_STICKY, so when the OS kills it during screen-off, it
restarts with a null intent and onStartCommand minted a NEW points file,
repointing gps_live_points.jsonl and orphaning accumulated background
points. Evidence: many tiny orphaned gps_points_*.jsonl fragments.

Fix: onStartCommand creates a new session file only on a genuine start
from JS (intent with extras). A START_STICKY restart (null intent) keeps
writing to the existing session file from the pointer — one file per
session until Stop.

Verified on device: am kill (= START_STICKY restart) no longer creates
a new file, pointer unchanged, trail resumes on return. Background
writing confirmed: 49 points in 70s with screen off (Dozing) at 1Hz.
2026-07-02 22:29:29 +03:00
97539c9110 Gimbal lock compensation, heading EMA smoothing, build.sh install command
Compass:
- Gimbal lock compensation: when phone tilts past horizontal (|beta|>90
  in landscape, |gamma|>90 in portrait), heading flips 180 — compensated
- EMA smoothing for heading (factor 0.3, ~300ms settle) — reduces jitter
  from road bumps while keeping responsive turns
- 3 orient modes: X(off) A(auto) L(lock) — lock freezes layout, compass
  still works with gimbal compensation
- Debug overlay on map shows alpha/beta/gamma/heading/mode

Build:
- ./build.sh install — install last APK without building
- ./build.sh -d install — install debug APK
- install_apk respects -d flag for debug/release selection
2026-07-01 01:12:06 +03:00
50ec5c21f8 Health-diet.ru products (24K), GPS session safety, build.sh upload fix
Products:
- health-diet.ru scraper (Tampermonkey) + import script
- 11469 products with 37 nutrients, Dodo 258, McDonalds 619, KFC 361, Ashan 2000
- Rust import_seed_products: dynamic column list, auto-create missing columns
- Seed version bumped to 10

GPS session safety:
- Separate gps_points_{ts}.jsonl per session, deleted only after DB save
- Toast on DB errors, NaN guard in finishGpsSession

Other:
- build.sh: router/VPS upload only with -u flag
- Orientation buttons bottom-right, weather via Yandex Pogoda
2026-06-28 23:17:51 +03:00
aabcde57da Update docs: activity types, training MET, tile import, road map 2026-06-26 21:05:31 +03:00
4ed6e6f039 Activity types (squats/jumprope/stretch/swim), road map, tile import, fixes
Activity types:
- New types: squats, jump rope, stretching, swimming pool
- MET-based kcal auto-calculation (6.0/12.3/2.3/8.0)
- Dynamic modal fields per type (sets/reps, distance, minutes)
- Fix: state.latestWeight is object {weight_kg}, not number

Road map:
- road_points table with 2m dedup, activity type (bike/walk)
- Rust rebuild_roads + get_road_points (bypass SQL plugin 28K limit)
- Segment detection by 100m gap, bike=blue walk=brown

Tile system:
- CSP fix: a.tile.openstreetmap.org matches *.tile pattern
- pack_tiles.js tool, import from file/router, ureq body limit bypass
- Tile errors: done(null,tile) prevents crash cascade

Other fixes:
- Manual routes: skip gap segmentation (synthetic timestamps)
- Draw route: own CSS class, not affected by landscape rotation
- Orientation lock button (A/L), hysteresis 65/35 degrees
- GPS placeholder only when state=waiting_gps
- Food amount inputmode=text for formula support (4*30)
- showModal onMount callback
2026-06-26 21:04:21 +03:00
6b250c0c5d Update docs: tile import, road map, orientation lock 2026-06-26 16:47:52 +03:00
184b86b18c Tile import, road map, orientation lock, CSP fix, tile error handling
- Tile import: pack_tiles.js (PNG→SQLite), Rust import_tiles + download_file
  commands, JS importFromSqlite, UI in settings (file + router download)
- Road map: road_points table with 2m dedup, rebuild_roads + get_road_points
  Rust commands (bypass Tauri SQL plugin 28K row limit), bike=blue walk=brown
- Orientation lock button (A/L) on recording map, hysteresis 65/35 degrees
- CSP fix: tile URL a.tile.openstreetmap.org (matches *.tile CSP pattern)
- Tile errors: done(null, tile) instead of Error to prevent crash cascade
- GPS placeholder: only show when state=waiting_gps (not on screen re-enter)
- ureq body size: use into_reader() to bypass 10MB limit for tile downloads
2026-06-26 16:41:14 +03:00
4d9c0f8de2 Update docs: compass rotation, landscape mode, sync, dead reckoning, placeholder 2026-06-25 21:02:37 +03:00
704c0892ec Landscape panel on the right side, nav hidden, GPS placeholder fix
- flex-direction:row in landscape — panel 200px on the right, stats vertical
- CSS rotation compensates phone orientation, user sees element coords directly
- Bottom nav hidden in landscape, restored on exit
- GPS placeholder overlay: separate div from map container to avoid Leaflet conflict
- display:contents removed, clean column layout for stats in landscape
2026-06-25 18:23:58 +03:00
fda820568e Compass map rotation, landscape layout, GPS placeholder
- leaflet-rotate plugin: map rotates by compass heading via deviceorientation
- Landscape detection: CSS viewport rotation when phone held sideways (gamma > 50)
- Bottom nav hidden in landscape mode
- GPS placeholder: pixel art overlay shown until GPS signal arrives
- invalidateSize() after map init to prevent black screen
2026-06-25 16:54:45 +03:00
ad33c6defe Update README with manual routes, export/import, OTA router 2026-06-18 22:55:32 +03:00
19282ded78 Sensors, walk limits, GPS fixes, water UI, manual routes, Gridnev products, OTA router
GPS altitude: accel-based glitch filter (accel=0 + speed = GPS drift), altitude
  jump >100m filter, timestamp sanity (>24h = remove). Per-point Crr from accel
  vibration (smooth/mixed/gravel). Gap interpolation on map with dashed lines
  when accel confirms movement. Walk/run separate speed limits (configurable).
  Adaptive GPS accuracy (auto x2 when signal bad). Dead reckoning points when
  GPS silent but accel shows movement. Locale.US fix for sensor JSON.

Sensors: accelerometer + gyroscope in GpsRecorderService (RMS, max, pitch, yaw).
  Columns in gps_points. Dynamic addGpsPoint INSERT (safe if columns missing).

Manual routes: draw on map by tapping points, elevation from past tracks +
  open-meteo API, realistic timestamps by activity speed. Manual sessions
  skip glitch filtering on recalculate. Speed from GPS.config not hardcoded.

Water tracking: water_pct field in product add/edit forms, searchProducts
  returns water_pct, "ml" label for liquids, recipe water from ingredients.

BMR: moving average weight (7 days), fixed charts using dailyBase not raw bmr.

BLE: ble_clear_hr on stop, recovery toast, serde alias for ts/timestamp.

Products: 217 Gridnev products (bakery, desserts, bread, ready meals).
  Magnit scraper fix (NUXT_DATA name/value pairs, not indices). Supplements
  addSupplement with all 41 columns. Nutrient fallback RDI includes omega/EPA/DHA.

Export/import: custom products + recipes + supplements in single JSON bundle.
  Export all or single product. Dedup on import by name_lower + source.

OTA: router upload (LAN, always), VPS upload (-u flag). Kotlin checks router
  first (2s timeout), then VPS. Update URL from update_info.json.

Session detail: renderToken prevents stale setTimeout callbacks after recalculate.
WAL disabled (PRAGMA journal_mode=DELETE).
2026-06-18 22:54:45 +03:00
926fd70c88 Update README and PROJECT_MAP with BLE HR, water tracking, OTA, nutrients, altitude filter 2026-06-07 01:41:33 +03:00
7c93bb75c3 OTA updates, BLE HR, water tracking, GPS altitude filter, BMR fix, nutrient units
OTA: VPS nginx endpoint, build.sh -u upload flag, Kotlin check + browser download
BLE HR: FitPro protocol (PAIR/CCCD/HR_SWITCH/REALTIME/MEASURE), GATT queue,
  ACK responses, auto-detect device type, auto-start with GPS, re-trigger 25s
  Kotlin BleHrService, btleplug for desktop, file IPC for Android
Water: products.water_pct column, getDailyWaterMl(), progress bar on today screen
GPS altitude: outlier spike filter (>4m), stuck run interpolation (5+ identical),
  configurable smoothing (altSmoothHalf setting), filtered raw for kcal calc
BMR: removed broken estimateRealBmr, added activity level dropdown (1.2-1.725),
  fixed 7-day trend using dailyBase instead of raw bmr
Nutrients: ginseng/resveratrol/coq10 columns, fixed folate/selenium units (removed
  mult:1000), iodine/chromium input in mcg, addSupplement now inserts all 41 columns
UI: parseAmount() for 4*30 expressions, recipe portion amount field, nutrient
  reserves strip, GPS sessions in today view, pan on charts, showToast(),
  HR norms in settings, HR on recording screen, HR chart in track details
Scrapers: 5ka Load JSON button, magnit_scraper.user.js (NUXT_DATA parsing)
2026-06-07 01:36:38 +03:00
0bafb2b317 АУП norms with star, nutrient half-life table, recipe nutrients in daily strip
RDI/АУП:
- rdi_values.norm_type column: 'rdi' or 'aup'
- АУП values seeded: omega3 1000mg, EPA/DHA 250mg, lutein 5mg,
  lycopene 5mg, silicon 30mg, vanadium 0.04mg (all 3 standards)
- Nutrient chips show * after label for АУП norms
- Supplement form placeholders show RDI/АУП values (with * for АУП)

Half-life table:
- nutrient_halflife: nutrient_key, min/max/avg days, source
- 32 nutrients seeded with whole-body biological half-life data
- Sources: PubMed, ICRP, Performance Lab, clinical studies

Recipe nutrients in daily strip:
- recipes.cached_nutrients TEXT column (JSON)
- cacheRecipeNutrients() computes and stores per-serving nutrients
- Called on recipe save in editor
- calcDailyNutrients() now reads cached_nutrients for recipe food entries
- getFoodForDate() JOINs r.cached_nutrients

BMR.loadRDI() now loads norm_type alongside values.
BMR.RDI_TYPES exposed for star rendering.
2026-06-02 18:39:09 +03:00
c72a22a965 Fix dedup FK: delete product_units refs before products, remove PRAGMA hack
FOREIGN KEY constraint failed because product_units table has FK to
products(id) and wasn't cleaned before DELETE. Now: delete orphaned
product_units rows first, then delete dupe products. No PRAGMA needed.

Verified on desktop: 16675 → 8330 with FK enforcement ON.
2026-06-01 22:43:02 +03:00
46ad9022c2 Fix dedup: PRAGMA foreign_keys=OFF before delete, alert on seed error
FOREIGN KEY constraint failed on desktop (rusqlite enables FK by default).
Android was silently failing too. Fix: disable FK during dedup, re-enable after.

Tested: desktop 16675 → 8330 products, idx_products_dedup created.
Also: alert() instead of boot-label for seed errors (visible to user).
2026-06-01 21:25:23 +03:00
ed20d4a9c8 Fix DB path: 3 levels up from app_data_dir, not 2 (files/dev.baldav.app/ → root) 2026-06-01 17:33:59 +03:00
9a4d62dd03 Fix dedup not running: Rust opened wrong DB path on Android
Root cause: app_data_dir() = files/dev.baldav.app/ but tauri-plugin-sql
stores baldav.db in app root /data/user/0/pkg/baldav.db (two levels up).
Rust created empty DB at wrong path, dedup ran on empty DB = no-op.

Fix: added p_root = app_data_dir/../../../baldav.db check.
Also: show seed error on boot label instead of swallowing silently.
2026-06-01 17:21:36 +03:00
a0c7dfb5e2 Fix dedup: run BEFORE version check, RDI standards from DB
Dedup fix:
- Dedup SQL now runs BEFORE seed version check (was inside "if new version" block,
  never executed when version matched). 16675→8330 products.
- UNIQUE INDEX on (name_lower, source, COALESCE(store_id,'')) prevents future dupes
- Errors propagated via map_err (no more silent failures)

RDI standards from DB (not hardcoded):
- rdi_standards table: RU, EU, US
- rdi_values table: nutrient_key + value per standard
- 26 nutrients seeded for all 3 standards
- BMR.loadRDI(code) reads from DB, replaces hardcoded RDI object
- Settings: "Стандарт РСП" dropdown (РФ/ЕС/США)
- Profile stores selected standard, loaded at init
- Fallback: hardcoded RU values until DB ready
2026-06-01 16:45:30 +03:00
01427941d4 Fix dedup: use execute() not execute_batch(), propagate errors
execute_batch() silently failed on multi-statement dedup SQL.
Split into 4 separate execute() calls with map_err — errors
now propagate to JS and show to user instead of being swallowed.
2026-06-01 14:58:15 +03:00
b2b6b6a7ef Dedup products, fix labels, fix track comparison, seed v9
Dedup products in Rust import (not JS — JS was too slow/hung):
- Key: (name_lower, source, COALESCE(store_id,''))
- Same product from same store = duplicate, deleted
- Different sources (magnit vs ion vs manual) = separate products
- Reassigns food_log + recipe_ingredients refs before deleting
- UNIQUE INDEX prevents future dupes (INSERT OR IGNORE now works)
- Tested: 16675 -> 8330 in 0.15s on device DB copy

Removed JS dedup migration (replaced by Rust).

Order in import_seed_products:
1. Index name_lower
2. Dedup (reassign refs + delete)
3. UNIQUE INDEX
4. INSERT OR IGNORE from seed
5. UPDATE store products
6. seed_version + COMMIT
2026-06-01 14:51:38 +03:00
45d3736a99 Chart zoom 2x faster, chart padding, clearer food cost format
Charts:
- Zoom speed: 0.02 -> 0.04 (all 5 chart instances)
- stats-chart-block: added padding 0 14px to match content margins

Food cost of workout:
- Was: "1.8 x Хлеб (102г, 273 кк)" — ambiguous "1.8 x"
- Now: "1.8 порц. Хлеб (по 102г, 273 кк/порц.)" — clear units
- Header: "485 ккал — это как:" instead of "485 ккал ="
- Math: 485/273 = 1.78 portions of 102g each

Duplicates: seed DB has none (verified). Device dupes from pre-dedup
imports — JS migration handles them on next launch.
2026-06-01 13:51:47 +03:00
8ad8fc2da8 Source badges in all search dropdowns (food, ingredient, edit food) 2026-05-31 01:30:25 +03:00
b4255ef2d5 Block product deletion if used in food log or recipes 2026-05-31 00:48:23 +03:00
bcb4124be0 Delete product button on expanded card, deleteProduct DB function
- "Уд." button (red) next to "Ред." on product card
- deleteProduct: nullifies food_log refs, zeros recipe_ingredients refs, deletes product
- List refreshes after deletion
2026-05-30 21:08:32 +03:00
d365554a7b Fix dedup migration: 3 bulk SQL queries instead of per-dupe loop
Was: for loop with 3 IPC calls per duplicate group = thousands of
round-trips = 10+ minutes on phone. Now: 3 bulk UPDATE/DELETE
statements that run in SQLite directly. One-time migration,
subsequent launches skip (COUNT=0).
2026-05-30 20:55:20 +03:00
0136b8ae0e Dedup products, fix labels, fix track comparison, seed v9
1. Deduplicate products: 13482 -> 8325 in seed DB. JS migration
   deduplicates app DB on launch: reassigns food_log and
   recipe_ingredients to kept product before deleting dupes.

2. MICRO_FIELDS: full chemical names + alternatives for all nutrients.
   "B3 / PP / ниацин", "B9 / фолиевая", "Кальций / Ca" etc.

3. Track comparison: newer track always compared against older.
   Green = newer faster (progress), Red = newer slower (regression).
   Removed separate cyan dashed track — only colored segments.
   Order of selection doesn't matter — date determines which is newer.

4. Source badges in search: source column was already in SELECT,
   badges render via sourceBadge(p.source) in prod card.

5. Gap threshold already cosmetic-only (confirmed, no changes needed).

Seed DB v9, VACUUM'd, indexed.
2026-05-30 18:09:16 +03:00
81125c03fa Remove unused Emitter import and mut inserted warning 2026-05-30 16:32:26 +03:00
44dfa0b88a Add name_lower index: seed import 56s -> 0.09s (600x faster)
ATTACH UPDATE with correlated subqueries on name_lower was doing
full table scans on 13000 rows. 56 seconds on desktop, 2-5 min
on phone. User saw "загрузка..." for minutes on every launch.

Fix: CREATE INDEX on name_lower in both main and seed DBs before
the UPDATE. Also pre-built index in seed DB file.

Benchmarked: 56.5s -> 0.09s on desktop. Phone should be <1s.
2026-05-30 16:24:47 +03:00
8d9b5aaf7e Source badges on products: Магнит (red), 5ка (green), KDV (orange), ИОН (gray), Свой (yellow)
- sourceBadge() helper renders colored badge by source field
- searchProducts SELECT now includes source column
- getFoodForDate SELECT includes p.source as product_source
- Badge shown on product cards in search and food log entries
- CSS: .src-badge with per-source colors (pastel, dark-theme friendly)
2026-05-30 15:59:50 +03:00
2df5908fca Fix product save: add name_lower, product-only micro fields, strawberry
Root cause: addProduct INSERT didn't include name_lower column.
Search uses WHERE name_lower LIKE — products with NULL name_lower
never found. User entered 20 min of strawberry data for nothing.

Fixes:
- addProduct: includes name_lower = name.toLowerCase()
- Added phosphorus_mg and carotene_mcg to addProduct INSERT
- PRODUCT_MICRO_FIELDS: subset of MICRO_FIELDS for product forms
  (excludes supplement-only fields: omega, EPA/DHA, fish oil etc.)
- showAddProductModal and showEditProductModal use PRODUCT_MICRO_FIELDS
- Rust: removed stale inserted+=1 line
- Seed DB v8: added "Клубника свежая" (alias for "Земляника садовая")
2026-05-30 15:33:59 +03:00
f20d45aa18 Bulk seed import: INSERT SELECT instead of per-row loop
Was: 13000 individual INSERT OR IGNORE in a loop = 2+ minutes.
Now: one INSERT ... SELECT FROM seed.products = seconds.

Flow:
1. ATTACH seed DB
2. INSERT OR IGNORE ... SELECT (bulk, new products)
3. UPDATE store products with KBJU + micronutrients (user_edited=0 only)
4. UPDATE nutrients_guessed flag
5. INSERT OR REPLACE seed_version into profile
6. COMMIT (all or nothing — crash-safe)
7. DETACH

seed_version written INSIDE transaction — if app crashes before COMMIT,
next launch re-does the import. No partial state.
2026-05-29 22:33:12 +03:00
ca45a5acc0 Protect user-edited products from seed overwrites
New column: user_edited INTEGER DEFAULT 0 on products table.
- JS migration adds column
- showEditProductModal sets user_edited=1 on save
- Rust seed import: UPDATE store products (source IN magnit/5ka/kdv/ion)
  WHERE user_edited IS NULL OR user_edited = 0
- Now updates KBJU + all micronutrients from seed (not just INSERT OR IGNORE)
- User edits preserved: user_edited=1 products never touched by seed import
- Manual/recipe_import products also never touched (source check)

Flow: seed update fixes wrong KBJU (e.g. bread 268→243) automatically,
but if user already fixed it manually — their fix stays.
2026-05-29 22:12:37 +03:00
3789747729 Edit product from search card, fix Ayutinskiy bread kcal
Product editing:
- "Ред." button on expanded product card
- showEditProductModal: edit name, KBJU, all micronutrients
- Updates product in DB directly, refreshes search

Bread fix:
- "Хлеб Тостовый пшеничный Аютинский 280г" was 268 in Magnit data
- Package says 243 kcal/100g — corrected in seed DB
- Seed version 7

Users can now fix any wrong KBJU directly from the product card
without re-importing or editing the seed DB manually.
2026-05-29 22:01:52 +03:00
25849b8537 Fix export: use Tauri save_file instead of blob, text button labels
Export was broken on Android — blob download (URL.createObjectURL)
doesn't work in WebView. Now uses Tauri save_file command which
triggers SAF file picker (same as GPX export).

Replaced cryptic arrow icons with readable text: "Импорт" / "Экспорт"
on both recipes and supplements screens.
2026-05-29 21:27:31 +03:00
5ecce90744 Export/import recipes and supplements via JSON files
Recipes:
- Export all: downloads baldav-recipes.json with ingredients + product KBJU
- Export one: button in recipe detail downloads {name}.json
- Import: reads JSON file, creates recipes + missing products

Supplements:
- Export all: downloads baldav-supplements.json
- Import: reads JSON, creates supplements (skips id, gets new one)

Buttons: arrow-down (import) and arrow-up (export) next to + on both screens.
Uses downloadFile() helper for browser download, pickFile() for file selection.
Import matches products by name, creates missing ones from exported KBJU.
2026-05-29 21:13:44 +03:00
7b41382ea4 Add-btn border: gray (--border) instead of primary 2026-05-29 20:30:36 +03:00
f871e5bb28 Add custom product with full nutrients, bigger + buttons with border
Custom product:
- "+" button next to search on Products tab
- showAddProductModal: name, KBJU, + all MICRO_FIELDS (34 nutrient fields)
- All optional except name
- Creates product with source="manual"
- Refreshes search after save

Button style:
- .add-btn: 0.5px border with primary color, rounded corners (6px)
- Min 40x36px for finger-friendly tapping on retina
- Flexbox centered content
2026-05-29 18:21:49 +03:00
b4b2914d00 Convert beta-carotene to vitamin A, remove from nutrient strip
12 mcg beta-carotene = 1 mcg retinol equivalent (vitamin A).
Carotene from food and supplements now adds to vit_a_mcg total.
Removed carotene_mcg from RDI table and NUTRIENT_LABELS —
no longer shows as separate "b-car 0%" chip misleading users.

Carrot 150g: 18000 mcg carotene / 12 = 1500 mcg vit A = 188% RDI.
Supplement field "Бета-каротин" kept in form for data entry.
2026-05-29 18:09:29 +03:00
cbac8d5d7b Fix carotene missing from food query, add fish oil field, full nutrient SELECT
- getFoodForDate: added p.phosphorus_mg, p.carotene_mcg (were missing)
- searchProducts: added carotene, selenium, zinc, copper, folate, D, K, B6, B12
- Supplement form: "Рыбий жир (мг)" field for total capsule weight
- Omega fields relabeled: "в т.ч." prefix to show they're part of fish oil

Carrot (12000 mcg carotene) was showing 0% because carotene_mcg
was not in the getFoodForDate SELECT.
2026-05-29 18:00:02 +03:00
e6674e2014 Fix B9 and Se showing 0%: unit mismatch mg vs mcg
B9 (folate) label said "мкг" but packaging shows "мг" — user entered
0.4 mg, DB stored 0.4 mcg, 0.4/200 = 0%. Fixed: label says "мг",
mult:1000 converts to mcg on save, back to mg on display.
Same for selenium: packaging uses mg, DB uses mcg.

Beta-carotene showing 0% is correct — Daily Men supplement contains
retinol acetate (vit A), not beta-carotene. They are different forms.
2026-05-29 17:53:57 +03:00
22a2f6296d Update PROJECT_MAP, README, session logs with current stats 2026-05-29 17:47:58 +03:00
4d97ba607b Always copy seed DB from assets, VACUUM seed to reclaim space
Size comparison was unreliable — SQLite reuses free pages internally,
so adding products didn't change file size. New seed never copied.
Now: always overwrite on every launch (~8MB, <1 sec).
VACUUM reduced seed from 8.5MB to 8.2MB.
2026-05-29 15:38:10 +03:00
636de9bc25 Add EPA and DHA fields to supplements (omega-3 breakdown) 2026-05-29 15:21:59 +03:00
c986b6af9a Russian names for minerals in supplement form: Кальций, Магний, Железо etc. 2026-05-29 14:59:15 +03:00