Skip to content

fix(gui): AetherRX and AetherTX check boxes and radio buttons draw their canon indicators again. Principle VIII. - #6345

Merged
ten9876 merged 1 commit into
mainfrom
canon-indicator-boxes
Oct 10, 2026
Merged

ten9876 merged 1 commit into
mainfrom
canon-indicator-boxes

Conversation

@ten9876

@ten9876 ten9876 commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part of #6239. Fixes a regression from #6339: in AetherRX and AetherTX, unchecked check boxes and radio buttons drew nothing. A checked box showed only a bare tick and a selected radio only a small dot, so it wasn't clear what was selected.

Cause: ModemChrome's canon look takes the sheet's own QCheckBox/QRadioButton indicator rules out before appending the painted canon indicators (canonIndicatorRules()). The pattern ended each rule at its first }. In the canon look those rules hold {{color.canon.*}} placeholders, so every match stopped inside a token and left }}; fragments behind. Qt rejects a stylesheet it cannot parse as a whole, so the stage column and the AetherNR page lost the entire canon chrome sheet, not only their indicators.

Fix: the pattern now ends a rule body at the first brace outside a token (src/gui/ModemChrome.cpp).

Visible result: the canon chrome sheet applies to the stage column and the AetherNR page again. That restores:

The AetherRX and AetherTX Settings dialogs also use ModemChrome's canon look, and they get the repaired sheet too. Windows whose canon sheet doesn't go through ModemChrome were never affected: Connect to Radio, AetherModem, AetherMap and Network Diagnostics.

Constitution principle honored

Principle VIII (Evidence Over Assertion). The existing test only counted ::indicator selectors, so it passed while the sheet was broken. The new checks test what Qt parses and draws.

Test plan

  • Local build passes
  • modem_chrome_test gains two checks, and both fail with the old pattern:
    • canonSheetLeavesNoRuleFragments: with tokens set aside, every brace in the canon sheet opens or closes a top-level rule.
    • canonIndicatorsRenderTheirImages: a check box and a radio button under the canon sheet draw the same pixels as under the canon indicator rules alone, both checked and unchecked.
  • modem_chrome_test, stage_tab_bar_drag_test and canon_window_test pass.
  • Before and after, rendered offscreen with scratch settings and the radio disconnected: AetherRX and AetherTX grabbed through the automation bridge. After the fix the stage-column boxes, the AetherNR radios and the AE Filter / Noise fill boxes all draw the canon box, tick, ring and dot.
  • check_engine_boundary.py --strict and gen_touchpoint_manifest.py --check pass. No new touchpoints.

Checklist

  • Commits are signed
  • No new AppSettings keys
  • Code is clean-room
  • All meter UI uses MeterSmoother (unchanged)
  • Documentation: n/a (restores the documented canon look)
  • Security-sensitive changes: n/a

🤖 Generated with Claude Code

…eir canon indicators again. Principle VIII.

ModemChrome's canon look takes the sheet's own QCheckBox/QRadioButton
indicator rules out before appending the painted canon indicators. The
pattern ended each rule at the first "}", but in the canon look those rules
hold {{color.canon.*}} placeholders, so every match stopped inside a token
and left "}};" fragments behind. Qt rejects a stylesheet it cannot parse as a
whole, so the stage column and the AetherNR page lost the entire canon chrome
sheet: unchecked boxes and radios drew nothing, a checked box was a bare tick
and a selected radio a small dot. A rule body now ends at the first brace
outside a token.

modem_chrome_test:
- canonSheetLeavesNoRuleFragments: with tokens set aside, every brace in the
  canon sheet opens or closes a rule at the top level.
- canonIndicatorsRenderTheirImages: a check box and a radio button under the
  canon sheet draw the same pixels as under the canon indicator rules alone,
  checked and unchecked.
Both fail with the old pattern.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ten9876
ten9876 requested a review from a team as a code owner October 10, 2026 16:16

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue fit

Yes. No separate bug issue; the body names this as part of #6239 and a regression fix for #6339 (2eb01f48a, merged the same day the regex landed). GOVERNANCE.md does not want an RFC for a bug fix with a clear root cause, and the root cause here is exact and reproducible.

I reproduced the defect without a build. Taking the five indicator rules at src/gui/ModemChrome.cpp:165-189, substituting the kCanon values from src/gui/ModemChrome.cpp:51-70 (%17→{{color.canon.lineHi}}, @radioFill@→{{color.canon.control}}, …) and running the old pattern (?:QCheckBox|QRadioButton)::indicator[^{]*\{[^}]*\}\s* leaves exactly what the body claims:

};
    background: {{color.canon.control}};
}
};
    background: {{color.canon.nested}};
}
};
}
...

Five orphan fragments, each starting with a top-level }. The new pattern removes all five rules cleanly and leaves the neighbouring QRadioButton:disabled, QCheckBox:disabled and QPushButton rules untouched. Fed the Modem-look hex literals instead of tokens, old and new produce byte-identical output — the new pattern is a strict superset of the old behaviour, so the Modem look (which does not run this branch anyway) cannot shift.

The body's explanation of why #6339 shipped broken is also correct and worth crediting: the old pattern did remove the ::indicator selector text, so canonLookPaintsItsIndicators (tests/modem_chrome_test.cpp:155-166) compared count("::indicator") on a sheet Qt would reject outright and passed. canonSheetLeavesNoRuleFragments closes precisely that gap, deterministically and without depending on how Qt recovers from a parse error.

Scope

File What it changes Claimed by the PR? Verdict
src/gui/ModemChrome.cpp:396-408 One regex widened to skip {{token}} braces, plus the comment explaining why Yes — this is the fix In scope
tests/modem_chrome_test.cpp Two new test slots + one indicatorPixels helper and its includes Yes — the Principle VIII evidence In scope

Everything in the diff is explained by the stated fix. No new public surface, no config key, no settings write, no protocol verb, no CHANGELOG.md entry, no behaviour-flag default moved. The only - lines are the replaced regex and the reflowed comment — no guard, early return or symptom-naming comment was deleted. Nothing bundled.

Blockers

None.

Nits (non-blocking)

  • The body's "never affected" list is wrong about two windows. It says "Windows whose canon sheet doesn't go through ModemChrome were never affected: Connect to Radio, AetherModem, AetherMap, Network Diagnostics and the Settings dialogs." But src/gui/AetherRxSettingsDialog.cpp:34-36 and src/gui/AetherTxSettingsDialog.cpp:35 both do go through it:

        applyCanonSheet(bodyWidget(), [] {
            return ModemChrome::styleSheet(ModemChrome::Scale::Dialog, ModemChrome::Look::Canon);
        });

    Those are the AetherRX/AetherTX Settings dialogs #6339 moved to CanonWindow — by the same mechanism they lost their whole canon sheet too, and this fix restores them. That makes the fix better than advertised, but the claim as written would send someone looking in the wrong place next time. Worth correcting in the body (the title's "AetherRX and AetherTX" covers it either way).

  • tests/modem_chrome_test.cpp:84 — the backdrop comment overstates what DrawChildren suppresses. "Over one fixed backdrop, without the widget's own background" — omitting DrawWindowBackground skips the backgroundRole fill, but a QSS background on QCheckBox is still painted in the widget's own paintEvent. The test passes today only because canon gives both QWidget and QRadioButton, QCheckBox background: transparent (src/gui/ModemChrome.cpp:105,156). If the canon palette ever gives a check box a non-transparent background, this test fails even though the indicators are perfectly correct. Not worth changing now; worth knowing when it next goes red.

  • tests/modem_chrome_test.cpp:76 — option.initFrom(&button) does not carry State_On, so subElementRect is computed state-blind for both the checked and unchecked passes. Harmless today (the canon :checked rules keep the same 18×18 geometry), but it means the render test would not notice a size change confined to a :checked rule.

What I tried to break

  • The new pattern against unbalanced input. (?:\{\{[^{}]*\}\}|[^{}])* only accepts a brace as part of a complete {{…}} pair, so a body carrying a single stray { or } would fail to match and the rule would survive — but no rule body in the sheet contains one, and the Modem look's values are plain hex. The alternation is also unambiguous (the first branch requires {{), so there is no catastrophic-backtracking path on a long body.
  • Whether the regex can over-reach. [^{]* between ::indicator and \{ cannot cross a brace, so it cannot swallow a following selector; and the multi-selector rules that are not indicator rules (QRadioButton,\nQCheckBox { at src/gui/ModemChrome.cpp:155, QRadioButton:disabled,\nQCheckBox:disabled { at :161) do not contain ::indicator and survive — confirmed in the reproduction above.
  • Ordering. The removal at :406 runs after the c.literals substitution loop at :393, which is what puts the {{…}} tokens into those bodies in the first place — so the fix is at the right seam. Worth saying explicitly that the "cleaner" alternative of never emitting the rules for Canon is not better here: %15/%16/%20 appear only in indicator rules, and QString::arg chaining fills the lowest remaining %N, so deleting them from the template would silently shift every later positional argument. Post-substitution removal is the right call.
  • Fail-closed path. canonIndicatorRules() returns {} when ThemedImageCache cannot write (src/gui/CanonIndicators.h:119-124), and the new QVERIFY(!indicators.isEmpty()) at tests/modem_chrome_test.cpp:197 would then fail rather than skip. That is not a new environment dependency: the pre-existing canonLookPaintsItsIndicators already asserts canon.contains("canon-indicators/check_on_"), so an unwritable cache already reds this target. And the production fallback is intact — with empty rules the canon sheet simply carries no indicator rules and Qt's own are used, as CanonIndicators.h:14-16 documents.
  • Sibling sheets. The other six callers of canonIndicatorRules() (ConnectionPanel.cpp:365, WaveformsDialog.cpp:928, Ax25HfPacketDecodeDialog.cpp:961, TailnetShimDialog.cpp:133, PskReporterMapDialog.cpp:471, NetworkDiagnosticsDialog.cpp:343) append to static sheets. Grepped all six for ::indicator: zero hits, so none carries its own indicator rules that would border the painted image, and none needs the regex. src/gui/ModemChrome.cpp:407 is the only place in src/ with this pattern — no sibling left unfixed.
  • Whether the new tests would pass against the unfixed code. canonSheetLeavesNoRuleFragments would not: the first orphan } drives --depth to -1 and trips QVERIFY2. canonIndicatorsRenderTheirImages is weaker than it looks — it only reds on the old code if Qt drops the whole sheet on the parse error (which the body's symptom implies it does). The depth test carries the regression coverage regardless, so I would not treat the render test as the proof.

Honesty notes. I am headless: no build, no test run, no GUI. The regex behaviour above is reproduced in re, which matches Qt's PCRE semantics for these constructs, but the claims about what Qt does with the resulting sheet are reasoned from code, not observed. Separately: modem_chrome_test appears in no ctest -R step in .github/workflows/ci.yml — the only filtered runs there are the DV/thumbdv group, cross_needle_meter_test, mac_nr_filter_test and asr_gpu_probe_test. All eight checks green on f0d05f3 proves these two tests compile, not that they pass. The target is registered correctly (tests/tests.cmake:2044-2057, with QT_QPA_PLATFORM=offscreen), and the body checks the box — pasting the actual ctest -R modem_chrome_test --output-on-failure output would settle it. No socket, no fake peer, no network behaviour anywhere in this diff.

Security audit. Ran the security-audit subagent over the nine files the harness listed. Verdict: no findings. All of CodeGuard's hits are on files this PR does not touch, and each is a pattern false positive — markdown backticks quoting radio wire commands inside // comments, C++ & references and || operators read as shell metacharacters, 4294967296.0 (the 2^32 DDS phase-word scaler) read as a bank account, RFC 5737 TEST-NET-1 documentation addresses and loopback in a UDP test, and typographic dashes read as homoglyphs. Nothing to act on in this PR. One incidental observation for later, not a finding: #include <QProcess> at src/gui/RadioSetupDialog.cpp:87 is unused (QProcess appears nowhere else in that file) and is what gives the command-injection rule a plausible anchor there — dropping it would stop the file re-flagging on every scan.

Recommendation

Approve. One-regex fix at the correct seam, with the failure mode reproduced exactly as the body describes and a deterministic regression test that reds on the old pattern — which is more than the test it supersedes could do. The two nits are about test fragility and one inaccurate sentence in the body, neither worth a round trip on its own. Concrete next step for the maintainer: since modem_chrome_test is not in any CI ctest -R filter, ask @ten9876 to paste the local ctest -R modem_chrome_test --output-on-failure run (and, while editing, correct the "Settings dialogs were never affected" line — AetherRxSettingsDialog.cpp:34 and AetherTxSettingsDialog.cpp:35 both route through ModemChrome's canon look and were broken too).

Nice root-cause write-up, and thank you for making the why a test rather than a comment.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (142 finding(s))

  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/anan/AnanBackend.cpp /tmp/aetherclaude/pr-6345/src/core/backends/anan/AnanBackend.cpp:1020
  • [LOW] CS-PII-IP — PII detected: ip_address in src/core/backends/anan/P2Client.cpp /tmp/aetherclaude/pr-6345/src/core/backends/anan/P2Client.cpp:236
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/backends/anan/P2Protocol.h /tmp/aetherclaude/pr-6345/src/core/backends/anan/P2Protocol.h:198
  • [HIGH] CS-PII-BANK — PII detected: bank_account in src/core/backends/anan/P2Protocol.h /tmp/aetherclaude/pr-6345/src/core/backends/anan/P2Protocol.h:89
  • [CRITICAL] CS-VLN-CMDI-PIPE — Vulnerability: CS-VLN-CMDI-PIPE in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:179
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:177
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:5033
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:5724
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:5880
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:842
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:1001
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:1331
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:1542
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:1827
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:2876
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:3377
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:3797
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:4272
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:6636
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:6793
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:6883
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:7082
  • [HIGH] CS-INJ-homoglyph — Mixed Unicode scripts detected (possible homoglyph attack) in src/gui/RadioSetupDialog.cpp /tmp/aetherclaude/pr-6345/src/gui/RadioSetupDialog.cpp:0
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:19
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:72
  • [LOW] CS-PII-IP — PII detected: ip_address in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:19
  • [LOW] CS-PII-IP — PII detected: ip_address in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:72
  • [LOW] CS-PII-IP — PII detected: ip_address in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:154
  • [LOW] CS-PII-IP — PII detected: ip_address in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:158
  • [LOW] CS-PII-IP — PII detected: ip_address in tests/anan_link_telemetry_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_link_telemetry_test.cpp:173
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in tests/anan_p2_protocol_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_p2_protocol_test.cpp:398
  • [MEDIUM] CS-VLN-SSRF-HEX — Vulnerability: CS-VLN-SSRF-HEX in tests/anan_p2_protocol_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_p2_protocol_test.cpp:401
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in tests/anan_p2_protocol_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_p2_protocol_test.cpp:43
  • [HIGH] CS-PII-BANK — PII detected: bank_account in tests/anan_p2_protocol_test.cpp /tmp/aetherclaude/pr-6345/tests/anan_p2_protocol_test.cpp:72
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/audio_engine_rates_test.cpp /tmp/aetherclaude/pr-6345/tests/audio_engine_rates_test.cpp:83
  • [CRITICAL] CS-VLN-CMDI-PIPE — Vulnerability: CS-VLN-CMDI-PIPE in tests/peripheral_auth_dialog_test.cpp /tmp/aetherclaude/pr-6345/tests/peripheral_auth_dialog_test.cpp:1633
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/peripheral_auth_dialog_test.cpp /tmp/aetherclaude/pr-6345/tests/peripheral_auth_dialog_test.cpp:265
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/peripheral_auth_dialog_test.cpp /tmp/aetherclaude/pr-6345/tests/peripheral_auth_dialog_test.cpp:328
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/peripheral_auth_dialog_test.cpp /tmp/aetherclaude/pr-6345/tests/peripheral_auth_dialog_test.cpp:485
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/peripheral_auth_dialog_test.cpp /tmp/aetherclaude/pr-6345/tests/peripheral_auth_dialog_test.cpp:630

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


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

@ten9876 ten9876 self-assigned this Oct 10, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The fix satisfies its stated part of #6239: it repairs the stylesheet regression introduced by #6339. The token-aware rule matcher removes complete indicator rules instead of stopping inside a {{color.canon.*}} placeholder. There is no standalone closing issue; this is a demonstrated bug fix restoring the approved canon appearance, with no new feature or architecture decision.

Scope

File Change Claimed? Verdict
src/gui/ModemChrome.cpp Remove complete canon indicator rules while preserving token braces Yes In scope
tests/modem_chrome_test.cpp Check rule structure and actual checked/unchecked checkbox/radio pixels Yes In scope

Everything in the diff is explained by the stated fix. No settings, defaults, public API, protocol, dependency, transmit, or CI-gate changes were found. The commit has a verified good signature. User documentation does not need a behavior update for this restoration of the documented appearance.

Blockers

None.

Nits — non-blocking

  1. Correct the unaffected-windows claim in the PR body. src/gui/AetherRxSettingsDialog.cpp:35 and src/gui/AetherTxSettingsDialog.cpp:35 both call ModemChrome::styleSheet(Scale::Dialog, Look::Canon). Consequently, the sentence saying Settings dialogs do not pass through ModemChrome and were never affected is incorrect. They also receive the repaired full stylesheet, although they do not themselves contain checkbox/radio controls. Replace that paragraph with:

    Windows whose canon sheet does not go through ModemChrome were never affected: Connect to Radio, AetherModem, AetherMap and Network Diagnostics. The AetherRX and AetherTX Settings dialogs also use ModemChrome and receive the repaired canon stylesheet.

Verification and attacks

  • Built the actual merge base deab0a763 and exact head f0d05f345 in owned scratch worktrees, using Qt 6.12 on Linux with the lean shared-core build. Built only AetherSDR and the three named test targets. Head modem_chrome_test, stage_tab_bar_drag_test, and canon_window_test all pass (3/3). These inspected targets own no sockets; the modified rendering test is socket-free.
  • Compiled the new assertions against the merge-base production object, reverting the actual fix rather than merely inspecting the assertions. Both new tests fail: the brace check reports a closing brace has no rule to close; the rendering check reports an actual 14×14 fallback checkbox versus the expected 18×18 image. Both pass on the head. This demonstrates that the tests detect the regression.
  • Drove merge-base and head AetherSDR through explicitly selected aethersdr-pr6345-base and aethersdr-pr6345-head bridge sockets, each with fresh isolated settings, offscreen rendering, and TX disabled. Connected only to DEMO-0001; the returned model was AetherSDR Demo, with one slice and pan and transmitting: false. Before captures show missing checkbox outlines and bare radio dots; head captures show full outlines, rings, and checked indicators. Clicked stage Gate checkboxes, checked AetherNR controls, exercised Default/Light themes, captured both Settings dialogs, and closed/reopened panels. All review-owned app instances were closed and their exit confirmed; the operator session was untouched.
  • Inspected token substitution, both Dialog/Compact sheets, the untouched Modem look, sibling call sites, and the rendering helper's checked-state handling. The helper uses the style option to locate the crop; the actual checked widget renders its own state, so the bot concern about a missing State_On in that crop option does not demonstrate a defect.
  • Independent canon/scope and lifecycle/test audits found no code blocker. The model-invocable automated code-review skill was unavailable, so this was a manual review with independent passes. Existing CodeGuard findings are outside this two-file diff; inspected representative critical entries were ordinary comments, not vulnerabilities introduced here. I did not individually validate every one of its 142 entries.

Boundary/capability/command-plane gate results are unchanged between base and head: 102 existing engine-boundary findings, 70 capability booleans, and 32 command-plane files / 368 commands; touchpoint manifest checks pass on both sides. No new boundary findings were introduced.

CI and limits

All eight reported checks are successful, including Linux, Windows, macOS, and Static checks. However, the earliest run was created at 2026-10-10 16:16:42 UTC, while current main is 5538b8b7af1cfaffc1423a6fdf14a7191754d977 dated 16:39:58 UTC. Those green pull-request checks describe an older merge result. With strict: false, a refresh is not forced. Local verification covers the exact PR head and its actual merge base, not the latest-main merge candidate. Windows/macOS runtime rendering and real-radio behavior were not exercised; their build checks passed, and this change contains no radio path.

Recommendation: Approve with nits. The original defect is reproduced, the new tests fail without the fix, and the corrected GUI survives checked/unchecked rendering, theme changes, and panel reopening. Correct the PR-body affected-window list, then refresh/verify the merge candidate against current main before merging. This COMMENT review leaves formal approval to the maintainer.

@ten9876 ten9876 assigned ten9876 and unassigned ten9876 Oct 10, 2026
@ten9876

ten9876 commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator Author

Both reviews have no blockers. Here's where each point landed, all at head f0d05f345:

  • "Never affected" list: corrected in the PR body. The AetherRX and AetherTX Settings dialogs do use ModemChrome's canon look (AetherRxSettingsDialog.cpp:34, AetherTxSettingsDialog.cpp:35), so they get the repaired sheet too.
  • modem_chrome_test isn't in a CI ctest -R filter: that's by design. New tests register in tests/tests.cmake, and the Full Suite on main runs the whole tree. Here is the local run at the head:
    PASS   : ModemChromeTest::canonLookPaintsItsIndicators()
    PASS   : ModemChromeTest::canonSheetLeavesNoRuleFragments()
    PASS   : ModemChromeTest::canonIndicatorsRenderTheirImages()
    Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 25ms
    
    With the old pattern, both new tests fail: a closing brace has no rule to close, and a 14×14 fallback check box against the expected 18×18 image. The second review reproduced the same result independently.
  • The crop rect is computed without State_On: no defect. The option is used only to locate the indicator, and the checked widget draws its own state. The canon :checked rules keep the 18×18 geometry.
  • The backdrop comment says more than DrawChildren guarantees: agreed that it relies on the canon sheet keeping QCheckBox/QRadioButton backgrounds transparent. That holds today, so I've left it as is rather than push another round for a comment.

@ten9876
ten9876 merged commit 16cfa80 into main Oct 10, 2026
8 checks passed
@ten9876
ten9876 deleted the canon-indicator-boxes branch October 10, 2026 16:51
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