Repository navigation
Conversation
There was a problem hiding this comment.
Issue fit
Yes, with one gap. #6752 asks for option 2 and the diff implements exactly what that option enumerates: Kind::Workspace, the canon/callsignLookup scope, geometry under the existing CallsignLookupDialogGeometry key, the ModemChrome Look::Canon sheet, FramelessResizer, unchanged object and accessible names, unchanged CallsignLookupService calls, LiveCwContactsDialog left alone. Nothing in the issue is unaddressed. The gap is the ruling, not the code: #6752 carries maintainer-review with no decision on it, and the PR body says so itself ("please don't merge before the pick").
I verified the mechanics the issue's triage listed as owed, and they are all here: the call site no longer goes through showOrRaisePersistent() (MainWindow_Callsign.cpp:200-209), FramelessResizer::install(this) is present (CallsignLookupDialog.cpp:104), and the base64 carry-over is real — CanonWindow::restoreGeometryFromSettings() falls back to restoreGeometry(QByteArray::fromBase64(...)) at src/gui/CanonWindow.cpp:181 and saveGeometryToSettings() rewrites x,y,w,h at :149.
Scope
| File / group | What it changes | Claimed? | Verdict |
|---|---|---|---|
src/gui/CallsignLookupDialog.{h,cpp} |
PersistentDialog → CanonWindow workspace, canon scope, canon sheet, min size 500×320, four per-widget sheets removed |
Yes | In scope |
src/gui/MainWindow_Callsign.cpp |
direct creation with WA_DeleteOnClose instead of showOrRaisePersistent() |
Yes | In scope — required, since a CanonWindow satisfies neither setFramelessMode() nor trackPersistentDialog(PersistentDialog*) |
tests/callsign_lookup_dialog_test.cpp, tests/tests.cmake |
new socket-free test, registered + AETHER_SETTINGS_CONSUMERS |
Yes | In scope |
docs/user/docs/callsign-lookup.md |
one paragraph on the new chrome | Yes | In scope |
docs/style/{aethersdr-style-guide,dialog-patterns,theme-style-guide}.md |
adds Callsign Lookup to the canon lists and canon/callsignLookup to the scope list |
Yes | In scope, but contingent: these sentences say the maintainer has moved this window, which becomes true only on merge. The body is upfront that merging is the pick |
No file is unexplained, no new public/protocol surface, no settings key added (the geometry key is the existing one), no CHANGELOG.md entry. Removals checked line by line: the four deleted applyStyleSheet() calls are replaced by the body sheet, and no guard, early return or symptom-naming comment is removed. CallsignLookupDialog.h keeping #include "PersistentDialog.h" is not dead — LiveCwContactsDialog in the same TU still derives from it, which is also why the cmake block compiles PersistentDialog.cpp and FramelessWindowTitleBar.cpp.
§6 preference check: the two visible changes (min size 420×260 → 500×320, monospace field font) are both disclosed in the body and both fall out of RFC #6226's sheet rather than being taste applied on the side. Not a smuggled preference — but they are part of what the maintainer is picking.
Blockers
1. src/gui/CallsignLookupDialog.cpp:107-112 — the callsign field's right-click menu loses its background.
applyCanonSheet(bodyWidget(), [] {
return ModemChrome::styleSheet(ModemChrome::Scale::Dialog, ModemChrome::Look::Canon)
+ QStringLiteral(
"QPushButton:default { color: {{color.canon.cyan}}; border-color: {{color.canon.cyan}}; }"
"QLabel#callsignLookupStatus { color: {{color.canon.muted}}; font-size: 11px; }");
});The canon sheet opens with a body-wide QWidget { color: …; background: …; } whose background is transparent under Look::Canon (src/gui/ModemChrome.cpp:104-108, with the Canon substitution at :52), and ModemChrome.cpp contains no QMenu rule at all. Qt parents a QLineEdit's standard context menu (Undo/Cut/Copy/Paste/Select All) to the field, so that transparent rule reaches it — the menu draws with no panel behind it.
This is the same defect you found and fixed in #6735's third commit (078b162a: "the menu on the RF and Tune power fields drew no background at all. The sheet now gives QMenu its canon panel back"). On main this window had no body-wide sheet — m_input carried its own scoped QLineEdit rule — so this is a regression introduced here, not pre-existing in this window. Reasoned from the code and from your own #6735 observation; I could not render it.
The established canon-workspace rules are at src/gui/RadioSetupDialog.cpp:1070-1084. Appending them is a paste:
applyCanonSheet(bodyWidget(), [] {
return ModemChrome::styleSheet(ModemChrome::Scale::Dialog, ModemChrome::Look::Canon)
+ QStringLiteral(
"QPushButton:default { color: {{color.canon.cyan}}; border-color: {{color.canon.cyan}}; }"
"QLabel#callsignLookupStatus { color: {{color.canon.muted}}; font-size: 11px; }"
// The body sheet clears every descendant to transparent, and Qt parents
// the field's standard context menu to the field, so give QMenu its
// canon panel back (#6735).
"QMenu { color: {{color.canon.inkSoft}}; background: {{color.canon.nested}};"
" border: 1px solid {{color.canon.lineHi}};"
" selection-background-color: {{color.canon.cyan}};"
" selection-color: {{color.canon.onAccent}}; }"
"QMenu::item:selected { color: {{color.canon.onAccent}};"
" background: {{color.canon.cyan}}; }"
"QMenu::item:disabled { color: {{color.control.unavailable}}; }");
});#6735 also added a check for this shape ("a field's right-click menu is parented inside the canon body, draws an opaque background"); mirroring it here would make the fix stick.
Worth raising separately as a follow-up rather than something to fix in this PR: AetherRxSettingsDialog and AetherTxSettingsDialog take the same sheet and also hold QLineEdits, so the gap arguably belongs in ModemChrome::styleSheet(Look::Canon) itself rather than in each window's suffix.
Nits
src/gui/CallsignLookupDialog.cpp:103—setMinimumSize(500, 320)plus the test attests/callsign_lookup_dialog_test.cpp:106(check(needed <= text.width(), …)) is a zero-slack fit assertion over a font stack that resolves differently on each platform:font-family: "SF Mono", "Menlo", "Consolas", monospace(src/gui/ModemChrome.cpp:309). You measured on macOS (SF Mono); Windows gets Consolas and Linux-offscreen gets the genericmonospacefallback (usually DejaVu Sans Mono, wider). By your own figures the headroom is ~28 px on 219 px of text, so it probably holds — but this test only runs post-merge infull-suite.yml, where a miss shows up as an unrelated-looking red. Same class as the other font-dependent fit checks in the suite. Consider asserting with a small margin, or skipping when the resolved family isn't one of the three named.tests/tests.cmake:2366—TIMEOUT 30is an explicit opt-out of the retrofit's 300 s default, where the siblingcanon_window_testtakes the default. This test does a theme load,qWaitForWindowExposedand up to 50×20 ms of polling; under the sanitizer lanes widget tests run several times slower. Why not match the sibling?src/gui/MainWindow_Callsign.cpp:202-206— withWA_DeleteOnClose, the ✕ (wired toQDialog::close,CanonWindow.cpp:74) destroys the window, while Escape and Ctrl+W go throughreject(), which hides it without a close event (CanonWindow.cpp:128-130). So the body's "reopened it from the menu (a fresh instance, empty field)" holds for the ✕ path; after Escape the next open shows the previous card and field text. Harmless, andQPointer+WA_DeleteOnCloseis the house pattern (MainWindow.cpp:3418) — just not quite what the body claims.docs/style/*are the collision point with #6735 as you note; whichever lands second rebases there.
What I tried to break
- Lifetime under the new
WA_DeleteOnClose. This is the riskiest part of the change — the window used to live for the app's life. It holds: the member isQPointer<CallsignLookupDialog>(src/gui/MainWindow.h:1723), and everyCallsignLookupServiceconnection passesthisas the context object (CallsignLookupDialog.cpp:166-202), so destroying the window mid-lookup disconnects rather than calling into freed memory. The service is a singleton that keeps fetching and caching — no back-pointer to the dialog, nostd::functioncallback registered from here. - Entry points the diff doesn't show. Repo-wide,
showCallsignLookupDialoghas exactly one caller,src/gui/MainWindow_Menus.cpp:1447, which passes no callsign; the action is created onviewMenuand moved to Tools at:1670-1672, so the header comment's "Tools →" and the user doc are right. Theconst QString& callparameter has no non-empty caller anywhere — a pre-existing dead path, not something this PR adds. - Whether the canon scope actually re-palettes the card.
canon/callsignLookupis declared nowhere in the theme files, andThemeManager::lookupRaw()falls back to root — not to the nearest ancestor — for an unknown path (src/core/ThemeManager.cpp:293-295,:242-249). It works anyway becausetheme::setContainer()→registerDeclaredContainer()→scopeOrCreate()builds the child under the existingcanonscope (:2156-2166,:1906-1914), and a theme load replays declared containers after wiping the tree (:514-519). So the card inheritscanon's 15 shadowed base tokens with no new theme token — matching the triage's correction to the issue body. The card is constructed with the window as parent (CallsignLookupDialog.cpp:154) before it applies its own sheets, so the container chain is visible at construction too. - Geometry against the larger minimum. A saved 420×260 rectangle from the old dialog cannot produce an under-minimum window: the restore path does
resize(QSize(w, h).expandedTo(minimumSize()))(CanonWindow.cpp:169), and a dead-screen position falls back to centring (:170-176). - The docs automation.
docs/user/screens.json:2130-2150drives this window byCallsignLookupDialog,callsignLookupInputandcallsignLookupGo; all three object names survive, so the spec still runs. The shot itself goes stale, which the body discloses. - Gates I could run locally on the head checkout, all clean:
gen_touchpoint_manifest.py --check(up to date — the file already includedcore/ThemeManager.h),check_test_registration.py,check_a11y.pyon the changed source. CI on8334dbbis green acrossbuild,check-macos,check-windowsandStatic checks. As the body says, the new test is not in the per-PRctest -Rgate, so green here means it compiled, not that its 16 checks ran. - What I could not check: anything at runtime. I have no build and no way to drive the GUI, so every finding above is reasoned from the code as it will exist after merge, and the blocker in particular is an inference from the sheet's selectors plus your own #6735 report — not something I observed rendering.
Recommendation
Needs maintainer decision — and one fix first. The governance position is exactly as you and the triage on #6752 describe it: #6239's "Persistent tool dialogs" bullet authorises a token re-skin that keeps PersistentDialog, docs/style/dialog-patterns.md:277 says "Other tool and workspace dialogs keep the persistent pattern", and a full CanonWindow move is an unlisted one-off of the same class as the still-unruled #6735. Two things are worth putting in front of the maintainer with the ruling: Kind::Workspace is asserted rather than argued for a 500×320 card (the sibling PRs #6745/#6749/#6751 all chose Kind::Dialog), and setDefault(true) is styling only under a workspace, as the body acknowledges.
Next step: paste the QMenu block, and ideally bring over #6735's right-click-menu check so it can't regress. The engineering underneath is careful — the lifetime change is safe, the geometry carry-over is real, the scope inheritance needs no new tokens, and the test has a genuine mutation story for each claim. Thanks for the detailed body; it made this much faster to verify.
🤖 aethersdr-agent · cost: $12.1997 · model: claude-opus-5
|
One correction to an aside in my review, so you don't chase it: I wrote that The blocker itself is unchanged: 🤖 aethersdr-agent · cost: $13.6234 · model: claude-opus-5 |
|
Thanks for the review. Addressed in Blocker, the field menu with no panel: fixed. The window's sheet suffix now carries the canon I kept the rules in this window rather than in Nit, the zero-slack font fit: fixed at the source instead of loosening the check. The field's minimum width is now measured from its placeholder in whichever monospace face the sheet resolves. The window's minimum is Nit, Nit, the close paths: the body now says so. ✕ destroys the window (fresh instance on reopen); Esc and ⌘W / Ctrl+W hide it through
The test is now 21 checks, all passing locally. |
|
Correction to my reply above, on the close paths. I wrote that Esc and ⌘W / Ctrl+W only hide the window through So the original body was right ("a fresh instance, empty field") for every close path. |
…herRX and AetherTX. Principle VIII. Tools → Callsign Lookup moves from PersistentDialog to a CanonWindow workspace window (style guide, RFC aethersdr#6226), following AetherRX and AetherTX: - Kind::Workspace: no title bar, the canon ground and header, the corner close button, Esc / ⌘W, and FramelessResizer on every edge. - theme::setContainer("canon/callsignLookup"): the canon scope maps the base tokens the Large contact card paints with onto the canon palette, so the card matches the window with no card change. - Size and position persist under CallsignLookupDialogGeometry, the key the old dialog wrote; CanonWindow restores its base64 rectangles. - The field and buttons take ModemChrome's canon sheet (as the AetherRX and AetherTX Settings dialogs do), with the default button in cyan; four per-widget sheets go. - MainWindow creates it directly with WA_DeleteOnClose instead of showOrRaisePersistent(), which would apply View → Frameless Window. - Object names are unchanged (docs/user/screens.json drives them). - callsign_lookup_dialog_test: 14 socket-free checks; no lookup is started. Live CW contacts, in the same file, is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e window's minimum size. Principle VIII. The canon sheet's monospace font is wider than the old field's, and its buttons take more of the row: at 440 px the field's text area was 187 px for a 219 px placeholder, so "Enter callsign — e.g. KI6BCJ" was elided. The minimum width is 500 px (247 px text area). A new check measures the placeholder against the field's styled text rect at minimum size; it fails at 440 px. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fits its placeholder in any monospace face. Principle VIII. The canon controls sheet clears every descendant to transparent, and Qt parents the callsign field's standard right-click menu to the field, so the menu drew with no panel. QMenu gets its canon panel back, as on TX Band Settings and RC-28 Button Mapping. The field's minimum width is now measured from its placeholder in the font the sheet resolves (SF Mono, Menlo, Consolas or a generic monospace fallback), and the window's minimum grows with it, so the whole placeholder shows at minimum size on every platform instead of only where 500 px happens to be enough. callsign_lookup_dialog_test gains the opaque-menu check and a check that the field's minimum comes from its placeholder; each fails with its part of the change reverted. Its timeout now takes the suite default, like canon_window_test, since it polls for the window under sanitizer lanes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…corner button each destroy the window. Principle VIII. With WA_DeleteOnClose, Qt 6.12's QDialog::done() closes the window rather than only hiding it, so Esc and Ctrl+W / Cmd+W destroy it just as the corner button does, and the next open is a fresh window. The new check drives each path on the real window and asserts the QPointer clears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c02a585 to
5b1c7ea
Compare
Resolves the conflicts in the three docs/style/* canon-window lists, where main added FFTW Wisdom / canon/nr2Wisdom. Every list keeps main's entries and this branch's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Summary
Closes #6752. Part of #6239 and approved RFC #6226. Waiting on the maintainer's pick: this full
CanonWindowmove is not on #6239'sCanonWindowlists. #6239's "Persistent tool dialogs (one PR per dialog)" candidate covers only moving a dialog's colours tocolor.canon.*. #6752 lays out both options; please don't merge before the pick. Merging is that pick:aethersdr-style-guide.mdanddialog-patterns.mdlist Callsign Lookup among the tool windows the maintainer has moved toCanonWindow, which is true only once this lands.Callsign Lookup (
CallsignLookupDialog, Tools → Callsign Lookup...) moves from aPersistentDialogto aCanonWindow::Kind::Workspace, built the way AetherRX and AetherTX are.Kind::Workspacefollows AetherRX/AetherTX: a window of its own with its own taskbar entry, not one held above the main window. The RC-28, Ulanzi and MQTT PRs (#6745, #6749, #6751) useKind::Dialog; if the maintainer prefers that here, it is a one-word change plus the workspace test.FramelessResizer.canon/callsignLookup.CallsignLookupDialogGeometry).CanonWindowreads the old base64 rectangles and rewrites them asx,y,w,hon the next save, so the saved size and position carry over.CallsignCardpaints with base tokens (background.1,accent,text.*,border.strong), and the canon scope maps each of them onto the canon palette inside the window, so the card follows the canon look and the theme without a new variant.ModemChrome::styleSheet(Scale::Dialog, Look::Canon)), applied once to the body throughapplyCanonSheet(), with the default button (Lookup) outlined in cyan asCanonMessageBoxdraws it. The four per-widget style sheets go. The status line usescolor.canon.muted. Because that sheet clears every descendant to transparent, and the field's right-click menu (Undo/Cut/Copy/Paste) is a child of the field, the window's sheet givesQMenuits canon panel back, as on TX Band Settings.returnPressed, as before. Lookup is still drawn as the default.59e538bb). The field's minimum width is measured from its placeholder in the resolved monospace face (SF Mono, Menlo, Consolas or a generic fallback), and the window's minimum grows with it, so the fit holds on every platform rather than only where 500 px is enough (third commit,750de805).MainWindow_Callsign.cpp):showCallsignLookupDialog()creates the window directly withWA_DeleteOnClose, like TX Band Settings (feat(gui): TX Band Settings is a CanonWindow workspace that follows the radio's TX profile, band values and answers. Principle II. #6735). Every way out destroys it: the corner ✕, and Esc and ⌘W / Ctrl+W too, since Qt 6.12'sQDialog::done()closes the window rather than only hiding it. So the next open is always a fresh window with an empty field, as with thePersistentDialog.showOrRaisePersistent()would apply View → Frameless Window, which a canon window ignores. Its one entry point, the Tools → Callsign Lookup... action (Ctrl+Shift+L is that action's shortcut), goes through that function.CallsignLookupDialog,callsignLookupInput,callsignLookupGo,callsignLookupRefresh,callsignLookupStatus), whichdocs/user/screens.jsondrives, the accessible names, and every call intoCallsignLookupService.docs/style/dialog-patterns.md,aethersdr-style-guide.mdandtheme-style-guide.mdlist the window and its scope.docs/user/docs/callsign-lookup.mdsays how the window moves, resizes and closes.No
CanonMessageBox: nothing on the Callsign Lookup path raises a question or notice; errors and status show on the card and the status line.Intent
After this merges, Callsign Lookup looks and behaves like the other canon tool windows and follows the theme, with the same controls doing the same things. Out of scope: Live CW contacts (
LiveCwContactsDialog, in the same source file), the contact card's layout, and the other persistent tool dialogs (one PR each, per #6239).Visible changes for the maintainer:
callsign-lookup-prefix.pngindocs/user/docs/callsign-lookup.md.Contrast, from the theme files: the card's text against its solid
background.1, as the canon scope resolves them. Every pair clears the 4.5:1 thatdocs/a11y.mdasks for:accent.brighttext.primarytext.secondarytext.labelaccentcanon.mutedon the groundOverlap with #6735: both PRs add a window to the same lists in the three style docs, so whichever lands second needs a small rebase there.
Constitution principle honored
Principle VIII — Evidence Over Assertion: the workspace shape, the canon scope, the geometry carry-over and the field width each have a check that fails when that part of the change is reverted (see the test plan).
Test plan
cmake --build build --target AetherSDR callsign_lookup_dialog_test, macOS arm64, Qt 6.12.0), with no warnings in the touched files.callsign_lookup_dialog_test(socket-free; it never starts a lookup, so the QRZ client makes no request): 24 checks, all passing:CanonWindow, a framelessQt::Window, and carries the canon header;color.background.1resolves through the canon scope, not the root theme;WA_DeleteOnClosewindow;CallsignLookupDialogGeometryrestores, and hiding the window savesx,y,w,hunder the same key.theme::setContainer()fails the canon-scope check;Kind::Dialogfails the workspace check;setGeometryKey()fails the restore and save checks;QMenurules fails the opaque-menu check.canon_window_test.ci.yml's per-PRctest -Rsteps (the per-PR gate is frozen,docs/agents/tests-ci.md), so the greenbuild/check-*jobs cover compilation, not the new checks; it runs post-merge infull-suite.yml. The local runs above are the evidence.Static checkssteps run locally pass: engine boundary, touchpoint manifest, generated user-docs reference, test registration, frozen per-PR test gate, build options.tools/audit_colours.py --compare-src <base> --strict): unique colours 487 (+0), colour references 1879 (+0),setStyleSheet599 (+0).CallsignLookupDialog.cpphas no hex colour literals.tools/check_a11y.pyon the touched files: no findings.Screenshots: before (
main, prefix data) and after (this branch, a QRZ.com lookup) are posted in a comment below.Checklist
docs/COMMIT-SIGNING.md)AppSettingscalls — use nested-JSON-under-one-key(Principle V)
CallsignLookupDialogGeometry.reverse-engineered from a proprietary binary (Principle IV)
MeterSmoother(AGENTS.md convention)docs/and theaffected READMEs. Not
CHANGELOG.md, which is a release-prep file aPR must not add to (AGENTS.md); describe it in the Summary above instead
docs/user/docs/callsign-lookup.md.🤖 Generated with Claude Code