diff --git a/AGENTS.md b/AGENTS.md index ff5f0493..4bcba0f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,11 @@ # AGENTS.md — OpenStrap `edge` -Reviewer context, verified against code at `kAlgoVersion 47`, schema `v25`, -`0.9.19+50`. Where a source comment disagrees with an implementation, **the -implementation wins** — header comments here go stale (e.g. +Reviewer context. This doc drifts from code between edits — check +`kAlgoVersion` (`lib/compute/derivation_engine.dart`), `schemaVersion` +(`lib/data/db.dart`), and the `version:` line in `pubspec.yaml` directly +rather than trusting a number written here. Where a source comment disagrees +with an implementation, **the implementation wins** — header comments here go +stale (e.g. `lib/compute/substrate.dart:10-12` still describes a wake-to-wake day model that `calendarDays()` at `:429` no longer implements; it walks local midnight to local midnight). @@ -48,8 +51,10 @@ unless it is pure orchestration. - `notify/` — `notification_center.dart` is the **single emitter**; `fired_keys.dart` is the persistent fire-once guard. - `coach/` — read-only SQL over allow-listed `v_*` views behind a deny-list guard. -- `ui/` — ~120 files: `ui/design` (design system), `ui/kit/charts.dart`, - `ui/screens/` (shared metric/trend IA). +- `ui2/` — 66 files (`lib/ui` was deleted in the UI rebuild): `ui2/theme.dart` + and `ui2/grammar.dart` (design system), `ui2/charts.dart`, `ui2/screens/` + (shared metric/trend IA), plus `ui2/onboarding/`, `ui2/activity/`, + `ui2/profile/`. - Also `ai/` (BYOK), `gps/`, `health/` (HealthKit/Health Connect export), `telemetry/` (opt-in), `widget/` (App-Group snapshot for WidgetKit/watch). diff --git a/README.md b/README.md index 93d2c88c..f6442b68 100644 --- a/README.md +++ b/README.md @@ -221,13 +221,22 @@ lib/ble/ Bluetooth + sync lib/data/ local storage + the repository seam the UI reads from lib/compute/ runs the analytics pipeline, writes results lib/state/ AppState, the one source of truth -lib/ui/ every screen +lib/ui2/ every screen ``` Protocol decoding and analytics live in their own repos — [protocol](https://github.com/OpenStrap/protocol), [analytics](https://github.com/OpenStrap/analytics). +## Guides + +- [`guides/IOS_INSTALLATION.md`](guides/IOS_INSTALLATION.md) — building and installing on an iPhone. +- [`guides/IOS_SIDELOAD.md`](guides/IOS_SIDELOAD.md) — sideloading without a paid developer account. +- [`guides/WATCH_SETUP.md`](guides/WATCH_SETUP.md) — the Apple Watch companion app. +- [`guides/AI_COACH.md`](guides/AI_COACH.md) — bring-your-own-key AI coach, briefings, and journal. +- [`guides/TASKER_INTEGRATION.md`](guides/TASKER_INTEGRATION.md) — buzzing the strap from Tasker/automation. +- [`guides/BUZZ_MEANINGS.md`](guides/BUZZ_MEANINGS.md) — what each buzz pattern means. + ## Contributing Found something broken? Open an issue. Found something broken and fixed it? Even better, diff --git a/lib/gps/route_math.dart b/lib/gps/route_math.dart index 8de0af88..eb7a4825 100644 --- a/lib/gps/route_math.dart +++ b/lib/gps/route_math.dart @@ -2,14 +2,17 @@ // // Everything here is a pure function of its inputs (no DB, no I/O, no clock), // so it is directly unit-testable and shared by the repository (splits) and the -// UI (zone-coloured polylines). Distances use the haversine great-circle -// formula on WGS84 mean radius; good to well under a metre at running scale. +// UI (zone-coloured polylines). Distance is latlong2's haversine great-circle +// calculator (WGS84 equatorial radius, not our old mean-radius constant — a +// ~0.1% difference, well under GPS fix noise); good to well under a metre at +// running scale. import 'dart:math' as math; +import 'package:latlong2/latlong.dart'; + import 'route_models.dart'; -const double kEarthRadiusM = 6371008.8; // WGS84 mean radius const double kMetersPerKm = 1000.0; const double kMetersPerMile = 1609.344; @@ -65,18 +68,9 @@ double? fallbackSpeedMps(RoutePoint? prev, RoutePoint cur) { } /// Great-circle distance in metres between two lat/lng points. -double haversineMeters(double lat1, double lng1, double lat2, double lng2) { - const deg2rad = math.pi / 180.0; - final dLat = (lat2 - lat1) * deg2rad; - final dLng = (lng2 - lng1) * deg2rad; - final a = math.sin(dLat / 2) * math.sin(dLat / 2) + - math.cos(lat1 * deg2rad) * - math.cos(lat2 * deg2rad) * - math.sin(dLng / 2) * - math.sin(dLng / 2); - final c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)); - return kEarthRadiusM * c; -} +const _distance = DistanceHaversine(roundResult: false); +double haversineMeters(double lat1, double lng1, double lat2, double lng2) => + _distance.distance(LatLng(lat1, lng1), LatLng(lat2, lng2)); /// Total path length in metres over an ordered list of route points. /// Implausible segments (a teleport across a recording gap — see