Skip to content

Reduce merged icon observation churn#1297

Merged
steipete merged 5 commits into
steipete:mainfrom
hhh2210:codex/lightweight-icon-observation
Jun 5, 2026
Merged

Reduce merged icon observation churn#1297
steipete merged 5 commits into
steipete:mainfrom
hhh2210:codex/lightweight-icon-observation

Conversation

@hhh2210
Copy link
Copy Markdown
Contributor

@hhh2210 hhh2210 commented Jun 4, 2026

Refs #1274.

Summary

  • Move the menu bar icon observation signature out of the broad status item controller file into StatusItemController+IconObservation.swift.
  • Remove full UsageSnapshot stringification (String(describing: snapshot)) from the icon observation signature.
  • In Merge Icons mode, sign only the provider actually rendered by the merged icon plus aggregate provider status, instead of every provider snapshot.
  • Replace the Codex menu-bar credit projection in the icon render/observation path with a cheap credits/rate-window helper.
  • Make IconStyle String-raw-represented and sign it via .rawValue, removing the last String(describing:) reflection (_adHocPrint_unlocked) from the icon-observation leaf.

Why

@giuseppebisemi sampled the residual lag after #1286 and the hot path was observeStoreIconChanges -> storeIconObservationSignature -> providerStoreIconObservationSignature. The old signature subscribed to every provider snapshot and used String(describing: snapshot), so account/email/timestamp and non-rendered provider churn could retrigger icon work even when the visible merged icon would not change.

Behavior proof

Live runtime proof on the exact reported platform — macOS 26.5 (build 25F71), Apple Swift 6.3.2 — against this branch head.

1) Signature contract tests (both directions: ignore churn / react to visible change)

$ sw_vers -productVersion && sw_vers -buildVersion
26.5
25F71
$ xcrun xctest -XCTest CodexBarTests.StatusItemIconObservationSignatureTests ...
✔ store icon observation signature ignores refresh and status metadata churn
✔ store icon observation signature ignores non visual snapshot churn
✔ merged store icon observation signature ignores non primary snapshot churn
✔ store icon observation signature ignores unused credit balance
✔ store icon observation signature changes when icon percentages change
✔ store icon observation signature changes when credit fallback changes
✔ merged store icon observation signature changes when non primary status changes
✔ store icon observation signature changes when status indicator changes
✔ Test run with 8 tests in 1 suite passed after 0.41 seconds.

The four ignores … tests cover the stale-content risk (non-rendered snapshot / account / timestamp / unused-credit churn no longer moves the signature); the four changes when … tests prove visible percent, Codex credits fallback, primary-vs-aggregate status, and status-indicator changes still move it. So the narrowed signature does not suppress required icon refreshes.

2) Packaged-app main-thread sample (Merge Icons on), before vs after the reflection-removal commit

Sampled sample <pid> over the merged status item while driving repeated store refresh churn (open/dismiss the menu).

Before — the icon-observation leaf still bottomed out in reflective string printing:

observeStoreIconChanges()                                           StatusItemController.swift
 └ storeIconObservationSignature()                                  IconObservation.swift:15
    └ providerStoreIconObservationSignature(for:showBrandPercent:)  IconObservation.swift:51
       └ String.init<A>(describing:)            (in libswiftCore.dylib)
          └ _adHocPrint_unlocked<A,B>(...)      (in libswiftCore.dylib)   ← Mirror reflection
             └ swift_getObjectType

After — providerStoreIconObservationSignature and _adHocPrint_unlocked are gone from the path; it now bottoms out at cheap value reads:

storeIconObservationSignature()                  IconObservation.swift:12 / :21 / :7
 ├ primaryProviderForUnifiedIcon()               StatusItemController+Animation.swift:897
 │  └ menuBarMetricWindowForHighestUsage(...)     UsageStore+HighestUsage.swift:29
 │     └ MenuBarMetricWindowResolver.automaticWindow(...)   (cheap credits/rate-window helper)
 └ shouldMergeIcons.getter

Whole-sample greps after the fix: _adHocPrint_unlocked = 0, providerStoreIconObservationSignature = 0 (it was present before). The only residual swift_getObjectType frames are in unrelated Foundation URL bridging / __JSONEncoder code, not the icon path.

Scope / honesty: this is deterministic contract proof plus a packaged-app main-thread sample on macOS 26.5, not a video. It demonstrates the icon-observation signature no longer does full-snapshot or enum reflection on the main thread, and that visible icon updates are preserved. The original field repro is @giuseppebisemi's post-#1286 sample in #1274.

Notes

This is the icon-observation half of the residual Merge Icons lag (#1286 covered the menu close/rebuild half). It does not change rendered icon output — only what retriggers the icon work.

Validation

  • swift build
  • swift build --build-tests
  • xcrun xctest -XCTest CodexBarTests.StatusItemIconObservationSignatureTests .build/arm64-apple-macosx/debug/CodexBarPackageTests.xctest (8 Swift Testing tests passed on macOS 26.5)
  • make check (0 lint violations)
  • Packaged-app main-thread sample before/after (above)

Residual merged-menu close freeze → separate next slice (#1274)

This PR is the icon-observation half of the residual Merge Icons lag (#1286 covered the menu close/rebuild half). The remaining menu-rebuild close freeze @giuseppebisemi profiled after 254412f0 (#1314) is a separate slice, not in scope here, captured for clarity:

388  rebuildClosedMenuIfNeeded   (deferred close task)
126    populateMenu
 56    NSHostingView.layout()

Root cause (verified in current main): menuDidClose only defers the merged menu when it is already stale at close time, but observeStoreChanges invalidates with allowStaleContentDuringDataRefresh: true and still falls through to prepareAttachedClosedMenusIfNeeded() — so a merged menu that closed fresh is rebuilt while closed on the next background tick.

The fix is self-contained: in merged mode, skip the closed merged-menu rebuild entirely (defer-until-next-open unconditionally in prepareAttachedClosedMenusIfNeeded; menuWillOpen already repopulates synchronously before display, so no stale flash or width jump). That takes the 388-sample path to 0. It will be its own measured slice on #1274; this PR lands independently of it.

Copilot AI review requested due to automatic review settings June 4, 2026 06:13
@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented Jun 4, 2026

Codex review: needs maintainer review before merge. Reviewed June 5, 2026, 2:52 PM ET / 18:52 UTC.

Summary
This PR moves icon-observation signature logic into a focused extension, signs only rendered icon inputs plus aggregate merged status, raw-represents IconStyle, reuses the Codex credits helper, adjusts one timing test, and adds signature contract tests.

Reproducibility: yes. the current-main source still shows broad provider snapshot stringification, and the PR body gives a concrete macOS 26.5 packaged-app sampling path plus focused contract tests. I did not rerun that live sampling in this read-only pass.

Review metrics: 2 noteworthy metrics.

  • Changed surface: 7 files changed, 235 added, 80 deleted. The diff is focused but touches the icon render path, signature path, tests, and changelog.
  • Signature coverage: 8 focused tests described in proof. The tests exercise both ignored churn and visible icon changes, which is the main stale-icon risk.

Merge readiness
Overall: 🦀 challenger crab
Proof: 🦀 challenger crab
Patch quality: 🦀 challenger crab
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Risk before merge

  • [P1] The narrowed signature intentionally stops treating non-rendered provider snapshots as icon work; if a visible icon input was missed, the menu-bar icon could stay stale until another signed field changes.
  • [P1] This read-only review did not rerun the packaged macOS 26.5 sampling, so runtime confidence rests on the posted sample output and related field retest discussion.

Maintainer options:

  1. Accept the focused signature narrowing (recommended)
    Merge after maintainer review if the posted contract tests and macOS 26.5 sample proof are sufficient for the stale-icon tradeoff.
  2. Pause for broader lag consolidation
    Hold this PR only if maintainers want to land the icon-observation, measured-height, and closed-menu rebuild slices together instead of independently.

Next step before merge

  • No automated repair is needed; maintainers should review and accept the availability-sensitive signature narrowing before merge.

Security
Cleared: The diff does not change dependencies, install scripts, release automation, credentials handling, or other supply-chain sensitive paths beyond ordinary app/test code and a changelog line.

Review details

Best possible solution:

Land the focused signature narrowing if maintainers accept the availability tradeoff, and keep the broader remaining menu-lag work tracked through #1274 and #1314.

Do we have a high-confidence way to reproduce the issue?

Yes: the current-main source still shows broad provider snapshot stringification, and the PR body gives a concrete macOS 26.5 packaged-app sampling path plus focused contract tests. I did not rerun that live sampling in this read-only pass.

Is this the best way to solve the issue?

Yes: the branch is a narrow performance repair that aligns the observation signature with the existing render inputs and keeps the remaining menu rebuild lag separate. The safer alternative would be to add another contract test only if maintainers identify a missing visible input.

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 65e39f4dcb3a.

Label changes

Label changes:

  • add rating: 🦀 challenger crab: Overall readiness is 🦀 challenger crab; proof is 🦀 challenger crab and patch quality is 🦀 challenger crab.
  • remove rating: 🦞 diamond lobster: Current PR rating is rating: 🦀 challenger crab, so this older rating label is no longer current.

Label justifications:

  • P1: The PR targets serious Merge Icons lag tied to active user reports on macOS 26.5.
  • merge-risk: 🚨 availability: A missed signed input could suppress required icon refreshes and leave visible menu-bar state stale.
  • rating: 🦀 challenger crab: Overall readiness is 🦀 challenger crab; proof is 🦀 challenger crab and patch quality is 🦀 challenger crab.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes after-fix terminal test output and packaged-app sampling logs showing the targeted reflection path removed on macOS 26.5, with a follow-up field retest in discussion.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix terminal test output and packaged-app sampling logs showing the targeted reflection path removed on macOS 26.5, with a follow-up field retest in discussion.
Evidence reviewed

What I checked:

  • Repository policy read: AGENTS.md was read fully; its guidance to prefer focused tests and avoid validation that can trigger macOS Keychain prompts shaped this read-only review. (AGENTS.md:1, 65e39f4dcb3a)
  • Current main still has the broad signature: Current main still signs every provider and includes String(describing: snapshot), so the central churn reduction is not already implemented on main. (Sources/CodexBar/StatusItemController.swift:470, 65e39f4dcb3a)
  • PR narrows the observation signature: The PR head signs merge mode, visible providers, primary provider, icon style, display mode flags, the primary provider signature, and aggregate merged status instead of all provider snapshots. (Sources/CodexBar/StatusItemController+IconObservation.swift:5, cffafc0b73f7)
  • PR signature tracks render inputs: The PR head's merged icon render path uses the primary provider, resolved percents, Codex credits helper, stale state, aggregate status, motion state, warning flash, and animation state; the new signature covers the store-driven inputs while non-store visual changes call updateIcons() directly. (Sources/CodexBar/StatusItemController+Animation.swift:238, cffafc0b73f7)
  • Focused contract coverage added: The PR adds signature tests for ignored non-visual churn and changed visible inputs, including percent, credits fallback, primary-vs-secondary merged snapshots, and status indicators. (Tests/CodexBarTests/StatusItemIconObservationSignatureTests.swift:61, cffafc0b73f7)
  • Proof in PR discussion: The PR body and follow-up comments report 8 passing signature contract tests, packaged-app macOS 26.5 sample output with _adHocPrint_unlocked removed from the icon-observation path, and a related field retest confirming the targeted icon-observation reflection path is gone. (cffafc0b73f7)

Likely related people:

  • hhh2210: Authored the merged menu latency work currently at main and has current merged-history context for the related performance path. (role: recent area contributor; confidence: high; commits: 65e39f4dcb3a; files: Sources/CodexBar/StatusItemController.swift, Tests/CodexBarTests/StatusMenuOpenRefreshTests.swift)
  • Peter Steinberger: Blame on current main attributes the existing icon-observation signature and render path snapshot to the v0.32.4 commit in the shallow checkout. (role: current-main implementation provenance; confidence: medium; commits: 723734ef3422; files: Sources/CodexBar/StatusItemController.swift, Sources/CodexBar/StatusItemController+Animation.swift, Tests/CodexBarTests/StatusItemIconObservationSignatureTests.swift)
  • Perry Story: Introduced earlier redundant icon observer callback suppression and the original signature guard history around this code path. (role: feature-history contributor; confidence: high; commits: d5bf26974b85; files: Sources/CodexBar/StatusItemController.swift, Sources/CodexBar/UsageStore.swift, Tests/CodexBarTests/StatusItemIconObservationSignatureTests.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR refactors menu bar icon observation signature generation and expands test coverage to ensure the signature only changes when user-visible icon state changes (not when non-visual snapshot metadata churns).

Changes:

  • Extracted storeIconObservationSignature() into a dedicated StatusItemController+IconObservation.swift extension and updated the signature contents.
  • Added several tests to validate that the signature ignores non-visual churn (e.g., account email / updatedAt) while still changing for visual deltas (percent bars, status, credit fallback).
  • Introduced shared helpers (menuBarCreditsRemainingForIcon, widened access for existing helpers) to keep icon rendering + signature logic aligned.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
Tests/CodexBarTests/StatusItemIconObservationSignatureTests.swift Adds tests covering signature stability vs. visual changes and merged/non-merged behaviors.
Sources/CodexBar/StatusItemController.swift Removes the inlined observation signature methods (moved to a new extension file).
Sources/CodexBar/StatusItemController+IconObservation.swift New implementation of the icon observation signature with merged/non-merged handling and reduced churn sensitivity.
Sources/CodexBar/StatusItemController+Animation.swift Reuses a new credits helper in icon rendering; loosens access control for helpers used by the new extension; removes a SwiftLint suppression.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +575 to +589
func menuBarCreditsRemainingForIcon(provider: UsageProvider, snapshot: UsageSnapshot?) -> Double? {
guard provider == .codex,
let creditsRemaining = self.store.credits?.remaining,
creditsRemaining > 0
else {
return nil
}

let rateWindows = [snapshot?.primary, snapshot?.secondary].compactMap(\.self)
guard rateWindows.isEmpty || rateWindows.contains(where: { $0.remainingPercent <= 0 })
else {
return nil
}
return creditsRemaining
}

@discardableResult
func applyIcon(phase: Double?) -> Bool { // swiftlint:disable:this function_body_length
func applyIcon(phase: Double?) -> Bool {
Comment on lines +570 to 573
static func iconSignatureValue(_ value: Double?) -> String {
guard let value else { return "nil" }
return String(format: "%.3f", value)
}
Comment on lines +8 to +24
let visibleProviders = self.store.enabledProvidersForDisplay().map(\.rawValue).sorted().joined(separator: ",")
let providerSignatures: String
let primaryProvider: UsageProvider?
if mergeIcons {
let primary = self.primaryProviderForUnifiedIcon()
primaryProvider = primary
providerSignatures = [
self.providerStoreIconObservationSignature(for: primary, showBrandPercent: showBrandPercent),
"mergedStatus=\(self.mergedIconStatusIndicator().rawValue)",
].joined(separator: "||")
} else {
primaryProvider = nil
providerSignatures = UsageProvider.allCases
.filter { self.isVisible($0) }
.map { self.providerStoreIconObservationSignature(for: $0, showBrandPercent: showBrandPercent) }
.joined(separator: "||")
}
}

private func primaryProviderForUnifiedIcon() -> UsageProvider {
func primaryProviderForUnifiedIcon() -> UsageProvider {
}

private func shouldAnimate(provider: UsageProvider, mergeIcons: Bool? = nil) -> Bool {
func shouldAnimate(provider: UsageProvider, mergeIcons: Bool? = nil) -> Bool {
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 4, 2026
A main-thread sample of the packaged build (Merge Icons on, macOS 26.5)
showed providerStoreIconObservationSignature still bottoming out in
String(describing:) -> _adHocPrint_unlocked reflection, from two
String(describing:) calls over the payload-free IconStyle enum.

Make IconStyle String-raw-represented (rawValue == case name, so the
signature string is byte-identical) and replace both String(describing:)
calls with .rawValue. The icon-observation leaf is now reflection-free;
a re-sample shows providerStoreIconObservationSignature and
_adHocPrint_unlocked gone from the path entirely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hhh2210
Copy link
Copy Markdown
Contributor Author

hhh2210 commented Jun 4, 2026

@clawsweeper re-review

Added the icon-observation behavior proof to the PR body: the 8 signature contract tests (ignore-churn + react-to-visible-change, both directions) on macOS 26.5, plus a packaged-app main-thread sample showing the icon-observation leaf is now reflection-free.

Also pushed a follow-up (fa267cb4): the sample caught a residual String(describing:) -> _adHocPrint_unlocked reflection on the payload-free IconStyle enum. Made IconStyle String-raw-represented (rawValue == case name, signature byte-identical) and switched both call sites to .rawValue. Re-sample confirms _adHocPrint_unlocked and providerStoreIconObservationSignature are gone from the icon path.

@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented Jun 4, 2026

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 Urgent regression or broken agent/channel workflow affecting real users now. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 4, 2026
@hhh2210
Copy link
Copy Markdown
Contributor Author

hhh2210 commented Jun 5, 2026

@clawsweeper re-review

CI is green on the current head fa267cb4, including lint-build-test, which runs the repo lint/build/test gate. The earlier function-length suppression finding looks stale for this head: restoring the suppression locally had triggered SwiftLint's superfluous-disable rule, while the current branch passes make check.

Additional runtime proof from #1274 is also positive for this PR scope: @giuseppebisemi retested packaged #1297 on macOS 26.5 with Merge Icons on, and the targeted icon-observation reflection path is gone (_adHocPrint_unlocked: 0, icon-leaf String(describing:): 0, signature functions: 2 total samples). The remaining lag is now a separate populateMenu / MenuCardItemHostingView.measuredHeight path, so I would keep #1297 focused and handle that as a follow-up.

@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented Jun 5, 2026

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 5, 2026
…lled predicate

menuBarCreditsRemainingForIcon (used by both the rendered menu-bar icon and
the icon observation signature) reimplemented the menu-bar credits fallback
with its own rate-window predicate over snapshot.primary/secondary. That is a
second source of truth for a decision the Codex projection already owns
(codexConsumerProjection -> menuBarFallback == .creditsBalance): equivalent
today, but free to drift from the rendered/menu fallback semantics as the
projection evolves.

Delegate to store.codexMenuBarCreditsRemaining instead, so render, signature,
and the menu-bar fallback all read one projection. The projection is pure
value composition over already-loaded snapshot/credits state (no IO), so the
icon/signature path stays cheap. Behavior is unchanged in the covered cases
(8 icon observation signature tests still pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jun 5, 2026
@clawsweeper clawsweeper Bot added the rating: 🦀 challenger crab Exceptional PR readiness: strong proof, clean patch, and convincing validation. label Jun 5, 2026
@clawsweeper clawsweeper Bot added rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🦀 challenger crab Exceptional PR readiness: strong proof, clean patch, and convincing validation. labels Jun 5, 2026
@clawsweeper clawsweeper Bot added rating: 🦀 challenger crab Exceptional PR readiness: strong proof, clean patch, and convincing validation. and removed rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. labels Jun 5, 2026
@steipete steipete merged commit de55f48 into steipete:main Jun 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 availability 🚨 Merging this PR could cause crashes, hangs, restart loops, stalls, or process outages. P1 Urgent regression or broken agent/channel workflow affecting real users now. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦀 challenger crab Exceptional PR readiness: strong proof, clean patch, and convincing validation. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants