Skip to content

feat(gui): the DVK panel on the AetherSDR style guide, in the title bar's vocabulary. Principle VIII. - #6251

Merged
jensenpat merged 1 commit into
mainfrom
feat/dvk-panel-canon
Oct 7, 2026
Merged

jensenpat merged 1 commit into
mainfrom
feat/dvk-panel-canon

Conversation

@ten9876

@ten9876 ten9876 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Closes #6249. Stacked on #6247. The base is fix/dvk-wiki-conformance, so the diff shows only this pass; retarget to main once #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

  • Header. Modelled on a title-bar radio tab: the title in ink, a status line led by a status dot, then a hairline. The status line moves up from the bottom of the panel. It reserves two lines, so a wrapped refusal never shifts the slots, and it reads like the title bar's subline: Idle, Previewing · slot 3 · 0.4s of 4.2s, On air · slot 3 · …, Play failed (slot 3): DVK requires an active SmartSDR+ subscription.
  • Slot rows. 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.*).
  • Label boxes fixed. The old row rule (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.
  • Controls. F-keys are 22 px key caps and the transport buttons are 24 px, both using the canon secondary-control recipe from the About window: control fill, lineHi edge, cyan text, aqua hover and focus ring, 4 px corners.
  • Enabled only when useful. STOP is disabled while idle, PLAY and PREV are disabled on an empty slot, and the other start controls are disabled while an operation runs. They also grey out while a start awaits its reply or a WAV transfer runs. That admission rule comes from fix(dvk): import converts to the radio's 24 kHz mono and uses the wiki's two-step upload; slot names keep their spaces. Principle I. #6247's review fixes (DvkModel::canStartOperation(), signalled by admissionChanged()); this PR only shows it.
  • Slot menu. It is the title bar's rounded menu. The title bar's helper and rules move into RoundedMenu.{h,cpp}, so the two menus stay identical; the title bar's own behaviour is unchanged.
  • Rename field. Control fill with a cyan edge.

States (provisional, as agreed; to be adjusted after review)

  • On air uses the MOX family: PLAY, the playing slot's F-key, its progress bar and the status dot take background.tx, tx.mox.border and tx.mox.text.
  • Recording and preview use the canon cyan active treatment.
  • Refusals use accent.danger and always say "failed".
  • No red anywhere else, since the canon keeps red for transmit indicators.

Mechanism. One tracked stylesheet on the panel (DvkPanel::styleTemplate()) resolves every colour from color.canon.*, color.titlebar.*, the MOX tokens and accent.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_test now links RoundedMenu.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 compile TitleBar.cpp directly; they now link RoundedMenu.cpp.

  • New dvk_panel_theme_test (33 cases). It drives a real DvkPanel from firmware 4.2.20 status lines and checks:

    • every panel token resolves in both Default Dark and Default Light
    • the panel's stylesheet has no colour literals, and no tokens outside the canon, title-bar and MOX groups
    • rows and labels carry no per-widget stylesheet
    • each state (selected, empty, preview, on air, recording, refusal and recovery, light theme) lands on the right widgets
    • a wrapped refusal doesn't move the slots

    Set DVK_PANEL_GRAB_DIR to 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 --strict against the base branch: unique colours 587 → 582, references 2522 → 2487, setStyleSheet calls 905 → 893.

  • check_a11y reports nothing on the touched files. check_test_registration, check_theme_seed, check_engine_boundary, check_ci_test_gate, check_command_plane and gen_touchpoint_manifest --check pass.

  • 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:

  • Light theme, on air. tx.mox.text / tx.mox.border are 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.
  • Title bar menu, disabled items. The shared rounded-menu rules give disabled items no colour of their own. The DVK menu adds canon.muted locally; the title bar's menu has the same gap and is left as is here.

Checklist

  • Commits are signed
  • No new flat-key AppSettings calls
  • Clean-room
  • No meter UI touched
  • Documentation: no doc describes the panel's look. CHANGELOG is untouched.
  • No security-sensitive changes

🤖 Generated with Claude Code

@ten9876
ten9876 requested review from a team as code owners October 7, 2026 16:33
@ten9876 ten9876 self-assigned this Oct 7, 2026

@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

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:

  • roundMenuTree moved from TitleBar.cpp's anonymous namespace into namespace AetherSDR in the new header — the unqualified calls left behind at TitleBar.cpp:974 and :1112 still resolve, because TitleBar.cpp:51 opens namespace AetherSDR { and closes it at :1749.
  • All four other tests.cmake targets that compile src/gui/TitleBar.cpp (lines 7242, 7274, 7308, 7335) gained src/gui/RoundedMenu.cpp. None left behind. aetherdesktop_support does not contain ${GUI_SOURCES} (CMakeLists.txt:1665-1672), so compiling DvkPanel.cpp/RoundedMenu.cpp into 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). Flattened scopes.root.tokens in both resources/themes/default-{dark,light}.json handling the mixed nested/dotted keys: all 18 panel tokens and all 4 RoundedMenu.h tokens are present in both. I also checked the test's own API shapes — ThemeManager::color(const QString&) takes the full dotted form extractReferencedTokens returns (ThemeManager.h:86, .cpp:1571) and setActiveTheme returns bool (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:89 look like it has bare \b and \( escapes, which would make the regex invalid and the check pass unconditionally. In the file they are \\b and \\(. The assertion bites.
  • The refusal test. DvkModel::handleCommandResponse (src/models/DvkModel.cpp:79-100) does emit commandFailed for 0x50004001 and does not also emit statusChanged(Disabled), so nothing re-enters onStatusChanged to clobber the failure text after the diff's deliberate ordering. The #3377 clobber the comment at :316-321 warns about does not reopen.
  • Target wiring. Every tests.cmake and CMakeLists.txt consumer of TitleBar.cpp was checked for the matching RoundedMenu.cpp (all four patched), the new test's symbols (DvkModel, DvkWavTransfer, DvkWavConverter, ThemeManager) all come from aethercore/aetherdesktop_support, ${THEME_TEST_RESOURCES} is present so the offscreen run has themes, and QFrame reaches the test transitively through <QLabel>. TIMEOUT 60 is an explicit per-test opt-out of the 300 s default, which is the intended shape.
  • Base ancestry. e9584dcb6 (the a11y pass) and e8a2affb7 (the import/upload fix) that the hunks sit on are both already on main, so this is not stacked on an unmerged PR and the diff is answering the real base.
  • QString::arg folding in the new status strings (:587, :594) — the two-argument arg(QString, QString) overload fills %2/%3 in one pass after the arg(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_DIR screenshots 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 does m_server->listen(QHostAddress::Any, port) on a port taken from the radio's reply and then adopts nextPendingConnection() 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 to main.
  • Discarded as false positives: the flagged memcpy in DvkWavConverter.cpp:40 is a 4-byte float/quint32 type-pun sized from the destination, and the frame walk at :176-183 is in bounds (dataBytes clamped, blockAlign >= channels * bytesPerSample enforced at :155); upload() caps size pre-read and re-encodes rather than passing bytes through; the CommandParser.cpp and TciProtocol.cpp index 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::convertForRadio took 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

…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>
@jensenpat
jensenpat force-pushed the feat/dvk-panel-canon branch from a9f1208 to 11dbe6b Compare October 7, 2026 20:59
@jensenpat jensenpat self-assigned this Oct 7, 2026

@jensenpat jensenpat 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.

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::refreshTransport enables STOP whenever canStartOperation() 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.

@jensenpat
jensenpat merged commit 5f86d2f into main Oct 7, 2026
7 checks passed
@jensenpat
jensenpat deleted the feat/dvk-panel-canon branch October 7, 2026 21:26
ten9876 added a commit that referenced this pull request Oct 12, 2026
… 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>
@aethersdr-agent aethersdr-agent Bot mentioned this pull request Oct 12, 2026
2 tasks
ten9876 added a commit that referenced this pull request Oct 12, 2026
… 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>
NF0T pushed a commit that referenced this pull request Oct 20, 2026
…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 |
|---|---|
| ![Original clipped DVK
slots](https://raw.githubusercontent.com/rfoust/AetherSDR/fix-6599-assets/.pr-assets/6599-before.png)
| ![Readable fixed DVK
slots](https://raw.githubusercontent.com/rfoust/AetherSDR/fix-6599-assets/.pr-assets/6599-after.png)
|

Slot 12 after scrolling:

![Slot 12 fully
visible](https://raw.githubusercontent.com/rfoust/AetherSDR/fix-6599-assets/.pr-assets/6599-last.png)

<details>
<summary>Tall window: all 12 rows expand without a scrollbar</summary>

![Tall DVK
panel](https://raw.githubusercontent.com/rfoust/AetherSDR/fix-6599-assets/.pr-assets/6599-tall.png)

</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.
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.

DVK panel on the AetherSDR style guide, in the title bar's vocabulary

2 participants