Skip to content

feat(gui): TX Band Settings is a CanonWindow workspace that follows the radio's TX profile, band values and answers. Principle II. - #6735

Merged
ten9876 merged 5 commits into
aethersdr:mainfrom
w5jwp:feat/txband-canonwindow
Oct 25, 2026
Merged

ten9876 merged 5 commits into
aethersdr:mainfrom
w5jwp:feat/txband-canonwindow

Conversation

@w5jwp

@w5jwp w5jwp commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #6734. Part of #6239 and approved RFC #6226. On #6734 the maintainer picked option 2: the full CanonWindow move, not colours only.

TX Band Settings (TxBandDialog) moves from a PersistentDialog to a CanonWindow::Kind::Workspace, following AetherRX, AetherTX and Radio Setup. It is a workspace rather than a dialog because it opens from Radio Setup, which is itself a workspace window, and a dialog parented to the main window would open underneath Radio Setup.

  • Window:
    • Canon header and ground, corner close button, Esc / ⌘W to close, and edge resizing through FramelessResizer.
    • Its own theme scope, canon/txBand.
    • Geometry stays under the existing key (TxBandDialogGeometry). CanonWindow reads the old base64 rectangles and rewrites them as x,y,w,h on the next save, so the saved size and position carry over.
  • Table:
    • Every hard-coded colour becomes a color.canon.* token, through one body sheet that selects on a txBandCell role property (header, band, value, note). All cells share one background token.
    • Check boxes use the canon indicators (canonIndicatorRules()).
    • A field's right-click menu gets its canon panel back: the body sheet clears every descendant to transparent, and Qt parents that menu to the field. The panel is raised with a lineHi border, cyan selection, color.control.unavailable for disabled items, and line separators.
    • Header labels are polished by the body sheet before they are measured, so each column fits its bold 10 px header text rather than the default font's.
    • The RF and tune power fields and the eight bandset check boxes have accessible names that give the column and the band, for example "RF power percent, band 40" or "RCA TX1, band 40".
  • Following the radio:
    • The table belongs to one TX profile, one radio session and one set of bands (by id and name). When any of these changes, the table is rebuilt. The heading and window title name the profile, and focus returns to the same control in the new table. Text being typed when that happens is dropped, not written.
    • Values the radio reports for a band (power, inhibit, keying outputs, hardware ALC) reach the open table without being written back. Only a value the radio changed since it was last shown is written, so a click or entry the radio has not echoed yet stays as the operator set it. A field with uncommitted typing keeps it. A change the radio refuses shows the radio's value again, and so does one that is never sent (for example during a reconnect). This live refresh is what makes the profile rebuild correct, because the radio can report the new profile before it reports that profile's band values.
    • Esc and ⌘W hide the window, and showing it again builds a fresh table, as closing with ✕ and reopening does. This is done in the window's own show handling, so it covers every way of opening it. A hidden window doesn't refresh or rebuild.
  • The radio's answer (IRadioBackend::requestTxBand, FlexBackend, RadioModel::requestTxBand): a TX band write now carries a completion, as a transverter write already does. FlexBackend passes it to the setup command sink. RadioModel fences it to the caller's session and context, and refuses it at once when the write is not dispatched. Both new parameters default, so other callers are unchanged. Only FlexBackend implements the method; the engine-boundary findings for the touched files and the command-plane count are the same as on main.
  • Opening it (MainWindow_Menus.cpp): the menu action creates the window directly, as AetherControl does. showOrRaisePersistent() would apply View → Frameless Window, which a canon window ignores. The isConnected() guard stays, and an existing window is shown and raised. The other two entry points (Radio Setup → Transmit, and Inhibit during TUNE ▸ Configure RCA suppression…) trigger the same action.
  • Docs:
    • docs/style/dialog-patterns.md, aethersdr-style-guide.md and theme-style-guide.md list the window and its scope.
    • docs/user/docs/radio-setup.md says how the window moves, resizes and closes, and that the table follows the radio. The troubleshooting note no longer tells users to reopen TX Band Settings after reconnecting.

Intent

After this merges, TX Band Settings looks and behaves like the other canon tool windows, follows the theme, and shows the active TX profile's values as the radio reports them. The same controls send the same commands. Out of scope: the table's columns and layout, and the other persistent tool dialogs (one PR each, per #6239).

Visible size change: the minimum grows from 780×450 to 816×450, measured with the same two-band test fixture at the same font on main (85882c10) and on this branch. The extra 36 px come from the canon cell padding, not from the check boxes.

Contrast, from the theme files against solid cell backgrounds (the window ground has a gradient, so the note row is approximate). Every pair clears the 4.5:1 that docs/a11y.md asks for:

Text Tokens Dark Light
Column headers cyan on raised 12.43:1 7.06:1
Band name ink on nested 15.50:1 11.49:1
Field text ink on control 14.01:1 9.54:1
Note muted on ground 6.71:1 5.94:1

Constitution principle honored

Principle II (Radio-Authoritative State): the open table shows what the radio reports for the active TX profile, and showing it never writes it back. Principle VIII (Evidence Over Assertion): each behaviour below has a check that fails when it is reverted.

Test plan

  • Local build passes (cmake --build build)
    • AetherSDR, tune_output_dialog_test and radio_setup_operations_test on macOS arm64 (Qt 6.12.0) and Linux x86_64 (Qt 6.12.0, shared core).
  • Behavior verified on a real radio if applicable
    • Opened on a FLEX-8600 (v4.3.16, macOS 27.0.1), and grabbed under Default Dark and Default Light with the two-band test fixture.
    • Demo radio (offscreen, isolated settings, DEMO-0001, TX off): opening from Settings → TX Band Settings..., closing with ✕ and reopening leaves exactly one visible window.
  • Existing tests pass (CI)
    • tune_output_dialog_test and radio_setup_operations_test pass locally. Neither is in ci.yml's per-PR ctest -R steps (the per-PR gate is frozen, docs/agents/tests-ci.md), so the per-PR jobs only compile them; they run after merge in full-suite.yml.
    • New checks: it is a CanonWindow; it is a frameless Qt::Window; legacy geometry restores and hiding saves under the same key; a field's right-click menu is opaque; a header's minimum width is its text in the canon sheet's font; the heading names the profile; a reported band value and a reported keying output reach the open table; a field being typed in keeps the operator's text; a new TX profile renames the heading and window and rebuilds the table; a band the radio adds gets its own row; a band whose name arrives later is rebuilt under its name; reopening a hidden window builds a fresh table; a click that can't be sent shows the radio's value again. In radio_setup_operations_test: constructing the table writes nothing and shows a confirmed-on value; an unrelated report and the radio's echo keep a click, and a later report moves the box without writing it back; a check box or power field the radio refuses after it was sent shows the radio's value again, and an accepted one stays; a new session rebuilds the table, and its fields write; text being typed when a new profile rebuilds the table is not written, and focus returns to the same field. None of this sends a command.
    • Each of these, when broken on purpose, fails its check:
      • removing setGeometryKey();
      • Kind::Dialog;
      • removing the QMenu rules;
      • measuring header labels before polish;
      • connecting a check box before setting its value;
      • not rebuilding on a profile change;
      • not refreshing on a status;
      • dropping the typing guard;
      • dropping the signal blocker on a refreshed check box;
      • writing every value rather than only the changed ones;
      • not rebuilding on a new session;
      • not rebuilding on a new band, or on a band's name;
      • not rebuilding when a hidden window is shown again;
      • not reverting a click that can't be sent;
      • FlexBackend dropping the completion, RadioModel not forwarding it, or RadioModel not refusing an undispatched write;
      • the dialog ignoring a refusal for a check box or a power field, or reverting an accepted change;
      • not restoring focus after a rebuild;
      • swapping two columns in the shared column table.
    • Static checks run locally: engine boundary, touchpoint manifest, seam probe table, generated user-docs reference, test registration, WDSP test links, capability records, command plane, frozen per-PR test gate, network timeouts, QSettings ratchet and theme seed all pass. The colour audit against the base gives unique colours 487 (+0), references 1868 (−11) and setStyleSheet 591 (−8). tools/check_a11y.py src/gui/TxBandDialog.cpp reports nothing.
  • Reproduction steps documented if user-reported bug
    • n/a: not a bug report.

Checklist

  • Commits are signed (docs/COMMIT-SIGNING.md)
  • No new flat-key AppSettings calls — use nested-JSON-under-one-key
    (Principle V)
    • The geometry key is the existing TxBandDialogGeometry.
  • Code is clean-room — not decompiled, disassembled, or
    reverse-engineered from a proprietary binary (Principle IV)
  • All meter UI uses MeterSmoother (AGENTS.md convention)
    • No meter UI touched.
  • Documentation updated if user-visible behavior changed — docs/ and the
    affected READMEs. Not CHANGELOG.md, which is a release-prep file a
    PR must not add to (AGENTS.md); describe it in the Summary above instead
    • The three style docs and docs/user/docs/radio-setup.md.
  • Security-sensitive changes reference a GHSA if applicable
    • n/a.

🤖 Generated with Claude Code

…the canon palette. Principle VIII.

TX Band Settings moves from a PersistentDialog to a CanonWindow::Kind::Workspace,
following AetherRX, AetherTX and Radio Setup: canon header and ground, its own
theme scope (canon/txBand), FramelessResizer, and geometry under the key it
used as a PersistentDialog (TxBandDialogGeometry), so saved size and position
carry over. The menu action creates it directly, like AetherControl, because
showOrRaisePersistent() would apply View → Frameless Window.

Every hard-coded colour in the table becomes a color.canon.* token through one
body sheet with role properties, and the check boxes use the canon indicators.
The grid's widgets, object names and accessible names are unchanged. Header
labels are polished by the body sheet before they are measured, so the bold
10 px text is never clipped.

tune_output_dialog_test gains the workspace shape and the geometry key,
including a legacy base64 rectangle; the style docs and Radio Setup's user
page list the window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@w5jwp
w5jwp requested a review from a team as a code owner October 24, 2026 17:17

@w5jwp w5jwp left a comment •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review — #6735 (head e3fcc6ec)

Edited after the triage pass on #6734: corrected the #6239 citation and the clip-check name, and added the second commit.

Issue fit. #6734 asks the maintainer to pick a scope for TX Band Settings under #6239: colours only, or the full CanonWindow workspace move this PR implements. The ask is: canon header and ground, canon/txBand scope, geometry kept under TxBandDialogGeometry, every colour a color.canon.* token, canon check boxes, same table and same requests. Intent (from the body): TX Band Settings looks and behaves like the other canon tool windows, with the same controls doing the same things. The diff does what the issue describes. Whether it should land is the maintainer's pick on #6734; see below.

Scope

File Change Claimed Verdict
src/gui/TxBandDialog.h / .cpp CanonWindow::Kind::Workspace; one token sheet; canon indicators; header-label polish before measuring Yes In scope
src/gui/MainWindow_Menus.cpp:856-866 Direct lazy-construct instead of showOrRaisePersistent() Yes In scope
tests/tune_output_dialog_test.cpp, tests/tests.cmake 7 checks; drops PersistentDialog / FramelessWindowTitleBar from the target Yes In scope
docs/style/*.md (3), docs/user/docs/radio-setup.md Window listed; user-facing move/resize/close paragraph Yes In scope

Everything in the diff is explained by the issue. There is one commit, authored today. The - side is only the old hard-coded styles, the per-widget style wrappers, the PersistentDialog base and its open helper. No connect, object name, accessible name, guard or request path was removed. There is no new settings key (the geometry key is the existing one) and no protocol surface. CHANGELOG.md is untouched. The body's checklist claims hold.

Blockers

None in the code.

Needs maintainer decision

  1. The #6239 pick (@ten9876). #6239: "Items under 'Candidates' are not approved scope until the maintainer picks them." Its "Persistent tool dialogs (one PR per dialog)" line is a token-only candidate (under "surfaces whose widgets move from base tokens to color.canon.*"), so a full CanonWindow move for this window is unlisted, a one-off pick like Radio Setup or AetherControl. #6734 lays out both options, and the PR body asks not to merge before the pick.

Nits (non-blocking)

  • Not run per-PR. tune_output_dialog_test and radio_setup_operations_test are not in ci.yml's per-PR ctest steps (the gate is frozen), so CI only compiles them here; they run post-merge in full-suite.yml.
  • Old builds can't read the new geometry format. After this lands, CanonWindow rewrites TxBandDialogGeometry as x,y,w,h on hide, so a downgrade to an older build would open the dialog at its default position. Same trade every earlier canon migration made (AetherControl, Radio Setup); noting it for completeness.

Verified vs. read

  • Size. Minimum 780×450 on main and 816×450 here, measured with the same fixture and font.
  • Before/after. I built the PR's test file against main (85882c10, scratch worktree, tune_output_dialog_test). Three checks fail there, as they should: "TX Band Settings is a CanonWindow", "it is a frameless workspace window…" and "hiding the window saves its size under the same key". "Geometry saved by the old dialog restores under its key" passes on main too, which is correct, since it pins carry-over. At the head, tune_output_dialog_test and radio_setup_operations_test pass.
  • Mutations (run while the change was being built, not re-run for this review):
    • dropping setGeometryKey() fails the restore and save checks;
    • Kind::Dialog fails the workspace check;
    • measuring header labels before polish fails the clip check (label 83 px < hint 85 px). The second commit (d0b6ad2e) renames it "the window at its minimum size cannot clip the header text" and sizes the window to minimumSize() instead of a 700×450 that Qt clamps; the same mutation still fails it.
  • Colour-audit ratchet. tools/audit_colours.py --compare-src <main src> --strict: unique colours 487 (+0), references 1868 (−11), setStyleSheet 591 (−8). tools/check_a11y.py src/gui/TxBandDialog.cpp: 0 findings.
  • Rendering (checked while the change was being built).
    • Demo-mode instance (offscreen, isolated settings, DEMO-0001, TX off): the window opens from Settings → TX Band Settings... (invoke … trigger) with the canon chrome. The demo reports no TX bands, so there are no rows there.
    • Rows checked from the test fixture (40 m / 20 m), grabbed under Default Dark and Default Light while switching themes with the window open: the token sheet and canon indicators rebuild on the switch.
  • Read, not run.
    • Escape / Ctrl+W go through QDialog::reject() on both main and the head. Neither PersistentDialog nor CanonWindow overrides Escape, so the hide/close behaviour (and the documented "close and reopen after reconnecting") is unchanged.
    • The corner ✕ calls close(), so WA_DeleteOnClose still gives a fresh session on reopen.
    • Return in a field still commits through editingFinished. With no buttons in the window, the workspace's skip-the-default-button rule has nothing to press either way.
    • Every entry point (Radio Setup → Transmit, Inhibit during TUNE ▸ Configure RCA suppression…) triggers m_txBandAction, so all of them go through the new construct path.
  • Not run. The code-review automated pass; I did the equivalent pass by hand. CI's build, check-macos and check-windows were still pending at review time; the runs were created after main's tip, so the merge base is current.
CleanShot 2026-10-24 at 12 11 57@2x

… its minimum. Principle VIII.

The check resized to a fixed 700x450, below the canon window's 816 px
minimum, so Qt clamped it and the "restoring a small window" wording no
longer matched what ran. It now resizes to minimumSize(), the worst case a
restored rectangle can produce, and says so. Measuring the header labels
before the sheet polishes them still fails it.

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

@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, for the option the issue labels option 2. #6734 lays out two scopes — colours only (the #6239 candidate line) or a full CanonWindow move — and this PR implements the second one completely: Kind::Workspace, canon header and ground, corner close button, Esc / ⌘W, FramelessResizer, canon/txBand, geometry carried over under the existing key, every hard-coded colour replaced by a color.canon.* token, canon indicators. Nothing in #6734's description of option 2 is left undone. The issue itself says the pick is the maintainer's, and the PR body repeats it — see the Recommendation.

Scope

File / group What it changes Claimed by the body? Verdict
src/gui/TxBandDialog.{h,cpp} PersistentDialog → CanonWindow::Kind::Workspace; one body sheet on a txBandCell role property replaces 10 per-widget sheets; ensurePolished() before measuring headers Yes In scope
src/gui/MainWindow_Menus.cpp:856-866 showOrRaisePersistent() → direct construction Yes In scope, and necessary: showOrRaisePersistent calls dlg->setFramelessMode(...) (MainWindow.h:2359), which CanonWindow does not have — the old call would not compile against the new base class
tests/tune_output_dialog_test.cpp canonWorkspace() (7 checks); clip check resized to minimumSize() Yes In scope
tests/tests.cmake:4212-4221 Drops PersistentDialog.cpp and FramelessWindowTitleBar.cpp from the target Implied by the migration In scope — no remaining TU in that target references either
docs/style/{aethersdr-style-guide,dialog-patterns,theme-style-guide}.md Adds TX Band Settings to the canon lists and canon/txBand to the scope list Yes In scope — but see Nit 1
docs/user/docs/radio-setup.md:97 One paragraph on chrome, resize and close Yes In scope

No CHANGELOG.md entry — correct. Everything in the diff is explained by #6734 and #6239; nothing to unbundle.

Blockers

None.

Nits (non-blocking)

  1. docs/style/aethersdr-style-guide.md:117 pre-writes the ruling the PR is waiting for. The sentence the diff edits reads "the tool windows the maintainer has moved to it (Updates, Waveforms, …, Radio Setup, TX Band Settings, …)". The PR body says the opposite — "this full CanonWindow move is not on #6239's CanonWindow lists … please don't merge before the pick" — and my own notes agree that #6239's tool-dialog line is token-only. These are consistent only if merging is the pick; that's a reasonable way to land it, but the doc line should not be read as citing a ruling that already exists. No code change needed — just flagging what the maintainer is ratifying by merging.

  2. tests/tune_output_dialog_test.cpp:199-238 does not run in PR CI. ci.yml:5 states "the only ctest -R steps are the ones listed in" that comment, and tune_output_dialog_test appears in no -R step in any workflow (its only tests.cmake list membership outside its own block is AETHER_SETTINGS_CONSUMERS, which is the settings-store contract, not a gate). All 11 checks on d0b6ad2e are green, but that green covers compilation on three platforms plus Static checks — not the 7 new canon-workspace checks. Your local run and your stated mutation checks are the only evidence those pass; worth saying so explicitly in the test plan so the maintainer doesn't read the badge as covering them.

  3. Reopen after Esc / ⌘W reuses the old instance; reopen after ✕ rebuilds it — MainWindow_Menus.cpp:858. CanonWindow wires ✕ to QDialog::close() (CanonWindow.cpp:74) but ⌘W to QDialog::reject() (CanonWindow.cpp:79), and Escape goes through QDialog's own reject path; reject() hides without a close event, so WA_DeleteOnClose does not fire and the QPointer stays live. The window title's Current TX Profile: %1, the table rows, and the RadioControlSession captured at TxBandDialog.cpp:145 are therefore refreshed on one close path and stale on the other. This is pre-existing, not introduced here — showOrRaisePersistent already set WA_DeleteOnClose (MainWindow.h:2358), so the asymmetry is unchanged by this PR, and the canon header shows the same windowTitle() the frameless title bar showed before. Mentioning it only because the new header makes the stale profile string more prominent; a follow-up issue rather than anything for this PR.

What I tried to break

  • Dropped behaviour from showOrRaisePersistent. The new handler omits trackPersistentDialog(dlg). I chased m_persistentDialogs to its only consumer (MainWindow.cpp:10254-10265) — it exists solely to propagate setFramelessMode(). Nothing else (shutdown, disconnect teardown) reads the list, and trackPersistentDialog takes PersistentDialog*, which TxBandDialog no longer is. The drop is complete and correct, and matches dialog-patterns.md's "not tracked by trackPersistentDialog()".
  • Sibling call sites. Grepped for every TxBandDialog construction in the head checkout: one, at MainWindow_Menus.cpp:859. The two other entry points (MainWindow_Menus.cpp:992, MainWindow.cpp:3592) both m_txBandAction->trigger(), so the body's "same action" claim holds and no surface is left on the old path. The isConnected() guard and the m_txBandAction->setEnabled(cmdPlane) gate at MainWindow.cpp:8296 are untouched.
  • Lifetime, now that the window can be deleted. FramelessResizer::install installs an application-wide event filter (FramelessResizer.cpp:46-51) and its own comment reasons about instances outliving close. It is parented to the window (QObject(window)), Qt removes the filter on destruction, and ~FramelessResizer clears m_manualResizeActive and calls leaveEdgeZone() so a destroyed-mid-drag resizer cannot strand an override cursor. No static registry, no accumulation across reopens. Clean. ControlAvailabilityRegistry is likewise parented to the dialog (TxBandDialog.cpp:141) and pruneDead()/QPointer entries cover widget death, so nothing dangles.
  • Stale-session writes. RadioModel::requestTxBand compares the captured session against controlSession() and returns Unsupported (RadioModel_Setup.cpp:196), so an instance that survived a reconnect cannot write to the new session. Pre-existing protection, unchanged by this PR.
  • Theme resolution. canon/txBand has no scope entry in default-dark.json or default-light.json — neither does canon/radioSetup, canon/aetherRx or any sibling; resolution falls through to the registered canon scope, and the sheet's {{color.canon.*}} tokens are root tokens present in both theme files. Consistent with every other canon window, not a gap.
  • Stylesheet specificity, since the comment at TxBandDialog.cpp:25-29 stakes a claim on it. QWidget#canonBody QWidget { background: transparent; } vs the cell rules: [txBandCell="value"] adds an attribute and outranks it; QLineEdit ties on specificity (one id + one type) and wins by appearing later; canonIndicatorRules() is appended last so its ::indicator rules win ties, and it degrades to Qt's own indicators by returning {} if the cache write fails. The reasoning in the comment is sound.
  • Migration shape. restoreGeometryFromSettings (CanonWindow.cpp:153-181) reads the legacy base64 form only when the value does not split into four comma-separated ints, and the next save rewrites it as x,y,w,h — a one-shot claim-and-replace, not a perpetual legacy fallback, which is what AGENTS.md asks for. TxBandDialogGeometry stays a flat key, which is allowed: it is app-global window geometry, not per-radio state.
  • The test's own teeth. check(dialog.windowType() == Qt::Window && ...FramelessWindowHint) does fail under Kind::Dialog, since CanonWindow.cpp:49 picks Qt::Dialog there — the stated mutation holds. The restore check writes the settings value before show(), so showEvent's one-shot restore sees it, and check(restored.height() != chosen.height()) fails safe rather than passing vacuously. saved.endsWith(",w,h") cannot pass on an empty value. Comparing height rather than width is consistent with the code: the offscreen screen is 800 px and the table's minimum is now 816, so a restored width is clamped.
  • CMake contract. The target is already in AETHER_SETTINGS_CONSUMERS (tests.cmake:9751), which it now needs since the test calls AppSettings::instance().load(), and it isolates through TestSettingsProfile.h.
  • Socket tests: none added, modified or removed. No QTcpServer/QUdpSocket/bind/listen/connectToHost/fake-peer anywhere in the changed test, and check(f.commands.isEmpty(), ...) asserts the new path sends nothing.
  • Security audit (required by the harness). Ran the security-audit subagent over the listed files. Every one of the 114 CodeGuard findings is a false positive, and all but two sit in files this PR does not touch — the harness list is main's tip, not the diff. The two that do intersect: CS-VLN-CMDI-AMP at MainWindow_Menus.cpp:851,859 is C++ address-of (&QAction::triggered, &m_radioModel) with no QProcess in the file, and CS-VLN-SSRF-HEX at tune_output_dialog_test.cpp:143 is the Flex status code 0x5000002d, not an IP. TxBandDialog.cpp's only untrusted input is QLineEdit::text().toInt(), guarded by the validity flag and qBound(0, parsed, 100) (lines 164-168, 178-181). Separately, the audit surfaced a pre-existing item in untouched code — FirmwareStager.cpp:554 skips MD5 verification of a downloaded installer entirely when the vendor hash fetch fails, rather than failing closed. Out of scope here; worth its own issue.

I could not verify any of this at runtime — no build, no test run, no GUI. Every finding and every clean result above is reasoned from the code in the head checkout, plus CI status. Specifically unverifiable: the contrast table, the radio_setup_operations_test positional-index claim, the 2 px "RCA off/TUNE" clip the polish ordering fixes, the 780→816 px measurement, and the visual result in either theme. Those rest on your local runs and the screenshot.

Recommendation

Needs maintainer decision — and the decision is the scope, not the code. The implementation is careful: it keeps every object name, accessible name and wire effect, routes the sheet through ThemeManager so a theme change reaches it, carries the saved geometry forward through a one-shot migration, and the one dropped helper call (trackPersistentDialog) is provably complete. I could not find a correctness defect. But #6734 is explicit that the full CanonWindow move is not on #6239's approved lists, and merging this PR is the pick — if the maintainer prefers option 1 (colours to color.canon.*, window chrome unchanged), this PR is the wrong shape and the three style-doc hunks would need to come back out.

Concrete next step for the maintainer: rule on #6734's option 1 vs option 2. If option 2, the only things I'd ask of the author first are the two nits — note in the test plan that tune_output_dialog_test is not in a PR -R gate, so the 7 new checks are local-only evidence. Nice work on the mutation checks and on measuring the minimum-size change before anyone asked.


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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

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

  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2581
  • [HIGH] CS-VLN-SSRF-LOCALHOST — Vulnerability: CS-VLN-SSRF-LOCALHOST in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:4221
  • [MEDIUM] CS-PII-EMAIL — PII detected: email in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:3879
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:1124
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2581
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2627
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2661
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2758
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:2798
  • [LOW] CS-PII-IP — PII detected: ip_address in docs/user/screens.json /tmp/aetherclaude/pr-6735/docs/user/screens.json:4221
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/FirmwareCurrency.h /tmp/aetherclaude/pr-6735/src/core/FirmwareCurrency.h:54
  • [HIGH] CS-PII-BANK — PII detected: bank_account in src/core/FirmwareStager.cpp /tmp/aetherclaude/pr-6735/src/core/FirmwareStager.cpp:165
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/FirmwareStager.h /tmp/aetherclaude/pr-6735/src/core/FirmwareStager.h:26
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/FirmwareStager.h /tmp/aetherclaude/pr-6735/src/core/FirmwareStager.h:47
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/core/FirmwareStager.h /tmp/aetherclaude/pr-6735/src/core/FirmwareStager.h:120
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/FirmwareReleasesDialog.cpp /tmp/aetherclaude/pr-6735/src/gui/FirmwareReleasesDialog.cpp:89
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/FirmwareReleasesDialog.cpp /tmp/aetherclaude/pr-6735/src/gui/FirmwareReleasesDialog.cpp:91
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:9721
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:9833
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:9955
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:9956
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:9957
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:6221
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:6300
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:521
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:3577
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:519
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1219
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1702
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1790
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1867
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1909
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:1944
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:2240
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:3510
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:3610
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:6163
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:8040
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:8114
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6735/src/gui/MainWindow.cpp:8199

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: $17.0758 · model: claude-opus-5

@ten9876

ten9876 commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Coordination note from #6736, which is landing now.

#6736 adds CanonMessageBox (src/gui/CanonMessageBox.{h,cpp}), a shared canon replacement for QMessageBox, used by the CTR2 window's questions and notices.

TX Band Settings raises no message boxes today, so nothing in this PR needs to change for it. Two notes:

…rinciple VIII.

The canon body sheet clears every descendant to transparent so the canon
ground shows through, and Qt parents a field's standard right-click menu
(Undo/Cut/Copy/Paste/Select All) to the field, so the rule reached it: the
menu on the RF and Tune power fields drew no background at all. The sheet now
gives QMenu its canon panel back (raised, lineHi border, cyan selection,
unavailable for disabled items, line separators).

tune_output_dialog_test gains a check that the menu is parented inside the
canon body (control) and draws an opaque background; it fails without the
QMenu rules.

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

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review of #6735 at head 078b162a

Maintainer pick on #6734: option 2, the full CanonWindow workspace move this PR implements. @ten9876 has ruled on the scope, so the "Needs maintainer decision" item in the earlier reviews is settled. The style-doc lines that list TX Band Settings among the windows the maintainer has moved are now accurate.

Issue fit. #6734's option 2 is fully implemented:

  • Kind::Workspace, with the canon header and ground, the corner ✕, Esc and ⌘W to close, and FramelessResizer;
  • the canon/txBand theme scope;
  • geometry under the existing TxBandDialogGeometry key;
  • every colour is now a color.canon.* token;
  • the check boxes use the canon indicators;
  • the table, the object names, the accessible names and the requests sent to the radio are unchanged.

The third commit (078b162a, the right-click menu fix) came after both earlier reviews, so this review looked hardest at that commit.

Scope

File Change Claimed Verdict
src/gui/TxBandDialog.{h,cpp} Moves to CanonWindow::Kind::Workspace; one token sheet keyed on txBandCell; canon indicators; header polish before measuring; QMenu panel rules Yes In scope
src/gui/MainWindow_Menus.cpp:856-866 Constructs the window directly instead of calling showOrRaisePersistent(); the isConnected() guard is kept Yes In scope, and required by the base-class change
tests/tune_output_dialog_test.cpp, tests/tests.cmake New canonWorkspace() and rightClickMenuIsOpaque(); the clip check now resizes to minimumSize(); PersistentDialog.cpp and FramelessWindowTitleBar.cpp dropped from the target Yes In scope
docs/style/*.md (3), docs/user/docs/radio-setup.md List the window and its scope; a paragraph on moving, resizing and closing it Yes In scope

Everything in the diff is explained by #6734. All three commits were authored today. The removed lines are only the old styles, their per-widget wrappers, the PersistentDialog base and its open helper. No connect, guard, object name or request path was removed. There is no new settings key, no protocol surface and no CHANGELOG.md entry.

Blockers

None.

Nits (non-blocking)

  1. On Linux, the header-clip check passes with the fix reverted. TxBandDialog.cpp:147-153 and tests/tune_output_dialog_test.cpp:113-121. I restored the original order (unparented grid, setMinimumSize(sizeHint()) before addWidget) on Linux (Qt 6.12, offscreen, Adwaita Sans), and every check still passed. The "RCA off/TUNE" label measures 101 px before polish and 80 px after. The default font there is wider than the sheet's bold 10 px, so measuring too early over-sizes the label instead of clipping it. The mutation evidence in the body (83 px < 85 px) is macOS-only, so the Full Suite on Linux won't catch a regression of the polish order. The fix itself is still correct: it is what makes the minimum follow the sheet's font on every platform. Consider a check that doesn't depend on which font is wider. I tried label->minimumSize() == label->sizeHint() and it doesn't work, because QLabel caches its size hint across the polish (both read 101 under the mutation).
  2. "Constructing the table sends no commands" doesn't guard the reorder that #6734's triage named. TxBandDialog.cpp:217-222. I moved chk->setChecked(cb.val) after the toggled connect, and both tune_output_dialog_test and radio_setup_operations_test still passed. The fixture's bands report every bandset flag false, so setChecked(false) on an unchecked box emits nothing. Adding tx1_enabled=1 to Fixture::current()'s interlock band status still didn't make the reorder fail, so the fixture needs a field that txBandSettings() actually picks up. The gap predates this PR, but the body cites this check as the guard, so please don't count it as one.
  3. The header shows a stale profile name (TxBandDialog.cpp:233, makeCanonHeader(windowTitle())). The automated pass found this. The title captures activeProfile() once, at construction. Switch the TX profile while the window is open and the 17 px heading still names the old one. The old title bar had the same problem, but the heading makes it prominent. On the demo radio it reads "TX Band Settings (Current TX Profile: )". Worth a follow-up issue, not this PR.
  4. Screen-reader names. Only the RCA off/TUNE box has an accessible name. The RF and Tune power fields and the eight bandset boxes have none (TxBandDialog.cpp:179-222). This predates the PR and check_a11y.py doesn't flag it, but these controls are in the constructor this PR rewrites, and #4896 is open. A follow-up, not a blocker.
  5. The CI green is from an older main. The PR's runs were created at 19:01 UTC, and main has moved 8 commits since (tip 3b4931f08, 22:26 UTC). git merge-tree against current main is clean, and none of those commits touches a file in this diff except one unrelated hunk in MainWindow_Menus.cpp (showUpdatesWindow). I have no reason to think the result changes; noting it because strict: false won't rerun it.

Automated pass: findings I checked and dropped:

  • The blanket QWidget#canonBody QWidget { background: transparent } will need a special case for every new popup type. That is the house pattern: ConnectionPanel.cpp's kConnectionStyle does the same and adds QMenu back.
  • The centre-pixel test can land on a glyph. Deleting the QMenu rules does fail it here.
  • Restored height clamped by the 600 px offscreen screen. The chosen height is 560 today, so not yet.
  • Downgrades can't read the new geometry format. Already disclosed in the earlier review.
  • The open block duplicates other canon windows. That is the same pattern the other canon windows use.
  • Taller rows. Speculative, and the author's FLEX-8600 screenshot shows the full table.

Verified vs. read

  • Built at the head on Linux (Qt 6.12, shared core): AetherSDR, tune_output_dialog_test and radio_setup_operations_test. Both tests pass, and all 10 new checks pass.
  • Mutations, with the source restored between runs:
    • deleting the QMenu rules fails "a field's right-click menu has an opaque background";
    • Kind::Dialog fails "it is a frameless workspace window";
    • the pre-polish order and the setChecked reorder pass, as described in nits 1 and 2.
  • Static gates, base vs. head: identical. Engine boundary is at 101 findings, the manifest is up to date, there are 70 capability booleans, and 125 command-plane entries. The colour audit matches the body: unique colours +0, references −11, setStyleSheet −8. check_a11y.py reports no findings.
  • Bridge, on the demo radio (offscreen, isolated settings, DEMO-0001, TX off): invoke "TX Band Settings..." trigger opens a visible AetherSDR::TxBandDialog. It is 761×450 with only the header row, because the demo reports no TX bands. grab shows the canon header, ground, ✕ and a cyan header row on raised cells.
  • Read, not run: the Esc / ⌘W / ✕ close paths and the drop of trackPersistentDialog. The earlier review covered both; I re-read them and agree.
  • No socket tests were added, modified or removed.

@ten9876 ten9876 self-assigned this Oct 25, 2026
…rofile, session or band rebuilds the table, refused changes show the radio's value. Principle II.

The open table belongs to one TX profile, one radio session and one set of
bands (by id and name). When any of them changes it is rebuilt; the heading
and window title name the profile, focus returns to the same control, and
text being typed is dropped rather than written. Esc and Ctrl+W hide the
window, and showing it again builds a fresh table, as closing with the corner
button and reopening does.

A value the radio reports for a band reaches the open table without being
written back. Only a value the radio changed since it was last shown is
written, so a click or entry the radio has not yet echoed stays as the
operator set it, and a field with uncommitted typing keeps it. A hidden
window does not refresh or rebuild.

A TX band write now carries a completion, as a transverter write does:
FlexBackend passes it to the setup command sink, and RadioModel fences it to
the caller's session and context and refuses it at once when the write is not
dispatched. A check box or power field the radio refuses, or that is never
sent, shows the radio's value again.

The RF and tune power fields and the eight bandset check boxes get accessible
names that give the column and the band. The eight columns, their headers and
their names come from one table that building and refreshing both read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ten9876 ten9876 changed the title feat(gui): TX Band Settings is a CanonWindow workspace re-skinned to the canon palette. Principle VIII. feat(gui): TX Band Settings is a CanonWindow workspace that follows the radio's TX profile, band values and answers. Principle II. Oct 25, 2026
…both the multiFLEX Dashboard and TX Band Settings.

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

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at 6a364bbb2

Maintainer rulings (@ten9876, through /pr-land):

  • #6734: option 2, the full CanonWindow move.
  • The open table is rebuilt when the TX profile changes.
  • Reopening the window after Esc or ⌘W builds a fresh table.
  • A TX band write gets a reply path, so a change the radio refuses shows the radio's value again.

What the landing commit 0a662a804 did with each finding

From my review on 078b162a, the earlier reviews, and two automated passes over the landing commit:

  1. On Linux, the header-clip check passed with the fix reverted. tests/tune_output_dialog_test.cpp now also checks that a header's minimum width is its text in the font the sheet gives it. Measuring before polish fails that check on Linux (101 px against 80 px), and the existing clip check catches the macOS direction.
  2. The check "constructing the table sends no commands" couldn't see a bandset write. That fixture has no setup sink. radio_setup_operations_test now builds the table with HWALC confirmed on, asserts the box is shown checked, and asserts nothing was written. Connecting a box before setting its value fails it.
  3. The header showed a stale profile (ruling: rebuild on profile change). A new TX profile, a new radio session, or a band added or renamed rebuilds the table. The heading and window title name the profile, focus returns to the same control, and text being typed is dropped rather than written. Values the radio reports reach the open table, and only values the radio changed since they were last shown are written. So a click or entry the radio hasn't echoed stays as the operator set it, and a field with uncommitted typing keeps it.
  4. Esc reused the old window while ✕ rebuilt it (ruling: rebuild on every open). TxBandDialog::showEvent rebuilds after a non-spontaneous hide, so this covers every way of opening it. The menu handler just shows and raises the window.
  5. A refused write left the widget wrong (ruling: add a reply path).
    • IRadioBackend::requestTxBand and FlexBackend::requestTxBand take a RadioSetupCompletion, as requestTransverter already does. FlexBackend passes it to the setup sink.
    • RadioModel::requestTxBand fences it with fencedSetupCompletion and answers Refused at once when the write isn't dispatched.
    • In the dialog, a refused or failed reply puts the radio's value back in the check box or power field.
    • Engine-boundary findings for the touched files are identical to main; the command-plane count stays 125.
  6. Screen-reader names. The RF and tune power fields and the eight bandset boxes are named by column and band, for example "RCA TX1, band 40". The eight columns, their headers and their names come from one table that both building and refreshing read.
  7. Docs. The radio-setup.md paragraph sits in the TX Band Settings section and describes what the table does. The troubleshooting note no longer tells users to reopen TX Band Settings after reconnecting.
  8. Merge. #6768 changed the same canon window lists. The merge 6a364bbb2 keeps both entries; the style docs differ from main only by TX Band Settings and canon/txBand.

Refuted from the automated passes:

  • Filter the refresh by status object name. That would put Flex status object names in a gui/ file, which is wire text above the seam (#5554). The refresh compares a few integers and booleans per band, and a hidden window doesn't run it.
  • Store a RadioControlSession member. That needs a core/backends include in a gui/ header, a new touchpoint. A captured closure avoids it.
  • A hidden window still runs per-box suppression syncs. Those existed before this PR, and they're unchanged.
  • Blocking signals before deleting the table. With real focus, deleting a focused field doesn't emit editingFinished. The test that types into a focused field and then switches profile proves nothing is written, so the guard was dead code and was removed.

Evidence

Built on Linux x86_64 (Qt 6.12, shared core): AetherSDR, tune_output_dialog_test, radio_setup_operations_test and radio_setup_control_routing_test. All pass on the merged head:

100% tests passed out of 3

Each guard in the landing code was broken on purpose and its test went red, 18 mutants in all:

  • measuring before polish;
  • connecting a box before setting it;
  • no rebuild on a profile, session, band, band name or reshow;
  • no refresh;
  • no typing guard, or no signal blocker, on refresh;
  • writing every value instead of only changed ones;
  • no focus restore;
  • swapped columns;
  • FlexBackend dropping the completion, or RadioModel not forwarding it or not refusing an undispatched write;
  • the dialog ignoring a refusal, or reverting an accepted change.

Static checks were run locally on the merged tree, and all pass:

  • engine boundary (101 findings, the same as main), touchpoint manifest, seam probe table;
  • user-docs reference, test registration, WDSP links;
  • capability records (70), command plane (125), per-PR test gate;
  • network timeouts, QSettings, theme seed.

The colour audit against the base gives unique +0, references −11 and setStyleSheet −8. check_a11y.py reports no findings.

Demo radio (offscreen, isolated settings, DEMO-0001, TX off):

  • Settings → TX Band Settings... opens one window, and triggering it again raises the same one.
  • ✕ deletes it, and reopening builds a new one.
  • The headers render as before.

Not verified here: live FLEX behaviour, which this pass did not touch. On real firmware, a radio that refuses a bandset with an error reply now drives the revert; the test backend's Failed reply stands in for that.

@ten9876
ten9876 merged commit cfe8abb into aethersdr:main Oct 25, 2026
11 checks passed
ten9876 added a commit to w5jwp/AetherSDR that referenced this pull request Oct 25, 2026
… RC-28 entries. Principle VIII.

aethersdr#6735 (TX Band Settings) added an entry to the same canon-window, geometry
and theme-scope lists in docs/style/aethersdr-style-guide.md,
dialog-patterns.md and theme-style-guide.md; the resolution keeps both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
w5jwp added a commit to w5jwp/AetherSDR that referenced this pull request Oct 25, 2026
…the import default and a small legacy rectangle. Principle VIII.

The window's 760x420 minimum was an explicit floor measured on macOS
fonts. An explicit minimum overrides the layout's own, so on a font with
wider metrics the seven buttons would clip at the minimum, and the
per-button fit test would go red with nothing regressed. The minimum is
now 760x420 or the layout's minimum size hint, whichever is larger,
recomputed on a theme change. The fit test replaces its fixed-number
check with the layout-relative one the review suggested, and runs it
under the application font and a 160% letter-spaced font (layout needs
929 px natively, 949 px offscreen; the 760 floor alone fails that row).

New cases:
- a 640x380 legacy base64 rectangle opens at the 760x420 minimum
  (Qt's restoreGeometry() clamps to it; the base64 branch has no
  expandedTo of its own);
- Return in the Import box takes Skip existing: the existing net keeps
  its name and only the new one is added (Qt's own file picker via
  AA_DontUseNativeDialogs);
- the Ctrl+W / Cmd+W row activates the window first, since the close
  shortcut is window-scoped; a desktop session that refuses the
  activation skips the row instead of failing it.

Also: the disabled-row ink is resolved once per fill, not per row; the
lifetime audit row goes back to its baseline line (173); the
CanonWindow hideEvent comment and dialog-patterns row say that reject()
closes the window without a closeEvent(); and the two Net Scheduler
screenshot recipes target the editor by its objectName
(netEditorDialog), with alt text naming the canon header and corner
close button.

Rebased onto main (aethersdr#6735, aethersdr#6745, aethersdr#6768 add windows to the same
style-doc lists; every entry is kept). The touchpoint manifest counts
this branch's ThemeManager include on top of main's: 184 -> 185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

TX Band Settings on CanonWindow (a #6239 persistent tool dialog)

2 participants