diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..770ba8d1 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,27 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +# +# The UI rebuild PR was refused outright — 436 files against a 100-file limit — +# and the bulk of that is not code anyone wants reviewed. Excluding it here is +# not a way around the ceiling, it is pointing the review at the ~230 files +# where a bug can actually hide. +# +# Kept on the ORG defaults except where noted, so this file does not silently +# become the place review settings drift. +language: en-US +reviews: + profile: assertive + path_filters: + # Generated or mechanical, and the platform dirs churn on every plugin + # bump. Genuine platform work (the HealthKit Swift bridge, the launcher + # icons) is small and gets read by hand anyway. + - "!android/**" + - "!ios/**" + - "!macos/**" + - "!linux/**" + - "!windows/**" + - "!web/**" + - "!assets/**" + # Tests are excluded because there are more of them than there is source + # and they are the least likely place for a shipped bug. Re-include them + # for a PR that is mostly test work. + - "!test/**" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b2e2896..3c2b0fad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -186,11 +186,15 @@ jobs: run: | echo "COMPANION_URL=${{ secrets.COMPANION_URL }}" > .env echo "BACKEND_URL=${{ secrets.BACKEND_URL }}" >> .env - # Health-data contribution is intentionally ON for GitHub release - # builds (matches the ios job) — see privacy.html's "GitHub - # releases" section. Still opt-in per user (a Settings/onboarding - # toggle, defaulting off) even with this build-time flag true; see - # the doc comment on kHealthDataContributionEnabled in + # Health-data contribution: this flag makes the feature EXIST in + # the binary (matches the ios job). It does not enable anything — + # the pref defaults off and the upload additionally requires the + # user to switch on Settings > Privacy > "Contribute my health + # data". Verify that row still exists before touching this line: + # this comment used to assert the toggle as a backstop at a time + # when the toggle had no UI at all, and that false reassurance is + # how PRIVACY.md ended up describing a control nobody could reach. + # See kHealthDataContributionEnabled in # lib/telemetry/health_uploader.dart. echo "ENABLE_HEALTH_DATA_CONTRIBUTION=true" >> .env @@ -287,15 +291,18 @@ jobs: run: | echo "COMPANION_URL=${{ secrets.COMPANION_URL }}" > .env echo "BACKEND_URL=${{ secrets.BACKEND_URL }}" >> .env - # Health-data contribution is intentionally ON for GitHub release - # builds (matches the android job) — see privacy.html's "GitHub - # releases" section. Still opt-in per user (a Settings/onboarding - # toggle, defaulting off) even when this build-time flag is true; - # see the doc comment on kHealthDataContributionEnabled in - # lib/telemetry/health_uploader.dart. App Store/Play Store builds - # (once that pipeline exists) must keep this false to hold - # PRIVACY.md's "we do not collect your health data" promise for - # those channels specifically. + # Health-data contribution: this flag makes the feature EXIST in + # the binary (matches the android job). It does not enable + # anything — the pref defaults off and the upload additionally + # requires the user to switch on Settings > Privacy > "Contribute + # my health data". Verify that row still exists before touching + # this line: this comment used to assert the toggle as a backstop + # at a time when the toggle had no UI at all, and that false + # reassurance is how PRIVACY.md ended up describing a control + # nobody could reach. GitHub Releases is the only channel that + # exists; there is no store pipeline, so nothing here distinguishes + # one. See kHealthDataContributionEnabled in + # lib/telemetry/health_uploader.dart. echo "ENABLE_HEALTH_DATA_CONTRIBUTION=true" >> .env - name: Install flutterfire (required by Crashlytics Xcode build phase) diff --git a/.gitignore b/.gitignore index b61a91a4..35bb79ca 100644 --- a/.gitignore +++ b/.gitignore @@ -98,3 +98,22 @@ lib/firebase_options.dart # into Discord/issues, and they're ~750kb a piece. docs/*.gif docs/*.mp4 + +# golden test failure output +test/failures/ + +# xcodebuild output from a direct target build +ios/build/ + +# Working notes, not deliverables. These are sweep reports, audits and +# measurement runs written against REAL exports — they quote dated personal +# readings (a resting heart rate on a named day, an irregular-rhythm flag with +# its date) and this repo is public. They stay on disk and out of git. +docs/internal/ + +# Golden images. 416 PNGs, 27 MB, regenerated locally with +# `flutter test --update-goldens`. They are machine- and SDK-specific — the two +# Flutter SDKs on this machine already disagree on antialiasing — so a +# committed set is a diff nobody can review and a merge conflict nobody can +# resolve. The test code stays; the pictures do not. +test/goldens/ diff --git a/PRIVACY.md b/PRIVACY.md index 653bc089..6efb2c43 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,48 +1,96 @@ # Privacy Policy — Edge / OpenStrap -_Last updated: July 27, 2026_ +_Last updated: August 18, 2026_ Edge ("the App") is an independent, open-source project. It is not affiliated with, sponsored by, or endorsed by WHOOP, Inc. +**How the App is distributed** +There is currently one distribution channel: GitHub Releases. There is no App +Store or Play Store build of the App today, and nothing in this policy depends +on which channel you installed from. If that changes, this policy changes with +it. + **We do not collect your health data** -This section describes the builds we ourselves distribute. In the official -public releases distributed through the App Store and Play Store, we do not -collect your health data. Your health information is processed and stored -entirely on your device in those releases. We do not upload it, we do not -operate a backend that receives it, and we never see it — *unless you -explicitly choose to install a GitHub release that has health data -contribution enabled, or separately enable AI Coach or Health app -integration, described below, which send specific data to services you -configure.* - -Edge is open source. The underlying code contains a compile-time flag -(`kHealthDataContributionEnabled`, see `lib/telemetry/health_uploader.dart`) -that an independent developer could enable in their *own*, separately-built -and separately-distributed copy of the app, pointed at a backend of their -own choosing. We do not enable that feature in the official public builds we -publish. If you explicitly download and install a GitHub release, you may -choose to enable this feature for the purpose of improving algorithm and -calibration insights. That choice is entirely under your control, and the -feature is off by default. A self-built copy compiled with that flag on is -that builder's own software and their own responsibility; it is not covered -by this policy. +Your health information is processed and stored entirely on your device. We do +not upload it, we do not operate a backend that receives it, and we never see +it — *unless you explicitly switch on health data contribution, or separately +enable AI Coach or Health app integration, described below, which send specific +data to services you configure.* + +Health data contribution is **off by default**, and it is off unless you turn +it on yourself in Settings › Privacy › "Contribute my health data". When it is +on, and only then, the App uploads a compressed copy of its **entire local +database** — every derived day and every raw sensor row from the band — at most +once a day, and only while you are on Wi-Fi and charging. It is used to improve +the App's algorithms. Our server keeps only the most recent copy per device. + +You can switch it off at any time from the same row, and nothing further is +uploaded from that moment. The App will then tell you the date of the last +upload that did happen, because switching a thing off does not un-send what was +already sent. + +Edge is open source, and this feature sits behind a compile-time flag +(`kHealthDataContributionEnabled`, see `lib/telemetry/health_uploader.dart`). +An independent developer can enable it in their *own*, separately-built copy +pointed at a backend of their own choosing. That is their software and their +responsibility; it is not covered by this policy. **Anonymous diagnostics** -In the official public releases distributed through the App Store and Play -Store, the App does not send your health data off your device. The only -thing the App sends off your device *automatically* — aside from the -optional, user-initiated integrations described next — is basic crash/error -and performance monitoring, via Firebase (Google) — Crashlytics, Performance -Monitoring, and Analytics. This is on by default in GitHub releases; you can -turn it off at any time in your profile, which stops any further collection -immediately. In App Store and Play Store releases, this data collection does -not occur. It never includes your health data — only crash reports, basic -device info (OS/model/app version), and coarse performance timing. This data -is handled under Firebase's own privacy and security practices, not a system -we built or operate ourselves — see Google's Firebase privacy & security +Aside from the optional, user-initiated things described here, the App sends +basic crash/error and performance monitoring off your device, via Firebase +(Google) — Crashlytics, Performance Monitoring, and Analytics. This is **off by +default**: nothing is collected until you turn it on in Settings › Privacy › +"Crash reports", and turning it back off stops any further collection +immediately. It never includes your health data — only crash reports, basic +device info (OS/model/app version), your phone's and band's battery level, +whether the band is connected, and coarse performance timing. It does not +include the band's serial number or any other hardware identifier. This data is +handled under Firebase's own privacy and security practices, not a system we +built or operate ourselves — see Google's Firebase privacy & security documentation: https://firebase.google.com/support/privacy. +**Update checks** +Because the App is distributed outside an app store, it can check our release +server for a newer version and for any urgent notice we need to show you. That +check is a plain request for a small file: it carries no health data, no +account and no identifier, but like any network request it discloses your IP +address (and therefore your approximate location) and the time you opened the +App. It runs when the App starts and when you bring it back to the foreground. + +You can turn it off in Settings › Privacy › "Check for updates", and then the +App makes no network request of its own accord at all. It is on by default +because a sideloaded app has no store to tell you a security fix exists. + +**Barcode lookup for food logging** +The food log can read a barcode with the camera and fill in the nutrition +figures for you. Doing that means asking a database, so it is **off until you +turn it on**, and the App asks you before the first lookup ever happens — not +after. + +- **What is sent is the barcode.** It goes to openfoodfacts.org, the free and + open food database. Nothing about you, your meals, your health or your device + goes with it. Like any network request it discloses your IP address to them. +- **Only when you scan.** There is no background lookup, no batch and no + pre-fetch. One barcode, one request, and only because you pointed the camera + at a packet. +- **A barcode you have scanned before is answered from your own phone.** The + App keeps a local copy of what it has fetched, so re-scanning the same packet + asks nobody anything. +- **The camera reads digits and nothing else.** No photo is taken, stored or + sent. Declining camera access leaves the rest of the food log working. +- **Everything still works with it off.** Typing the numbers off the pack was + always the way in and still is. + +You can turn it off at any time in Settings › Privacy › "Look barcodes up +online", and the App then makes no food-related network request at all. + +Their data is contributed by the public, is licensed under the Open Database +License, and their own terms say it must not be used for medical purposes. So a +scanned figure is treated as something you typed rather than something the App +measured: anything that fails a basic plausibility check is left blank instead +of filled in, and every filled box is yours to edit before you save. + **Location and workout routes** If you record a run, ride or walk, the App uses your device's location to draw that workout's route. This is the most sensitive permission the App asks for, @@ -65,14 +113,9 @@ so to be specific about it: included in anonymous diagnostics, and it is not sent to your AI Coach provider — the coach is technically prevented from reading route data, not merely asked not to. -- **Viewing a route on a map fetches map tiles.** The route itself is never - uploaded, but drawing a map under it requires downloading the map squares it - covers from CARTO (basemaps.cartocdn.com), a third-party tile provider. That - request tells CARTO which part of the world you are looking at, and their - server sees your IP address. It happens only while a map is being drawn: - opening a route, or rendering the map into a share image. The request carries - no health data and no account, and nothing is fetched if you never open a - route. +- **No map tiles are fetched.** A route is drawn on your phone from your own + points, against no basemap. The App does not contact a tile provider, so + opening a route tells nobody which part of the world you are looking at. - **The one exception is you.** If you tap Share on a workout, the image you are shown includes a picture of your route, and whatever you send it to receives it. That is your choice, you see the image before it is sent, and it @@ -93,7 +136,12 @@ configure: requires your own API key. - **Health app integration** — if you enable it, the App can write derived daily metrics to Apple Health or Google Health Connect, which are controlled - by your device's own OS-level health app, not by us. + by your device's own OS-level health app, not by us. The App can also *read* + from that same health app when you tap an import — your height, weight, date + of birth and sex, your resting heart rate, blood pressure, blood glucose and + body temperature readings, and your workouts and their routes. Every import + is something you start by hand, never a background sync, and what it reads + stays on your device: reading from your health app sends nothing anywhere. **What we don't do** We do not sell your data. We do not send your health data to WHOOP, Inc. or @@ -101,12 +149,31 @@ any advertising network. We do not require a WHOOP account or credentials to use the App. We do not operate a backend that stores your health data. **Your controls** -Turn off anonymous diagnostics at any time in your profile — this stops any -further collection immediately. You can also disable AI Coach or Health app -integration at any time in Settings if you'd previously turned them on. If -you explicitly installed a GitHub release and enabled health data -contribution, you can disable that feature at any time from the app's -settings. +Every one of these is a row in Settings › Privacy, and each takes effect the +moment you tap it: +- **Crash reports** — off by default; turning it off stops any further + collection immediately. +- **Contribute my health data** — off by default; turning it off stops any + further upload immediately, and the App tells you when the last one was. +- **Check for updates** — turning it off stops the App making any network + request of its own accord. +- **Look barcodes up online** — off by default; turning it off stops any + further lookup immediately, and the food log keeps working by hand. + +You can also disable AI Coach or Health app integration at any time in Settings +if you'd previously turned them on. + +**Getting your data out, and deleting it** +Settings › Your data exports everything the App holds: your derived days, +workouts, sleep, journal, lab results, meals, medication, habits, breathing +sessions and logged sets as spreadsheets, and the complete database as a single +file. The same screen can take a scheduled local copy, and restore one. + +Settings › "Reset all data" deletes everything on the device: every table in +the database, every preference, any AI key you stored, the home-screen widget's +copy of your numbers, and every scheduled reminder. The dialog lists what it +covers. It is not reversible and the band cannot re-send history it has already +handed over, so export first if you want a copy. Uninstalling the App deletes all of your locally stored data immediately. That's the whole picture *unless* you had separately enabled one of the diff --git a/UI_AUDIT.html b/UI_AUDIT.html deleted file mode 100644 index d6d1382a..00000000 --- a/UI_AUDIT.html +++ /dev/null @@ -1,762 +0,0 @@ - - - - - -The Discarded Five-Sixths - - - - -
-

OpenStrap · edge · v0.9.26+57 · UI/UX audit

-

The Discarded Five-Sixths

-

- The analytics engine returns six fields per metric. The interface renders one of them. - Every other problem in this audit is downstream of that. -

-
- 41,588 lines of UI - 93 files - 40 screens - 5 parallel auditors - 0 golden tests -
-
- -
-

Verdict

-
-

5.9 /10

-

- Not bad — undifferentiated. The craft is well above the Flutter-health median: - real tokens, real null-honesty copy, a measured contrast test already in CI. What it is not - is this product's interface. It renders like a competent WHOOP clone sitting on top - of an engine that could beat WHOOP on the one axis WHOOP can't compete on: telling the truth - about what it doesn't know. -

-
-
- -
-

The finding

-

Four auditors, working blind to each other, found the same wall.

-

- Analytics hands the UI a Metric. It carries a value, a confidence, a tier - (AUTH / HIGH / ESTIMATE / RELATIVE), - the inputs it used, the drivers that moved it, and a note saying why it's absent. Five of those - six survive the trip to a pixel almost nowhere. -

- -
- - - - - - - - ANALYTICS - SCREEN - - - - UI BOUNDARY - - - - value - - - 72 - - - - confidence - tier - inputs_used - drivers - note - - - - - - - - - - - - - - - - - - 102 times - -

- The band's whole claim is that it never guesses.
The interface renders that claim as a dash. -

-
- -
-
-

102

-

bare literals in lib/ui. 1 call site of needMessageFromNote, the function that turns need_baseline:have=3,need=14 into a sentence a human can act on. detail_cards.dart:818

-
-
-

1 of 13

-

ArcGauge call sites pass confidence; it defaults to 1.0. Every sparkline, bar and time series has no confidence parameter at all. An ESTIMATE respiratory rate and an AUTH heart rate render as identical pixels. charts.dart:36

-
-
-

3

-

reads of Metric.note across all 41,588 lines. The reason a number is missing is computed, stored, shipped to the device — and then dropped one layer before the eye.

-
-
-

0

-

places where a drivers list is shown as an attribution. Oura's Contributors panel is the category baseline in 2026; this app computes something strictly better and never draws it.

-
-
-
- -
-

By discipline

-

Where the points went.

-
-
-

Design system — tokens, type, components

-

6.5

-
-
-
-

UX craft — states, a11y, motion

-

6.0

-
-
-
-

Information architecture — nav, screens, flows

-

5.5

-
-
-
-

Data visualisation — charts, gauges, hypnogram

-

5.5

-
-
-
-

- The design system scores highest because it exists: a 4 pt spacing scale at 93% adoption, - 753 AppColors and 615 AppText call sites, dark mode re-derived rather than - inverted. The failure is not absence of a system — it is two systems, both re-exported - from the same barrel, with 21 files importing both. design.dart:36 -

-
- -
-

Ranked findings

-

What actually costs quality.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SevFindingWhereFix
P0Muted caption text fails WCAG AA — 2.71:1, 2.40:1 and 2.20:1 depending on surface. It is the app's caption and label voice, so it fails everywhere.theme.dart:159,165
223 uses
Two hex values. A contrast test harness already exists.
P0Zero reduced-motion support. 13 infinite .repeat() loops, including a 2.6 s breathing icon on every empty state that never stops.motion.dart:32
state_card.dart:43
One guard inside the DsMotion extension fixes all 34 caller files.
P0A 958-line design gallery ships in release builds, routed from Settings under a "Developer" heading.profile_screen.dart:747Wrap the route in kDebugMode.
P0No downsampling anywhere. A night of 1 Hz HR is ~30,000 chart points per frame, with an O(n) linear scan on every drag pixel.charts.dart:381,624Min/max bucketing to 2× pixel width. _bucketAvg already exists — promote it.
P0The Insights screen returns SizedBox.shrink() while loading and when empty. Its content silently does not exist.coach_cards.dart
:176,365,436,534
Skeleton, then StateCard with the have/need line.
P1Today's tiles push Sleep/Heart/Body — the same classes that are already tabs 1/2/3. Two live instances with divergent scroll and drill state; "back" means two different things depending on how you arrived.today_screen.dart:441
vs app.dart:236
Tiles switch tabs. Never push a destination that is also a tab.
P1The onboarding validator contradicts its own copy: the screen promises "leave a field blank and only that metric stays unknown — never guessed", then requires all four fields.profile_setup_screen
.dart:139 vs :193
Allow blanks. Gate Continue on sex alone.
P1No escape from pairing. Bond-refused and picker-cancelled both fall through to one generic "Couldn't pair". This is the drop-off cliff of a hardware app.pairing_screen.dart
:178,190
Distinct refused/cancelled states, plus a skip-to-shell path.
P1The app's most visible element ignores the type scale. BigStat and MetricCard override AppText.hero/display/metric; hero numbers render at 26, 27, 36, 40, 44, 46, 48, 52, 54, 64 and 72 px across screens, at 14 different letter-spacings.big_stat.dart:66
metric_card.dart:112
Promote metricXl…metricSm into AppText; delete the overrides.
P1A shadow chart library. Five line-chart implementations, three gauges, two hypnograms, two sparklines — and coach_render.dart's copies interpolate straight across null gaps, which is a lie in a null-honest app.coach_render.dart
coach_chart.dart
One engine each. ~400 lines net deletion.
P1A dual-axis chart normalises each series to its own min/max on a shared grid. Every crossing and convergence it draws is an artifact.coach_render.dart:234Delete. Two stacked charts, or index to a common base.
P1Journey — the richest view in the app at 1,300+ lines — is reachable through exactly one card that hides itself for the first 24 hours. New users never learn it exists.today_screen.dart:416Promote to a tab.
P223 Semantics against 236 onTap:. Charts are blanket-ExcludeSemantics. Confidence is a 7 px hue-only dot with no label.kit.dart:498
charts.dart:437
Semantic labels on every interactive surface; colour + word + shape for status.
P2Sub-44 pt tap targets on primary controls (34 pt segmented controls, 40 pt back button); 68 candidate sites.controls.dart:70
app_scaffold.dart:43
Enforce the floor inside Pressable so no call site can opt out.
P2Unbounded workout history built into a non-lazy ListView(children:), each row wrapped in its own animation ticker. One ListView.builder exists in the entire UI.workouts_screen.dart
:354,461
ListView.builder; stagger only the first viewport.
P2Hypnogram — the signature visual of any sleep app — has no minimum segment width (a 30 s wake epoch is sub-pixel on an 8 h night), no time axis, no scrub, and shouldRepaint => true.hypnogram.dart:99,123Clamp to ≥1.5 px, add hour ticks and scrub-to-read.
P2Four god-screens: 2,728 / 2,208 / 2,113 / 1,700 lines. Meanwhile ~5,300 bespoke day-view lines sit beside a 489-line generic one that already works.live_session · workouts
profile · today
Split by responsibility; route the rest through MetricScreen.
P2The app calls itself "OpenStrap" during onboarding and "Edge" once you're inside. Jargon leaks as primary labels: MS RMSSD, pNN50, SD1/SD2.welcome_screen.dart:365
spot_check_screen.dart:186
One name. Method notes hold the method names, not stat captions.
-
-
- -
-

Ship blockers

-

Four things that would fail a store review or an accessibility complaint.

-
-
- A11Y -

Caption text below AA contrast, app-wide

-

2.20:1 at worst against a 4.5:1 requirement, across 223 call sites. Two hex values in theme.dart.

-
-
- A11Y -

No reduced-motion path

-

Users with vestibular disorders get a permanently pulsing empty state and a shimmer that never stops. Also a ticker burning battery on an idle screen.

-
-
- REL -

Debug surface in production

-

A design gallery and a fake-route fixture reachable from user-facing Settings.

-
-
- PERF -

30,000-point charts

-

Every sleep detail view rebuilds a 30k-segment path per frame and scans it linearly on each drag pixel. This is the jank users will describe as "the app feels cheap".

-
-
-
- -
-

Target

-

What production grade looks like here.

-

- Not "more polish". The target is an interface whose structure is the honesty guarantee, - where a fabricated number is unrepresentable rather than merely avoided. -

- -
-
-

1

-

A dash is a bug. One MetricValue widget: value → note-derived reason ("4 of 14 nights") → device reason ("not synced since 2 pm") → a dash only as an assertion failure. Target: ≤5 literal dashes in lib/ui, down from 102.

-
-
-

2

-

Tier is an encoding, not a badge. ESTIMATE renders in lighter weight with a hatched band; RELATIVE plots against a zero-line that means "your normal"; confidence below 0.4 dashes the stroke. Enforced in the one chart engine so no call site can regress it. No yellow, no warning triangle — the way Apple Weather greys distant forecast days.

-
-
-

3

-

Drivers are the hero, not a tap-through. Three signed attributions under the score, always expanded — "HRV −18% vs baseline · sleep debt 1 h 40 m · late alcohol logged". This is Oura's Contributors panel, except causal, and the engine already computes it.

-
-
-

4

-

Day 0 shows a contract, not a skeleton. No zeros, no shimmer implying a number is imminent: "Readiness needs 3 nights. 1 done. First score Thursday morning" — alongside live raw HR, the only thing that is real on day 0 and the proof the band is talking.

-
-
-

5

-

One hero, not three dials. WHOOP's three-dial home forces the user to arbitrate. Readiness alone above the fold in the morning; strain-so-far against today's ceiling in the evening; everything else earns its way below. The AI briefing is not a co-hero — an LLM paragraph at the top makes a local-first app look like a wrapper.

-
-
-

6

-

The receipt. Every score links to its inputs: which records, which gaps, coverage in real units ("6 h 12 m of 7 h 40 m in bed"), which algo version. When a version change re-scores a past day, say so on the card instead of silently mutating history.

-
-
-

7

-

Lead with what WHOOP cannot do. A smart alarm that wakes you in light sleep, using live per-second staging, with a morning receipt — "woke you at 06:31, 4 minutes into light sleep". Plus a raw 1 Hz viewer and one-tap export. No subscription product can offer either.

-
-
-
- -
-
-

Sequence

-

Four phases, ordered by perceived quality per unit of effort.

-
- -
-

1

-

Stop the bleeding

-

Roughly a day. Almost entirely mechanical.

- -
- -
-

2

-

Ship the differentiator

-

The phase that changes what the product is.

- -
- -
-

3

-

Collapse the duplication

-

Net deletion. The diff should be negative.

- -
- -
-

4

-

Earn the feature slot

-

Where it stops looking like a hobby build.

- -
-
- -
-
-

- The uncomfortable summary: the hard part is already built. On-device staging, corrected RR - intervals, personal baselines, per-metric confidence, honest nulls — years of work that no - competitor ships. The interface throws away five of the six fields that work produces, and - then looks like every other recovery app. -

-

- Phase 1 is a day. Phase 2 is what makes it this app. -

-
-
- - - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6b3b6863..27fd5b98 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,12 @@ + + + @@ -22,6 +28,20 @@ + + - + + tools:node="remove" /> + + + + + @@ -80,7 +115,10 @@ + android:icon="@mipmap/launcher_icon" + android:allowBackup="false" + android:dataExtractionRules="@xml/data_extraction_rules" + android:enableOnBackInvokedCallback="true"> + - - - - + diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png index 06b21fd4..1c4e1d8c 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png index cdbf9fe2..4bff76b6 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png index e43a6f0a..c89ba6a0 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f8..e7064241 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,7 @@ - + - - - - + diff --git a/android/app/src/main/res/drawable/widget_bg_char.xml b/android/app/src/main/res/drawable/widget_bg_char.xml index 5a047e83..c108024f 100644 --- a/android/app/src/main/res/drawable/widget_bg_char.xml +++ b/android/app/src/main/res/drawable/widget_bg_char.xml @@ -1,5 +1,5 @@ - + - + diff --git a/android/app/src/main/res/drawable/widget_bg_paper.xml b/android/app/src/main/res/drawable/widget_bg_paper.xml index 645362f6..44da6093 100644 --- a/android/app/src/main/res/drawable/widget_bg_paper.xml +++ b/android/app/src/main/res/drawable/widget_bg_paper.xml @@ -1,5 +1,5 @@ - + - + diff --git a/android/app/src/main/res/layout/widget_openstrap_nodata.xml b/android/app/src/main/res/layout/widget_openstrap_nodata.xml new file mode 100644 index 00000000..8a593d61 --- /dev/null +++ b/android/app/src/main/res/layout/widget_openstrap_nodata.xml @@ -0,0 +1,35 @@ + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png index 2fc52a5b..804ea9cb 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png index f7b0ee2f..da2101ad 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png index 05b6d2ba..da9a5167 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png index e2a9f247..48b87c6f 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png index 2c1d6812..6249c2a7 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml new file mode 100644 index 00000000..87af5cca --- /dev/null +++ b/android/app/src/main/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + + #0B1017 + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be7..5c9008eb 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -13,6 +13,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index a812b9cd..2598f511 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -1,4 +1,11 @@ - #F4F1EC + #020D1C + + #F8FAFC \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef880..021cf7dd 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -13,6 +13,6 @@ This Theme is only used starting with V2 of Flutter's Android embedding. --> diff --git a/android/app/src/main/res/values/widget_strings.xml b/android/app/src/main/res/values/widget_strings.xml index ce81bb55..73402773 100644 --- a/android/app/src/main/res/values/widget_strings.xml +++ b/android/app/src/main/res/values/widget_strings.xml @@ -1,7 +1,7 @@ - Edge + OpenStrap Readiness, strain, sleep and HRV at a glance. Band battery - Your strap\'s battery, from the last connection. + Your band\'s battery, from the last connection. diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..7d2b537c --- /dev/null +++ b/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/2.0x/mascot_wellness.png b/assets/images/2.0x/mascot_wellness.png new file mode 100644 index 00000000..9f27cb58 Binary files /dev/null and b/assets/images/2.0x/mascot_wellness.png differ diff --git a/assets/images/2.0x/mascot_workout.png b/assets/images/2.0x/mascot_workout.png new file mode 100644 index 00000000..e8e1e309 Binary files /dev/null and b/assets/images/2.0x/mascot_workout.png differ diff --git a/assets/images/3.0x/mascot_wellness.png b/assets/images/3.0x/mascot_wellness.png new file mode 100644 index 00000000..7a405695 Binary files /dev/null and b/assets/images/3.0x/mascot_wellness.png differ diff --git a/assets/images/3.0x/mascot_workout.png b/assets/images/3.0x/mascot_workout.png new file mode 100644 index 00000000..0503449e Binary files /dev/null and b/assets/images/3.0x/mascot_workout.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png index 737c06fa..ce6b52a6 100644 Binary files a/assets/images/icon.png and b/assets/images/icon.png differ diff --git a/assets/images/icon_bw.png b/assets/images/icon_bw.png new file mode 100644 index 00000000..0c33ad1a Binary files /dev/null and b/assets/images/icon_bw.png differ diff --git a/assets/images/mascot_wellness.png b/assets/images/mascot_wellness.png new file mode 100644 index 00000000..065546db Binary files /dev/null and b/assets/images/mascot_wellness.png differ diff --git a/assets/images/mascot_workout.png b/assets/images/mascot_workout.png new file mode 100644 index 00000000..c544f7ef Binary files /dev/null and b/assets/images/mascot_workout.png differ diff --git a/assets/launcher/icon_adaptive.png b/assets/launcher/icon_adaptive.png new file mode 100644 index 00000000..c28ec74f Binary files /dev/null and b/assets/launcher/icon_adaptive.png differ diff --git a/assets/splash/splashscreen.mp4 b/assets/splash/splashscreen.mp4 deleted file mode 100644 index 4bc5c91f..00000000 Binary files a/assets/splash/splashscreen.mp4 and /dev/null differ diff --git a/docs/notice.html b/docs/notice.html index eb4286f8..3c953676 100644 --- a/docs/notice.html +++ b/docs/notice.html @@ -33,6 +33,28 @@

Notice

see the protocol package's README for methodology notes.

+ +

Open Food Facts

+ +

Contains information from + Open Food Facts, which is made + available under the + Open Database + License (ODbL) v1.0. Individual contents of the database are + available under the + Database + Contents License.

+ +

When you scan a food barcode, Edge asks Open Food Facts about that + barcode and shows you the nutrition figures on file, which you can then + edit before saving. Edge does not fetch or display Open Food Facts + product photographs: those are licensed separately, per photographer, + and may carry third-party trademark restrictions of their own.

+ +

Open Food Facts' terms state that the data must not be used for medical + purposes. Edge treats a scanned figure as something you entered, never as + something it measured, and drops any value that fails a basic + plausibility check rather than showing it to you.