Skip to content

CI: release workflow bumps version (patch/minor/major) in source before releasing - #8

Merged
ryanbr merged 1 commit into
mainfrom
snapshot-v8.2.2
Jul 7, 2026
Merged

ryanbr merged 1 commit into
mainfrom
snapshot-v8.2.2

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What

Updates the manual "Release build (fork)" workflow to bump the version before releasing.

New bump choice input — patch (0.0.x) / minor (0.x.0) / major (x.0.0) / none, plus an optional explicit version override. The bump job edits the version in source — android/app/build.gradle.kts (versionName+versionCode) and project.yml (MARKETING_VERSION+CURRENT_PROJECT_VERSION) — commits it to the dispatched branch (authored ryanbr), and the platform jobs build that bumped commit so the app reports the new version. Then it cuts the non-prerelease v<NEW> release.

Notes

  • Commits the bump to the branch you dispatch from (normal bump-and-tag flow).
  • Verified locally: version math (8.2.2 → 8.2.3 / 8.3.0 / 9.0.0) and the seds hit only the version lines (-debug/-staging/-demo suffixes and $(MARKETING_VERSION) refs untouched).
  • Still never touches the testing-latest snapshot; still a non-prerelease so releases/latest (the update check) resolves against it.

…before releasing

The release build now takes a `bump` choice input (patch = 0.0.x, minor = 0.x.0, major = x.0.0;
plus `none`, and an explicit `version` override). The bump job increments the version IN SOURCE —
android/app/build.gradle.kts (versionName + versionCode) and project.yml (MARKETING_VERSION +
CURRENT_PROJECT_VERSION) — commits it to the dispatched branch (authored ryanbr), and the platform
jobs check out that bumped commit, so the built app actually REPORTS the new version. Then it cuts
the non-prerelease v<NEW> release. Each platform's build number increments independently.

Verified locally: version math (8.2.2 → 8.2.3 / 8.3.0 / 9.0.0) and the sed edits hit only the
version lines (the -debug/-staging/-demo suffixes and $(MARKETING_VERSION) refs are untouched).
@ryanbr
ryanbr merged commit 631ddb8 into main Jul 7, 2026
ryanbr pushed a commit that referenced this pull request Jul 8, 2026
A WHOOP MG that tops up short of full every day never trips the near-full
anchor (rule 1), so the discharge fit fell back to scanning the SoC
buffer's global max — which, across many partial-top-up cycles, can sit
several days back. The endpoints-only fit then nets across every
undetected intermediate top-up in between, diluting the actual recent
drain rate into something far flatter than reality (reported "9% = ~3
days" when the true recent drain was closer to 6h left).

Bound the max search to at most the last two charge-step cycles so it
stays anchored to how the strap is being used right now. Buffers with 0
or 1 charge-steps search from the start exactly as before, so #8 and
#919 are unaffected — verified against all existing fixtures plus a new
multi-cycle regression test. Ported the same fix to the Kotlin twin to
keep both platforms behaviour-identical.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TheBoroer added a commit to TheBoroer/ryanbr-noop that referenced this pull request Jul 10, 2026
…WIP)

One windowed readings list drives the trend chart, the 'N readings'
caption, and the new readings table, so the three can never disagree.
Source labels reuse TodayView.provenanceDisplayLabel — no new source
vocabulary. Swift + Kotlin twins with projection tests.
TheBoroer added a commit to TheBoroer/ryanbr-noop that referenced this pull request Jul 10, 2026
ryanbr added a commit that referenced this pull request Jul 16, 2026
…description typos (#492) (#496)

* Today (Android): unclip the day title + drop the bogus Strain % (#492)

Two Android-only visual bugs from #492 (iOS already handles both correctly):

- #1: the tappable day-title Column had .clip(RoundedCornerShape(cornerSm)) with
  indication = null — no ripple to shape, but the rounded corners clipped the
  title/date text's bottom-left (the 'W' of 'Wednesday'). Drop the clip; the
  clickable still defines the tap area. iOS uses contentShape(Rectangle()), no clip.

- #8: the Strain Key-Metrics tile hardcoded unit = "%". Strain/Effort is a load
  index (0–21 WHOOP / 0–100 NOOP), not a percentage, so the % was wrong — most
  visibly on the 0–21 scale ('5.7%'). Recovery/Rest are genuine 0–100 % and keep
  it. iOS shows the strain axis as an 'of 21'/'of 100' caption with no %.

compileFullDebugKotlin: BUILD SUCCESSFUL. Reported by bartmuskala.

* Trends (Android): fix two metric-description punctuation typos (#492 follow-up)

Spotted in the #492 screenshots (not in the reporter's list): the Charge/Sleep
metric-explorer descriptions had a space before the comma, and the Sleep one
also used a comma where iOS uses a colon. These are documented to 'match the
macOS MetricCatalog entries exactly' (TrendsExploreScreen :190), so this
restores that parity:
  - 'you are , led'      -> 'you are, led'
  - 'sleep was , dura...' -> 'sleep was: dura...'  (matches MetricCatalog.swift)

String-literal only; no logic.
DX23876 referenced this pull request in DX23876/noop Jul 20, 2026
… the coaching model

Reported: the two background-role model fields (chat summaries, card
analyses) were plain TextFields — no picker, no validation against the
provider's real catalogue, no "Custom…" affordance, no refresh — unlike the
main coaching model, which already has a Picker/searchable-sheet/Custom…
pattern (modelSelector) driven by coach.availableModels.

roleModelField now reuses that exact mechanism per role instead of a bare
TextField:
- Below the searchable threshold: a menu Picker listing every model the
  provider actually returned, plus "Same as coaching model" (empty — the
  role's real default, kept distinct from Custom) and "Custom…" (typed id
  outside the fetched list, same two-step tag/TextField dance as the main
  picker).
- Above the threshold (OpenRouter's 300+): the same searchable-sheet button,
  routed through a new enum-driven ModelSearchTarget (chat/summary/
  cardAnalysis) rather than a THIRD independently-toggled `.sheet(isPresented:)`
  — the exact stacked-modifier shape R2 just fixed for the goal dialogs, so
  this package doesn't reintroduce it three fields over.
- A Refresh-models affordance in the Background models card itself, calling
  the same coach.refreshModels() the coaching model's button already does
  (one shared per-provider model list — independent-provider-per-role is
  #8, still deferred) — repeated here for discoverability in this card.

Both roles are independently selectable; empty still means "inherit the
provider's cheap model" exactly as before, just chosen from a real option
instead of an empty text field.

2 new UI strings ("Same as coaching model", the reworded background-models
caption), localized de/es/fr. Both targets build; full suite green (1210, 0
failures); i18n audit exit 0 with 44 pure catalog insertions, 0 deletions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DX23876 referenced this pull request in DX23876/noop Jul 22, 2026
CI: release workflow bumps version (patch/minor/major) in source before releasing
DX23876 referenced this pull request in DX23876/noop Jul 22, 2026
…br#99)

A WHOOP MG that tops up short of full every day never trips the near-full
anchor (rule 1), so the discharge fit fell back to scanning the SoC
buffer's global max — which, across many partial-top-up cycles, can sit
several days back. The endpoints-only fit then nets across every
undetected intermediate top-up in between, diluting the actual recent
drain rate into something far flatter than reality (reported "9% = ~3
days" when the true recent drain was closer to 6h left).

Bound the max search to at most the last two charge-step cycles so it
stays anchored to how the strap is being used right now. Buffers with 0
or 1 charge-steps search from the start exactly as before, so #8 and
ryanbr#919 are unaffected — verified against all existing fixtures plus a new
multi-cycle regression test. Ported the same fix to the Kotlin twin to
keep both platforms behaviour-identical.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DX23876 referenced this pull request in DX23876/noop Jul 22, 2026
…description typos (ryanbr#492) (ryanbr#496)

* Today (Android): unclip the day title + drop the bogus Strain % (ryanbr#492)

Two Android-only visual bugs from ryanbr#492 (iOS already handles both correctly):

- #1: the tappable day-title Column had .clip(RoundedCornerShape(cornerSm)) with
  indication = null — no ripple to shape, but the rounded corners clipped the
  title/date text's bottom-left (the 'W' of 'Wednesday'). Drop the clip; the
  clickable still defines the tap area. iOS uses contentShape(Rectangle()), no clip.

- #8: the Strain Key-Metrics tile hardcoded unit = "%". Strain/Effort is a load
  index (0–21 WHOOP / 0–100 NOOP), not a percentage, so the % was wrong — most
  visibly on the 0–21 scale ('5.7%'). Recovery/Rest are genuine 0–100 % and keep
  it. iOS shows the strain axis as an 'of 21'/'of 100' caption with no %.

compileFullDebugKotlin: BUILD SUCCESSFUL. Reported by bartmuskala.

* Trends (Android): fix two metric-description punctuation typos (ryanbr#492 follow-up)

Spotted in the ryanbr#492 screenshots (not in the reporter's list): the Charge/Sleep
metric-explorer descriptions had a space before the comma, and the Sleep one
also used a comma where iOS uses a colon. These are documented to 'match the
macOS MetricCatalog entries exactly' (TrendsExploreScreen :190), so this
restores that parity:
  - 'you are , led'      -> 'you are, led'
  - 'sleep was , dura...' -> 'sleep was: dura...'  (matches MetricCatalog.swift)

String-literal only; no logic.
DX23876 referenced this pull request in DX23876/noop Jul 22, 2026
… the coaching model

Reported: the two background-role model fields (chat summaries, card
analyses) were plain TextFields — no picker, no validation against the
provider's real catalogue, no "Custom…" affordance, no refresh — unlike the
main coaching model, which already has a Picker/searchable-sheet/Custom…
pattern (modelSelector) driven by coach.availableModels.

roleModelField now reuses that exact mechanism per role instead of a bare
TextField:
- Below the searchable threshold: a menu Picker listing every model the
  provider actually returned, plus "Same as coaching model" (empty — the
  role's real default, kept distinct from Custom) and "Custom…" (typed id
  outside the fetched list, same two-step tag/TextField dance as the main
  picker).
- Above the threshold (OpenRouter's 300+): the same searchable-sheet button,
  routed through a new enum-driven ModelSearchTarget (chat/summary/
  cardAnalysis) rather than a THIRD independently-toggled `.sheet(isPresented:)`
  — the exact stacked-modifier shape R2 just fixed for the goal dialogs, so
  this package doesn't reintroduce it three fields over.
- A Refresh-models affordance in the Background models card itself, calling
  the same coach.refreshModels() the coaching model's button already does
  (one shared per-provider model list — independent-provider-per-role is
  #8, still deferred) — repeated here for discoverability in this card.

Both roles are independently selectable; empty still means "inherit the
provider's cheap model" exactly as before, just chosen from a real option
instead of an empty text field.

2 new UI strings ("Same as coaching model", the reworded background-models
caption), localized de/es/fr. Both targets build; full suite green (1210, 0
failures); i18n audit exit 0 with 44 pure catalog insertions, 0 deletions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
brendankowitz pushed a commit to brendankowitz/noop that referenced this pull request Aug 9, 2026
The mockup's own note for this screen is "the one-line flat-ink fix, plus a
provenance sentence in the header" — this is that, plus the two group cards the
tree can honestly fill.

- ScreenScaffold now takes `topBackground: liquidFlatInkBackground()`: Data
  Sources is an archive/utility page, not a lived moment.
- Header sentence is built only from figures the screen already prints (the WHOOP
  card's own days/sleeps counts) and closes with this screen's existing on-device
  line, verbatim. NOT the mockup's "four sources feeding today, everything current
  except Apple Health, last read yesterday": nothing in the tree records a
  per-source last-read time for the file importers, so that sentence would be
  invented provenance.
- The live-strap status card becomes GroupCard("Connected") with the mockup's
  status dot — the same three-state read the Live screen's pill uses (ryanbr#8), sage
  bonded / amber connected-not-streaming / terracotta offline — and the one real
  "synced N ago" the app has (`live.lastSyncedAt` + `relativeAgo`, the pairing the
  Health screen's sync status already renders). The file importers get no dot:
  they have no connection state to report.
- New ink ActionCard "Sync now" reaching the SAME gated entry point Health's
  Sync-now button uses (`BLEManager.syncNow()` is a no-op when no strap is
  connected or a sync is running), disabled with an honest subtitle when it can't
  run.
- GroupCard("Stored on <device>") carries the real days / sleep-sessions counts.
- Every importer card, binding, picker and destructive action is untouched.

Verified: xcodebuild -scheme Strand -destination platform=macOS build succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GPcFU4xvYzxovzd4aQ7uGB
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…description typos (ryanbr#492) (ryanbr#496)

* Today (Android): unclip the day title + drop the bogus Strain % (ryanbr#492)

Two Android-only visual bugs from ryanbr#492 (iOS already handles both correctly):

- ryanbr#1: the tappable day-title Column had .clip(RoundedCornerShape(cornerSm)) with
  indication = null — no ripple to shape, but the rounded corners clipped the
  title/date text's bottom-left (the 'W' of 'Wednesday'). Drop the clip; the
  clickable still defines the tap area. iOS uses contentShape(Rectangle()), no clip.

- ryanbr#8: the Strain Key-Metrics tile hardcoded unit = "%". Strain/Effort is a load
  index (0–21 WHOOP / 0–100 NOOP), not a percentage, so the % was wrong — most
  visibly on the 0–21 scale ('5.7%'). Recovery/Rest are genuine 0–100 % and keep
  it. iOS shows the strain axis as an 'of 21'/'of 100' caption with no %.

compileFullDebugKotlin: BUILD SUCCESSFUL. Reported by bartmuskala.

* Trends (Android): fix two metric-description punctuation typos (ryanbr#492 follow-up)

Spotted in the ryanbr#492 screenshots (not in the reporter's list): the Charge/Sleep
metric-explorer descriptions had a space before the comma, and the Sleep one
also used a comma where iOS uses a colon. These are documented to 'match the
macOS MetricCatalog entries exactly' (TrendsExploreScreen :190), so this
restores that parity:
  - 'you are , led'      -> 'you are, led'
  - 'sleep was , dura...' -> 'sleep was: dura...'  (matches MetricCatalog.swift)

String-literal only; no logic.
ryanbr added a commit that referenced this pull request Aug 27, 2026
Re-review found a fourth didBond reader I had missed. CLAUDE.md warns that
didBond is load-bearing well beyond the handshake; I audited the bond watchdog,
the #982 detector and the give-up, and stopped there. The keep-alive timer reads
it too.

keepAliveFire gated on didBond, which a suppressed 5/MG never reaches. That
switched the whole timer off for exactly the link the suppression keeps up for
hours, taking the liveness watchdog with it - so a stalled stream would have had
nothing left to bounce it, and the "stable live HR" this port promises would
have quietly depended on a stream nothing was watching. The Android twin gates
on `bonded`, the live-HR shortcut, and so keeps reaching its watchdog.

Worse, the first attempt at this was dead code: BOTH existing startKeepAlive()
call sites are post-bond, so on a suppressed link the timer never starts at all
and a corrected gate changes nothing. It now starts at the suppression point
too. Safe before HR arrives, because the gate returns false until the stream
marks the link established (#8).

Deliberately narrower than Android's gate: only a 5/MG is admitted unbonded, and
only as far as the watchdog - a second didBond guard closes the door before
anything puffin-framed, which an unbonded link cannot land anyway. A 4.0 sitting
unbonded is a real fault, not a suppressed link, and must not be kept alive.

Verification: the gate is extracted as pure keepAliveMayRun so it is pinned
without a radio - 4 tests incl. every DeviceFamily case either side, and the
whole pure file re-compiled standalone with swiftc and every rule exercised.
doc_comment_lint clean. Still not hardware-validated.
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.

1 participant