Repository navigation
feat(gui): the multiFLEX Dashboard is a canon dialog that stays open beside the main window. Principle VIII. - #6768
Conversation
…beside the main window. Principle VIII. The dashboard moves from PersistentDialog to CanonWindow (Kind::Dialog), with the maintainer's choices: - Non-modal: MainWindow creates it once (QPointer, WA_DeleteOnClose) and raises it on a second open, instead of exec(). A radio without multi-client sessions closes it, so it never drives one. - Canon header "multiFLEX Dashboard" replaces the in-body "multiFLEX Stations" title; the corner close button, Escape and Ctrl+W replace the bottom Close button. - Every color is a canon token under canon/multiFlex. The Enabled / Disabled button keeps green and red through color.toggle.success.* and color.accent.danger; the PTT tick is color.accent.success, our own station color.canon.cyan, Disconnect color.accent.danger. - Return and Enter no longer click the default button (setReturnClicksDefault(false)): with a station row current, Return in the table clicked the Enabled / Disabled toggle and turned multiFLEX off. - The PTT tick is U+2713, which Inter carries: U+2714 fell back to Noto Color Emoji on Linux and drew a fixed gray glyph whatever its color. Size and position still persist under MultiFlexDialogGeometry, so saved geometry carries over; FramelessResizer makes it resizable from every edge. tests/multiflex_dashboard_window_test pins the canon chrome, the geometry carry-over, non-modality, the button's colors in both default themes, the table's tick, station and Disconnect colors, and Return on a station row. Breaking the repolish, the geometry key, the off-state color, the tick glyph, the modality or the Return guard each turns it red. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Reviewed head 73486c660608095d7936324942e26b7792b5ffe0.
Issue fit: yes. No closing issue is linked; reviewed against the stated intent and RFC #6226, including its approval and per-surface migration ruling. This converts the multiFLEX Dashboard to a non-modal canon dialog, preserves its geometry key, replaces hard-coded colors, and prevents Return in the table from activating the multiFLEX toggle. These are the explicitly disclosed choices of the maintainer/author, rather than preferences bundled into an unrelated fix. No further maintainer design decision is needed for this scope.
Scope
| Files | Change / disclosed? | Verdict |
|---|---|---|
src/gui/MultiFlexDialog.{cpp,h} |
CanonWindow, token styles, resizer, geometry, Return guard and tick glyph; yes | In scope |
src/gui/MainWindow.{cpp,h} |
One reusable non-modal dashboard and closure on unsupported connected radios; yes | In scope |
tests/multiflex_dashboard_window_test.cpp, tests/tests.cmake |
Registered offscreen, socket-free regression coverage; yes | In scope; registration-maintenance nit below |
docs/user/docs/multi-flex.md, docs/user/screens.json |
Window interaction, control position and alt text; yes | In scope; unchanged screenshot explicitly disclosed as stale |
docs/style/{dialog-patterns,aethersdr-style-guide,theme-style-guide}.md |
Canon adoption and scope lists; yes | In scope; stale reference nit below |
Everything in the diff is explained by the stated migration. One commit authored October 24; no unrelated files, new wire commands, settings keys, theme seed/default changes, or engine-boundary baseline edits. The existing screenshot and first-row selection behavior are explicitly excluded in the body.
Blockers
None found.
Non-blocking nits
-
Add the new test to the settings-consumer list —
tests/tests.cmake:8172–8186and9728. It directly uses AppSettings and TestSettingsProfile, butmultiflex_dashboard_window_testis absent fromAETHER_SETTINGS_CONSUMERS.docs/agents/tests-ci.mdsays: “A test that touchesAppSettingsalso needs its target name in theAETHER_SETTINGS_CONSUMERSlist.” Insert this entry in that list:multiflex_dashboard_window_testThis is maintenance of the documented direct SQLite-link declaration; the target currently links and passes in the review configuration, so I am not treating it as an observed build blocker.
-
Remove the old reference example —
docs/style/dialog-patterns.md:228. MultiFlexDialog is still listed under “Existing dialogs that follow this pattern” for PersistentDialog, immediately before the new CanonWindow exception that now correctly names it. Delete this bullet from the former list:
src/gui/MultiFlexDialog.{h,cpp}
What I verified and tried to break
-
Built the actual desktop app plus
multiflex_dashboard_window_testandcanon_window_testat this head, in an isolated scratch worktree using the lean Linux shared-core review configuration. Both registered CTests passed: 2/2, 0.23 seconds. An inherited GTK platform-theme setting initially tried to open the unavailable display; clearingQT_QPA_PLATFORMTHEMEand selecting Fusion allowed the intended offscreen runs. The unchanged CanonWindow test showed the same environment issue; no PR code change was needed. -
Independently changed
setReturnClicksDefault(false)back to true in a temporary source copy and linked it into the same dashboard test.returnInTheTableDoesNotToggleMultiFlexfailed with actualmodel.multiFlexEnabled() == 0, expected1. The delivered test detects this accidental toggle. I did not independently rerun all six mutations listed in the body. -
Additional socket-free probes fed legacy base64 geometry into the existing key and changed Dark -> Light -> Dark with a populated, disconnected model. The legacy value was read (Qt constrained the oversized width to the offscreen screen); all three live theme transitions retained two rows and the PTT success-token foreground. The supplied tests cover per-item colors, rendered tick/button colors, self-row exclusion, disconnect signaling/disable, and Return/Enter separately. These prove presentation and injected-model behavior, not convergence with Flex firmware.
-
Drove the exact head app through
/tmp/aethersdr-pr6768, identitypr6768-review, offscreen, with a fresh isolated settings store andAETHER_AUTOMATION_NO_TX=1. Initially confirmedconnected:false. Invoked the realmultiFLEX...menu action twice, inspected Dark and Light dashboard captures, closed and reopened it, then connected only toDEMO-0001and confirmed modelAetherSDR Demo. Extracted bridge evidence:{"afterTwoOpens":{"dashboardCount":1,"visible":1}, "afterClose":{"visible":0}, "afterReopen":{"dashboardCount":1,"visible":1}, "afterDemoConnect":{"dashboardCount":0,"visible":0}, "afterDemoDisconnectReopen":{"dashboardCount":1,"visible":1}}The demo has no multi-client sessions, so the unsupported-radio closure is exercised. Disconnected and closed this owned review instance; it exited successfully. No live radio was connected and no transmit action was used. Populated live multi-client sessions, real client eviction, native platform placement/resizing and Windows/macOS rendering remain unverified. The body already discloses its populated-table hardware limitation and stale screenshot.
-
All four engine gates pass on base and head with identical outputs; no touchpoint growth or retagging. Test registration, theme seed, generated references and accessibility checks also pass (four touched GUI files, zero a11y findings). The dedicated automated code-review skill was unavailable; this was a manual adversarial pass with executed tests and bridge checks.
At the final status check, Static checks, the docs-site build and completed auxiliary checks passed; Linux, Windows and macOS build jobs were still running. Pull-request CI/Docs/Static runs were created at 23:23:51 UTC; current main remains 3b4931f087ca0f9b4976e3720af51cfab6ab6aa4 from 22:26:36 UTC, so no newer-main gap was present. This is not a claim that the full suite or native-platform behavior has been verified.
Recommendation: Approve with nits. The intended migration and Return protection survived the focused tests, mutation and real-app lifecycle checks. Clean up the two small omissions and let the remaining CI builds finish. Posted as COMMENT under the review workflow; approval remains the operator's call.
…stentDialog list drops the dashboard. Principle VIII. The #6768 review's two nits: - multiflex_dashboard_window_test uses AppSettings and TestSettingsProfile, so it joins AETHER_SETTINGS_CONSUMERS (docs/agents/tests-ci.md). - docs/style/dialog-patterns.md still listed MultiFlexDialog among the PersistentDialog reference dialogs; it is a CanonWindow now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Both nits from the review of
No code changed beyond these two lines. 🤖 Generated with Claude Code |
…olors on selection, and shares the canon table rules with SpotHub. Principle VIII. From the landing pass on #6768, with the maintainer's calls: - Losing the radio closes the dashboard (connectionStateChanged(false)), so its toggle, PTT and Disconnect buttons never act on a radio that is gone and no pending disconnect outlives the session. - A selected row keeps its own item colors: a delegate keeps the green PTT tick and our cyan station on the selection band instead of Qt's highlighted-text color. - Hovering the Enabled / Disabled toggle brightens its border to color.canon.aqua in both states; its on/off rules had outranked the generic hover rule. - A theme change recolors the tick and station in place (applyItemColors()) instead of rebuilding every row and Disconnect button. - The canon table rules move to canonTableRules() in CanonTableStyle.h. SpotHub and the dashboard both use it and append their own sizes; SpotHub keeps its selection ink. An offscreen render of SpotHub's table with the old and new sheets matches pixel for pixel in both default themes. multiflex_dashboard_window_test gains a slot for each: selected-row colors, recolor without rebuild (same Disconnect button object), hover border and close on disconnect. Removing the delegate, the in-place recolor, the hover rule or the close each turns its slot red. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Issue fit
No linked issue; the PR is reviewed against its own stated intent and RFC #6226. It does what it says: MultiFlexDialog now derives from CanonWindow (Kind::Dialog), the call site at src/gui/MainWindow.cpp:753-772 becomes the canon show/raise pattern, the sheet is token-resolved under canon/multiFlex, FramelessResizer::install(this) is present (dialog-patterns.md:275-276 asks for it), and geometry keeps the MultiFlexDialogGeometry key. Both entry points route through the one slot — the Settings menu (MainWindow_Menus.cpp:846) and the title-bar multiFLEX button (MainWindow.cpp:5460) — so there is no sibling call site left on exec(). The maintainer pick is the author's own, so the #6239 question other PRs in this wave carried does not arise here — maintainer-authored under an approved RFC and its tracker, so GOVERNANCE.md owes no separate issue for it.
Scope
| File / group | What it changes | Claimed in the body? | Verdict |
|---|---|---|---|
src/gui/MultiFlexDialog.{h,cpp} |
PersistentDialog → CanonWindow, canon header, token sheet, setReturnClicksDefault(false), resizer |
Yes | In scope |
src/gui/MainWindow.{h,cpp} |
QPointer member + fwd-decl; exec() → show/raise; capability-driven close() |
Yes | In scope |
MultiFlexDialog.cpp:237 |
PTT glyph U+2714 → U+2713 | Yes (with the Inter/Noto reason) | In scope |
MultiFlexDialog.cpp:142 |
drops the m_pttLabel text.secondary sheet (now canon.inkSoft via the body QLabel rule) |
Covered by "every color is now a token" | In scope |
tests/multiflex_dashboard_window_test.cpp, tests/tests.cmake |
new registered test + AETHER_SETTINGS_CONSUMERS entry |
Yes | In scope |
docs/style/*.md, docs/user/docs/multi-flex.md, docs/user/screens.json |
canon lists, scope list, window behavior, alt text | Yes | In scope |
Everything in the diff is explained by the stated change. No CHANGELOG.md entry (correct). No new settings key, protocol verb, capability field or public surface: the geometry key is the pre-existing one, and check_capability_records / check_command_plane have nothing to see. No removed guard — the deleted Close button is replaced by the canon corner button, Escape and Ctrl+W, and the deleted hard-coded sheet is the point of the change.
Blockers
None.
Nits (all non-blocking)
-
src/gui/MainWindow.cpp:759-767— the comment andWA_DeleteOnClosedisagree, and one guard is the cost:// A canon dialog that stays open beside the main window: created once, // raised if it is already open. if (!m_multiFlexDialog) { auto* dlg = new MultiFlexDialog(&m_radioModel, this); dlg->setAttribute(Qt::WA_DeleteOnClose);
Since Qt 6.3
QDialog::done()routes throughQDialogPrivate::close(), which runs the close machinery — so Escape / Ctrl+W destroys the instance and the next open builds a new one; "created once" is true per open-cycle only. This does not contradictCanonWindow.cpp:128-130("Escape and Ctrl+W go throughQDialog::reject(), which hides the window without a close event"): noQCloseEventreaches acloseEvent()override, and the object is still deleted. Two different claims; the comment only makes the first. The pattern itself is the house one —MainWindow.cpp:3242,3566,4640,4669andMainWindow_Menus.cpp:667(SpotHub) all pairWA_DeleteOnClosewith aQPointer, andMainWindow.cpp:3426spells the contract out.The reachable consequence is
MultiFlexDialog.h:34-39's own promise:m_pendingDisconnectskeeps a row disabled "so a second click can't queue a duplicate disconnect for a handle already on its way out". Close the dashboard while a disconnect is in flight, reopen before the radio evicts the client, and the Disconnect button is enabled again for that handle. Not a regression — the oldexec()on a stack object lost the set too — but this dialog is the one in that group that keeps state worth carrying, and some canon surfaces do keep their instance (FreeDvReporterDialogis held in a rawMainWindowpointer;CanonWindowsets the attribute nowhere itself). Dropping the attribute here would make the comment literally true and carry the guard across opens. -
Contrast, resolved from the bundled themes under the
canonscope (manual pass —check_a11yhas no contrast rule,docs/a11y.md:143is the 4.5:1 / 3:1 floor):Pair Dark Light Enabled, on: toggle.success.foreground.checkedon…background.checked4.16 3.78 Disabled, off / Disconnect: accent.dangeroncanon.control4.84 3.95 Disconnecting: button.danger.*.disabledpair3.13 2.48 (disabled — exempt) PTT tick accent.successoncanon.nested9.52 3.92 Table canon.inkoncanon.nested15.50 11.49 Header canon.cyanoncanon.raised12.43 7.06 The Enabled-on button is 13px bold, so 4.5:1 applies, and in dark it is down from 5.74 (old
#40ff80on#1a6030). But the pair is the ratified canon toggle idiom, already used byDxClusterDialog,RadioSetupDialogandFlexControlDialog, so it is a shared-token residue and not this PR's invention — and against it, the old sheet painted a fixed dark dialog in Light at all, which this fixes. Named, not charged to the PR. -
tests/multiflex_dashboard_window_test.cpp:6-10— the header claims "geometry saved before the migration carries over", but the slot only round-trips the newx,y,w,hform. What makes the claim true isCanonWindow.cpp:181, therestoreGeometry(QByteArray::fromBase64(...))fallback for aPersistentDialog-era value. One extra assertion — pre-seedMultiFlexDialogGeometrywith a base64saveGeometry()blob, show, and check the size came back — would pin the migration itself rather than the format that replaces it. -
docs/user/docs/multi-flex.md:44/docs/user/screens.json:2242— the body discloses the recapture as out of scope, which is fair (the harness needs a multi-client radio), but the shipped page now describes a round corner close button whiledocs/user/static/img/screens/multiflex-dashboard.pngstill shows the retired "multiFLEX Stations" header and the bottom-right Close button. Sighted and screen-reader readers get different windows until it is recaptured; worth a follow-up issue so it does not sit on the page indefinitely. -
#6239's Picked list is not updated. The three style docs are the only registry of canon windows (no test or generator enumerates them), so the tracker drifting from the docs is the one place a future reviewer looks first.
Security audit
Ran the required security-audit subagent over docs/user/screens.json, src/gui/MainWindow.{cpp,h} and the new test. No confirmed findings. All 52 CodeGuard hits are pre-existing code swept in because the 12k-line MainWindow.cpp is in the changed set; the audit pinned each to an identical line on main (50000053 → :5535 is a Flex file-server error code, not a bank account; /proc/self/status → :6300 is a constant read-only path; CS-INJ-role_override → :11677 is the word "override" in the comment // CNN override:; the 17 CMDI-BACKTICK hits are Markdown backticks inside C++ comments naming firmware verbs, with no shell anywhere near them; 127.0.0.1 / 224.0.0.1 in screens.json and MainWindow.cpp:6936 are documentation prose and the WSJT-X multicast default). The one in-PR trigger, CS-VLN-SSRF-HEX on QStringLiteral("client 0x7594C952") at multiflex_dashboard_window_test.cpp:191,237, is a Flex client handle, not a hex-encoded host. The only in-PR filesystem write is window.grab().save(...) at :252-254, gated on the developer's own MULTIFLEX_TEST_GRAB_DIR. Nothing in the diff crosses a trust boundary.
What I tried to break
- Lifetime.
WA_DeleteOnCloseon a parented,QPointer-held dialog: Escape →QDialog::done()→close()fireshideEvent, which flushes geometry beforedeleteLater, so the save is not lost to the delete; the capability path atMainWindow.cpp:8266is guarded by the sameQPointerin the sameif; the Disconnect lambda'semitis its last statement, so a handler that destroyed the dialog could not return into a deadthis; and the attribute is set at the call site only, so the five stack-allocatedMultiFlexDialogs in the new test are not double-freed (the trap #6759 hit). No finding. - The capability gate.
connected && !caps.hasMultiClientSessionscloses the window, but plain disconnect does not — I expected a stale station list to survive there. It does not:RadioModel.cpp:8473,8489clearsm_clientInfoMapand emitsotherClientsChanged, so the open dashboard refreshes to an empty table. The Enabled toggle stays clickable while disconnected, which matches the menu action's ownsetVisible(!connected || …). No finding. - The test, against the unfixed code.
returnInTheTableDoesNotToggleMultiFlexwould be vacuous if the table swallowed Return — it does not:MultiFlexDialog.cpp:135setsQAbstractItemView::NoEditTriggers, so Return propagates toCanonWindow::keyPressEventand onlysetReturnClicksDefault(false)stops the toggle. The body's mutation list (remove the repolish, rename the key, draw the off state green, restore U+2714,setModal(true), removesetReturnClicksDefault(false)) names a red for each of the five slots. - The token sheet. All sixteen tokens the new sheet references exist in both bundled themes, so no
{{…}}resolves to nothing and no rule block is silently dropped; the bool property selector[multiFlexOn="true"]matches becauseQVariant(bool).toString()is"true"/"false", and theunpolish/polishpair runs on every flip. The body-level bareQLabelrule does not steal the canon header title:makeCanonHeaderapplies its ownQLabel#canonHeaderTitlesheet to the header widget (CanonWindow.cpp:550-555), which wins on both proximity and specificity. - The CMake block. It names no
ThemeManager.cppand links onlyaethercore Qt6::Gui Qt6::Widgets Qt6::Test, where every other CanonWindow test compilesThemeManager.cpp/ThemeSeedGenerated.cppor linksaetherdesktop_support— those symbols live inaetherdesktop_support, notaethercore, so this looked like a link failure. It is not:aether_retrofit_tests()(tests/tests.cmake:10340-10344) linksaetherdesktop_supportinto any test target that linksaethercoreandQt6::Widgets, andaether_assert_tests_retrofitted()fails configure if it ever misses. The block is correct as written, and the compile lanes agree —build,check-macosandcheck-windowsare all green on the head72b934a(they arecancelledon73486c6, the superseded first commit). - "Table and header styling matches SpotHub's." True for the header:
DxClusterDialog.cpp:491-499(thecanon/spotHubsurface) carries the sameQHeaderView::sectiontokens —canon.cyanoncanon.raised,border: nonewith acanon.linebottom rule — differing only in padding and font size. - Could not check. Nothing was built or run here, and no GUI was driven, so every statement above is reasoned from the code in the PR head checkout; the pixel-probe slots (
pixelsNear(..., 6)on a 1px antialiased border, andother != 0demanding zero pixels of the opposite token) are the ones I would most expect to be platform- or font-sensitive, and only a real run settles them. The U+2714 → U+2713 rationale (Inter lacks U+2714, so Linux fell back to Noto Color Emoji and drew a fixed gray glyph) is a font-coverage claim I cannot verify headless, and the test's tick probe exercises whatever font the offscreen platform resolves, not Inter specifically. The contrast figures hold for the two bundled themes only — a user theme with nocanonscope resolves the base tokens from root and the table above does not apply.
Recommendation
Approve with nits. This is the most complete PR of the canon wave: the call-site conversion, the resizer, the geometry carry-over and the setReturnClicksDefault(false) fix are all present and each has a named mutation that reddens its test, and the body discloses its own gaps (the screenshot, the auto-selected first row, the untested populated table) rather than papering over them. All three compile lanes are green on the head. The concrete next step is a maintainer read of nit 1 (WA_DeleteOnClose versus the m_pendingDisconnects guard) and a follow-up issue for the screenshot; neither needs a round trip of changes to this branch. Thanks for the mutation-check table — it made the test-strength half of this review checkable instead of a guess.
🤖 aethersdr-agent · cost: $15.2395 · model: claude-opus-5
|
Cisco CodeGuard — static analysis of this PR (57 finding(s))
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: $15.3971 · model: claude-opus-5 |
…y-over, and the open path's comment states the per-open lifetime. Principle VIII. From aethersdr-agent's review of #6768: - geometrySavedByThePersistentDialogCarriesOver seeds MultiFlexDialogGeometry with a base64 saveGeometry() blob, the form PersistentDialog wrote, and checks the dashboard opens at that size. A corrupted blob falls back to the default 760x320 and turns it red. - MainWindow::showMultiFlexDialog's comment said "created once"; WA_DeleteOnClose destroys the dashboard on close, so it is created per open and raised while open. The maintainer kept the lifetime and asked for the comment to say so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Replies to the aethersdr-agent review (posted against
The CodeGuard findings in the other comment are all on unchanged lines (your security audit pinned each to the same line on 🤖 Generated with Claude Code |
…ntries. Principle VIII. aethersdr#6768 (multiFLEX Dashboard) and this branch each added an entry to the same canon-window 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>
…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>
Summary
Moves the multiFLEX Dashboard (Settings → multiFLEX...) from
PersistentDialogtoCanonWindow(Kind::Dialog, style guide RFC #6226). The maintainer chose how it looks and behaves:MainWindowcreates the dashboard once (QPointer,WA_DeleteOnClose) and raises it on a second open; it used to beexec(). It now stays open beside the main window. It belongs to one radio session: losing the radio closes it (connectionStateChanged(false)), and so does a radio connecting without multi-client sessions (applyCapabilitiesToUi()). Its controls therefore never act on a radio that is gone, and no pending disconnect outlives the session.canon/multiFlex:color.toggle.success.*andcolor.accent.danger.color.accent.success.color.canon.cyan.color.accent.danger, with thebutton.danger.*.disabledtokens while a disconnect is pending.canonTableRules()(src/gui/CanonTableStyle.h), which SpotHub now uses too. SpotHub appends its own sizes and selection ink. An offscreen render of SpotHub's table with the old and new sheets matches pixel for pixel in both default themes.color.canon.aqua.setReturnClicksDefault(false)). Before this change, with a station row current, Return in the table clicked the Enabled / Disabled toggle and sentmf_enable=0to the radio.Size and position still persist under
MultiFlexDialogGeometry, so saved geometry carries over.FramelessResizermakes the window resizable from every edge.Docs updated:
docs/user/docs/multi-flex.md: window behavior; Enabled / Disabled sits above the table, not below it.docs/user/screens.json: alt text.dialog-patterns.md,aethersdr-style-guide.mdandtheme-style-guide.md, including the new scope.Intent
After this merges, the multiFLEX Dashboard is a canon window like SpotHub and AetherControl. It is theme-correct in Light and stays open while you work, and Return can no longer turn multiFLEX off by accident.
Out of scope:
multiflex-dashboard.png. The screenshot needs a radio with multi-client sessions; recapture it after merge.Constitution principle honored
Principle VIII (Evidence Over Assertion): every behavior claimed here has a test that goes red when it is broken (below). Principle XI applies too: the demonstration is the CI re-run on
main.Test plan
tests/multiflex_dashboard_window_test, 12 of 12 passing. It pins:Qt::Dialog,canon/multiFlexscope, no Close button;saveGeometry()form that existing installs hold;setModal(true);setReturnClicksDefault(false);canon_window_test,theme_manager_test,theme_seed_test,aethercontrol_window_test.check_engine_boundary --strict,gen_touchpoint_manifest --check,check_capability_records --strict,check_command_plane --strict;check_test_registration,check_theme_seed,check_a11y(0 findings),gen_reference --check.embed_screenshots --checkflags 15 pages that this PR doesn't touch;multi-flex.mdis in step.client 0x…status into a bareRadioModel. The live FLEX-8600 wasn't used.Checklist
AppSettingscalls (geometry uses the existingMultiFlexDialogGeometrykey throughCanonWindow::setGeometryKey)MeterSmoother(no meters touched)docs/user,docs/style); noCHANGELOG.mdentry🤖 Generated with Claude Code