Skip to content

feat(ux): Shared Qt 6.12 window chrome refactor and radio switcher — macOS testing - #5489

Draft
jensenpat wants to merge 11 commits into
aethersdr:mainfrom
jensenpat:aether/qt612-window-chrome
Draft

feat(ux): Shared Qt 6.12 window chrome refactor and radio switcher — macOS testing#5489
jensenpat wants to merge 11 commits into
aethersdr:mainfrom
jensenpat:aether/qt612-window-chrome

Conversation

@jensenpat

@jensenpat jensenpat commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator
image

Summary

MacOS-first testing continuation of the unified-window-chrome work from closed PR #4906 and RFC #4764. This draft preserves the rebased feature history and adds the Qt 6.12 preview migration, radio-switcher usability changes, and native traffic-light/brand alignment. It is not ready for release or cross-platform approval.

  • Share the 52-pixel themed AetherSDR bar; use Qt expanded client areas and native captions on macOS/Windows, with the shared Linux fallback.
  • Remove the old custom macOS frame/shadow and Windows native-event frame code. Keep a small Cocoa presentation adapter for the native unified toolbar, hidden title text, and measured traffic-light bounds.
  • Place the brand one 16-logical-pixel gap after the native traffic lights, with both vertically centered.
  • Add searchable, bounded radio discovery and explicit Disconnect, Rename, Radio setup, Remove from tabs, and Add to tabs actions. Manual connection opens the correct page; selecting a radio never implicitly connects or disconnects.
  • Preserve the existing dock/float fixes and theme-backed header, status bar, and popover backgrounds. The S-Meter remains intentionally pinned above the scrolling applets; this draft does not change that layout.

PR split

The grouped Add: applet picker, brighter category headings, and pinned S-Meter viewport alignment now live independently in draft PR #5490: #5490. That branch is based directly on main and retains the Qt 6.8 minimum; it does not depend on this Qt 6.12 chrome branch.

This PR remains window chrome and radio-switcher work only. Signed follow-up f7f1786f updates the scope/evidence documentation; chrome implementation remains unchanged from 0de5a2b6. No new chrome build or tests are claimed for this documentation-only follow-up. Both branches merge together without conflicts in the local merge-tree check.

Constitution principle honored

Principle XI — Fixes Are Demonstrated: distinguish headless checks, native Cocoa geometry, automation-bridge behavior, packaging, and outstanding native-platform acceptance. Principle V — radio-switcher visibility is a nested client-owned UI document rather than radio operating state.

Test plan

  • Local Qt 6.12.0 preview macOS application build passed using cmake --build build -j22 before committing this identical source tree. The existing app embeds the prior bf53789b dirty-build identity; it was not rebuilt just to stamp the new commit.
  • Re-ran four focused offscreen CTest selections immediately before commit: unified_title_bar_test, titlebar_headphone_mute_test, hl2_pc_audio_lock_test, and connection_panel_size_test; 4/4 passed.
  • Native Cocoa title-bar test passed. Isolated disconnected bridge checks measured caption bounds [19,19,60,14], brand bounds [95,0,109,52], a 16-pixel gap, and shared vertical center at 26 pixels across resize, maximize/restore, and fullscreen round trips.
  • Computer Use clicked the + button; bridge readback and a separate popup capture confirmed the radio list opened correctly.
  • Simulator-only bridge checks covered search, rescan, rename/reset, remove/restore, manual/LAN selection, explicit connect/disconnect, action gating, and restart persistence. No live-radio or TX evidence is claimed.
  • Packaged and deployed this source build to the test Mac; verified bundle file hashes, symlinks, ad-hoc signing, quarantine removal, and loader smoke check. Manual laptop acceptance remains with the operator.
  • Native macOS traffic-light tiling-menu acceptance.
  • Windows/Linux CI migration and native Snap Layouts/compositor/fractional-scaling validation. Existing release toolchains are below this branch's Qt 6.12 requirement and cannot build it unchanged.
  • Full regression suite and exact-head CI.

Testing notes

Build requirements, architecture choices, and validation boundaries are documented in docs/qt612-window-chrome.md. Qt 6.12 is a preview SDK in this branch. The current macOS test package lacks QtKeychain, so credentials are session-only. It is ad-hoc signed, not a notarized release.

Latest deployed combined app: ~/Desktop/AetherSDR qt612-applet-picker-0de5a2b6-dirty 1748.app on the test Mac. It includes both the chrome and picker changes, with verified bundle hashes, symlinks, ad-hoc signing, quarantine removal, and a loader smoke check. It is not a deployment of either standalone PR head. The prior qt612-spacing-bf53789b-dirty 1627 chrome package remains separate. Manual laptop acceptance remains with the operator.

Generated with OpenAI Codex (GPT-6 Astra)

jensenpat and others added 10 commits September 7, 2026 15:22
…hersdr#4764)

Replaces the 32 px in-app strip with a single 52 px bar that is the window's
only title bar on all three platforms. The window stays frameless everywhere
(Windows via WM_NCCALCSIZE, as before), so exactly one thing draws chrome.

What the bar owns, left to right: window controls, brand mark, radio tabs,
spacer, audio cluster, applet-dock selectors.

New widgets
  * BrandMark          logo + wordmark, "SDR" filled with the brand gradient
  * RadioTabBar        per-radio tabs, status dots, discovered-radios popover
  * WindowCaptionButtons  three control styles: macOS traffic lights (12 px on
                       a 20 px pitch, glyphs on group hover, grey when the
                       window is inactive), Windows 46 px caption buttons,
                       Linux bordered chips
  * mac/WindowChrome   objc++ trim for the frameless NSWindow — puts
                       NSWindowStyleMaskResizable back so AppKit keeps owning
                       edge resize, plus continuous rounded corners and shadow

Heartbeat folded into the active tab's dot
  The standalone lamp is retired; one dot now answers both "which radio is
  this" and "is its link alive". The state machine is unchanged, including the
  two rules that are easy to lose in a rewrite: three missed sweeps is the
  alarm threshold, and the alarm holds SOLID red when the operator has blinking
  disabled rather than going quiet. Only its output moved.

Platform chrome
  * Windows: WM_NCHITTEST answers HTMAXBUTTON over the maximize control so Snap
    Layouts opens on hover, and DwmSetWindowAttribute asks for DWMWCP_ROUND
    (Win10 falls back to square). Hover and click there are driven from the
    WM_NC* messages, since answering HTMAXBUTTON takes the mouse away from Qt.
  * Resize band widened to 8 px, still via QWindow::startSystemResize() so the
    compositor keeps ownership and OS tiling/snapping behave.

No more emoji as controls
  The speaker/headphone controls were literal 🔊 / 🎧 glyphs, which render in
  the platform's own colour and weight and cannot follow the theme. Replaced
  with painter-drawn thin-line marks at the house 1.8 px stroke.

Theming
  15 new tokens under color.titlebar.* and color.brand.* in both bundled
  themes, seed regenerated, canonical-tokens.md updated. The traffic-light and
  chip fills are deliberately NOT tokenised — red/amber/green is the affordance
  being borrowed from the platform, and a themed "red" close control would stop
  reading as one.

Automation
  New `get titlebar` model and `titlebar` verb (selectRadio / showDiscovery /
  minimize / maximize / close). Both the bar and each tab report a screenRect,
  and the model exposes a live linkPulse level so the heartbeat is assertable
  instead of eyeballed. docs/automation-bridge.md regenerated.

Tests
  tests/unified_title_bar_test.cpp, headless. Pins the bar height, that nothing
  reserves a strip above it (offsetInWindow == 0), that a status-only change
  reuses the tab widget rather than rebuilding it, and that state is never
  encoded by dot colour alone — every tab spells it in words on the rendered
  status line and in its accessible name. 242/242 pass.

Not yet verified on hardware: Windows (Snap Layouts, DWM corners, and the aethersdr#4557
top-edge-drift path this touches) and Linux (chips, fractional-scaling drag
jitter). Per RFC review those are PR gates and need someone else's machines.

Refs aethersdr#4764, aethersdr#3445

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things the textual rebase merged cleanly but got semantically wrong,
plus one regression of my own that only became visible once the bar landed.

setHeadphoneMuted() was setting an emoji again (aethersdr#4722)
  Upstream added the reconcile setter while this branch was replacing the
  🔊/🎧 emoji with painter-drawn marks, so git kept its setText() verbatim and
  the button would have carried an emoji AND an icon. Routed through
  applyAudioIcon(). The setter's contract is unchanged and still load-bearing:
  the QSignalBlocker suppresses `toggled`, so the repaint has to be explicit or
  the checked state and the mark disagree.

titlebar_headphone_mute_test asserted on those emoji bytes
  It would have failed outright. Both contracts it exists for are still exactly
  right — the explicit repaint, and that a reconcile must not re-emit and turn
  the radio's status echo into a fresh command (Principle II) — so the test is
  retargeted from the label text to the rendered icon rather than weakened.
  Comparing the drawn mark is also stronger: it survives the next change of
  mark, where a byte comparison against a glyph does not.

Minimal mode had lost the radio-link indicator
  This branch folded the heartbeat lamp into the active tab's dot, and
  setMinimalMode() hides the radio strip — so minimal mode would have had no
  link indicator at all, where the old standalone lamp stayed visible. Added
  RadioTabBar::setCompactMode(): active tab only, no "+", indicator intact.
  Minimal mode is precisely when an operator has nothing else on screen that
  would show them a dropped link.

Also: TitleBar.h's class comment still described the macOS approach this branch
abandoned (keeping the real NSWindow and floating the native traffic lights
over the bar). Rewritten to describe what actually ships. Bridge verb table
regenerated — 65 verbs with main's additions alongside `titlebar`.

Test suite on the new base: 282 tests, all green. Five hl2_* tests fail under
`ctest -j8` and pass serially; they share a single-client hpsdrsim and carry no
RESOURCE_LOCK, and this branch touches no HL2 file. Pre-existing, not from here.

Refs aethersdr#4764

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The 52 px bar's three applet controls each toggled a field of their own —
dock side, visibility, floating — with no single owner, so the fields could
disagree. A dock click while the panel was floating docked it back and then
fell through and hid it: one click doing two things, leaving an invisible
panel that took two more clicks to recover. That is the "takes two or three
pop-outs" report.

Route all three controls, Ctrl+Shift+S and the bridge through one entry
point, applyAppletPanelState(floating, dockedLeft, visible), which takes the
COMPLETE desired state instead of mutating one field. Two combinations are
now unrepresentable by construction: floating-and-hidden (an empty float
window has no affordance to bring the contents back), and side-set-without-
visibility. The dock-click rule is stated once — clicking the wall the panel
already occupies is the hide gesture, and that only reads as "hide" when the
panel is genuinely docked there and on screen.

Two further defects surfaced only under test, both of which would have
survived a reading:

  * QSplitter will not re-slot a HIDDEN child. Setting the wall while the
    panel was hidden persisted the new side but left the widget in its old
    slot, so it reappeared on the wrong wall. Show before reordering.

  * setAppletPanelDockedLeft() was not idempotent — pre-existing, and the
    one with teeth. insertWidget() detaches the panel first, which shifts
    m_panStack down one index, so re-inserting at the now-stale panIdx drops
    the panel on the far side and silently flips the dock. Docking left twice
    moved it right. The title-bar path never hit it because clicking the same
    wall means hide; the new verb hit it on the first call. Re-asserting the
    current side is legitimate (it happens on un-float and on restore), so
    the guard lives in the setter rather than in its callers.

The dock and pop-out controls carried only tooltips: no objectName, no
accessibleName. They were absent from dumpTree and invisible to screen
readers, and had to be driven by raw pixel coordinates. Named
appletDockLeftBtn / appletDockRightBtn / appletPopOutBtn, with accessible
names and descriptions.

New `applet` verb (dock left|right, float on|off, show, hide, state), 66
verbs total. The snapshot returns geometry and splitterIndex/panIndex beside
the flags, because splitterIndex < panIndex is what actually proves the left
dock — asserting on `side` alone lets a zero-width panel pass as a success.

The mute toggles kept an accent focus ring painted around them after a
click. Fixed with focusPolicy(Qt::TabFocus) rather than by deleting the
:focus rule, so the ring still appears for keyboard navigation, where it is
the only indication of where focus sits.

Verified against the built-in simulator over the bridge: 158 checks, 0
failures, driven through the real widgets via clickAt on the labels rather
than through the verb, so it exercises the operator's own path. Covers the
original repro, eight consecutive pop-out clicks alternating every time, the
full 3-control x 4-state transition matrix with invariants asserted after
every transition, one-click recovery onto the REQUESTED wall from every
state, verb/echo parity, and 30 rapid-fire clicks with no settle. Asserting
the wall rather than just visibility is what caught the hidden-reorder bug.

The state machine is radio-independent and was reproduced with the radio
fully disconnected — it is not specific to the simulator.

Not covered: the applet logic lives inside MainWindow, which has no test
harness, so none of these three bugs is guarded in CI. The driver script is
not checked in.

Developed in parallel with the preceding reconcile commit, which fixed the
same setHeadphoneMuted() emoji leftover and retargeted
titlebar_headphone_mute_test to the rendered icon independently; that work is
kept as it stands there and dropped from here.

Refs aethersdr#4764

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Moving the applet panel to the LEFT wall left a 260 px strip on the right
that showed the desktop through the window. Measured on real screen
captures: 6/6 left docks on a fresh launch, 0/6 right docks, with the strip
falling at exactly the slot the panel had occupied when docked right.

The panel does not resize when it changes walls, it TRANSLATES, so the
panadapter has to slide the same 260 px the other way. Geometry was already
correct — the widget reported the full 1450 px while its native surface was
still the pre-flip 1190. So the pan repainted happily, every frame, into a
drawable 260 px too narrow, and the strip beyond it was painted by nobody.
MainWindow is WA_TranslucentBackground and its paintEvent is the only source
of pixels for regions the tree leaves bare, and Qt excludes native children
from that backdrop, so "painted by nobody" composites to the desktop rather
than to the app background.

The asymmetry is why the report was always "panel on the left": when the
panel moves RIGHT the strip needing new cover is taken by the applet panel,
an ordinary widget that paints correctly. Only the left flip depends on the
native surface keeping up.

Two changes, because either alone leaves a hole:

  * PanadapterStack::refreshAfterLayoutShift() re-realizes each spectrum's
    native window after a same-window move. resetGpuResources() + update()
    was tried first and did NOT work — re-rendering into the same undersized
    surface changes nothing, which is also why the continuously redrawing
    waterfall never healed it. Destroying the native window is the part that
    re-establishes the geometry, so this reuses the full reparent path at the
    cost of a Metal re-bind on an explicit, infrequent operator action.

  * AppletPanel paints its own background. It had none — no
    autoFillBackground, no WA_StyledBackground, no paintEvent — so its area
    depended entirely on a backdrop that does not reach behind native
    children. The FLOATING panel has always been opaque; this gives the
    docked panel the same guarantee.

Also: the pop-out opened full-screen. restoreGeometry() faithfully restores a
MAXIMIZED state if one was ever captured, and the float window re-saves its
geometry on every Move/Resize — so one accidental maximize (a title-bar
double-click will do it) poisons the setting and every later pop-out opens
across the display. A 260 px applet rail maximized full-screen is never the
intent, so the state is dropped on restore and clean geometry written back,
healing an already-poisoned config on first use.

Verified over the bridge against the built-in simulator: 0/6 left docks
broken (was 6/6), 0/8 right docks, plus 14 rapid clicks with no settle,
float/dock interleaving, and hide/show interleaving — all clean. Every
measurement gates on confirming AetherSDR is frontmost first, after an
earlier run measured an overlapping window and reported false failures.

Refs aethersdr#4764
…rier, Windows minimal mode

Five blockers from @ten9876 and @aethersdr-agent, plus the two nits. The radio-tab
commit is split out to its own branch as asked; what remains here is the chrome.

**A re-clicked tab stayed unchecked forever.** RadioTab is checkable and belongs
to no exclusive group, so QAbstractButton toggles it off on press;
setActiveRadio() then early-returned on an unchanged id, so applyActiveState()
never re-checked it — and nothing else did either, since setRadios() also
early-returns on an identical list. Clicking the tab of the radio you are on is
the most natural thing on the strip to click, and it stopped the strip showing
which radio you were on. The click now re-asserts the checked state.

**Activation was optimistic.** The click moved m_activeId even though MainWindow
deliberately opens the picker instead of switching, so the strip and the bridge's
activeId both claimed a radio the client had never connected to, with the link
visuals following the wrong tab. The active tab now follows the session only —
setActiveRadio() from refreshRadioTabs(). Same fix on the popover row, which had
the same optimistic claim.

**The link indicator had no carrier while disconnected.** Only the tab matching
m_activeId took the override and alarm, so with nothing connected the state
rendered nowhere — and "searching" is reported precisely when nothing is
connected, which is the state it exists for. Minimal mode compounded it: the
strip showed only the active tab, so with no active radio it rendered empty, the
exact loss the strip is kept in minimal mode to prevent. The carrier now falls
back to the first tab, and compact mode shows whichever tab is carrying. An
active id that matches no tab yet (mid-rebuild) is deliberately left uncarried
rather than painted onto an unrelated radio.

**Windows: the maximize control stopped exiting Minimal Mode.** WM_NCHITTEST
answered HTMAXBUTTON unconditionally, so Windows routed the click non-client,
Qt never saw it, and the branch maximized a strip that is not meant to be
maximized — a regression against main, where the control was a QLabel through
TitleBar::eventFilter. Both the hit-test answer and the click are now gated on
minimal mode; declining HTMAXBUTTON there also stops Snap Layouts offering to
tile the strip. Also added the missing TrackMouseEvent(TME_LEAVE|TME_NONCLIENT),
without which WM_NCMOUSELEAVE never arrives and the maximize button stays lit
after the cursor moves into the client area.

**refreshAfterLayoutShift() fired on the no-op path.** applyAppletPanelState()
routes show, hide, un-float, pop-out, Ctrl+Shift+S and the bridge verb through
setAppletPanelDockedLeft(), so a per-panadapter native re-realize ran on
transitions where nothing moved — and on the hide path it ran before the
visibility change that causes the geometry shift. Now scoped to an actual wall
change. The visibility flip does need the same treatment (the panadapter gains or
loses the panel's 260 px), so that call moved into setAppletPanelVisible() where
the width actually changes, guarded on a real change.

Nits: the pulse decay was 1400 ms against a ~1 s beat interval, so the ticker was
re-armed mid-decay and ran at 12.5 Hz for the whole session — the opposite of the
"an idle bar costs nothing" claim next to it. Now 850 ms, with the comment made
honest about why. The macOS CMake comment described keeping the real NSWindow
chrome, which is the approach the branch abandoned; reworded per the suggestion.

All four behaviours are now guarded in unified_title_bar_test, which is where
the review found the gap — blockers 1, 2 and 7 were caught by a reviewer's
scratch harness and by running the app, not by CI. Verified as real guards:
reverting the fixes fails all four assertions.

Verified: 282 tests pass; 158 bridge checks for the dock/float state machine;
and the see-through strip stays fixed after the scoping change — 0/10 across six
dock flips and four hide/show cycles, the path the refresh moved to.

NOT verified, no hardware here: the Windows minimal-mode fix, the TrackMouseEvent
change, and the two PR-gate items the RFC review moved from RFC to PR (aethersdr#4557
frameless top-edge drift, Wayland fractional-scaling drag jitter).

Refs aethersdr#4764
Use Qt expanded client areas and native captions where supported, retaining a shared Linux fallback. Improve radio-switcher search, action gating, naming and tab visibility. Align the macOS brand with measured native traffic-light bounds using a native unified toolbar.

Validated with the macOS Qt 6.12 preview build, four focused offscreen tests, native Cocoa geometry assertions and isolated automation-bridge checks. Windows/Linux CI and native tiling certification remain deferred; this is a testing branch, not a release-toolchain migration.
@jensenpat jensenpat changed the title Shared Qt 6.12 window chrome and radio switcher — macOS testing feat(ux): Shared Qt 6.12 window chrome refactor and radio switcher — macOS testing Sep 8, 2026
@aethersdr-agent

Copy link
Copy Markdown
Contributor

Thanks for this — the unified title bar work is a lot of careful design, and the docs/qt612-window-chrome.md write-up (especially the honesty about what native testing still owes) is exactly the kind of scoping that makes a branch like this reviewable.

All five checks on 0de5a2b6 are red, but it's two distinct causes, and only one of them is a bug you can just fix.

1. Four jobs die at CMake Configure — the Qt 6.12 floor

build, check-macos, check-windows and Sanitizer option configures all fail at the Configure step (nothing compiles — Build is skipped). The cause is this line:

find_package(Qt6 6.12 REQUIRED COMPONENTS ...)

Every CI leg pins Qt 6.8.3, so find_package can never be satisfied:

  • Windows — .github/workflows/ci.yml:298 (version: '6.8.3')
  • macOS — .github/workflows/ci.yml:550 (QT_VERSION: '6.8.3', installed via aqt)
  • Linux — .github/docker/Dockerfile:15 (the CI image takes Qt 6.8.3 from aqt, not the distro)

This isn't infrastructure breakage and it isn't a mistake in your C++ — it's the expected consequence of the migration, and your README note and design doc already say so ("Release CI and the Windows/Linux packaging toolchains have deliberately not been migrated yet"). Worth stating plainly though: there is no code change on this branch that makes CI green while the floor stays at 6.12. The options are a maintainer decision, not yours:

  • Land the CI toolchain bump first (a separate PR moving those three pins to a 6.12 preview), then rebase this one; or
  • keep this as a draft / explicitly CI-exempt development branch until 6.12 is a real release and the packaging toolchains can follow; or
  • make the 6.12 path opt-in — keep the required floor at 6.8 and gate only the expanded-client-area chrome behind if(Qt6_VERSION VERSION_GREATER_EQUAL 6.9), falling back to the shared caption cluster below that. The flags you actually depend on (Qt::ExpandedClientAreaHint, Qt::NoTitleBarBackgroundHint) are 6.9 features by your own doc, so this may be less painful than the 6.12 requirement suggests — worth confirming what genuinely needs 6.12 rather than 6.9.

One thing I'd flag regardless of which route you take: the diff also removes the find_package(Qt6 QUIET COMPONENTS Core) probe and its FATAL_ERROR block in CMakeLists.txt. That block existed specifically so a below-floor Qt produced an actionable message naming the floor and the way out, instead of CMake's bare "the version found is not compatible". Raising the floor while deleting the explainer means anyone below it — which right now is all three CI images and every contributor on Homebrew/aqt 6.8 — gets the unhelpful error. If the floor moves, that block is worth keeping and retargeting to 6.12 rather than dropping.

2. Static checks — one stale generated file (real fix, one line)

This one is genuinely independent of Qt and is a quick fix. I ran the full Static-checks suite against your branch locally; five of the six pass (engine boundary, test registration, frozen CI gate, theme seed, network timeouts, shader dialects — and the hardcoded-colour ratchet is clean too, all three counters down vs base: unique_colours 608 (-5), total_references 2674 (-50), setstylesheet 1057 (-28), nice). The failure is:

::error::aetherd-touchpoints.md is stale — run `python tools/gen_touchpoint_manifest.py`

Your three new GUI files include ThemeManager.h, so its reference count moved. The entire fix:

python tools/gen_touchpoint_manifest.py

which produces exactly one changed line in docs/architecture/aetherd-touchpoints.md:

-| `core/ThemeManager.h` | 144 | ui-support — ... | unconverted |
+| `core/ThemeManager.h` | 147 | ui-support — ... | unconverted |

Commit that and Static checks goes green on its own, regardless of what happens with the Qt decision.

(The accessibility scan in that job is warnings-only and never fails it — see docs/a11y.md.)

Summary

Check Cause Fixable here?
build, check-macos, check-windows, Sanitizer option configures find_package(Qt6 6.12) vs CI's pinned 6.8.3 — fails at Configure No — needs a toolchain decision, or a 6.9-gated opt-in path
Static checks stale aetherd-touchpoints.md Yes — regenerate, one line

No Copilot or reviewer comments on the PR to reconcile against.

Regenerate the manifest whenever you get a moment, and it'd be worth a maintainer weighing in on the Qt floor before you invest more in rebases — that call decides whether this branch can ever be green in its current shape. Thanks again for the thorough validation notes; they made this much faster to diagnose.


🤖 aethersdr-agent · cost: $5.2388 · model: claude-opus-5

@jensenpat jensenpat self-assigned this Sep 8, 2026
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