Repository navigation
feat(gui): the DVK panel on the AetherSDR style guide, in the title bar's vocabulary. Principle VIII. - #6251
Conversation
There was a problem hiding this comment.
Issue fit
Mostly yes. #6249 names four defects and a direction; the diff closes the first three cleanly and the fourth almost completely.
| #6249 requirement | Where | Verdict |
|---|---|---|
| 11 hard-coded colours + pre-canon tokens | src/gui/DvkPanel.cpp:24-88, one tracked template |
Closed — I extracted all 18 {{…}} tokens and all 18 resolve in both bundled themes (walked scopes.root.tokens, which mixes nested and dotted keys) |
"Boxes inside boxes" — QFrame { … } also matching its QLabels |
:146 property selector QFrame[dvkRole="slot"], plus border: none; background: transparent on the name/length rules |
Closed — the slot rule can no longer reach a QLabel, and the test asserts the per-widget sheets are gone (tests/dvk_panel_theme_test.cpp:111-112) |
| Red/green checked states; PLAY should read like MOX | :63-66 QPushButton#dvkPlay:checked → background.tx / tx.mox.*; REC/PREV → cyan active |
Closed |
| "Buttons that can't act stay enabled" | refreshTransport(), :447-459 |
Partially — see nit 1: STOP is enabled (and the only enabled control) when the radio reports DVK Disabled |
| Header modelled on a title-bar radio tab | :114-135 |
Closed |
| Slot menu uses the title bar's rounded menu | RoundedMenu.{h,cpp} extraction + DvkPanel.cpp:716-724 |
Closed |
Coverage layer is right: tests/dvk_panel_theme_test.cpp is a socket-free CTest driving a real DvkPanel from fw 4.2.20 status text through CommandParser::parseLine → DvkModel::applyStatus — no fake peer, no radio, no TX. No socket test is added or modified by this PR (I grepped the PR's own test source for QTcpServer/QUdpSocket/QLocalServer/bind/listen/connectToHost/Fake*: zero hits).
Scope
| File / group | Change | Claimed? | Verdict |
|---|---|---|---|
src/gui/DvkPanel.{h,cpp} |
Theming, property-driven states, transport enablement, status wording | Yes — the whole of #6249 | In scope |
src/gui/RoundedMenu.{h,cpp} (new) |
Verbatim lift of kRoundedMenuProperty / kHasChecksProperty / syncMenuCheckColumn / roundMenuTree out of TitleBar.cpp's anonymous namespace, plus the rules string |
Yes — the issue's "Menu" bullet; sharing requires the extraction | In scope |
src/gui/TitleBar.cpp |
Deletes the moved code, includes the header | Follows from the extraction | In scope |
CMakeLists.txt:1446, tests/tests.cmake (4 titlebar targets) |
RoundedMenu.cpp added |
Mechanical consequence | In scope |
tests/tests.cmake:3163-3176 |
New dvk_panel_theme_test |
Required for a behaviour change | In scope |
Everything in the diff is explained by #6249. Notably nothing is out of scope: no CHANGELOG.md entry, no settings keys, no new protocol verb or capability field. styleTemplate() / verbLabel() are public statics but they are test seams on a GUI class, not public protocol surface.
Preference check (§6): the status-line rewording ("Status: Playback 3 / 0:04" → "On air · slot 3 · 0:04"), the middle dots, and the amber/cyan assignment are preference-shaped changes to working UI — but #6249 is maintainer-requested, carries maintainer-review, specifies the wording direction ("always with the word 'failed'") and explicitly marks the colour split provisional. Ratified, not smuggled. One sentence is all this needs.
Blockers
None. Two things I expected to be blockers both held up:
roundMenuTreemoved fromTitleBar.cpp's anonymous namespace intonamespace AetherSDRin the new header — the unqualified calls left behind atTitleBar.cpp:974and:1112still resolve, becauseTitleBar.cpp:51opensnamespace AetherSDR {and closes it at:1749.- All four other
tests.cmaketargets that compilesrc/gui/TitleBar.cpp(lines 7242, 7274, 7308, 7335) gainedsrc/gui/RoundedMenu.cpp. None left behind.aetherdesktop_supportdoes not contain${GUI_SOURCES}(CMakeLists.txt:1665-1672), so compilingDvkPanel.cpp/RoundedMenu.cppinto the new test and also linking that library is not a duplicate-symbol risk.
Nits (non-blocking)
1. src/gui/DvkPanel.cpp:458 — STOP is the one enabled control on an unlicensed radio, which is the inverse of #6249's fourth bullet.
m_stopBtn->setEnabled(!idle);DvkModel::canStartOperation() is m_status == Idle || m_status == Unknown (src/models/DvkModel.h:34), so idle is false for Disabled. On dvk status=… enabled=0 the panel shows "Disabled · SmartSDR+ required" with REC/PLAY/PREV correctly greyed and STOP alone clickable — and pressing it sends dvk *_stop to a radio that will refuse it. The new test never feeds enabled=0, so it cannot catch this.
m_stopBtn->setEnabled(!idle && s != DvkModel::Disabled);(and a feed(model, QStringLiteral("S1|dvk status=idle enabled=0")) step asserting all four are off would pin it.)
2. src/gui/DvkPanel.cpp:124 — the two-line reservation silently clips the longest refusals, which is the #3377 symptom the comment above it preserves.
m_statusLabel->setFixedHeight(2 * m_statusLabel->fontMetrics().lineSpacing() + 2);setWordWrap(true) plus a hard setFixedHeight means a third line is clipped with no ellipsis and no tooltip. The longest refusal is 68 characters — "Play failed (slot 3): DVK requires an active SmartSDR+ subscription" (DvkModel::dvkErrorString, src/models/DvkModel.cpp:107) — which is right at the edge of two lines in a ~230 px label at 11 px, and the WAV path can be longer still: DvkWavTransfer.cpp:346 emits "Cannot open file: " + f.errorString(), so announceStatus renders "Transfer failed: Cannot open file: <Qt error string>". Reasoned from the strings, not measured. Keeping the fixed height (the slot-stability property the test asserts) and adding the full text as a tooltip costs one line in announceStatus:
m_statusLabel->setText(text);
m_statusLabel->setToolTip(text);3. src/gui/DvkPanel.cpp:124 — the reservation is computed before the sheet is applied. applyStyleSheet(this, styleTemplate()) lands at :269, so fontMetrics() here is the app default font, not the font-size: 11px the rule sets. Harmless direction (over-reserves), but it does mean "a wrapped refusal keeps the slots where they were" (tests/dvk_panel_theme_test.cpp:181-183) asserts that setFixedHeight was called, not that a wrap is actually absorbed.
4. src/gui/DvkPanel.cpp:546 — dropping the "Status: " prefix changes what a screen reader hears. m_statusLabel has no accessibleName (the a11y pass in e9584dcb6 named the panel, slots and controls but not this label), so the QAccessible::NameChanged event at :409 now announces a bare "Idle" / "On air · slot 3 · 0:04". The U+00B7 separators are also announced verbatim by some readers. docs/a11y.md has no sentence covering text-only status labels, so this is a convention note, not a rule.
5. CI has told you nothing about compilation. Only "Static checks" and "Sanitizer option configures" have reported on 582d681 — no build or test lane. The new target and the TitleBar.cpp extraction are compile-unverified in CI as of this review; dvk_panel_theme_test is not in any ctest -R filter in ci.yml, so it will only ever run in the full-suite lane.
What I tried to break (and could not)
- Theme tokens. I expected a false-resolving token (a dotted
color.canon.*grep returns nothing even when defined). Flattenedscopes.root.tokensin bothresources/themes/default-{dark,light}.jsonhandling the mixed nested/dotted keys: all 18 panel tokens and all 4RoundedMenu.htokens are present in both. I also checked the test's own API shapes —ThemeManager::color(const QString&)takes the full dotted formextractReferencedTokensreturns (ThemeManager.h:86,.cpp:1571) andsetActiveThemereturnsbool(ThemeManager.h:198) — so the "resolves every panel token" assertion is real, not vacuous. - The colour-literal assertion. The rendered diff makes
tests/dvk_panel_theme_test.cpp:89look like it has bare\band\(escapes, which would make the regex invalid and the check pass unconditionally. In the file they are\\band\\(. The assertion bites. - The refusal test.
DvkModel::handleCommandResponse(src/models/DvkModel.cpp:79-100) does emitcommandFailedfor0x50004001and does not also emitstatusChanged(Disabled), so nothing re-entersonStatusChangedto clobber the failure text after the diff's deliberate ordering. The #3377 clobber the comment at:316-321warns about does not reopen. - Target wiring. Every
tests.cmakeandCMakeLists.txtconsumer ofTitleBar.cppwas checked for the matchingRoundedMenu.cpp(all four patched), the new test's symbols (DvkModel,DvkWavTransfer,DvkWavConverter,ThemeManager) all come fromaethercore/aetherdesktop_support,${THEME_TEST_RESOURCES}is present so the offscreen run has themes, andQFramereaches the test transitively through<QLabel>.TIMEOUT 60is an explicit per-test opt-out of the 300 s default, which is the intended shape. - Base ancestry.
e9584dcb6(the a11y pass) ande8a2affb7(the import/upload fix) that the hunks sit on are both already onmain, so this is not stacked on an unmerged PR and the diff is answering the real base. QString::argfolding in the new status strings (:587,:594) — the two-argumentarg(QString, QString)overload fills%2/%3in one pass after thearg(int)for%1, which is what the format strings expect.- Could not check: anything requiring a build, a run, or pixels. I have no compiler and no display here, so every finding above is reasoned from the code in the head checkout rather than reproduced at runtime — in particular nits 2 and 3 are text-metric arguments, not measurements, and the
DVK_PANEL_GRAB_DIRscreenshots are the right way to settle them.
Security audit
Ran as required on the harness's file list. Every finding is pre-existing on main; this PR introduces none of them — #6251's diff does not touch any of the five flagged files. Credited to security-audit (CodeGuard):
src/core/DvkWavTransfer.cpp:190/:226— worth routing as its own issue, not a finding against this PR. The DVK export path doesm_server->listen(QHostAddress::Any, port)on a port taken from the radio's reply and then adoptsnextPendingConnection()unconditionally, so during the 10 s connect window any reachable host can race the radio and have its bytes written to the operator's chosen export path as a successful "Exported slot N". Bounded (5 MB cap, user-picked destination, nothing executed) and LAN-adjacent. Verified byte-identical tomain.- Discarded as false positives: the flagged
memcpyinDvkWavConverter.cpp:40is a 4-bytefloat/quint32type-pun sized from the destination, and the frame walk at:176-183is in bounds (dataBytesclamped,blockAlign >= channels * bytesPerSampleenforced at:155);upload()caps size pre-read and re-encodes rather than passing bytes through; theCommandParser.cppandTciProtocol.cppindex accesses are all length-guarded, and the "path" triage reason for those three files matches comment prose only. - Triage staleness worth noting to the maintainers:
DvkWavTransfer::validateWavFile, named in the triage reason, does not exist anywhere in the tree —DvkWavConverter::convertForRadiotook that role over.
Recommendation
Approve with nits. This is a careful, well-structured pass: one tracked template, state carried as properties with a correct unpolish/polish helper, the QFrame-cascade defect actually fixed at the selector level rather than papered over, and a test that drives real firmware status text through a real panel and checks states on the specific widgets rather than asserting the implementation back to itself. Nit 1 is the only functional gap against the issue and is a one-line change plus one test step; nit 2 is worth a tooltip before merge. Neither needs a new round of review — fold them in and this is ready for the maintainer's call on the provisional amber/cyan split. Thanks for the extraction of RoundedMenu as a shared seam rather than copy-pasting the rules; that was the right call.
🤖 aethersdr-agent · cost: $9.2865 · model: claude-opus-5
582d681 to
a9f1208
Compare
…ar's vocabulary. Principle VIII. Closes #6249. RFC #6226 (style guide), #6198 (title bar). Structure - Header modelled on a title-bar radio tab: the title in ink over a status line led by a status dot, then a hairline. The status line moves up from the bottom of the panel, reserves two lines so a wrapped refusal never shifts the slots, and reads like the title bar's subline ("Previewing · slot 3 · 0.4s of 4.2s", "Play failed (slot 3): …"). - Slot rows are 8 px canon.nested cards with a canon.line hairline; hover brightens the edge, and the selected row takes the title bar's active-tab pair (titlebar.tab.active.*). The row rule is scoped by property, so it no longer leaks into its QLabels (QLabel is a QFrame), which drew every name and length as a box inside the box. - F-keys are 22 px key caps and the transport buttons are 24 px, both on the canon secondary-control recipe (control fill, lineHi edge, cyan text, aqua hover and focus, 4 px corners). - Controls that cannot act are disabled: STOP while idle, PLAY and PREV on an empty slot, and the other start controls while one runs. - The slot menu is the title bar's rounded menu; the title bar's helper and rules move to RoundedMenu.{h,cpp} so both stay identical. Disabled items get canon.muted in the DVK menu, which routinely disables Clear/Export. - The rename field gets a control fill and a cyan edge. States (provisional, to be adjusted after review) - On air (PLAY, the playing slot's F-key, its progress bar and the status dot) uses the MOX family: background.tx, tx.mox.border, tx.mox.text. - Recording and preview use the canon cyan active treatment. - Refusals use accent.danger and always say "failed". - No red elsewhere: the canon keeps red for transmit indicators. One tracked stylesheet on the panel resolves every colour from color.canon.*, color.titlebar.*, the MOX tokens and accent.danger; widgets carry state as properties. All 11 colour literals are gone (audit ratchet: -5 unique colours, -35 references, -12 setStyleSheet calls). Tests: dvk_panel_theme_test (33 cases) drives a real DvkPanel from fw 4.2.20 status lines. It checks that every token resolves in Default Dark and Default Light, that there are no colour literals, and that each state (selected, empty, preview, on air, recording, refusal) lands on the right widget without moving the slots. Set DVK_PANEL_GRAB_DIR to save a PNG of each state. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
a9f1208 to
11dbe6b
Compare
jensenpat
left a comment
There was a problem hiding this comment.
Reviewed 11dbe6bd40d9d4ec45bf1001354ca2a68f399570 (replay of a9f12087 onto main 248bccd23 after #6247). Issue fit for #6249: the DVK panel moves onto the style-guide / title-bar vocabulary, tokens only, no token-value edits. Flex-only panel. No code blockers.
Scope
| Path | What landed |
|---|---|
src/gui/DvkPanel.cpp / .h |
One styleTemplate() stylesheet (color.canon.*, color.titlebar.*, MOX, accent.danger); state via properties; header, slot cards, F-keys, transport, rename field |
src/gui/RoundedMenu.cpp / .h |
Verbatim lift of the title-bar rounded-menu helper and rules |
src/gui/TitleBar.cpp |
Calls the shared helper; call sites unchanged |
CMakeLists.txt, tests/tests.cmake |
RoundedMenu.cpp in the GUI target and in every test that compiles TitleBar.cpp or DvkPanel.cpp |
tests/dvk_panel_theme_test.cpp |
Real panel, both themes, state properties, no colour literals |
Nits (non-blocking)
DvkPanel::refreshTransportenables STOP whenevercanStartOperation()is false (DvkPanel.cpp:475). That includes status Disabled and an in-flight WAV transfer.stopActiveOperation()does not stop a transfer and does not act on Disabled, so STOP is the only enabled control in those states.- Empty-slot F-keys stay enabled and only look muted. The click still no-ops through the existing guards.
Verification
Offscreen, Qt 6.12, on this SHA: dvk_panel_theme_test and dvk_panel_admission_test passed. unified_title_bar_test failed Alt+F opening the File menu, and therefore the late-submenu rounding that depends on that popup. Rebuilding the same test against main's TitleBar.cpp (this extraction removed) failed those same two checks and passed the same earlier ones, including existing menus rounded and menu text at x=32 with or without a check column. Linux full-suite on main already passed unified_title_bar_test. The local offscreen Alt+F miss is pre-existing.
Required checks on this SHA are green: build, check-macos, check-windows, Static checks, Sanitizer option configures. The per-PR gate does not run dvk_panel_theme_test. The app bundle was not built. Socket-owning DVK transfer tests were not run.
Follow-ups (not merge prerequisites)
- STOP-while-Disabled and STOP-during-transfer are unasserted.
- Colour audit numbers in the PR body were not re-run here.
… Principle VIII. An audit of every page against the 106 PRs in v26.10.2, each fact checked against the source at the release commit: - Menu paths: AetherMap, AetherModem, Callsign Lookup and FreeDV Reporter are on View, and Waveforms is on Tools. - New coverage: Windows Firewall rules and the Network Diagnostics page (#6289), live CW contacts (#6268), NB2 on the HL2 and ANAN-G2 (#5966), ANAN-G2 AGC-T and per-radio memory (#6282, #6129), HL2 state restore and its level changes (#6259, #6254, #5975), DVK import (#6247, #6262, #6251), Copy Assist from unprocessed audio (#6216), keyboard-operable status-bar indicators (#6267), the KPA1500 (#5905), CTR2 extensions and the AetherKnob (#6338), live PPM (#6082), and Icom COMP and the squelch scale (#6174, #6184). - Known issues whose GitHub issues are closed are removed. - The style guide now asks for US spelling, and every page follows it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and re-take the Stable snapshot. Principle VIII. (#6396) ## Summary This brings the user guide (docs.aethersdr.com) up to date with everything in v26.10.2 and re-takes the Stable snapshot so the default site shows it. Docs only, apart from the TCI page prose in `tools/docs/gen_reference.py`; no application source, test or CI changes. Every page was audited against the 106 PRs in the release. Every fact that was added or kept was checked against the source at `f753dbfc4`, the v26.10.2 release commit, not taken from the PR bodies alone. ## What changed - **Menu paths.** - AetherModem, Callsign Lookup, AetherMap and FreeDV Reporter are on **Tools**. They are created on View and then moved to Tools (`MainWindow_Menus.cpp:1519-1520`, `:1569-1575`); the style guide's example says **Tools → AetherMap...**. - **Show live CW contacts** is on **View**. - Waveforms is on **Tools**, not File. - The VUDU applet button was called TXDSP. - Labels now match the source's `...` vs `…`. - **Gaps from this cycle.** - **Windows and Network Diagnostics:** Windows Firewall rules and the Network Diagnostics Windows Firewall page (#6289); the `aether.network` and `aether.nvafx` log categories (#6289, #6308). - **Remote access:** Tailscale shim revocation semantics (#6325). - **Sleep, App Nap and power throttling (#6195):** per-OS notes. - **Hermes-Lite 2:** state restore, RX audio at unity, CW at TUNE level, multi-interface discovery and a discovery troubleshooting entry (#6259, #6254, #5975, #6009, #6293). - **ANAN-G2:** AGC-T range, a "What is remembered" section, and supply voltage and link status (#6282, #6129, #6137, #6211). - **NB2** on the HL2 and ANAN-G2 (#5966). - **DVK:** WAV import requirements, the slot menu without Delete, status text and licensing (#6247, #6262, #6251). - **Copy Assist** from unprocessed audio (#6216). - **Live CW contacts** replace the decoder's contact card (#6268). - **Status-bar indicators** work from the keyboard (#6267). - **Volume:** controllers, hotkeys and TCI `volume` follow the active output (#6129, #6260). - **Peripherals:** the KPA1500 (#5905), CTR2 link extensions and the AetherKnob (#6338), TMate 2 udev (#6250), MIDI RADE Modem and FDV modes (#6031, #6038). - **RTL-SDR:** live PPM (#6082). - **Icom:** COMP and the MK2 squelch scale (#6174, #6184). - **Known issues.** Entries whose GitHub issues are closed are removed (each checked with `gh issue view`). As decided, this includes three closed today without a demonstrated fix: #5107, #4824 and #5665. - **US spelling.** `docs-style-guide.md` asked for British spelling; it now asks for US, by the maintainer's decision, and every page is converted. The generated TCI pages are converted at their source, the prose strings in `tools/docs/gen_reference.py`. Code spans, links and URLs were left alone. UI labels were checked against the source strings, and none of them are British. Two in-page anchors and two Log Analyzer rule anchors (`#bnr-is-grayed-out-…`) follow the renamed headings. - **Stable snapshot.** `snapshot_stable.py 26.10.2` was re-run on the release tree with these corrections, before `main` was merged in, so Stable carries no post-release docs. `docs/` also has `main`'s post-release pages, in US spelling. Stable alone keeps the IC-9700 waterfall-rate entry (#5996): #6402 fixed it after the 26.10.2 tag, so 26.10.2 still has it. The PDF manual already attached to the v26.10.2 release keeps the tag's docs. ## Validation - `npm ci && npm run build` in `docs/user`: success. This is the broken-link and broken-anchor check. - `node tools/docs/test_log_rules.mjs`: 707 checks passed for 45 rules. - `python3 tools/docs/gen_reference.py --check`: up to date (8 pages). - `git diff --check`: clean. - Menu paths on a build of this branch (offscreen, isolated settings, no radio), `menu list`: `Tools → AetherModem...`, `Tools → Callsign Lookup...`, `Tools → AetherMap...`, `Tools → FreeDV Reporter...`, `Tools → Waveforms...`, `View → Show live CW contacts`. ## Screenshots now out of date (not recaptured) These need a recapture pass on the radio: - **Old panadapter title strip (#6386):** `panadapter`, `main-window`, `first-session-*`, `multi-slice-two-slices`, `split-pair`, `tnf-markers`, `offscreen-vfo-indicator`, `pan-3d-stacked-trace`, `default-light-theme`. - **Pre-canon windows (#6334, #6339, #6341, #6372):** `aetherrx*`, `aethertx*`, `aethermodem-aprs`, `bnr-panel`, `connect-dialog`, `connect-by-ip-card`, `connect-icom-rows`, `connect-smartlink-card`, `rx-agc-calibration`. - **Other:** - `title-bar` (lowercase tab states, #6281); - `menu-help` (#6294); - `display-panel` (#6371); - `support-diagnostics-dialog`; - `waveforms-dialog` and `tailnet-remote-access` (show shim 0.4.0); - `about-window` (v26.10.1); - `radio-setup-qrz`. ## Found during the audit, not fixed here - **App string bug:** `TailnetShimDialog.cpp:628` tells the user to "Connect remotely with Connect → Manual"; the real path is **File → Connect to Radio... → Connect by IP**. - **ANAN AGC-T tooltips:** the RX applet and VFO tooltips show the slider position as dB, which is wrong on ANAN. #6282 lists this as a follow-up. - **MIDI Master Volume on non-Flex radios:** it is refused while PC Audio is off, whereas hotkeys, FlexControl and HID drive the line out. The docs describe the code as it is. - **RTL mode names:** `rtl-sdr.md` writes "FM-N" and "CW-R", but the source strings are `FMN` and `CWR`. Left as is until someone confirms what the mode menu displays. - **IC-705 COMP readback:** inferred from the MK2 (#6174) and not verified on an IC-705. - **Not yet documented:** the #6230 Radio Health row "Forward responses skipped", and the Store/MSIX package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…6611) Fixes #6599. Opening DVK in a short main window compresses all 12 recording rows below the height of their F-key buttons and labels. At 1280 × 400, the original layout gives each row only 12 px around a 22 px button, reproducing the reporter's clipped text. Wrap only the recording grid in a resizable, transparent `QScrollArea`, with a content-derived row minimum. The header, status and transport buttons stay outside the scrolling region. Taller windows still expand all 12 rows. Recording names can shrink within their column so a long name cannot push the F-key or duration outside the viewport. This follows the existing CWX grid solution (#4945); it preserves the panel's styling from #6251 and updates the DVK user guide. ## Agent automation bridge proof Built the original `DvkPanel.cpp` from current upstream/main and then the fixed version. Both captures used the same Mac ARM64 host, FLEX-6700 firmware 4.3.16, Radio DVK and 1280 × 400 main-window geometry. The panel was 250 × 302 in both runs. | Reading | Original | Fixed | |---|---:|---:| | Rows whose button/name/duration escaped their bounds (`dumpTree`) | 12 | 0 | | First row / F-key height | 12 / 22 px | 32 / 22 px | | Transport controls fully inside the panel | All four | All four | | Slot 12 reachable (`scrollTo dvkPlaySlot12`) | No scroll container | `inViewport=true`, `vScroll=241` | At 1600 × 900, the fixed first row expands to 54 px and both scrollbar ranges return to zero. `grab dvkPanel` supplied the screenshots below. Both runs restored the initial slice mode, confirmed `transmitting=false`, disconnected, terminated the owned app and released the exclusive radio lock. Transmit automation was pinned off throughout. ## Long-name regression check (socket-free) At the normal 250 px panel width, a maximum-length, unbroken recording name previously expanded the content to 828 px inside a 216 px viewport, leaving an inaccessible horizontal scroll range of 612 px. The name label now yields horizontal space: content stays at 216 px, the horizontal range is zero, and the F-key and duration stay visible. The complete name is retained in the label and its accessible text. The focused regression test drains queued layout updates and fails on both clipped duration and hidden horizontal overflow without the size-policy correction. Additional checks using the real `DvkPanel` widget confirmed inline rename, Tab navigation fully into view at F12, and no horizontal overflow in either bundled theme at 220, 300 and 700 px panel heights. These checks use an isolated widget fixture with no socket or radio connection. The live bridge screenshots were captured from `bad2792f5`; the name-width correction is verified by the socket-free tests above. ## Proof Same short window, before → after: | Original | Fixed | |---|---| |  |  | Slot 12 after scrolling:  <details> <summary>Tall window: all 12 rows expand without a scrollbar</summary>  </details> These cropped images are hosted on the separate `fix-6599-assets` fork branch; no images are included in this PR's diff. Keep that branch while the PR is open. ## Validation - Full default ARM64 build passed, with RADE enabled. Host/system processors and executable are ARM64; no RNNoise x86 sources appear in the Ninja graph. - `dvk_panel_theme_test`, `dvk_panel_admission_test` and `dvk_panel_test` passed (3/3). The new constrained-splitter geometry checks cover row containment/non-overlap, fixed transport visibility, scrolling fully to slot 12, and tall-window expansion. - Mutation check: restoring the original layout makes the theme test fail on clipped row contents and the missing scroll container; restoring the fix passes. - `npm ci` / `npm run build` passed for the user guide. Generated references are current; log-rule validation passed all 707 checks; GUI accessibility lint reported zero findings. The reported Windows/FLEX-8600 combination still needs CI and reporter confirmation; live proof here used macOS/FLEX-6700. One preparation launch aborted in HID enumeration (`Ctr2ProxyModel` → `hid_init`) before connecting; that was not investigated in this layout change, and both recorded proof runs completed successfully. The agent automation bridge was sufficient; no missing capability was needed for this proof. Generated with OpenAI Codex.
Summary
Closes #6249. Stacked on #6247. The base is
fix/dvk-wiki-conformance, so the diff shows only this pass; retarget tomainonce #6247 merges.This moves the Digital Voice Keyer panel onto the AetherSDR style guide (RFC #6226), using the vocabulary of the unified title bar (#6198). The pass covers layout, controls and states as well as colour.
Structure
Idle,Previewing · slot 3 · 0.4s of 4.2s,On air · slot 3 · …,Play failed (slot 3): DVK requires an active SmartSDR+ subscription.canon.nestedcards with acanon.linehairline. Hover brightens the edge, and the selected row takes the title bar's active-tab pair (titlebar.tab.active.*).QFrame { … }) also matched the row's QLabels, because QLabel is a QFrame subclass, so every name and length was drawn as a box inside the box. The rule is now scoped by property.lineHiedge, cyan text, aqua hover and focus ring, 4 px corners.DvkModel::canStartOperation(), signalled byadmissionChanged()); this PR only shows it.RoundedMenu.{h,cpp}, so the two menus stay identical; the title bar's own behaviour is unchanged.States (provisional, as agreed; to be adjusted after review)
background.tx,tx.mox.borderandtx.mox.text.accent.dangerand always say "failed".Mechanism. One tracked stylesheet on the panel (
DvkPanel::styleTemplate()) resolves every colour fromcolor.canon.*,color.titlebar.*, the MOX tokens andaccent.danger. Widgets carry their state as properties (selected,empty,live,mode,state,tone). All 11 colour literals are gone, and no token values change.Constitution principle honored
Principle VIII. Each state is checked against a real panel driven by recorded firmware status lines, not just asserted. The colour ratchet moves down.
Rebased onto #6247's review fixes (
b8bac9d4c). PLAY keeps its TX-keying marker.dvk_panel_admission_testnow linksRoundedMenu.cpp, and it passes against the themed panel too: a refused start can't even be pressed now.Test plan
Full local build and
ctest: 668 tests pass offscreen (after the rebase). That includes the four title-bar tests that compileTitleBar.cppdirectly; they now linkRoundedMenu.cpp.New
dvk_panel_theme_test(33 cases). It drives a realDvkPanelfrom firmware 4.2.20 status lines and checks:Set
DVK_PANEL_GRAB_DIRto save a PNG of each state for review. That is how I checked them, the on-air state included, without transmitting.Live app, offscreen, through the bridge. I checked the panel, the slot menu (rounded, with Clear and Export greyed on an empty slot) and the rename field.
tools/audit_colours.py --strictagainst the base branch: unique colours 587 → 582, references 2522 → 2487,setStyleSheetcalls 905 → 893.check_a11yreports nothing on the touched files.check_test_registration,check_theme_seed,check_engine_boundary,check_ci_test_gate,check_command_planeandgen_touchpoint_manifest --checkpass.Not yet seen on a real display or with a radio connected. A smoke run on the maintainer's display is the natural next step.
For review:
tx.mox.text/tx.mox.borderare pale on light canon cards. These are the same tokens the MOX button uses, and the guide says a migration PR doesn't change token values, so I left them.canon.mutedlocally; the title bar's menu has the same gap and is left as is here.Checklist
AppSettingscalls🤖 Generated with Claude Code