Skip to content

feat(gui): Callsign Lookup is a CanonWindow workspace, built like AetherRX and AetherTX. Principle VIII. - #6753

Open
w5jwp wants to merge 5 commits into
aethersdr:mainfrom
w5jwp:feat/callsign-lookup-canonwindow
Open

w5jwp wants to merge 5 commits into
aethersdr:mainfrom
w5jwp:feat/callsign-lookup-canonwindow

Conversation

@w5jwp

@w5jwp w5jwp commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #6752. Part of #6239 and approved RFC #6226. Waiting on the maintainer's pick: this full CanonWindow move is not on #6239's CanonWindow lists. #6239's "Persistent tool dialogs (one PR per dialog)" candidate covers only moving a dialog's colours to color.canon.*. #6752 lays out both options; please don't merge before the pick. Merging is that pick: aethersdr-style-guide.md and dialog-patterns.md list Callsign Lookup among the tool windows the maintainer has moved to CanonWindow, which is true only once this lands.

Callsign Lookup (CallsignLookupDialog, Tools → Callsign Lookup...) moves from a PersistentDialog to a CanonWindow::Kind::Workspace, built the way AetherRX and AetherTX are. Kind::Workspace follows 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) use Kind::Dialog; if the maintainer prefers that here, it is a one-word change plus the workspace test.

  • Window:
    • Canon header and ground, corner close button, Esc / ⌘W close, and edge resizing through FramelessResizer.
    • Its own theme scope, canon/callsignLookup.
    • Geometry under the existing key (CallsignLookupDialogGeometry). CanonWindow reads the old base64 rectangles and rewrites them as x,y,w,h on the next save, so the saved size and position carry over.
  • Contact card: unchanged code. The Large CallsignCard paints 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.
  • Controls: the field and buttons take the canon controls sheet the AetherRX and AetherTX Settings dialogs use (ModemChrome::styleSheet(Scale::Dialog, Look::Canon)), applied once to the body through applyCanonSheet(), with the default button (Lookup) outlined in cyan as CanonMessageBox draws it. The four per-widget style sheets go. The status line uses color.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 gives QMenu its canon panel back, as on TX Band Settings.
  • Return: a workspace window does not turn Return into a default-button click, so Return in the field looks up once, through returnPressed, as before. Lookup is still drawn as the default.
  • Size: the minimum grows from 420×260 to 500×320. The canon sheet's monospace field font shows a slashed zero (N0CALL vs NOCALL) but is wider; at 440 px the field's text area was 187 px for a 219 px placeholder, so "Enter callsign — e.g. KI6BCJ" was elided (second commit, 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).
  • Opening it (MainWindow_Callsign.cpp): showCallsignLookupDialog() creates the window directly with WA_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's QDialog::done() closes the window rather than only hiding it. So the next open is always a fresh window with an empty field, as with the PersistentDialog. 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.
  • Unchanged: the object names (CallsignLookupDialog, callsignLookupInput, callsignLookupGo, callsignLookupRefresh, callsignLookupStatus), which docs/user/screens.json drives, the accessible names, and every call into CallsignLookupService.
  • Docs:
    • docs/style/dialog-patterns.md, aethersdr-style-guide.md and theme-style-guide.md list the window and its scope.
    • docs/user/docs/callsign-lookup.md says 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:

  • The minimum size above.
  • The callsign field is in the canon sheet's monospace font, not the old bold 14 px.
  • Stale screenshot, to recapture: callsign-lookup-prefix.png in docs/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 that docs/a11y.md asks for:

Text Token Dark Light
Callsign accent.bright 13.83:1 6.53:1
Name text.primary 15.50:1 11.49:1
Location text.secondary 11.61:1 6.75:1
Grid · county · QSL text.label 5.96:1 5.08:1
Licence chip accent 11.58:1 5.55:1
Status line canon.muted on the ground 6.71:1 5.94:1

Overlap 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

  • Local build passes (cmake --build build --target AetherSDR callsign_lookup_dialog_test, macOS arm64, Qt 6.12.0), with no warnings in the touched files.
  • Behavior verified on a real radio if applicable
    • Ran the branch build on the author's station (FLEX-8600, macOS 27.0.1) and looked up W5JWP: the card filled from QRZ.com ("Fetched from QRZ.com"), in the canon palette. Screenshot below.
    • Through the automation bridge, offscreen with isolated settings and no radio: opened the window from Tools → Callsign Lookup..., looked up N0CALL (prefix data, no QRZ account), closed it, and reopened it from the menu (a fresh instance, empty field).
  • Existing tests pass (CI)
    • New callsign_lookup_dialog_test (socket-free; it never starts a lookup, so the QRZ client makes no request): 24 checks, all passing:
      • it is a CanonWindow, a frameless Qt::Window, and carries the canon header;
      • the window, field, Lookup (still the default button), Refresh and status line keep their object names;
      • the card's color.background.1 resolves through the canon scope, not the root theme;
      • at minimum size the field's styled text area holds its whole placeholder, and the field's minimum width is measured from that placeholder;
      • the field's right-click menu is parented inside the canon body and draws an opaque background;
      • Esc, Ctrl+W / ⌘W and the corner button each destroy the WA_DeleteOnClose window;
      • a legacy base64 rectangle saved under CallsignLookupDialogGeometry restores, and hiding the window saves x,y,w,h under the same key.
    • Mutation-checked:
      • removing theme::setContainer() fails the canon-scope check;
      • Kind::Dialog fails the workspace check;
      • removing setGeometryKey() fails the restore and save checks;
      • the old 440 px minimum fails the placeholder check (text area 187 px, placeholder 219 px);
      • removing the field's measured minimum width fails the placeholder-minimum check;
      • removing the QMenu rules fails the opaque-menu check.
    • The test takes the suite's default timeout, like canon_window_test.
    • CI does not run this test before merge. It is not in ci.yml's per-PR ctest -R steps (the per-PR gate is frozen, docs/agents/tests-ci.md), so the green build / check-* jobs cover compilation, not the new checks; it runs post-merge in full-suite.yml. The local runs above are the evidence.
    • Static checks steps run locally pass: engine boundary, touchpoint manifest, generated user-docs reference, test registration, frozen per-PR test gate, build options.
    • Colour-audit ratchet (tools/audit_colours.py --compare-src <base> --strict): unique colours 487 (+0), colour references 1879 (+0), setStyleSheet 599 (+0). CallsignLookupDialog.cpp has no hex colour literals.
    • tools/check_a11y.py on the touched files: no findings.
  • Reproduction steps documented if user-reported bug
    • n/a: not a bug report.

Screenshots: before (main, prefix data) and after (this branch, a QRZ.com lookup) are posted in a comment below.

Checklist

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

🤖 Generated with Claude Code

@w5jwp
w5jwp requested a review from a team as a code owner October 24, 2026 20:44
@w5jwp

w5jwp commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Before:

Image

After:

Image Image

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Issue fit

Yes, 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 at tests/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 generic monospace fallback (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 in full-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 30 is an explicit opt-out of the retrofit's 300 s default, where the sibling canon_window_test takes the default. This test does a theme load, qWaitForWindowExposed and 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 — with WA_DeleteOnClose, the ✕ (wired to QDialog::close, CanonWindow.cpp:74) destroys the window, while Escape and Ctrl+W go through reject(), 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, and QPointer + WA_DeleteOnClose is 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 is QPointer<CallsignLookupDialog> (src/gui/MainWindow.h:1723), and every CallsignLookupService connection passes this as 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, no std::function callback registered from here.
  • Entry points the diff doesn't show. Repo-wide, showCallsignLookupDialog has exactly one caller, src/gui/MainWindow_Menus.cpp:1447, which passes no callsign; the action is created on viewMenu and moved to Tools at :1670-1672, so the header comment's "Tools →" and the user doc are right. The const QString& call parameter 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/callsignLookup is declared nowhere in the theme files, and ThemeManager::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 because theme::setContainer() → registerDeclaredContainer() → scopeOrCreate() builds the child under the existing canon scope (:2156-2166, :1906-1914), and a theme load replays declared containers after wiping the tree (:514-519). So the card inherits canon'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-2150 drives this window by CallsignLookupDialog, callsignLookupInput and callsignLookupGo; 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 included core/ThemeManager.h), check_test_registration.py, check_a11y.py on the changed source. CI on 8334dbb is green across build, check-macos, check-windows and Static checks. As the body says, the new test is not in the per-PR ctest -R gate, 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

@aethersdr-agent

Copy link
Copy Markdown
Contributor

One correction to an aside in my review, so you don't chase it: I wrote that AetherRxSettingsDialog and AetherTxSettingsDialog "also hold QLineEdits". They don't — their only QLineEdit references are QInputDialog::getText(..., QLineEdit::Normal, ...) (AetherRxSettingsDialog.cpp:152,245 and the same two lines in the TX file), which is a separate top-level dialog, not a field inside the canon body. So those two escape the QMenu gap today.

The blocker itself is unchanged: ModemChrome::styleSheet(Scale::Dialog, Look::Canon) still carries no QMenu rule, and Callsign Lookup is the case that puts an editable QLineEdit under it. Adding the rules to the shared sheet instead of this window's suffix is still worth considering for the rest of the #6239 wave, just on "the next one will hit it too" rather than on an existing instance.


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

@w5jwp

w5jwp commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Addressed in 93c1afb6:

Blocker, the field menu with no panel: fixed. The window's sheet suffix now carries the canon QMenu rules from TX Band Settings: raised panel, lineHi border, cyan selection, control.unavailable for disabled items, line separators. I added #6735's check too: the field's standard menu is parented inside the canon body and its centre pixel is opaque. Renaming the QMenu selectors so they match nothing fails it.

I kept the rules in this window rather than in ModemChrome::styleSheet(Look::Canon). Changing the shared sheet would restyle AetherRX/AetherTX Settings in a PR that doesn't claim them, and #6751 would need the same edit at the same time. Moving them into the shared sheet is a good follow-up once this wave lands.

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 minimumSizeHint().expandedTo(500×320), so it grows when a platform's face is wider. A new check asserts that the field's minimum covers the placeholder plus its frame; removing the measured minimum fails it. On macOS the numbers are unchanged (text area 251 px, placeholder 219 px).

Nit, TIMEOUT 30: dropped. The test now takes the default, like canon_window_test.

Nit, the close paths: the body now says so. ✕ destroys the window (fresh instance on reopen); Esc and ⌘W / Ctrl+W hide it through reject() (last card and field on reopen). The bridge run in the test plan closed with ✕, so its "fresh instance" holds for that path.

Kind::Workspace: the body now says why. It follows AetherRX/AetherTX: a window of its own, not held above the main window. It also notes that Kind::Dialog is a one-word change if the maintainer prefers the sibling PRs' shape.

The test is now 21 checks, all passing locally. check_a11y, check_test_registration and check_ci_test_gate pass.

@w5jwp

w5jwp commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Correction to my reply above, on the close paths. I wrote that Esc and ⌘W / Ctrl+W only hide the window through reject(), so the next open shows the last card. That's wrong on Qt 6.12, and so is the same point in the review's nit. Measured on the real window with WA_DeleteOnClose: Esc, QKeySequence::Close and the corner button each destroy it. The QPointer clears after the deferred delete, because QDialog::done() closes the window rather than only hiding it.

So the original body was right ("a fresh instance, empty field") for every close path. c02a5855 adds a check for each path (callsign_lookup_dialog_test, now 24 checks), and the body now says every close destroys the window.

w5jwp and others added 4 commits October 24, 2026 22:07
…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>
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>

This branch has not been deployed

No deployments
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.

Callsign Lookup on CanonWindow (a #6239 persistent tool dialog)

1 participant