diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..7f54a51a
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# GitHub's funding config only accepts URLs, not raw wallet addresses, so the
+# "Sponsor" button points at DONATE.md where the BTC/EVM addresses live.
+custom:
+ - https://github.com/OpenStrap/edge/blob/main/DONATE.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..dce06bc6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,94 @@
+name: Bug report
+description: Something in the app is broken or wrong
+labels: ["bug"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for reporting. A couple of things make bugs much faster to fix:
+ the app version, and whether the number is *wrong* or *missing* — those
+ are usually different causes.
+
+ - type: textarea
+ id: what
+ attributes:
+ label: What happened?
+ description: What you saw, and what you expected instead.
+ placeholder: |
+ Readiness showed "—" all morning even though the band synced overnight.
+ I expected a score by the time I woke up.
+ validations:
+ required: true
+
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce
+ description: If you can. "It just happens every morning" is a valid answer.
+ placeholder: |
+ 1. Wear the band overnight
+ 2. Open the app in the morning
+ 3. Today screen shows "—"
+ validations:
+ required: false
+
+ - type: input
+ id: version
+ attributes:
+ label: App version
+ description: Profile → scroll to the bottom, or the release you installed.
+ placeholder: "0.9.20"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: platform
+ attributes:
+ label: Platform
+ options:
+ - iOS (TestFlight)
+ - iOS (sideloaded IPA)
+ - Android (APK)
+ validations:
+ required: true
+
+ - type: input
+ id: device
+ attributes:
+ label: Phone model and OS version
+ placeholder: "Pixel 8, Android 15 / iPhone 15 Pro, iOS 18.5"
+ validations:
+ required: false
+
+ - type: dropdown
+ id: band
+ attributes:
+ label: Which band?
+ options:
+ - WHOOP 4.0
+ - WHOOP 5.0 / MG (experimental — expect breakage)
+ - Not sure
+ validations:
+ required: true
+
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots
+ description: >-
+ Very helpful for anything visual. Note this issue is PUBLIC and stays
+ public — please redact health data, dates, names, and any device or
+ account identifiers before uploading. Blurring the numbers is fine;
+ we usually only need the layout and the state of the screen.
+ validations:
+ required: false
+
+ - type: checkboxes
+ id: checks
+ attributes:
+ label: Quick checks
+ options:
+ - label: The official WHOOP app is not also connected to this band
+ required: false
+ - label: I've searched existing issues for this
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..987f63b5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,31 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Protocol / byte-level finding
+ url: https://github.com/OpenStrap/protocol/issues/new
+ about: >-
+ A new record type, opcode, event, or a field we decode wrongly. Those live
+ in the protocol repo, not here.
+
+ - name: A new metric, or how one is computed
+ url: https://github.com/OpenStrap/analytics/issues/new
+ about: >-
+ The math lives in the analytics repo. Open it there if you're proposing a
+ method rather than reporting an app bug.
+
+ - name: Questions and general discussion
+ url: https://github.com/OpenStrap/edge/discussions
+ about: >-
+ Not sure whether something's a bug? Want to ask how a metric works, or
+ show what you built? Start here.
+
+ - name: Installing on iOS
+ url: https://github.com/OpenStrap/edge/blob/main/guides/IOS_SIDELOAD.md
+ about: >-
+ Install trouble is usually covered by the guide — TestFlight is the easy
+ path now.
+
+ - name: Report a security vulnerability (private)
+ url: https://github.com/OpenStrap/edge/security/advisories/new
+ about: >-
+ Please don't open a public issue for a security problem — this reports it
+ privately instead. See SECURITY.md for what's in scope.
diff --git a/.github/ISSUE_TEMPLATE/wrong_number.yml b/.github/ISSUE_TEMPLATE/wrong_number.yml
new file mode 100644
index 00000000..6a45bf3f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/wrong_number.yml
@@ -0,0 +1,85 @@
+name: A metric looks wrong
+description: A number is displayed, but you think it's incorrect
+labels: ["analytics", "needs-triage"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Worth saying up front, so nobody wastes time: **OpenStrap's numbers are
+ not meant to match WHOOP's.** Different algorithms, published methods,
+ computed from a reverse-engineered byte stream. "It doesn't match the
+ WHOOP app" on its own isn't a bug.
+
+ What *is* a bug: a number that's physiologically implausible, one that
+ contradicts the app's own raw data, one that jumps around without
+ cause, or one that's confidently wrong when the underlying data is
+ clearly missing.
+
+ - type: input
+ id: metric
+ attributes:
+ label: Which metric?
+ placeholder: "Readiness / RHR / Sleep stages / Strain / Steps / …"
+ validations:
+ required: true
+
+ - type: textarea
+ id: shown
+ attributes:
+ label: What it showed, and why you think it's wrong
+ description: >-
+ Include the value, the day, and what you'd have expected. If another
+ screen in the app disagrees with it, that's a strong signal — say so.
+ placeholder: |
+ Readiness 100 after about 10 minutes of wear. There's no overnight data
+ yet, so it shouldn't be able to produce a confident score at all.
+ validations:
+ required: true
+
+ - type: dropdown
+ id: kind
+ attributes:
+ label: What kind of wrong?
+ options:
+ - Physiologically implausible (impossible value)
+ - Contradicts other screens in this app
+ - Confident number where the data is missing (should show "—")
+ - Shows "—" where there clearly is data
+ - Jumps or changes without new data
+ - Other
+ validations:
+ required: true
+
+ - type: input
+ id: version
+ attributes:
+ label: App version
+ placeholder: "0.9.20"
+ validations:
+ required: true
+
+ - type: dropdown
+ id: band
+ attributes:
+ label: Which band?
+ description: >-
+ Matters more than it looks — WHOOP 5.0 / MG feeds a thinner record than
+ 4.0 does, so a metric can be wrong on one and fine on the other.
+ options:
+ - WHOOP 4.0
+ - WHOOP 5.0 / MG (experimental — expect breakage)
+ - Not sure
+ validations:
+ required: true
+
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots
+ description: >-
+ The metric screen plus its trend view is ideal. This issue is PUBLIC and
+ stays public — redact health values, dates, names, and any device or
+ account identifiers first. The shape of a chart is usually enough; we
+ don't need your actual numbers to debug this.
+ validations:
+ required: false
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..3b31bb16
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,67 @@
+name: test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+concurrency:
+ group: test-${{ github.ref }}
+ cancel-in-progress: true
+
+# Read-only: this job only builds and tests. Nothing here needs write access,
+# and it executes code from pull requests.
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ # Don't leave GITHUB_TOKEN in .git/config where PR-authored test code
+ # could read it. Nothing in this job pushes back to the repo.
+ persist-credentials: false
+
+ # PINNED, not `channel: stable`. Two reasons, both found the hard way when
+ # this workflow first ran:
+ #
+ # 1. The repo does NOT currently build on Flutter 3.44.x. phosphor_flutter
+ # 2.1.0 extends IconData, which became a `final class` in 3.44, so
+ # every widget test fails to compile:
+ # "The class 'IconData' can't be extended outside of its library
+ # because it's a final class."
+ # Upgrading past 3.41.x needs phosphor_flutter bumped or dropped first.
+ # 2. CupertinoPageTransitionsBuilder moved between cupertino.dart and
+ # material.dart across those versions, so theme.dart's imports are
+ # version-sensitive too.
+ #
+ # A floating channel means the toolchain changes underneath an already
+ # reviewed commit — the same reasoning as the commit-SHA pins for sibling
+ # packages in pubspec.yaml. Bump this deliberately, with the phosphor
+ # dependency sorted out in the same change.
+ - uses: subosito/flutter-action@v2
+ with:
+ flutter-version: 3.41.6
+ channel: stable
+ cache: true
+
+ # Sibling packages resolve from their pinned commit SHAs in pubspec.yaml.
+ # There is no pubspec_overrides.yaml here — that file is gitignored and
+ # local-dev only — so CI tests exactly the pins a release would ship.
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Analyze
+ run: flutter analyze
+
+ # --concurrency=1: the suite uses sqflite_common_ffi against real database
+ # files, and parallel workers race on them.
+ #
+ # The golden capture (whoop_hist.jsonl) is a real band recording kept
+ # beside the repo rather than committed to it, so the two derivation-replay
+ # tests SKIP here and run locally.
+ - name: Test
+ run: flutter test --concurrency=1 --reporter=expanded
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..a1f22989
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,116 @@
+# Contributing
+
+Thanks for looking. This is a small project and PRs genuinely get read.
+
+## Which repo does my change go in?
+
+This is the one thing worth getting right before you start, because it decides
+where the code lives. OpenStrap is three packages and the split is strict:
+
+| Your change | Repo |
+|---|---|
+| A new record type, opcode, event, or anything about the bytes on the wire | [**protocol**](https://github.com/OpenStrap/protocol) |
+| A new metric, or a change to how an existing number is computed | [**analytics**](https://github.com/OpenStrap/analytics) |
+| Bluetooth reliability, storage, background sync, UI, anything app-shaped | [**edge**](https://github.com/OpenStrap/edge) (here) |
+
+If you're not sure, open an issue first and ask — it's cheaper than moving code
+between repos afterwards.
+
+## Ground rules
+
+**Never fabricate a number.** If an input isn't there, the metric returns
+`null`, not a plausible-looking guess. Every metric carries a confidence and a
+tier (`AUTH` / `HIGH` / `ESTIMATE` / `RELATIVE`), and those need to stay honest.
+A metric that quietly invents a value when the data is missing is worse than no
+metric.
+
+**Cite the method.** Anything in analytics implements a published, peer-reviewed
+algorithm, and the citation goes in a comment next to the code. If nothing in
+the literature fits what you want to do, that's fine — mark it `ESTIMATE`, give
+it low confidence, and say so. Don't invent constants and present them as
+science.
+
+**Some ceilings are real, not bugs.** HRV here is PRV derived from 1 Hz beat
+timing. Deep sleep is a low-confidence HR-flatness overlay. SpO2 and skin
+temperature are relative ADC values, never absolute. These are properties of
+what the band actually hands over. Please don't "fix" them by making the output
+look more confident than the input justifies.
+
+**Bump `kAlgoVersion`** (in `lib/compute/derivation_engine.dart`) whenever a
+change alters any analytics output. Stored day results are versioned and
+immutable; without a bump, devices keep serving stale values. If your bump's
+changelog entry cites an analytics change, check that the pinned analytics SHA
+in `pubspec.yaml` actually contains it — that mismatch has shipped a bug before.
+
+**Put logic in the pure policy classes.** Bluetooth decisions belong in
+`lib/ble/ble_state.dart` or `lib/sync/sync_policy.dart` as small, testable
+classes; the engine wires them together and the policies decide. That's what
+makes any of this testable without a band on your wrist.
+
+## Running it
+
+```bash
+git clone https://github.com/OpenStrap/edge.git
+cd edge
+cp .env.example .env
+flutter pub get
+flutter run --dart-define-from-file=.env
+```
+
+Quit the official WHOOP app before pairing — Bluetooth only lets one app own the
+band at a time.
+
+For local work across all three packages at once, create a `pubspec_overrides.yaml`
+(it's gitignored) pointing at your sibling checkouts:
+
+```yaml
+dependency_overrides:
+ openstrap_protocol:
+ path: ../protocol
+ openstrap_analytics:
+ path: ../analytics
+```
+
+## Tests
+
+```bash
+flutter analyze
+flutter test --concurrency=1
+```
+
+`--concurrency=1` matters: the suite runs against real database files and
+parallel workers race on them.
+
+A few tests replay `whoop_hist.jsonl`, a real band capture kept *beside* the
+repo rather than committed to it. If you don't have it those tests skip
+automatically — that's expected, and CI runs the same way. Everything else runs
+everywhere.
+
+CI runs `flutter analyze` and the full suite on every PR. Please make sure both
+are green locally first.
+
+## Pull requests
+
+- Branch off `main`. One logical change per PR.
+- Explain *why*, not just what. If it fixes an issue, link it.
+- If it changes anything a user sees, say what it looked like before and after.
+- Protocol changes: say how you verified it. "Decoded N real records off my own
+ band and the values were plausible" is a perfectly good answer, and honestly
+ more useful than a unit test alone.
+- No `Co-Authored-By` trailers.
+
+## Reporting protocol findings
+
+If you've worked out a field, an opcode, or an event we don't decode yet, that's
+one of the most valuable things you can contribute. Open an issue in
+[protocol](https://github.com/OpenStrap/protocol/issues) with the raw bytes, what
+you think the field is, and how you convinced yourself. A lot of the current
+event table is empirical guesswork by one person — more eyes genuinely helps.
+
+## A note on scope
+
+This project talks to hardware people already own, using their own data, on
+their own device. Please keep contributions within that: no scraping WHOOP's
+services, no redistributing their code, firmware, or assets, and no vendored
+material from other reverse-engineering projects whose licences don't permit it.
+Facts about a protocol are fine. Someone else's source code is not.
diff --git a/DONATE.md b/DONATE.md
new file mode 100644
index 00000000..3ae09b4b
--- /dev/null
+++ b/DONATE.md
@@ -0,0 +1,46 @@
+# Support OpenStrap
+
+OpenStrap is free, MIT-licensed, and has no company, no subscription, and no
+revenue behind it. It exists because a perfectly good sensor turned into a
+bracelet and that seemed like a stupid reason to throw hardware away.
+
+If it gave your band a second life and you'd like to chip in, these are the only
+addresses. There is no other donation channel, no token, and nothing for sale.
+
+### Bitcoin
+
+```
+bc1qvtcch38dcwp967ar764uu6eetw7tf907844wfq
+```
+
+### EVM — Ethereum, Base, Arbitrum, Optimism, Polygon
+
+```
+0x8310C89393366b7eBCD47ABa82e1dfB5ECeFFbD9
+```
+
+---
+
+**Please don't feel obliged.** The genuinely valuable contributions are free:
+
+- **Open an issue** when a number looks wrong. Bug reports from real bands on
+ real wrists are worth more than money — there's only one person's physiology
+ in the test data otherwise.
+- **Decode something.** A lot of the event table is empirical guesswork. If you
+ work out a field we don't understand, that helps everyone with one of these
+ bands. See [protocol](https://github.com/OpenStrap/protocol/issues).
+- **Tell someone** whose strap is in a drawer.
+
+### What donations do and don't buy
+
+They don't buy priority, features, or support. This isn't a paid product and
+turning it into one would defeat the point. What they realistically cover is the
+Apple Developer Program membership that keeps the TestFlight build alive, test
+hardware (a second band is the main thing that would speed up WHOOP 5 support),
+and coffee.
+
+Nothing is gated behind paying, and nothing ever will be.
+
+---
+
+*Not affiliated with, endorsed by, or connected to WHOOP.*
diff --git a/README.md b/README.md
index 4970c997..b0a61526 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,28 @@
An app that makes a WHOOP 4.0 useful without a WHOOP subscription. Connects to the band over Bluetooth, computes everything on your phone locally iOS and Android.
+[](https://github.com/OpenStrap/edge/actions/workflows/test.yml)
+[](LICENSE)
+[](https://testflight.apple.com/join/2BVSwq65)
+[](https://github.com/OpenStrap/edge/releases/latest)
+
> Not affiliated with WHOOP. Not a clone of their app or their scores — see below.
+## Install
+
+| | |
+|---|---|
+| **iOS** | **[Join the TestFlight beta →](https://testflight.apple.com/join/2BVSwq65)** — normal TestFlight install, no sideloading, no computer needed. |
+| **Android** | **[Download the APK →](https://github.com/OpenStrap/edge/releases/latest)** — allow installs from unknown sources and open it. |
+
+Quit the official WHOOP app before you pair. Bluetooth only lets one app own the
+band at a time.
+
+Prefer to sideload the unsigned IPA instead of using TestFlight? That still
+works — see [`guides/IOS_SIDELOAD.md`](guides/IOS_SIDELOAD.md).
+
## What made me build this app
My subscription lapsed and a perfectly good sensor turned into a bracelet. The hardware
@@ -53,23 +71,37 @@ spot-check, real-time breathing coherence.
**Activity** — auto-detected workouts, live workout tracking with GPS routes, heart-rate
zones.
+**Your data, elsewhere** — writes to **Apple Health** (HealthKit) and **Google Health
+Connect**: sleep stages, resting HR, HRV, respiratory rate, active energy and workouts.
+Only things the band actually measures — never the derived scores, which have no native
+type and would be fabricated. Exports are idempotent, so a day re-deriving never
+duplicates samples. You can also export the entire local SQLite database to a file
+whenever you like — it's your data, in a format anything can open.
+
+**Background sync** — the band drains without you opening the app. Android runs a
+foreground service with a 15-minute watchdog worker and re-attaches via
+CompanionDeviceManager. iOS uses a background processing task plus a light refresh task,
+and a separate restore Bluetooth central that relaunches the app when the band
+reconnects.
+
**Everything else** — trends/history, a journal with on-device correlation insights
("what actually moves your numbers"), cycle tracking, a deterministic coach, a shareable
weekly recap, a BYOK AI assistant, home-screen widgets, iOS Live Activities, Siri
-shortcuts.
+shortcuts, a smart alarm that buzzes the band.
## What doesn't work (yet, or maybe ever)
-- iOS background sync is best-effort. Apple doesn't give third-party apps a real
- background-service option, so syncing while you haven't opened the app in a while is
- "usually," not "always."
+- iOS background sync is best-effort. It genuinely works (see above), but Apple doesn't
+ give third-party apps a real background-service option, so the OS decides when those
+ tasks actually run. Syncing while you haven't opened the app in a while is "usually,"
+ not "always." Android has no such limit.
- Metrics are approximations off published research — not medical-grade, not validated
against a lab, don't treat any of it as a diagnosis.
-- Sideload only right now — not on the App Store or Play Store. You're installing an APK
- or an unsigned IPA straight off Releases. Android's just "allow unknown sources" and
- you're done. iOS needs one extra tool — see
- [`guides/IOS_SIDELOAD.md`](guides/IOS_SIDELOAD.md) if you just want the app and aren't
- planning to build it yourself.
+- Not on the App Store or Play Store yet. iOS is a public TestFlight beta, which is a
+ normal install but still a beta; Android is an APK straight off Releases.
+- WHOOP 5.0 / MG support is in progress and **experimental** — the band is detected and
+ spoken to, but it hasn't been validated against real 5.0 hardware. WHOOP 4.0 is the
+ only one that's actually tested.
## Run it
@@ -114,10 +146,27 @@ you're into this kind of thing.
## Your data stays on your phone
Everything's computed and stored locally. No cloud account required, no backend this
-needs to work day to day. The only network calls this app ever makes are three narrow,
-optional things — a one-time legacy-account import if you had an old cloud account, an
-OTA/announcement pointer, and a BYOK LLM proxy for the AI assistant. None of them are
-required for the app to work.
+needs to work day to day. **Your health data never leaves the device unless you
+explicitly send it somewhere.**
+
+Being precise about the network, since "no cloud" gets said too loosely. Nothing below
+is required for the app to work, and none of it carries health data except the two you
+turn on yourself:
+
+- **Anonymous diagnostics** (Firebase crash/performance). **On by default in GitHub
+ release builds** — switch it off in your profile and collection stops immediately.
+ **Not present at all** in App Store / Play Store builds. Never includes health data.
+- **OTA/announcement pointer** — checks whether there's a newer build.
+- **Legacy account import** — one-time, only if you had an old OpenStrap cloud account.
+- **BYOK AI assistant** — only if you configure a provider. Your key, your account. Be
+ aware that **the prompts contain your health data**: to answer "why is my recovery
+ low", the assistant is given your metrics to read. That data goes to whichever
+ provider you chose, under their policies, not ours.
+- **Health-data contribution** — opt-in, off by default, GitHub builds only. Uploads
+ your local database wholesale, which is the entire point of it. It's the only thing
+ here that sends the whole database rather than a slice.
+
+Full detail in [PRIVACY.md](PRIVACY.md).
## Repo layout
@@ -139,3 +188,18 @@ Found something broken? Open an issue. Found something broken and fixed it? Even
send the PR. Protocol-level stuff (new record types, opcodes) belongs in the protocol
repo, metric/formula changes belong in analytics, anything about the app itself —
Bluetooth, storage, UI — belongs here.
+
+[**CONTRIBUTING.md**](CONTRIBUTING.md) has the details: which repo a change belongs in,
+how to run the three packages together locally, and the two rules that matter most —
+never fabricate a number when the data isn't there, and cite the published method you're
+implementing.
+
+Security problems shouldn't go in a public issue — see [SECURITY.md](SECURITY.md) for
+private reporting.
+
+## Support the work
+
+Free, MIT, no company behind it. If it gave your band a second life:
+[**DONATE.md**](DONATE.md) has BTC and EVM addresses. Bug reports from real bands are
+worth more than money, though — there's only one person's physiology in the test data
+otherwise.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..4fad16c8
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,59 @@
+# Security Policy
+
+## Reporting a vulnerability
+
+Please **don't** open a public issue for a security problem.
+
+Use GitHub's private reporting instead:
+[**Report a vulnerability →**](https://github.com/OpenStrap/edge/security/advisories/new)
+
+That goes straight to the maintainer and stays private until there's a fix.
+
+Rough expectations, set honestly — this is a one-maintainer project, not a
+company with an on-call rota:
+
+- Acknowledgement within about a week.
+- An assessment, and a fix or a clear "won't fix and here's why", within 30 days
+ for anything that puts user data at risk.
+- Credit in the release notes if you want it.
+
+## What's in scope
+
+- Anything that discloses a user's health data off their device.
+- Anything that lets a third party read, write to, or hijack the Bluetooth
+ session with someone's band.
+- Local data-at-rest problems: the database, exports, the App Group container,
+ widget snapshots.
+- The optional companion worker in
+ [backend](https://github.com/OpenStrap/backend): auth, the import endpoints,
+ the opt-in telemetry and health-upload paths.
+- Anything that causes the app to send data anywhere the user did not agree to.
+
+## What's out of scope
+
+- The band's own firmware. We don't ship it, can't patch it, and won't publish
+ attacks against it.
+- WHOOP's own apps and services. Please report those to WHOOP.
+- The fact that sideloaded builds are unsigned, or that a rooted/jailbroken
+ device can read app storage. Both are known properties of the distribution
+ model, documented in the README.
+- Metric accuracy. Wrong numbers are bugs — open a normal issue.
+
+## Where your data actually is
+
+Worth knowing before you go looking: OpenStrap computes and stores your health
+data on-device, and there's no account or server holding it. Two qualifications,
+so the boundary is exact:
+
+- **Anonymous diagnostics** (Firebase crash/performance, never health data) are
+ **on by default in GitHub release builds** and absent from App Store / Play
+ Store builds. Switchable off in-app.
+- **Health-data contribution** uploads the local database, but is opt-in, off by
+ default, and compiled out of store builds entirely.
+
+Everything else the companion worker does — legacy import, an update pointer —
+is optional and carries no health data. See [PRIVACY.md](PRIVACY.md).
+
+That means the realistic attack surface is the phone, the Bluetooth link, and
+the local database — not a cloud backend. Reports focused there are the most
+useful.
diff --git a/docs/index.html b/docs/index.html
index 9a68c7d4..29c1fb39 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -3,8 +3,14 @@
-Edge — Legal
-
+OpenStrap Edge — use your WHOOP 4.0 without a subscription
+
+
+
+
+
+
+
@@ -12,35 +18,165 @@
Edge
-
Legal
-
Edge (OpenStrap) is an independent, open-source, local-first fitness and wellness
- companion application. This page links the legal documents for the app.
Your WHOOP 4.0 doesn’t stop working when the subscription does.
+
+ Only the app goes dark. Edge pairs with the band over Bluetooth, decodes what it
+ recorded, and works out your sleep, recovery, strain and the rest —
+ entirely on your phone. No account, no subscription, and no server that ever
+ receives your health data.
+
+ The band talks to the app, the app decodes the bytes and computes every metric
+ locally, and the results are stored on the device. There’s no server that
+ sees your health data — that isn’t a setting, it’s the
+ architecture.
+
+
+ Precisely, because “no cloud” gets said too loosely: GitHub Release
+ builds send anonymous crash diagnostics by default (switchable off); store
+ builds send none. Neither ever includes health data.
+ Detail →
+
+
+
+
Published methods, cited
+
+ Banister TRIMP, Cole–Kripke, Lomb–Scargle, van Hees and friends.
+ Nothing invented, no neural net guessing at WHOOP’s formulas. You can go
+ read the paper and decide whether you trust the number.
+
+
+
+
It admits what it doesn’t know
+
+ Every metric carries a confidence and a tier. When the data isn’t there
+ you get a dash, not a plausible-looking guess. SpO2 and skin
+ temperature are relative-only, because that’s what the band actually
+ gives up.
+
+
+
+
Not a WHOOP clone
+
+ Different algorithms, different numbers. They have years of research and a
+ team; this is textbook methods over a reverse-engineered byte stream. It trends
+ correctly and it’ll tell you when you’re under-recovered. It
+ isn’t their secret sauce and never claims to be.
+
+
+
+
+
+
Before you install
+
+
Quit the official WHOOP app before pairing. Bluetooth only lets one app own the band at a time.
+
Pick one and stay on it. A firmware push from the official app could change the records this depends on, and there’s no fixing that from here.
+
Not medical-grade. These are approximations from published research, not validated against a lab. Nothing here is a diagnosis.
+
There are bugs. It’s a beta built by one person. Open an issue when something looks wrong — that’s genuinely the most useful thing you can do.
+
+
+
+
+
How it’s put together
+
band → Bluetooth → protocol decoder → local storage → analytics → the UI
Edge (OpenStrap) is an independent, open-source, local-first fitness and wellness
+ companion application. This page links the legal documents for the app.