Skip to content

docs: refresh stale READMEs (android/README.md + root README architecture) - #779

Merged
ryanbr merged 1 commit into
mainfrom
docs/android-readme-refresh
Jul 24, 2026
Merged

ryanbr merged 1 commit into
mainfrom
docs/android-readme-refresh

Conversation

@ryanbr

@ryanbr ryanbr commented Jul 24, 2026

Copy link
Copy Markdown
Owner

android/README.md was the original initial-port README and had drifted badly out of date. Fixes:

  • /path/to/NOOP placeholder (intro + cd command) — never filled in → now Strand/ / <your-noop-clone>.
  • "This project has NOT been compiled or run on a device… authored without a JDK or Android SDK" — flatly false now: Android is a shipped app (CI-built, dependency-locked, running on real devices/straps — e.g. the WHOOP 4.0 users in the issue tracker). Rewrote the Status section to reflect that, while keeping the (still-true) "new BLE changes need real-hardware validation" note.
  • Gradle 8.5 → 8.7 (matches the checked-in wrapper); AGP 8.5.2; WHOOP 5.0/MG now shipped/verified.
  • assembleDebug/installDebug + app-debug.apk → the Full/Demo-flavored assembleFullDebug/installFullDebug + app-full-debug.apk.
  • Double-Strand path (Strand/Strand/BLE/…) → Strand/BLE/BLEManager.swift.
  • Package layout + screen names ("Live / History / Support" → Today/Sleep/Trends/Coach/Settings; added the real ingest/oura/polar/alarm/notif/… packages) brought current.

Kept the still-accurate BLE contract table, permissions section, and hardware-verification checklist. Docs-only.

…unbuilt draft

android/README.md was the initial-port README and had drifted badly:
- '`/path/to/NOOP`' placeholder never filled in (intro + build cmd) -> Strand/.
- 'This project has NOT been compiled or run on a device' / 'authored
  without a JDK or Android SDK' -> false: it's a shipped, CI-built app
  running on real devices (e.g. the WHOOP 4.0 users in the tracker).
- Gradle 8.5 -> 8.7 (matches the wrapper); WHOOP 5.0/MG now shipped/verified.
- assembleDebug/installDebug + app-debug.apk path -> the flavored
  assembleFullDebug/installFullDebug + app-full-debug.apk.
- Double-Strand path (Strand/Strand/BLE/...) -> Strand/BLE/BLEManager.swift.
- Package layout + screen names (Live/History/Support) brought current.
Kept the still-accurate BLE contract table + hardware-verification checklist;
reframed 'must be validated' as 'new BLE changes need hardware' (it IS validated).
@ryanbr
ryanbr merged commit 4d32544 into main Jul 24, 2026
1 check passed
@ryanbr ryanbr changed the title docs(android): refresh the stale README (shipped app, not an unbuilt draft) docs: refresh stale READMEs (android/README.md + root README architecture) Jul 24, 2026
@ryanbr

ryanbr commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

Expanded this PR to also fix two stale items in the root README.md (verified against the repo, not eyeballed):

  • Architecture package list omitted 3 of the 8 packages → added OuraProtocol, PolarProtocol, NoopLocalAccess.
  • Schema version said "currently through v9" but the migrator is at ~v28 → corrected (as "past v28" so it drifts gracefully).

Verified-accurate and left untouched: Tools/Backfill exists, and every StrandAnalytics file the README names is real. The android/README.md rewrite (the original commit) is unchanged.

taylor-cao pushed a commit to taylor-cao/noop that referenced this pull request Jul 28, 2026
…calibration count (ryanbr#530)

* Liquid Today: carry Charge like Rest, honest state pill, battery-ring truth table (B3/A3/B2/B1)

Liquid Today is the DEFAULT screen (iOS + macOS), but the v8 rebuild dropped
carry-overs the classic TodayView has. On an unscored-today day (post-04:00
rollover, or a no-wear night) the Charge and Effort heroes read
`displayDay?.recovery`/`.strain` RAW and blanked, while the Rest hero
(freshRestScore) and vitals (Repository.lastVitalsDay) carried right beside
them — and the Charge pill still said "Calibrating" at a wearer 14 nights past
the seed gate. Liquid was the only surface that blanked; classic carries via
lastScoredCharge (ryanbr#543), widget/watch/Live Activity via widgetAnchor (ryanbr#911),
Android via TodayScreen.lastScoredRecoveryDay.

B3: ChargeDisplay.resolve carries the last scored night's REAL Charge, stamped
"Last night · <date>" (or "Latest sleep" once ryanbr#779-stale) via the SAME
TodayView.lastScoredRecoveryDay + carriedCaption the classic screen uses, so
the two Today screens can't drift. The state pill tells the truth — only a
genuinely calibrating baseline reads "Calibrating"; a trusted baseline with no
scored night reads "No data". The Key-Metrics Recovery tile reads the same
resolved value so tile and hero can't disagree. Effort deliberately does NOT
carry: it is today's accumulation, so yesterday's number would be a false
statement, not a stale one.

A3/B2: the strap-battery ring resolves through StrapBatteryDisplay so a known
charging state survives a missing %, and a % never outlives its link.
B1: LiquidSyncStatusRow surfaces backfill progress in the Data Sources card.

Adds a DEBUG-only `--demo-screen liquidtoday` (plain "today" renders classic).

Tests: LiquidChargeCarryTests (9), LiquidBatteryDisplayTests. App-target Swift,
so no CI covers it — verified the full macOS StrandTests suite green, the iOS
target builds, and drove it in the simulator on the real on-device DB with
today's row nulled: before = Charge "–"/pill "Calibrating"; after = Charge 47
carried from Jul 14/pill "Last night".

* Liquid Today: surface the "N of 4 nights" calibration count

The v8 Liquid Today (the default iOS Today) showed a bare "Calibrating" for a
pre-seed-gate baseline, dropping the "N of 4 nights" progress the classic
TodayView surfaces (calibrationDetail / ScoreStatePill). A brand-new user in
their first 4 nights saw the progress on classic but not on Liquid.

ChargeDisplay already carried the count (.calibrating(nights:), 9251ae2); this
adds ChargeDisplay.calibrationDetail returning classic's exact copy ("Learning
your baseline, N of 4 nights.") and swaps it into the synthesis card in place of
the readiness one-liner (chargeDisplay.calibrationDetail ?? synthLine) — the same
replacement classic makes (calibrationDetail ?? synthesisCardDetail). The compact
greeting pill stays short ("Calibrating") per its fixedSize row, so the count
lives in the card. Reuses classic's String Catalog key verbatim; both Today
screens now read identically. New-users-only, no owner impact.

Android TodayScreen already surfaces this, so no Kotlin twin is needed — iOS
Liquid catching up to existing behaviour.

Verified (app-target Swift, not covered by CI): LiquidChargeCarryTests
(test-first, RED→GREEN, 11/11) on the resolver/label; macOS Strand + iOS NOOPiOS
app targets compile; --demo-screen liquidtoday on a clean sim renders "Learning
your baseline, 0 of 4 nights." in the synthesis card with the short "Calibrating"
pill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
vipulog pushed a commit to vipulog/noop that referenced this pull request Jul 31, 2026
…ghts (ryanbr#401)

* Baselines: imported rows with nil values no longer shadow computed nights

The nightly-into-history merge only checked KEY presence before letting a
computed night in. A Health Connect / backup import that creates a row for
a day with a nil HRV/RHR value therefore shadowed the real strap-computed
night forever: zero nights reached the baseline fold, the baseline never
left CALIBRATING, and Today rendered "No Data" / "Needs the strap" despite
scored sleep (the reported bug).

Fix, per-field and identical on both platforms: imported non-nil wins; a
nil (or absent) slot is backfilled by the computed value.

- Swift: hist[day] == nil on [String: Double?] is only true for an absent
  key (.some(.none) passed the old check). Also mark the helper
  nonisolated so it is testable off the main actor.
- Kotlin: day !in hist had the same shape; hist[day] == null covers both
  absent keys and null values.

Tests (RED first on both platforms): IntelligenceBaselineShadowTests
(Swift) and IntelligenceBaselineShadowTest (Kotlin) cover imported-non-nil
wins, nil backfilled, no-computed stays missing, and the 7/7 strap-night
starvation end-to-end shape.

* Tests: render LocalizedStringKey titles instead of comparing keys

The carriedLastNight titles are now interpolated ("Last night · %@")
so the string-catalog extractor sees the format key (ryanbr#779), which makes
LocalizedStringKey equality against a literal key always false. Resolve
the key + arguments via Mirror and pin the rendered user-visible copy.

---------

Co-authored-by: Boro Vukovic <hello@loudtronix.ca>
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…ghts (ryanbr#401)

* Baselines: imported rows with nil values no longer shadow computed nights

The nightly-into-history merge only checked KEY presence before letting a
computed night in. A Health Connect / backup import that creates a row for
a day with a nil HRV/RHR value therefore shadowed the real strap-computed
night forever: zero nights reached the baseline fold, the baseline never
left CALIBRATING, and Today rendered "No Data" / "Needs the strap" despite
scored sleep (the reported bug).

Fix, per-field and identical on both platforms: imported non-nil wins; a
nil (or absent) slot is backfilled by the computed value.

- Swift: hist[day] == nil on [String: Double?] is only true for an absent
  key (.some(.none) passed the old check). Also mark the helper
  nonisolated so it is testable off the main actor.
- Kotlin: day !in hist had the same shape; hist[day] == null covers both
  absent keys and null values.

Tests (RED first on both platforms): IntelligenceBaselineShadowTests
(Swift) and IntelligenceBaselineShadowTest (Kotlin) cover imported-non-nil
wins, nil backfilled, no-computed stays missing, and the 7/7 strap-night
starvation end-to-end shape.

* Tests: render LocalizedStringKey titles instead of comparing keys

The carriedLastNight titles are now interpolated ("Last night · %@")
so the string-catalog extractor sees the format key (ryanbr#779), which makes
LocalizedStringKey equality against a literal key always false. Resolve
the key + arguments via Mirror and pin the rendered user-visible copy.

---------

Co-authored-by: Boro Vukovic <hello@loudtronix.ca>
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…calibration count (ryanbr#530)

* Liquid Today: carry Charge like Rest, honest state pill, battery-ring truth table (B3/A3/B2/B1)

Liquid Today is the DEFAULT screen (iOS + macOS), but the v8 rebuild dropped
carry-overs the classic TodayView has. On an unscored-today day (post-04:00
rollover, or a no-wear night) the Charge and Effort heroes read
`displayDay?.recovery`/`.strain` RAW and blanked, while the Rest hero
(freshRestScore) and vitals (Repository.lastVitalsDay) carried right beside
them — and the Charge pill still said "Calibrating" at a wearer 14 nights past
the seed gate. Liquid was the only surface that blanked; classic carries via
lastScoredCharge (ryanbr#543), widget/watch/Live Activity via widgetAnchor (ryanbr#911),
Android via TodayScreen.lastScoredRecoveryDay.

B3: ChargeDisplay.resolve carries the last scored night's REAL Charge, stamped
"Last night · <date>" (or "Latest sleep" once ryanbr#779-stale) via the SAME
TodayView.lastScoredRecoveryDay + carriedCaption the classic screen uses, so
the two Today screens can't drift. The state pill tells the truth — only a
genuinely calibrating baseline reads "Calibrating"; a trusted baseline with no
scored night reads "No data". The Key-Metrics Recovery tile reads the same
resolved value so tile and hero can't disagree. Effort deliberately does NOT
carry: it is today's accumulation, so yesterday's number would be a false
statement, not a stale one.

A3/B2: the strap-battery ring resolves through StrapBatteryDisplay so a known
charging state survives a missing %, and a % never outlives its link.
B1: LiquidSyncStatusRow surfaces backfill progress in the Data Sources card.

Adds a DEBUG-only `--demo-screen liquidtoday` (plain "today" renders classic).

Tests: LiquidChargeCarryTests (9), LiquidBatteryDisplayTests. App-target Swift,
so no CI covers it — verified the full macOS StrandTests suite green, the iOS
target builds, and drove it in the simulator on the real on-device DB with
today's row nulled: before = Charge "–"/pill "Calibrating"; after = Charge 47
carried from Jul 14/pill "Last night".

* Liquid Today: surface the "N of 4 nights" calibration count

The v8 Liquid Today (the default iOS Today) showed a bare "Calibrating" for a
pre-seed-gate baseline, dropping the "N of 4 nights" progress the classic
TodayView surfaces (calibrationDetail / ScoreStatePill). A brand-new user in
their first 4 nights saw the progress on classic but not on Liquid.

ChargeDisplay already carried the count (.calibrating(nights:), 9251ae2); this
adds ChargeDisplay.calibrationDetail returning classic's exact copy ("Learning
your baseline, N of 4 nights.") and swaps it into the synthesis card in place of
the readiness one-liner (chargeDisplay.calibrationDetail ?? synthLine) — the same
replacement classic makes (calibrationDetail ?? synthesisCardDetail). The compact
greeting pill stays short ("Calibrating") per its fixedSize row, so the count
lives in the card. Reuses classic's String Catalog key verbatim; both Today
screens now read identically. New-users-only, no owner impact.

Android TodayScreen already surfaces this, so no Kotlin twin is needed — iOS
Liquid catching up to existing behaviour.

Verified (app-target Swift, not covered by CI): LiquidChargeCarryTests
(test-first, RED→GREEN, 11/11) on the resolver/label; macOS Strand + iOS NOOPiOS
app targets compile; --demo-screen liquidtoday on a clean sim renders "Learning
your baseline, 0 of 4 nights." in the synthesis card with the short "Calibrating"
pill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
simoncad7 pushed a commit to simoncad7/noop that referenced this pull request Aug 17, 2026
…unbuilt draft (ryanbr#779)

android/README.md was the initial-port README and had drifted badly:
- '`/path/to/NOOP`' placeholder never filled in (intro + build cmd) -> Strand/.
- 'This project has NOT been compiled or run on a device' / 'authored
  without a JDK or Android SDK' -> false: it's a shipped, CI-built app
  running on real devices (e.g. the WHOOP 4.0 users in the tracker).
- Gradle 8.5 -> 8.7 (matches the wrapper); WHOOP 5.0/MG now shipped/verified.
- assembleDebug/installDebug + app-debug.apk path -> the flavored
  assembleFullDebug/installFullDebug + app-full-debug.apk.
- Double-Strand path (Strand/Strand/BLE/...) -> Strand/BLE/BLEManager.swift.
- Package layout + screen names (Live/History/Support) brought current.
Kept the still-accurate BLE contract table + hardware-verification checklist;
reframed 'must be validated' as 'new BLE changes need hardware' (it IS validated).
ryanbr added a commit that referenced this pull request Sep 3, 2026
Parity pass on the Kotlin fix. Apple has the same OR predicate —

    ($0.avgHrv != nil || $0.restingHr != nil || $0.respRateBpm != nil) && $0.day < todayKey

— so `lastVitalsDay` resolves the freshest row with ANY of the three and blanks
whichever one that row happens to lack. Three live surfaces read it for HRV and
resting HR: `TodayView.recoveryVitalsCard`, and both `recoveryVitalsSection` and
`keyMetricsSection` on Liquid.

`TodayView.dashboardValue` was worse than the Android card that was reported. Its
`.hrv` and `.restingHr` cases had NO carry at all — today's row or "—" — while
`.respiratory`, `.bloodOxygen` and `.skinTemp` beside them all carry, and the Key
Metrics tile has carried via `carriedVital(perField:)` the whole time. So that
card blanked every rollover next to a tile showing a number, which is the
reported symptom with a different cause.

`lastHrvDay` / `lastRestingHrDay` are the fourth and fifth per-field twins after
SpO₂, skin temp and respiratory, same shape and same future-clock bound.

Deliberately NOT staleness-bounded, and the doc says why so it doesn't get
"fixed" later: respiratory took a hard `vitalCarryDays` bound because one CSV
import's 15.6 read as today's for a fortnight (#1331), whereas HRV/RHR disclose
age through the caption instead — `carriedCaption` relabels a weeks-old carry to
"Latest sleep" (#779). Bounding them here would blank values that render today.

The provenance footnote needed a decision. One card-level caption now covers
vitals that can come from DIFFERENT rows, so it stamps the OLDEST row any SHOWN
carried vital came from: erring old is the only safe direction for a caption
whose whole job is to stop a stale read passing as today's, and it keeps the
"Latest sleep" relabel firing on the value that actually is weeks old. A row
counts only if it SUPPLIED the value — `lastVitalsDay` can hold a nil
respiratory, which carries nothing and must stamp nothing.

Tests: the four Kotlin cases as XCTest in DailyMetricLastVitalsDayTests, pinning
the bug (lastVitalsDay picks the respiratory-only row, yielding no HRV) as well
as the fix. GRDB's CSQLite will not build on Linux, so these were also run here
by extracting the five selector bodies VERBATIM into an isolated harness —
identical fixtures to the Kotlin, all pass, twins confirmed behaviourally. The
XCTest file itself is unrun until app-build.
ryanbr added a commit that referenced this pull request Sep 3, 2026
…rinting -0.0 (#1843)

Reported on #1842: the HRV and Resting HR cards read "No data" while tapping through to the same metric graphed a value, and Skin Temp showed -0.0 dC.

Cause is one level below the cards. The vitals carry predicate is an OR across HRV / resting-HR / respiratory, so it resolves the freshest row holding ANY of the three -- including a respiratory-only row whose HRV is null. The card reads that null and blanks while a tile carrying a different row shows a value on the same screen. Same failure the SpO2 and skin-temp per-field rows were split out to correct; HRV and resting HR were the two fields still on the shared predicate.

Adds lastHrvRow / lastRestingHrRow (Kotlin) and lastHrvDay / lastRestingHrDay (Swift) as the fourth and fifth per-field twins, same shape and same future-clock bound.

Apple needed more than a straight port. TodayView.dashboardValue's .hrv and .restingHr had no carry at all -- today's row or an em-dash -- while .respiratory, .bloodOxygen and .skinTemp beside them all carried, and the Key Metrics tile had carried via carriedVital(perField:) throughout. Same symptom, different cause. Liquid's recoveryVitalsSection and keyMetricsSection had the OR-predicate form.

Two judgement calls, both documented in place: the new carries are deliberately NOT staleness-bounded (respiratory took a hard bound because one CSV import's value read as today's for a fortnight, #1331; HRV/RHR disclose age through the caption's "Latest sleep" relabel instead, #779), and the single card-level provenance footnote now stamps the OLDEST row any shown carried vital came from, since vitals can come from different rows and erring old is the only safe direction for a caption meant to stop a stale read passing as today's.

Skin Temp's -0.0 was a signed zero: a deviation rounding to zero from below kept its minus through %+.1f. Both platforms re-read the formatted string and drop the sign at zero magnitude, so it renders 0.0 -- unsigned, not a fake positive.

Tests pin the bug as well as the fix: each asserts the shared predicate picks the respiratory-only row and yields no HRV, so a future simplification back onto it fails in CI rather than in the field. Kotlin full suite 5098/0; Swift package tests green in CI; app-build green on both legs.
ryanbr added a commit that referenced this pull request Oct 3, 2026
#2525 made a Charge baseline read only nights inside `ChargeBaselines.windowDays`,
so after a gap longer than that the folded history is empty, the calibration count
is non-nil (0, which #335 deliberately includes) and calibrating takes precedence
over the carry. The Today Charge tile therefore reads "Calibrating" on such a gap
where #779 had it read "Latest sleep".

That is a reasonable call, but it arrived as a side effect: the state mappers were
not touched, only their input, so the existing mapper tests kept passing because
they drive the count directly with synthetic values. Nothing asserted that a gap
this long now yields a non-nil count, which left the display free to be flipped
back silently in either direction.

Each test pins BOTH displays from the SAME fourteen nights, so the contrast is the
test rather than a claim about it. Fourteen matters: fewer than `minNightsSeed`
banked nights calibrate under either rule and would pin nothing, so these would
have been trusted by the old whole-history fold and the carry genuinely used to
win. The Swift side converts the count with the same expression TodayView uses, so
the nil that lets the carry through is the production one.

Mutation-checked rather than assumed: neutralising `windowDays` turns the pin red
(expected 0 but was 14). An earlier draft of this test used a single night, which
calibrated under BOTH rules and so pinned nothing; that is why the night count is
spelled out above.

No production code changes.
ryanbr added a commit that referenced this pull request Oct 3, 2026
#2525 made a Charge baseline read only nights inside `ChargeBaselines.windowDays`,
so after a gap longer than that the folded history is empty, the calibration count
is non-nil (0, which #335 deliberately includes) and calibrating takes precedence
over the carry. The Today Charge tile therefore reads "Calibrating" on such a gap
where #779 had it read "Latest sleep".

That is a reasonable call, but it arrived as a side effect: the state mappers were
not touched, only their input, so the existing mapper tests kept passing because
they drive the count directly with synthetic values. Nothing asserted that a gap
this long now yields a non-nil count, which left the display free to be flipped
back silently in either direction.

Each test pins BOTH displays from the SAME fourteen nights, so the contrast is the
test rather than a claim about it. Fourteen matters: fewer than `minNightsSeed`
banked nights calibrate under either rule and would pin nothing, so these would
have been trusted by the old whole-history fold and the carry genuinely used to
win. The Swift side converts the count with the same expression TodayView uses, so
the nil that lets the carry through is the production one.

Mutation-checked rather than assumed: neutralising `windowDays` turns the pin red
(expected 0 but was 14). An earlier draft of this test used a single night, which
calibrated under BOTH rules and so pinned nothing; that is why the night count is
spelled out above.

No production code changes.
TrapstarKS added a commit to TrapstarKS/troop that referenced this pull request Oct 8, 2026
ryanbr#2525 made a Charge baseline read only nights inside `ChargeBaselines.windowDays`,
so after a gap longer than that the folded history is empty, the calibration count
is non-nil (0, which ryanbr#335 deliberately includes) and calibrating takes precedence
over the carry. The Today Charge tile therefore reads "Calibrating" on such a gap
where ryanbr#779 had it read "Latest sleep".

That is a reasonable call, but it arrived as a side effect: the state mappers were
not touched, only their input, so the existing mapper tests kept passing because
they drive the count directly with synthetic values. Nothing asserted that a gap
this long now yields a non-nil count, which left the display free to be flipped
back silently in either direction.

Each test pins BOTH displays from the SAME fourteen nights, so the contrast is the
test rather than a claim about it. Fourteen matters: fewer than `minNightsSeed`
banked nights calibrate under either rule and would pin nothing, so these would
have been trusted by the old whole-history fold and the carry genuinely used to
win. The Swift side converts the count with the same expression TodayView uses, so
the nil that lets the carry through is the production one.

Mutation-checked rather than assumed: neutralising `windowDays` turns the pin red
(expected 0 but was 14). An earlier draft of this test used a single night, which
calibrated under BOTH rules and so pinned nothing; that is why the night count is
spelled out above.

No production code changes.

(cherry picked from commit 22ce207)
Iskrata pushed a commit to Iskrata/noop that referenced this pull request Oct 12, 2026
ryanbr#2525 made a Charge baseline read only nights inside `ChargeBaselines.windowDays`,
so after a gap longer than that the folded history is empty, the calibration count
is non-nil (0, which ryanbr#335 deliberately includes) and calibrating takes precedence
over the carry. The Today Charge tile therefore reads "Calibrating" on such a gap
where ryanbr#779 had it read "Latest sleep".

That is a reasonable call, but it arrived as a side effect: the state mappers were
not touched, only their input, so the existing mapper tests kept passing because
they drive the count directly with synthetic values. Nothing asserted that a gap
this long now yields a non-nil count, which left the display free to be flipped
back silently in either direction.

Each test pins BOTH displays from the SAME fourteen nights, so the contrast is the
test rather than a claim about it. Fourteen matters: fewer than `minNightsSeed`
banked nights calibrate under either rule and would pin nothing, so these would
have been trusted by the old whole-history fold and the carry genuinely used to
win. The Swift side converts the count with the same expression TodayView uses, so
the nil that lets the carry through is the production one.

Mutation-checked rather than assumed: neutralising `windowDays` turns the pin red
(expected 0 but was 14). An earlier draft of this test used a single night, which
calibrated under BOTH rules and so pinned nothing; that is why the night count is
spelled out above.

No production code changes.

(cherry picked from commit 22ce207)
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