Commit Graph
100 Commits
Author SHA1 Message Date
Mathieu Moran e223a43170 docs: BUG-11 closed — GPS lat/lon from API, 30s staleness aligned across pages 2026-08-11 16:18:59 -04:00
Mathieu Moran 3202045654 fix: restore separate checkbox+ID cells, add matching headers (8 columns server) 2026-08-11 15:25:29 -04:00
Mathieu Moran e8c5bfec9a fix: put checkbox inside date cell — both Pi and server now share same 6-column layout 2026-08-11 15:22:05 -04:00
Mathieu Moran 22f4ae2e69 fix: merge ID into date cell, add empty checkbox header — columns now align 2026-08-11 15:17:51 -04:00
Mathieu Moran 5713417f4f Server: restore checkbox + batch-delete. Conditional ID (server only) and checkbox via data-show-id/data-batch-delete-url. 2026-08-11 15:11:12 -04:00
Mathieu Moran 2ee4862edd Remove checkbox column and batch-delete from trip history 2026-08-11 15:05:40 -04:00
Mathieu Moran 23ac6c218b feat: add trip ID column (#18517) to history table on Pi and server 2026-08-11 14:58:25 -04:00
Mathieu Moran f6a50b6bc1 docs: BUG-06 closed — migration shifted 3593 rows +4h EDT, recomputed 43 trips, times now correct 2026-08-11 14:53:18 -04:00
Mathieu Moran 7ce2a1e5f5 fix: BUG-06 — convert naive Pi timestamps to TZ-aware before upload so Postgres TIMESTAMPTZ stores correct instant 2026-08-11 14:47:37 -04:00
Mathieu Moran 9733036510 fix: BUG-11 — Dashboard GPS now shows lat/lon from API when stale, Status page uses same 30s staleness threshold 2026-08-11 14:36:58 -04:00
Mathieu Moran 7a6de8b589 docs: BUG-07 closed — history.js rewrite verified on Pi and server 2026-08-11 14:33:38 -04:00
Mathieu Moran fa93384a8e Rewrite history.js: loading spinner, active-row highlight, unified state, 300ms debounce, signal guards throughout trip loader 2026-08-11 05:37:15 -04:00
Mathieu Moran 3d6955f20f fix(phase26): fix broken click handler, remove replay button, row click only 2026-08-11 05:11:09 -04:00
Mathieu Moran 90edc73735 fix(phase26): debounce trip clicks, guard against stale renders 2026-08-11 05:07:35 -04:00
Mathieu Moran cdb3fc19c7 fix(phase26): abort preload fetches when switching trips to prevent server freeze 2026-08-11 05:01:18 -04:00
Mathieu Moran ad56380e23 fix(phase26): revert batch geocode, use rate-limited concurrent requests to avoid server freeze 2026-08-11 04:57:44 -04:00
Mathieu Moran 5018c6c531 fix(phase26): batch geocode endpoint for faster server trip scrubber 2026-08-11 04:44:37 -04:00
Mathieu Moran bdc7a9afb7 fix(phase26): server trip delete persists across recompute + batch delete with checkboxes 2026-08-11 04:18:58 -04:00
Mathieu Moran e6bc488e84 docs: update Phase 26 with dual-radio + trip fix details 2026-08-11 03:57:06 -04:00
Mathieu Moran cf096744d1 fix(phase26): BUG-10 side effect — preserve trips when OBD never connected 2026-08-11 03:56:00 -04:00
Mathieu Moran ae7f6358b2 docs: update BUG-01 dual-radio fix status 2026-08-11 03:46:18 -04:00
Mathieu Moran 592e049f59 fix(phase26): dual-radio — wlan0 permanent AP, wlan1 internet uplink (BUG-01) 2026-08-11 03:44:01 -04:00
Mathieu Moran 89bbc4fdb8 fix(phase26): skip netwatch AP scan when clients connected (BUG-01) 2026-08-11 03:11:13 -04:00
Mathieu Moran f9d967e71c docs: update Phase 26 bug count in PLAN.md 2026-08-11 03:02:02 -04:00
Mathieu Moran e83e5eaa7c docs: tighten features list in readme 2026-08-11 03:00:35 -04:00
Mathieu Moran 6355869a8d docs: fix bug count in readme project status 2026-08-11 02:58:01 -04:00
Mathieu Moran f7d305a036 docs: update Phase 26, simplify readme project status, de-emoji 2026-08-11 02:57:01 -04:00
Mathieu Moran 1487fd3478 docs: bold feature names, clean up readme 2026-08-11 02:55:27 -04:00
Mathieu Moran e733ab1ac1 docs: update Phase 26 progress, de-emoji readme, refresh status 2026-08-11 02:50:57 -04:00
Mathieu Moran 936064e963 docs: move BUG-09/10 from Open to Resolved 2026-08-11 02:47:26 -04:00
Mathieu Moran 7beb882136 docs: restructure BUGS.md, close BUG-09/10 (already deployed) 2026-08-11 02:45:40 -04:00
Mathieu Moran 5a3be5347a docs: close BUG-09/10, update resolved section 2026-08-11 02:41:12 -04:00
Mathieu Moran 7e913558f6 fix(phase26): geocode pre-caching (BUG-09) + gate trips on OBD presence (BUG-10) 2026-08-11 02:38:00 -04:00
Mathieu Moran a709517973 docs: close BUG-04 server trip merge (verified in prod) 2026-08-11 02:31:13 -04:00
Mathieu Moran 96fb8971eb fix(phase26): server trip detection mirrors Pi motion-aware logic (BUG-04) 2026-08-11 02:27:17 -04:00
Mathieu Moran 447d73bbe5 fix(phase26): close BUG-08 Pi trip deletion (verified in prod) 2026-08-11 02:12:40 -04:00
Mathieu Moran c5c478bef6 fix(phase26): add Pi trip deletion (BUG-08) 2026-08-11 02:07:59 -04:00
Mathieu Moran 786d5b8f3e docs: close BUG-03/05, add Phase 27 features + renumber Android to Phase 28 2026-08-11 02:04:55 -04:00
Mathieu Moran aa1f247d80 docs(bugs): record Phase 26 deploy/verify status for BUG-03 and BUG-05
Amend the Phase 26 progress note: BUG-03 was deployed to the Pi (pulled to
4f975ad, service active, /api/health ok) and real in-car behavior verification
is still pending; BUG-05 is code-ready but the unraid/server side is not yet
deployed (needs docker compose build api && docker compose up -d api).
2026-08-11 01:23:39 -04:00
Mathieu Moran 4f975ad079 docs(bugs): mark BUG-03 and BUG-05 FIXED (e2f2314) + update PLAN Phase 26
BUGS.md: BUG-03 and BUG-05 status -> FIXED with commit ref and fix summary.
PLAN.md: Phase 26 items 3 and 5 marked FIXED; add progress note.
2026-08-11 00:48:05 -04:00
Mathieu Moran e2f2314318 fix(phase26): persist DTC/MIL state + server speed_source (BUG-03, BUG-05)
BUG-03: DTC/MIL codes were never persisted; lost on OBD disconnect or reboot.
- Add new dtc table schema (code, description, MIL, first/last seen, IS_SET)
  and a migration that rebuilds the original unused NAME/STATUS/DTC_ID table.
- Persist MIL + codes to SQLite on each successful OBD poll (_persist_dtc_state).
- /api/dtc falls back to the last-persisted snapshot when OBD is offline/stale.
- Fix get_overview_stats to count the real dtc table (was querying non-existent
  dtc_codes table).
- Add cleared/since tracking via IS_SET + FIRST_SEEN_AT/LAST_SEEN_AT.

BUG-05: server trip replay hardcoded speed_source:'gps'; mirror Pi's
'obd if speed>0 else gps' logic in server get_trip_route.

Adds tests/test_dtc_persistence.py (7 tests).
2026-08-11 00:38:05 -04:00
Mathieu Moran cd0583ac84 docs: add FEAT-04 4G LTE modem (Huawei E3372h-153) to replace SIM868 2G hat 2026-08-11 00:03:15 -04:00
Mathieu Moran bd43003a7e docs: rename features.md to FEATURES.md (all caps) 2026-08-10 23:46:43 -04:00
Mathieu Moran ef3a9510ab docs: add features.md tracker with odometer-fallback idea 2026-08-10 23:45:52 -04:00
Mathieu Moran 3e626830d5 docs: sign off Phase 25 road test, add BUGS.md, renumber Android to Phase 27 2026-08-10 23:42:39 -04:00
Mathieu MoranandCopilot d216aac97e Refresh docs and reorder roadmap
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:48:59 -04:00
Mathieu MoranandCopilot 457e3dcc08 Record dashboard card order swap
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:43:46 -04:00
Mathieu MoranandCopilot b815e0aaaa Swap dashboard map and overview
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:43:01 -04:00
Mathieu MoranandCopilot 6ee6215662 Record status action verification
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:41:34 -04:00
Mathieu MoranandCopilot 8907c51016 Add status page quick actions
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:40:22 -04:00
Mathieu MoranandCopilot ef38ec92b4 Record status page verification
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:38:16 -04:00
Mathieu MoranandCopilot 5406935bf5 Add Pi status page
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:37:26 -04:00
Mathieu MoranandCopilot a7302229d1 Tighten Pi dashboard layout
Co-authored-by: Copilot <[email protected]>
2026-08-10 18:25:30 -04:00
Mathieu MoranandCopilot f6de658a88 docs: record live Pi GPS status check
- note that GPS thread is alive on the Pi but has no current fix
- clarify that missing live updates are currently due to absent GNSS fix data

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:18:50 -04:00
Mathieu MoranandCopilot be40bbc15f feat: add GPS status endpoint and dashboard indicator
- add /api/gps/status for thread/fix-age visibility
- surface GPS status in Pi health and live GPS cards
- add tests for app_gps status snapshots

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:18:14 -04:00
Mathieu MoranandCopilot 675bffb4a4 feat: add server-side device deletion
- add portal API and UI action to delete non-default devices
- protect the configured portal default device from deletion
- document that trip/history pages still scope to the single DEVICE_NAME

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:09:15 -04:00
Mathieu MoranandCopilot 690cf0a5d4 docs: record live Phase 25.5c deployment validation
- mark Phase 25.5c complete in PLAN
- record unraid migration/deploy and Pi API smoke tests
- note sync-ack metadata and temp provisioning helper validation

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:05:39 -04:00
Mathieu MoranandCopilot ba682fbf3a feat: add multi-device provisioning and local remote-config UI
- add multi-device portal device management, key rotation, and sync request APIs
- add Pi settings UI for remote server URL, device name, and API key
- add local test-connection and sync-now actions
- make upload and config sync reuse dynamic shared credentials
- add Phase 25.5c migration and docs updates

Co-authored-by: Copilot <[email protected]>
2026-08-10 18:02:09 -04:00
Mathieu MoranandCopilot fbc41a5db5 docs: record live Phase 25.5b sync verification
- update plan with live portal route deployment status
- document fixed Pi sync blockers and successful round-trip test
- refresh passing test count after runtime sync coverage additions

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:48:42 -04:00
Mathieu MoranandCopilot a114939914 fix: authenticate device config sync like upload client
- send X-Device-Name and X-Api-Key to /api/device-config
- require YARIS_DEVICE_NAME alongside shared upload creds
- fix server create_device output to match actual Pi env names
- document shared upload/config sync credential requirements

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:42:40 -04:00
Mathieu MoranandCopilot 5a5058adca fix: reuse upload credentials for device config sync
- load server sync creds from db/upload_creds.env like uploads do
- fall back from YARIS_DEVICE_KEY to existing YARIS_API_KEY
- document shared credential flow for server-synced identity
- add test coverage for sync credential fallback

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:37:52 -04:00
Mathieu MoranandCopilot 585da18225 feat: add remote device settings portal and Pi hot reload
- add /settings/device portal page and portal device-config GET endpoint
- add portal navigation and client-side device settings editor UI
- hot-reload Account/Car display config after local saves and server syncs
- document portal workflow and Phase 25.5b completion
- add runtime reload tests for account, car, and sync callback

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:34:22 -04:00
Mathieu MoranandCopilot c4ccc3efb9 docs: record verified Pi and unraid deployment state
- note verified Pi IP after WiFi card change
- record successful 2026-08-10 Pi/unraid rollout
- document unraid checkout divergence and temp-clone deploy workaround

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:26:17 -04:00
Mathieu MoranandCopilot cd004fbdde fix: wire Phase 25.5a deploy docs and sync connectivity
- use app_network status for device sync connectivity checks
- mount migrate_phase25_5a.sql in server docker compose
- document manual server upgrade migration steps
- record verified Pi and unraid SSH access details in PLAN

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:22:02 -04:00
Mathieu MoranandCopilot 0bda44a7bc Phase 25.5a: Server-side device config API and Pi sync thread
- Created app_device_sync.py: background sync thread with connectivity-driven scheduling (5min online, 60sec backoff when offline)
- Server /api/device-config endpoints: GET for Pi device auth, POST for admin user auth
- Database migration adds config_version, owner_name, car_model, device_name, avatar_path to devices table
- Pi syncs config from server on network availability; offline-first design (loads from local JSON at startup)
- Settings UI fields read-only when server sync enabled

All 63 tests passing. Ready for testing with actual server connection.

Co-authored-by: Copilot <[email protected]>
2026-08-10 17:17:04 -04:00
Mathieu MoranandCopilot 2395cf6dc4 feat: add Phase 25.5a server-side device config API endpoints
- Add database migration (migrate_phase25_5a.sql):
  * Add owner_name, car_model, device_name, avatar_path columns to devices table
  * Add config_version and config_updated_at for cache invalidation

- Add server/modules/device_config.py:
  * get_device_config(device_id): retrieve config for Pi sync
  * update_device_config(device_id, data): update config with validation

- Add server API endpoints:
  * GET /api/device-config (authenticated with device_key, Pi fetches config)
  * POST /api/device-config (authenticated as user, admin updates config)

- Enhance server/modules/db.py:
  * Add get_device_by_id() function to retrieve all device config fields

Phase 25.5a enables server-side device identity management and connection-driven sync.
Pi will fetch from this endpoint when internet available, keeping config in sync.

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:46:52 -04:00
Mathieu MoranandCopilot ed78700005 docs: update Phase 25.5 to reflect settings UI implementation
- Settings UI now implemented for device config editing
- Sync toggle blocks field edits when enabled
- All behaviors tested and verified on Pi
- Phase 25.5a will add server-side config endpoints and connection-driven sync

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:44:03 -04:00
Mathieu MoranandCopilot d7b9bb634f feat: add device config settings UI with optional server sync
- Add app_device_config.py module for managing device configuration (owner_name, car_model, avatar_path, device_name)
- Add sync_enabled flag to control whether Pi syncs config from server
- When sync is enabled, device config fields are read-only (server is source of truth)
- When sync is disabled, user can edit device config via Settings page
- Add /api/device-config endpoint (GET to retrieve, POST to update)
- Add Device identity section to settings.html with form fields for all config values
- Update settings.js to load/save device config with sync toggle
- Gracefully handles both config file (at startup) and database (live updates via settings UI)
- All 63 tests passing

Phase 25.5: Offline-first device config with eventual consistency. Config loads from JSON at startup (no network needed), can be edited via settings if not syncing, and will sync from server on next connectivity when Phase 25.5a server endpoints are implemented.

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:41:45 -04:00
Mathieu MoranandCopilot c65a635906 implement: Phase 25.5 device config from JSON file
- Create config/device.json with owner_name, car_model, device_name, avatar_path
- Update app_account.py to load owner_name and avatar_path from config
- Update app_car.py to load car_model and device_name from config
- Update base.html template to use avatar_path from config instead of hardcoded
- Add graceful fallback to hardcoded defaults if config file missing
- Document in deploy/readme.md and readme.md how to customize device identity
- Update PLAN.md Phase 25.5 to reflect current implementation and future server sync

Benefits:
- No code edits needed to customize Pi (edit JSON file only)
- Offline-first: all values loaded from local file at startup
- Connection-driven sync planned for Phase 25.5a (sync when internet available, not on timer)
- Server becomes source of truth after Phase 25.5a implementation

All 63 tests passing. Database filename still based on owner_name, with migration warning in docs.

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:31:59 -04:00
Mathieu MoranandCopilot 69beb07430 docs: add Phase 25.5 device config sync plan
Plan for replacing hardcoded user/car/avatar with managed server-synced configuration, enabling multi-device and zero-code-edit deployment scenarios.

Co-authored-by: Copilot <[email protected]>
2026-08-10 16:21:49 -04:00
Mathieu MoranandCopilot 235ca65822 docs: add profile and car customization guide
Co-authored-by: Copilot <[email protected]>
2026-08-10 16:21:05 -04:00
Mathieu MoranandCopilot 2c0e9f7e25 docs: add RTL8812BU driver install to deploy guide
Co-authored-by: Copilot <[email protected]>
2026-08-10 16:14:38 -04:00
Mathieu MoranandCopilot d2f9ea4209 Re-add redacted UI screenshot after history purge
Co-authored-by: Copilot <[email protected]>
2026-08-10 16:10:04 -04:00
Mathieu MoranandCopilot ae0bcc7ea8 Add fresh-Pi deploy guide and uplink consistency fixes
- add deploy/readme.md with full fresh install + quick bootstrap block
- update README with deploy guide link and hardware photo
- fix app_network interface detection for wlan1-first setup
- make dispatcher hook handle wlan0 and wlan1
- update PLAN.md with Phase 24.8 completion notes

Co-authored-by: Copilot <[email protected]>
2026-08-10 15:54:36 -04:00
Mathieu MoranandCopilot 0cd798ee19 fix: use total trip distance as odometer fallback when PID 0xA6 unavailable
If no real odometer data exists (all NULL — expected on 2013 Yaris which
likely doesn't support PID 0xA6), fall back to SUM(distance_km) from the
computed trips table so the overview shows something meaningful.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:59:53 -04:00
Mathieu MoranandCopilot 8f8c03a26a fix: server odometer stat shows 0 when no odometer data exists
COALESCE(MAX(odometer), 0) returned 0 when all tracking rows have NULL
odometer (expected before the car is connected — PID 0xA6 likely unsupported
on 2013 Yaris). Now returns NULL which the overview JS renders as '—'.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:58:22 -04:00
Mathieu MoranandCopilot a7ab088cf5 docs: update PLAN.md and readme for phases 24.5-24.7
- PLAN.md: add Phase 24.7 section (diagnose.sh, map tile fixes, app.py cleanup)
- readme.md: update project status to reflect phases 24-24.7 complete,
  add remote portal to features list, add diagnostics/recovery section

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:55:37 -04:00
Mathieu MoranandCopilot f90a5e5826 fix: switch server portal map tiles to CARTO (OSM was rate-limiting/blocking)
OSM tile.openstreetmap.org was returning grey tiles on the portal (likely
rate-limiting the Cloudflare tunnel egress IP). Switch to CARTO Voyager
tiles which are free, CDN-backed, and don't have the same restrictions.

Also add data-tile-subdomains attribute support in history.js so tile
provider subdomains can be configured per-deployment.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:51:18 -04:00
Mathieu MoranandCopilot 5e0b30759d fix: always add tile layer at map init, fix zoom bounds
- tiles.addTo(map) now called at init instead of lazily on first trip click
  (fixes blank map on page load on the server portal)
- minZoom/maxZoom read from data-min-zoom/data-max-zoom attributes with
  correct NaN fallback (parseInt || default)
- Pi template: data-min-zoom=8 data-max-zoom=15 (matches cached tile range)
- Server template: data-min-zoom=3 data-max-zoom=19 (full OSM range)

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:46:45 -04:00
Mathieu MoranandCopilot c287ab67a3 fix: map zoom limits configurable per-deployment via data attributes
Pi offline tiles only cover zoom 8-15; OSM live tiles support 0-19.
history.js now reads data-min-zoom/data-max-zoom from the #history-map div
instead of hardcoding 3/19. Pi template caps to 8-15 (prevents blank tiles
at extreme zooms), server template allows full 3-19 OSM range.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:42:04 -04:00
Mathieu MoranandCopilot 264d38a6dc cleanup: fix duplicate app_gps import, remove debug prints from index route
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:38:49 -04:00
Mathieu MoranandCopilot d96a971f15 fix: diagnose.sh DB backup line shows literal $DB_PATH
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:31:50 -04:00
Mathieu MoranandCopilot 437434b972 feat: add scripts/diagnose.sh — system diagnostic & recovery tool
Read-only health check (no args) or auto-fix mode (--fix):
  - Service: active state, crash loop detection, recent log
  - Hardware: OBD USB adapter (/dev/ttyUSBx), SIM868 UART (/dev/serial0),
    dashcam USB mount, permissions
  - Network: hotspot IP, web UI reachability, internet, upload server
  - Storage: disk usage, SQLite integrity check, row counts, upload backlog,
    log file size
  - OBD status: parsed from log (last connect/disconnect/speed/errors)
  - Python env: venv present, key packages importable
  - Pi health: throttling flags decoded, CPU temp, free memory
  - Manual recovery command cheatsheet printed at the end

Exit code = number of critical issues found (0 = healthy).

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:31:03 -04:00
Mathieu MoranandCopilot c03cd6d48d fix: add OBD_SKIP_VOLTAGE_CHECK support for clone ELM327 adapters
Counterfeit ELM327 chips (common in cheap PL2303 USB adapters) often report
bad/zero voltage even when the OBD connection is working. python-obd's
check_voltage=True default rejects these. Add OBD_SKIP_VOLTAGE_CHECK=1 env
var to pass check_voltage=False to obd.OBD(), documented in yaris-obd.service.

Also document Phase 24.6 Yaris 2013 compatibility analysis in PLAN.md.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:26:40 -04:00
Mathieu MoranandCopilot 10dea22f9a fix: Yaris 2013 OBD compatibility hardening
- _decode_odometer: catch all exceptions and return None instead of raising.
  PID 0xA6 (odometer) is not guaranteed on 2013 Toyota Yaris — a garbage
  response or unsupported PID previously raised ValueError through python-obd's
  decoder, which app_obd._query() caught and treated as a lost connection,
  killing the entire OBD session. Now it gracefully returns None.

- connect_obd: reset all sensor globals (rpm, coolant_temp, fuel_level,
  throttle_position, mil_on, dtc_codes, odometer, timestamps) on each new
  connection so the UI doesn't display stale readings from the previous
  ignition cycle while fresh polls are completing.

- yaris-obd.service: add commented OBD_PROTOCOL=6 hint for post-confirmation
  pinning (eliminates 3-5s auto-probe delay after first confirmed connection).

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:22:36 -04:00
Mathieu MoranandCopilot a56e4252d3 docs: document Phase 24.5 code audit in PLAN.md
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:17:32 -04:00
Mathieu MoranandCopilot 1b39ac0cc8 fix: pre-car-test bug fixes from full code audit
- app_alerts.py: null guard on list_local_videos() result (.get('files', []))
- app_cellular.py: remove erroneous .encode(errors='replace') on str response body
- app_settings.py: initialize apn='' when isinstance check fails (NameError guard)
- app_upload.py: move json import to top of file instead of inside except handler
- server/app.py: wrap int(request.args.get('limit')) in try/except ValueError
- server/app.py: wrap os.path.commonpath() in try/except ValueError (cross-root paths)
- tests/test_app_settings.py: add missing trips_upload_enabled/video_convert_enabled
  to valid_payload() (fields added in Phase 23.5 but test not updated)

All 63 tests pass.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:15:27 -04:00
Mathieu MoranandCopilot f30143c7b7 docs: document Phase 24 bug fixes and UI polish in PLAN.md
Co-authored-by: Copilot <[email protected]>
2026-08-10 05:07:20 -04:00
Mathieu MoranandCopilot 2f1e35414d Fix sidebar arrow direction: use CSS rotation not JS icon swap
The CSS already handles the arrow direction with transform:rotate(180deg) on
.app-sidebar.collapsed .sidebar-collapse-btn. The JS icon className swap was
fighting with CSS rotation causing a double-flip. Remove JS icon manipulation,
let CSS handle it. Also suppress the button transition on page load.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:04:57 -04:00
Mathieu MoranandCopilot 3c3c8bba71 Fix sidebar collapse flash on page navigation
Suppress CSS transitions during initial state restore from localStorage.
Re-enable after two rAF frames so the toggle button still animates smoothly.

Co-authored-by: Copilot <[email protected]>
2026-08-10 05:00:22 -04:00
Mathieu MoranandCopilot 21e44a8586 Fix server sidebar collapsed state: hide badge and user footer text
- sidebar-env-badge hidden when collapsed
- sidebar-user-text collapses like nav link spans
- User icon stays centered when collapsed via sidebar-user-footer flex

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:56:44 -04:00
Mathieu MoranandCopilot b172b89318 Simplify delete confirmation to native window.confirm
Bootstrap Modal async was unreliable. Replace with synchronous
window.confirm() which always works and needs no dependencies.

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:52:03 -04:00
Mathieu MoranandCopilot 72e4c1b1d7 Fix trip delete button: layout and click handler
- text-nowrap on action cell so Replay+trash stay on same line
- Delete button inline with Replay (no whitespace between)
- Explicit deletable guard in click handler

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:45:05 -04:00
Mathieu MoranandCopilot 7aa9ffda16 Add delete with confirmation for trips and dashcam
- Confirmation modal in server base.html (window.confirmDelete promise helper)
- Trip delete: trash button in Replay column (server-only via data-deletable on
  trip-list div), DELETE /api/v1/portal/trip/<id>, removes computed trip only
  (raw tracking_points kept)
- Dashcam delete: trash button next to download, DELETE /api/v1/portal/dashcam/<id>,
  removes from DB + disk (best-effort file removal)
- history.js: delete button gated on data-deletable='1' attribute; click handler
  intercepts before replay logic
- db.py: delete_trip(), delete_dashcam_file() helpers

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:40:22 -04:00
Mathieu MoranandCopilot a13d74e18e Fix remote portal map tiles and geocode
- history.js reads tile URL from data-tile-url attribute on #history-map div,
  falling back to local offline tiles (Pi behavior unchanged)
- server history.html sets data-tile-url to live OSM tiles
- maxZoom raised to 19 (OSM supports it, local tiles were limited to 15)
- server geocode returns 'label' key to match what history.js/yaris.js expect

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:34:49 -04:00
Mathieu MoranandCopilot e2a319ae08 Fix history.js null guard for speed and distance
formatSpeed and formatDistance return '—' on null/undefined values.
Server trips may have null avg/max speed if no speed data was recorded.

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:30:54 -04:00
Mathieu MoranandCopilot 693848aba1 Fix remote portal trip list
- /api/trips now returns {trips:[...]} to match what history.js expects (result.trips)
- odo_start_km null guard in history.js (server trips have no odometer data)
- require_login returns 401 JSON for /api/* paths instead of HTML redirect

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:27:57 -04:00
Mathieu MoranandCopilot 64b9481ce0 Phase 24: add password change page
- GET/POST /account/password with CSRF token and bcrypt re-hash
- 'Password' link in sidebar next to Logout
- change_password.html template

Co-authored-by: Copilot <[email protected]>
2026-08-10 04:22:28 -04:00
Mathieu MoranandCopilot c4dfcbaf25 Phase 24: remote trip + dashcam portal
- Add Flask-Login browser session auth (separate from device X-Api-Key auth)
  - modules/user_auth.py: User model, require_login decorator, bcrypt helpers
  - /login with CSRF token, /logout
  - scripts/create_user.py to provision admin user

- Add server-side trip computation from tracking_points
  - db.compute_trips_for_device(): gap > 5 min = new trip, haversine distance,
    upserts into trips table, auto-triggered in bg thread after each upload batch
  - db.list_trips(), db.get_trip_route(): same JSON shape as Pi API
  - db.get_overview_stats(): aggregated totals for overview page
  - db.list_dashcam_files(), db.get_dashcam_file(): browse/stream helpers

- Add web portal to server/app.py
  - Overview page (/): stat cards + recent trips + recent clips
  - Trip history (/history): reuses Pi history.js + data-list.js unchanged
    via /api/trips + /api/trip/<id> compatibility aliases
  - Dashcam archive (/dashcam): file list + inline video player
  - /api/v1/portal/dashcam/stream/<id>: Range-request streaming, path traversal guard
  - /api/geocode: same Nominatim→reverse_geocoder offline fallback as Pi

- Add server templates (server/templates/)
  - base.html: shared shell with 'Remote server' badge in sidebar + mobile topbar
  - login.html, index.html, history.html, dashcam.html, components.html

- DB schema: users + trips tables added to init.sql (fresh deploys) +
  migrate_phase24.sql (existing deployments)

- Infrastructure: Dockerfile uses repo-root build context to copy shared
  static/ assets; docker-compose updated; SECRET_KEY + DEVICE_NAME in .env;
  requirements: Flask-Login, bcrypt, reverse_geocoder

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:58:45 -04:00
Mathieu MoranandCopilot 51c6be3a94 docs: update PLAN.md phase 23.5, README features/hardware/status, new screenshots
- PLAN.md: Phase 23.5 documents all work from this session
- README: hardware table adds T3U, features add geocoding/offline maps/T3U, status updated
- pics/ui.png: desktop + mobile side-by-side screenshot (updated)

Co-authored-by: Copilot <[email protected]>
2026-08-10 03:43:09 -04:00