Skip to content

Claude/whoop app fork improvements 897f7b - #5

Open
MatB57 wants to merge 30 commits into
ParthJadhav:mainfrom
MatB57:claude/whoop-app-fork-improvements-897f7b
Open

MatB57 wants to merge 30 commits into
ParthJadhav:mainfrom
MatB57:claude/whoop-app-fork-improvements-897f7b

Conversation

@MatB57

@MatB57 MatB57 commented Aug 12, 2026

Copy link
Copy Markdown

What this PR does

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

How it was tested

Checklist

  • Swift package tests pass for any package I touched (swift test in Packages/<name>)
  • Android unit tests pass if I touched android/ (./gradlew testFullDebugUnitTest)
  • No new build warnings introduced
  • UI changes use only StrandDesign tokens — no hardcoded colors, fonts, or spacing
  • No hardcoded hex frame bytes; protocol facts live in the schema / decoders
  • Follows the conventions in docs/CONTRIBUTING.md
  • I did not commit generated output (Strand.xcodeproj/) or any secrets/keystores

Related issues

noaetmatthias and others added 8 commits August 12, 2026 18:05
The in-app update checkers (macOS + Android), AltStore/SideStore source
metadata, bug-report links, and install/README docs all still pointed at
ParthJadhav/noop. Retarget them at MatB57/noop so future releases published
here are the ones this fork's users (and their sideloaded devices) actually
discover and auto-update to.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
French was ~15% complete on the main catalog (458/2988 keys) and partial
elsewhere. Fills in all remaining keys across the four Xcode string
catalogs (Strand, NOOPWatch, NOOPWatchComplications, StrandDesign) and adds
the missing android/values-fr/strings.xml, matching the established
"vous"-register terminology already used in the existing French strings
(Récupération, Effort, VFC, Bracelet, Sources de données, etc.).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…897f7b

Claude/whoop app fork improvements 897f7b
Version bump only, to trigger a clean CI build/publish of the update-
redirect and French-localization changes for MatB57/noop, and to give the
family's devices a distinct version to update to.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TestReportLinkTest.kt and TestReportLinkTests.swift still asserted the old
ParthJadhav/noop issue URL, which broke Android CI's unit tests after the
source was retargeted to MatB57/noop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The first real release built and published from this fork. Prepends the
8.2.1 entry (keeps 8.2.0 for AltStore's version history) and fixes the
remaining ParthJadhav source-attribution string.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Version-only bump to verify the in-app "Check for updates" flow works
end-to-end for both a currently-installed app and a freshly-checking one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jaedonvs pushed a commit to jaedonvs/noop that referenced this pull request Aug 15, 2026
…+ Android single-slot arbiter (ParthJadhav#5)

ParthJadhav#4/ParthJadhav#6 (iOS): the iOS smart alarm only armed the strap firmware buzz and posted a local notification
AFTER the strap reported it fired, so a failed strap buzz or a suspended phone past day one meant NO
wake at all. applySmartAlarm() now also schedules a REPEATING daily UNCalendarNotificationTrigger
(the WindDownNudge idiom: lives in the notification center, survives relaunch) at the wake time per
selected weekday, gated on the wrist-alerts master + notification auth, replaced on every re-arm and
cancelled on disarm. HONEST: it is a best-effort backup, not a guaranteed loud alarm (a sideloaded
build has no critical-alert entitlement, so Focus/silent can still mute it) , the honesty card now
says so and still tells users to keep their Clock alarm.

ParthJadhav#5 (Android): "Buzz WHOOP 4/5" and the "Strap wake-alarm" both drove the single firmware-alarm slot
with no coordination, so toggling one off ran disableStrapAlarm() and killed the other. Introduced
one reconcileStrapAlarm() as the SOLE caller of arm/disable: it computes both requested times and
arms the slot to the EARLIEST when either is on, else disarms. All six call sites routed through it.
Test: both on, turn one off, slot stays armed to the other.
jaedonvs pushed a commit to jaedonvs/noop that referenced this pull request Aug 15, 2026
iOS Backup & Sync: mirror Android adjustable keep-count (default 7)
jaedonvs pushed a commit to jaedonvs/noop that referenced this pull request Aug 15, 2026
fitnessReadyLead() returns an already-localized String (via
String(localized:)), but ReadinessChecklistCard.lead was typed
LocalizedStringKey?, which SwiftUI treats as a lookup key rather than
literal text. Retype to String? — Text(_:) accepts both, and this is
the correct type for pre-resolved copy.

Pre-existing break from commit 75a096a (PR #81), unrelated to this
branch's localization change; fixing here so PR ParthJadhav#5's CI build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MatB57 and others added 19 commits September 10, 2026 22:47
AndroidDiagnostics already flags "NOT exempt from battery optimisation" as the
#1 cause of missed overnight background BLE, but nothing ever asked the user to
fix it. Without the exemption Android freezes the foreground service during
Doze: the strap link drops overnight, no R-R/HRV is banked, and the recovery
baseline never leaves "calibrating" — on a WHOOP 4 or 5.

- Add REQUEST_IGNORE_BATTERY_OPTIMIZATIONS to the manifest (Play permits it for
  a continuous companion-device link).
- New BatteryOptimization helper: isExempt() + the request / settings intents.
- Settings → "Keep connected in the background": an amber warning row with an
  "Allow" button, shown only while background connection is on and the
  exemption isn't granted. Android-only; no iOS/macOS counterpart.

Fixes the likely root cause of "calibrating for weeks" on Android.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The sideloaded APK had no real update path — "Check for updates" only opened
the GitHub release page in a browser, which a non-collaborator (e.g. a parent
on a shared build) can't act on easily.

- UpdateCheck now also returns the release's .apk asset URL + size.
- InAppUpdate: download the APK into cache/updates/, hand it to the system
  package installer via the existing FileProvider; routes through the per-app
  "install unknown apps" grant on first use; browser fallback on any failure.
- UpdateGate: a throttled (<=1/6h) launch check from NoopRoot that pops a
  dialog when a newer version is available, with one-tap "Update" (download +
  install) or "Later" (snoozes that version). Onboarded users only.
- Settings "Check for updates" now installs in-app too instead of opening a
  browser.
- Manifest: REQUEST_INSTALL_PACKAGES. file_paths: cache/updates/.

Android-only; iOS gets one-tap updates from its AltStore/SideStore source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two fixes for the sideloaded Android build (redo of the reverted pair — the
first attempt used the unimported M3 `Button`; this uses `NoopButton`).

In-app update (so a non-collaborator parent doesn't have to hunt a GitHub
asset in a browser):
- UpdateCheck also returns the release's .apk asset URL + size.
- InAppUpdate: download the APK into cache/updates/, hand it to the system
  package installer via the existing FileProvider; routes through the per-app
  "install unknown apps" grant on first use; browser fallback on any error.
- UpdateGate: a throttled (<=1/6h) launch check from NoopRoot that pops a
  dialog when a newer version is available — one-tap "Update" (download +
  install) or "Later" (snoozes that version). Onboarded users only.
- Settings "Check for updates" now installs in-app instead of opening a browser.
- Manifest: REQUEST_INSTALL_PACKAGES. file_paths: cache/updates/.

Battery-optimisation exemption (likely root cause of "calibrating for weeks"):
- Manifest: REQUEST_IGNORE_BATTERY_OPTIMIZATIONS.
- BatteryOptimization helper: isExempt() + the request / settings intents.
- Settings → background connection: an amber warning row with an "Allow"
  button, shown only while background connection is on and the exemption
  isn't granted.

Android-only; iOS gets updates from its AltStore/SideStore source and needs
no power-exemption for CoreBluetooth background delivery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ships the Android in-app update prompt + the Doze battery-optimisation
exemption. Cut as its own release so installs on 8.2.2 detect it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Prepend the 8.2.3 entry (real IPA size from the published release) and
point the legacy top-level fields at it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each release run signed with a fresh ephemeral debug key, so Android refused
to update in place and forced an uninstall (wiping onboarding + profile +
local DB). release.yml now decodes a keystore from the ANDROID_KEYSTORE_B64
secret (+ password/alias secrets) and signs with it, so every release shares
one key and in-app updates install over the top. Falls back to the debug key
with a loud warning when the secret is unset. Keystore never enters git.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Android: a backup restore / data import used to leave the app showing stale
  "calibrating" / "No Data" until the user force-quit and reopened. It now
  relaunches the process automatically once the DB is swapped (AppRestart).
- Support / About: the contact row (macOS/iOS SupportView + Android Settings)
  now opens this fork's GitHub — code, releases, issues — instead of the
  upstream project's email. The "Built on" attribution credits are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Groundwork for translating the Android UI without a mass strings.xml
extraction: Fr.load() parses the bundled EN->FR dictionary (the same pairs
the iOS String Catalog ships) once at startup when the device language is
French, and tr("English literal") swaps in the French rendering at the call
site — falling through to English for anything unknown or format-bearing, so
it is always safe. No call sites yet; wrapping the screens follows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Source had newer UI copy than the last catalog extraction (Live Sessions,
Smart Alarm explainers, Scoring Guide, Trends report legend, sleep-move
warnings, a few labels/buttons), so those showed in English on a French
device. Added as manual entries with fr translations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SUPPORT.md and the issue-template config linked the upstream project's
Discord / subreddit / wiki / contact email, none of which this family fork
runs. Now they point at this repo's issues + README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mechanical pass over ~50 Compose screens: every user-facing English string
literal (Text, contentDescription, titles/subtitles, button labels, helper
copy, concatenation fragments) is now tr("…"), which returns the string
unchanged unless the device language is French. Behaviour-neutral on an
English device. French renderings for ~1000 of these already come from the
shared iOS dictionary; the rest are being translated next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Translated the 885 tr() strings not already covered by the shared iOS
dictionary (settings, device pairing, data sources, sleep/health/vitals,
trends/insights, workouts, live sessions, onboarding). Every distinct tr()
call site on the ~50 wrapped screens now resolves to French on a French
device; English devices are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
French localisation: iOS/macOS catalog gap-filled, and the ~50 Android
Compose screens now render in French on a French device. Plus the stable
release-signing key wiring, in-app-update polish, auto-reload after a
restore, and Support pointing at this fork.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An update that turns into a forced uninstall (signature mismatch, or any
other install hiccup) wipes the app-private database. AutoBackup.beforeUpdate
now writes a fresh .noopbak to the public Downloads folder (survives an
uninstall, unlike app-private storage) right before the APK download starts,
from both the launch-time UpdateGate prompt and the Settings "Update" button.
Best-effort and silent: any failure is swallowed and never blocks or delays
the update. API 29+ only (no extra permission needed there); skipped on
older devices rather than requesting one just for this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the pre-update auto-backup to Downloads. This is the first release
signed with the stable keystore (repo secrets), so a device on 8.2.4 or
earlier needs one last manual uninstall/reinstall; every release after this
one installs in place.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MatB57 and others added 3 commits September 11, 2026 13:46
Pass 1 wrapped literal Text("English") call-site arguments. It systematically
missed English returned from a when/if-based computed property or function —
DashboardCard.title/subtitle, ScoreState/RecordingState title+detail, sport
display names, sync-state labels, and (a much bigger class) full sentences
generated in com.noop.analytics (WeeklyDigest, ReadinessEngine,
RecoveryDrivers "What shaped Charge", IllnessSignalEngine, FitnessAgeEngine,
CyclePhaseEngine, VitalityEngine) that the UI only displays verbatim.

- TodayScreen.kt: Charge/Effort/Rest dashboard-card labels, the Synthesis
  sheet (Steady/Depleted/Primed/Peak, "Charge is low/steady/strong"),
  calibrating notes, sync-state chip, "Start session", HR chart subtitle,
  "Synced from:" footer, Key Metrics editor. Onboarding gets a "Skip" button
  (top bar) so re-onboarding after a reinstall doesn't force the full flow.
- WorkoutsScreen/WorkoutEditing/DevicesScreen/AddDeviceWizard/LiveWorkoutScreen:
  sport display names (fixes the "Walking, Walking, Walking" workout list),
  device pairing/sync labels.
- SleepScreen: remaining labels (duration subtitle, ledger paragraph, time
  pickers) + a layout fix for French labels overflowing/wrapping onto their
  own line in fixed-width stat tiles (SparkTile, StageBreakdownRow,
  NapSummaryCell, ChartFooter).
- TrendsScreen/TrendsExploreScreen/WeeklyDigestCard/HealthScreen/
  SkinTempCardsScreen/StressScreen: chart-legend words (Avg/Peak/Low/Days),
  "Trailing N days", vitals detail stats, illness/cycle confidence lines.
- TrendsReport.kt: the whole file was missed by pass 1 entirely (raw
  Canvas.drawText PDF renderer, not Compose Text()) — title, section
  headers, AVG/MIN/MAX/DAYS legend, footer legend, empty state, the export
  sheet, share-sheet strings.
- analytics/*.kt (7 files): added `Fr.tr(...)` (the i18n bridge from a
  different package) around every static text fragment in the insight/
  explanation sentence generators, preserving all interpolated numbers/
  formatting exactly. Two `const val`s had to become `val` getters since
  Fr.tr() isn't a compile-time constant.

fr_strings.json: 3805 -> 4034 (+229), 100% coverage of all tr()/Fr.tr() call
sites app-wide (2233 distinct strings). Verified no double-wraps, every
tr(/Fr.tr( call takes a string literal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes the remaining English left after the first FR pass: dashboard-card
labels, the Synthesis sheet, sync/calibration states, sport display names,
device labels, sleep-screen layout overflow, chart legends, the Trends
Report PDF (entirely missed by pass 1), and full sentences generated in the
analytics layer (weekly digest, readiness, recovery drivers, illness,
fitness age, cycle phase, vitality). Onboarding also gets a "Skip" button.
This installs in place over 8.2.5 (stable signing key already active).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants