Fix #521: stop the repeated "New data added" notification when nothing new arrived#522
Closed
ryanbr wants to merge 119 commits into
Closed
Fix #521: stop the repeated "New data added" notification when nothing new arrived#522ryanbr wants to merge 119 commits into
ryanbr wants to merge 119 commits into
Conversation
…t top bar Rebuild the iOS Today hero to match WHOOP's polish: - New GlowRing component (StrandDesign): a crisp full-circle arc over a clearly visible track, a bold count-up number, and a tight colour-matched glow (no fuzzy bloom), with a spring sweep-in that re-animates on day nav. Replaces the 240 deg bevel gauge in the three-up hero — Charge centred + enlarged, Rest / Effort flanking — floating on the page rather than boxed in a card. - Compact top bar: profile/settings button (left, opens Settings sheet), centred bold day-nav with chevrons (the full date shows only when navigated off today), and the strap-battery badge (right). Drops the big "Today" title and the duplicated date. - Move the greeting + SOLID/CALIBRATING data-confidence pill onto the Synthesis card's top-right, and remove the "At a glance / Today's Synthesis" header so the rings lead the screen. - ScreenScaffold.title is now optional (iOS Today supplies its own top bar; macOS keeps its header). DEBUG --demo-seed sets a demo strap battery so the badge renders without a connected strap.
…ild 122) The .xcodeproj is generated by xcodegen from project.yml; the release build now regenerates it so the bundle version always tracks the bump.
v4.6.2 only swapped the ring component into Android's old layout, so it kept the big gold RecoveryRing hero + 'At a glance' header and didn't match the iOS/macOS WHOOP-style hero. Restructure Today to parity: - Three GlowRings as the hero, Charge centred + enlarged, Rest/Effort flanking, floating on the scenic backdrop with tappable label+chevron beneath each. - Drop the big RecoveryRing hero; recovery reads as the enlarged Charge ring. - Move the Support heart into the scaffold's compact top bar (new trailing slot). - Re-home the Synthesis card (greeting + SOLID/CALIBRATING pill) and the HRV/Resting HR/Respiratory rows below the hero, mirroring TodayView.heroSection. Verified on emulator (demo data): Rest/Charge(enlarged)/Effort render correctly. Android-only; iOS/macOS were already on this layout in 4.6.2.
The new three-ring Today hero sized the centre ring off (width/2.3) without allowing for the two inter-ring gaps, so centre + 2×side + 2×gap overflowed the row and the Row squeezed the last (Effort) column horizontally — its GlowRing box became taller-than-wide and the arc drew as an ellipse. - ScoreHeroRow: size the centre ring off (maxWidth - 2×gap) / 2.4 so all three fit. - GlowRing: draw the arc off min(width,height), centred, so it stays a circle even in a non-square box (defense in depth). Verified on emulator (zoomed): Rest / Charge / Effort all render as equal-radius circles, Charge enlarged. Android-only.
Flagship + adopted PRs (reimplemented as NoopApp, credited): - NoopApp#35 (matt): Xiaomi Mi Band import — Mi Fitness on-device DB, fully offline; StrandImport parser + GRDB, dedicated iOS/Mac page + Android Data Sources card, Explore/Compare/Correlations, sleep hypnogram. (+ NoopApp#36 chart perf folded in.) - NoopApp#30 (ryanbr): WHOOP 4.0 sleep — sparse-gravity gate median->largest gap so a clumped night with a long dropout is bridged, not shredded. Fixes NoopApp#28, NoopApp#33. - NoopApp#34 (ryanbr): device/toggle-aware raw-export empty-state message. Fixes NoopApp#32. Issue fixes (cross-platform): - NoopApp#41: TrendChip suppresses the direction glyph for plain magnitudes ("874 kcal" no longer renders a leading dash that read as -874). Swift + Kotlin. - NoopApp#38: iOS Explore rows push detail via a direct closure NavigationLink (no more value+destination double-push). - NoopApp#22: hide 5/MG-only Settings controls on a 4.0, gated on the auto-detected model (keeps the generic CSV export visible; never hides from a real 5/MG owner). - NoopApp#25: auto-continue backfill also fires on HISTORY_COMPLETE, so a strap slicing a deep offload drains back-to-back; cap engages. Swift + Kotlin + tests. - NoopApp#27: pruneRaw bounds unsynced raw by maxUnsyncedBytes + Puffin capture dir cap. - NoopApp#20: iOS reads Apple Health body composition (weight/body-fat/lean/BMI), read-only. Verified: macOS/iOS/Android builds green; StrandAnalytics 410 + WhoopStore 122 + StrandImport 103 package tests + 722 Android unit tests pass; kcal fix render-confirmed. Lockstep 4.7.0 (Apple 154/123, Android vc 211).
…ort, steps explainer - NoopApp#127 Manual HRV snapshot: a "HRV reading" button on Live captures ~60s of live R-R and reports RMSSD/SDNN/mean-HR (reuses HRVAnalyzer + the proven Breathe ingest path). All platforms. - NoopApp#460 Haptic Clock: the strap buzzes the current time (pure HapticClock encoder + buzzTimeNow trigger). Apple wires it as a double-tap Automation action (MacActionKind.hapticClock); Android exposes a "Buzz the time" Settings button. Pulse.isLong keeps the timing table module-internal. - NoopApp#461 Phase 1 sleep marks: "Going to sleep" / "I'm awake" buttons on the Sleep screen log a timestamped, persisted mark (sleep_mark series) + a greppable strap-log line. - NoopApp#510 scheduled debug export (Android): bounded 24h rolling strap-log buffer + a daily WorkManager export at a chosen time with timestamped filenames; Settings card + app-start reschedule. - NoopApp#37 steps "no motion synced yet" explainer on the calibration sheet (Swift + Android). Lockstep: mac CFBundle 155, iOS 124, Android versionCode 212. Verified: mac+iOS xcodebuild, Android assembleFullRelease (cert 3b1716ab…), 173 WhoopProtocol + 410 StrandAnalytics + Android JVM tests.
…opApp#33/PR#46), step-calibration phone-source fix (NoopApp#37), brew docs (NoopApp#44) - NoopApp#39/PR#45 (ryanbr): route every live-HR display (Health hero, macOS menu bar) through the spike-filtered AppModel.bpm/AppViewModel.bpm median instead of raw live HR, so a one-off optical spike (real 91 shown as 170+) no longer leaks to the hero/menu-bar. Disconnect clears the median to "—" on both platforms (iOS ingest guard + Android collect-block parity). - NoopApp#33/PR#46 (ryanbr): durable DismissedSleep tombstone (Room v9→v10) mirroring DismissedWorkout so a deleted night stays deleted across on-device recompute; OR'd into the sleepKept overlap filter. +4 tests. - NoopApp#37 (pikapik487): step calibration now reads phone steps from appleDaily (the table the Android Apple-Health/Health-Connect importers actually write steps to) and UNIONS Health Connect — fixes the "Not calibrated" stuck state for HC and Apple-Health Android users (engine read was hitting the empty dailyMetrics.steps table). - NoopApp#44 (tonyjacked): corrected the Homebrew install commands in README + HOMEBREW.md to the explicit self-hosted tap URL (the bare shorthand resolved to a dead host). Lockstep: mac CFBundle 156, iOS 125, Android versionCode 213. Verified: mac+iOS xcodebuild exit 0, Android assembleFullRelease + JVM tests (incl. new SleepEditDurability + Room v9→v10 migration), cert 3b1716ab…
…(caused 'given data not valid JSON') The GitHub host is gone, so the old raw.githubusercontent.com URLs return an HTML error page instead of JSON/PNG — sideloaders reported 'given data not valid JSON' when adding the source, and the source icon was blank. Point both at the self-hosted noop.fans raw URL. (Recurring iOS-install complaint on the subreddit.)
…day fix, honest smart-alarm copy From the r/NOOPApp bug-hunt: - Calories too high: estimateDayCalories applied the gross-exercise Keytel rate to ordinary daytime HR (active gate at 0.30 HRR ≈ 94 bpm). Day path now gates at 0.50 HRR and floors the active rate at BMR, so light activity isn't credited exercise-intensity burn. Bout (detected/manual workout) calories unchanged. Cross-platform (StrandAnalytics + Kotlin twin + tests). - NoopApp#34/NoopApp#53 (Android): Today HR-graph workout markers queried only "my-whoop"; Health-Connect (and apple-health) imported workouts were excluded. Widened to the same source union the Last-Workouts feed uses, via a shared workoutsAllSources() seam. - Android WHOOP-export import keyed the sleep→DailyMetric fold off sleep ONSET (prev evening) instead of WAKE-day, landing a night a day early + splitting it; now keys to wake-day to match the cycle row + macOS + AppleHealth. +JVM test. - Smart-alarm Automations card (Apple) over-promised a strap-driven wake; now leads with the experimental/keep-a-backup caveat, mirroring Android. Copy only, no alarm-logic change. Also shipped earlier this session: iOS sideload source URL fix (AltStore "given data not valid JSON"). Lockstep: mac CFBundle 157, iOS 126, Android versionCode 214. Verified: mac+iOS xcodebuild exit 0, Android assembleFullRelease + JVM tests, 412 StrandAnalytics tests, cert 3b1716ab…
NOOP becomes a raw-signal platform: it reasons from the strap's beat-to-beat R-R, PPG, motion and skin temperature, computes everything on-device (private, free), and can act on the body through the strap's haptic motor. Seven pillars + Lab Book (all opt-in, non-clinical, local-only): - Haptic biofeedback — personal resonance breathing paced by the wrist motor (screen-off), a Calm-me buzz-below-HR down-regulation, and a passive stress check-in nudge. - Insights 'What Moves You' — n-of-1 lag-aware ranking of what actually moves your recovery, plus a personal alcohol/caffeine dose-response with an evening damage forecast. - Skin-temperature suite — opt-in cycle-phase awareness, a Body Clock / jet-lag helper, and a confounder-suppressed illness Heads-Up. - Your Data, Fused — best-source-wins arbitration across WHOOP + a band + Apple Health/Health Connect, on-device, with provenance + honest conflict compare (never a silent average). - Lab Book — your own private logbook for bloods / BP / scans / notes; trend each marker and line it up against a wearable signal. Ships no reference ranges; not medical advice. - Rhythm (experimental) — a beat-to-beat regularity Poincaré visualization behind a consent gate. Descriptive only: no diagnosis, no condition names. - Smarter BYO-key AI Coach — can optionally fold in your on-device patterns + Lab Book markers, summaries only, nothing raw leaves the device. Architecture: pure on-device engines in the shared Swift packages with value-for-value Kotlin twins, pinned by golden-vector parity tests (533 StrandAnalytics + 132 WhoopStore Swift, 892 Android JVM, all green). UI per pillar across macOS/iOS/Android, wired into the analytics pass, Settings opt-ins and consent gates. Builds verified on all three platforms. Lockstep: macOS CFBundle 158, iOS project 127, Android versionCode 215. (Also scrubs the maintainer's first name out of a handful of code comments + design specs that the content gate flagged — the pre-push hook only guards commit metadata, not file contents.)
The three Lab Book sheets sized themselves with .frame(minWidth:minHeight:) on macOS. A macOS sheet hosting a ScrollView (ScreenScaffold) needs a DEFINITE height — with only a minimum, the scaffold's height stayed ambiguous and every row collapsed to the top, so the title, fields and the full marker catalog rendered on top of each other (the 'Add a reading' overlap). Switched all three to a fixed .frame(width:height:), matching every other working editor sheet (FusedRecord, AddDeviceWizard, ManualWorkout, KeyMetricsEditor, Sleep edit). macOS build SUCCEEDED.
…issues After the Lab Book 'Add a reading' overlap, audited every v5 page (SwiftUI + Compose). Fixes: macOS sheet collapse (ScrollView sheet needs a FIXED frame, not minWidth/none): - Breathe trainer sheet (StressView), Your-Data-Fused conflict-compare (FusedRecordView), Trends report (pre-existing), Workout detail (pre-existing) — all given fixed frames. - HealthView Fitness-Age: consolidated 2 stacked sheets into one enum-driven .sheet(item:) + frame. Clipping / layout: - Lab Book marker picker now SCROLLS (was clipping ~24 of 30 markers under a maxHeight cap). - Breathe pace pills scroll horizontally instead of truncating on a narrow iPhone. - Insights hub: moved the outcome/dose segmented controls onto their own row (no more crowding). - Compose: Fused conflict dialog scrolls (Done no longer clips); illness chips wrap (FlowRow); cycle headline + fused value no longer crowd; Lab Book value ellipsizes. Correctness / parity: - Rhythm 'Extra/Skipped' tile was the app's CRITICAL RED — recoloured to the calm Rest tone (the screen's 'never red/alarm' contract), both platforms. - Android Breathe now offers the locked Resonance pace; coherence bar/tile use Rest colours. - Android Health gains the 'Records & sources' deep-links (Lab Book / Your Data Fused) for parity. Verified: macOS + iOS xcodebuild SUCCEEDED, Android compileFullDebugKotlin SUCCEEDED — 0 errors.
- MARKETING 5.0.1, iOS CURRENT_PROJECT_VERSION 128, macOS CFBundle 159, Android versionCode 216 - The v5 UI/UX sweep fixes (macOS sheet-collapse class, Lab Book catalog scroll, never-red Rhythm, Compose clipping + parity) shipped in 1624e06; this release-bumps them. - Regression gate: 533 StrandAnalytics + 892 Android JVM tests green; mac/iOS/Android builds SUCCEEDED.
Replaces the native tab bar + floating FAB (which overlapped + carried a gold glow) with a single frosted bar: Today · Trends · [add] · Sleep · More. The add is a small contained gold disc inline (same gold language, differentiated, no longer hogging space). Real iOS 26 Liquid Glass with a .ultraThinMaterial fallback below; cleaner flat icons; soft neutral shadow only. Native bar hidden via .toolbar(.hidden, for: .tabBar). Verified on the iPhone 17 Pro simulator.
The gold blooms/glows read as 'too much' against the cleaner design. Toned down at the source so it propagates app-wide (mac+iOS): primary gold button now a soft neutral lift (was gold cast-glow); selected segmented-pill loses its gold/accent glow (crisp flat fill); the global additive bloom (ring/hero/sparkline halos on dark) dialed to 0.55; the Breathe orb glow radius 26→12 @0.18. Verified crisper on the iPhone 17 Pro sim in dark mode. (Android parity in the parity pass.)
…ndroid bar/glow/pills - SegmentedPillControl (shared): selected pill now fills the track height so its inset is EQUAL on all sides (was more vertical margin than horizontal). - iOS onboarding: new 'Make it yours' appearance step (System/Light/Dark, re-themes live) so new users discover they can change the look — points to Settings → Appearance. - Android (Compose) parity: unified glass bottom bar (Today·Trends·[add]·Sleep·More, small contained gold disc, no overlap/glow), de-glow (neutral CTA elevation, restrained ring bloom, halved orb), equal-margin pills, matching onboarding appearance step. Verified: iOS-sim + macOS xcodebuild SUCCEEDED, Android compileFullDebugKotlin SUCCEEDED — 0 errors.
The + button moves off the bottom bar (which is now four even tabs: Today·Trends·Sleep·More) into the top-right of the Today header, balancing the profile avatar on the left. It routes to the shell's quick-action sheet via a new NavRouter.requestQuickActions() signal. Matches the mockup. Verified on the iPhone 17 Pro sim.
…ottom bar Mirrors the iOS change — drops the AddDisc from GlassBottomBar (now four even tabs Today·Trends· Sleep·More) and adds a 34dp gold quick-action disc to the Today header's trailing edge, wired to the existing quick-action sheet. Verified on the emulator.
A new UpdateStore (persisted) + Updates inbox sheet, with a bell + unread badge in the Today header (between the day-nav and the +). The Today info-cards (Live-now, New-here) get an × that tucks them into the inbox; the inbox offers Restore-to-Today, Mark-all-read, Clear-all, and deep-links tapped items (e.g. a 'new data landed' reading → Trends via NavRouter.trends). Seeds What's New once per version. Verified on the iPhone 17 Pro sim (bell + badge + card × render). Android mirror + profile picture (C) next.
…heet Hand-port of the iOS Updates inbox: UpdateStore.kt (org.json-persisted, observable, idempotent What's-New seed), an UpdatesInboxScreen presented as a ModalBottomSheet from a bell in the Today header (gold unread badge), the two Today info-cards made dismissible (× → tuck into inbox, per-card prefs flag), Restore-to-Today, Mark-all-read/Clear-all, and a 'new data landed' reading poster that deep-links to Trends. Verified on the emulator.
…P-4 pairing fix (NoopApp#50, Android) - NoopApp#69 (iPhone & Mac): the Today Synthesis card's greeting + recovery/calibration pill no longer crowd the card headline on narrow iPhones — overlay width-capped + greeting line-limited; InsightCard gains an opt-in titleTrailingInset so its title rows reserve room. - NoopApp#50 (Android): OnePlus BT stack fires onMtuChanged twice with the same value, wedging CCCD subscription so the WHOOP 4.0 bond never finishes (infinite watchdog bounce). Dedupe a same-value MTU callback within 1s (device-agnostic, always spurious) + a 450ms CCCD settle on OnePlus before the first descriptor write. Watchdog intact. - Lockstep bump 5.2.4 (iOS build 135, mac CFBundle 166, Android versionCode 223).
…opApp#78) Fixes a 5.2.3 regression: the 'put the strap in pairing mode' hint was keyed off lastBondedPeripheralUUID, so a strap that had bonded in a PRIOR session but now refuses the encrypted bond (held by the WHOOP app, or a stale iOS pairing after a Restored- CONNECTED-peripheral) got the wrong 'transient reconnect race' message and NO actionable guidance — exactly NoopApp#78. Now: a bondRefusalStreak counter (reset only on a genuine bond, not on disconnect, so it accumulates across the reconnect loop) surfaces the guidance from the 2nd consecutive refusal, while a single transient refusal right after a good bond (NoopApp#74) stays quiet. Guidance also now tells the user to Forget This Device in iOS Bluetooth. Unreleased on main — rides the next build; the user's immediate fix is the workaround.
…usal guidance (NoopApp#78) - forgetDevice(): removing a WHOOP from Devices now stops auto-reconnect, drops the BLE link, and clears the targeted-connect pin + iOS state-restoration ref — so NOOP lets go instead of re-grabbing it, freeing a stuck 5/MG to enter pairing mode and re-pair. - bondRefusalStreak guidance (already on main): surface the real pairing-mode + forget-device steps on a persistent 5/MG bond refusal (fixes the 5.2.3 'transient race' regression). - Apple-only (shared Swift BLE engine). Lockstep bump 5.2.5 (iOS build 136, mac CFBundle 167).
… the mirror - AltStore (altstore-source.json + update-altstore-source.sh): IPA downloadURL → GitHub release assets - Homebrew (update-homebrew-cask.sh): cask url → GitHub release asset; dual-push tap to GitHub + forge mirror; simpler default-host tap command - README + docs + issue templates + community files: Releases/Issues/Wiki/badge URLs → github.com/NoopApp/noop; brew install simplified; noop.fans noted as mirror - Badges (refresh-stats-badges.py): read from GitHub API, write docs/stats/*.json locally (multi-push distributes them) — ends the noop.fans-via-API divergence - New Tools/release.sh: publishes a release to BOTH GitHub (canonical) + Forgejo (mirror) - Stats refreshed from GitHub (latest v5.2.5)
…ain) - UpdateChecker.swift + UpdateCheck.kt: endpoint noop.fans/api/v1 → api.github.com/repos/NoopApp/noop/releases/latest (drop-in; same tag_name/html_url/body fields). User-initiated, on-device, nothing sent. - Settings → About 'project home & source' link + donation URL → github.com (noop.fans kept as mirror). - AltStore + Homebrew already pull from GitHub releases (channel configs repointed). - Lockstep 5.2.6 (iOS build 137, mac CFBundle 168, Android versionCode 224).
… nothing new arrived The Today "New data — N days added" announcement compared repo.days.count to a baseline held in @State (iOS) / remember (Android) — both NON-durable. repo.days / days fills ASYNC from EMPTY, so on every relaunch / view re-create the baseline was captured at a transient 0, and the next (full) load saw "0 -> N" and false-posted "N new days of history landed" — repeating on each reopen. Persist the baseline (iOS @AppStorage, Android SharedPreferences) so the "announce genuine growth once" invariant survives relaunch, and guard out the transient-empty load (count <= 0 = "not loaded yet", not "zero days") so 0 never becomes the baseline. First real load still sets the baseline silently; only a genuine increase vs the durable baseline posts. Both platforms.
b0e6d95 to
434d513
Compare
Author
|
Superseded — this is already fixed in v5.3.0 (#521: the "New data added" inbox spam). The shipped fix persists the last announced day key rather than a day-count, which dedups more cleanly than this approach, so closing. Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #521.
The Today "N new days added" announcement compared
repo.days.countagainst a baseline held in@State(iOS) /remember(Android) — both non-durable.repo.days/daysfills async from empty, so on every relaunch / view re-create the baseline was captured at a transient 0, and the next (full) load saw0 → Nand false-posted "N new days of history landed" — repeating on each reopen (the reporter's "every hour").Fix: persist the baseline (iOS
@AppStorage, AndroidSharedPreferences) so the "announce genuine growth once" invariant survives relaunch, and guard out the not-yet-loaded state (count <= 0= "not loaded yet", not "zero days") so 0 never becomes the baseline. First real load still sets the baseline silently; only a genuine increase vs the durable baseline posts. Both platforms.Notes: