From 93879a0ce114070a07e72e98e30b053688ec6626 Mon Sep 17 00:00:00 2001 From: Gregory Salaun Date: Thu, 13 Aug 2026 11:54:09 +0200 Subject: [PATCH] fix(compact): size the window to the strip instead of a tuned constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compactH was 158px, "tuned so the compact entry strip fits in a single row". A constant tuned against a layout stops being true the moment the layout changes, and this one outlived a strip that had since shrunk — leaving about 70px of empty window under the fields. The frontend now measures what it rendered and asks for that height, watched by a ResizeObserver so a strip that wraps at a narrow width is followed too. The topbar is added as its declared h-8 rather than measured, since it is fixed. Bounded in the backend: a measurement of zero — a layout not yet painted — must not collapse the window, and the call is ignored unless compact is on so nothing can shrink the normal window. Rounded to whole pixels so a sub-pixel reflow cannot start a resize loop. --- app.go | 26 ++++++++++++++++++++++++++ changelog.json | 6 ++++-- frontend/src/App.tsx | 26 ++++++++++++++++++++++++-- frontend/wailsjs/go/main/App.d.ts | 2 ++ frontend/wailsjs/go/main/App.js | 4 ++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index ed03794..944508a 100644 --- a/app.go +++ b/app.go @@ -17774,3 +17774,29 @@ func (a *App) SetSpotTTLMinutes(min int) error { a.setSetting(keyClusterSpotTTL, strconv.Itoa(min)) return nil } + +// SetCompactHeight resizes the compact window to fit its content. +// +// The height used to be a constant "tuned so the compact entry strip fits in a +// single row". Constants tuned against a layout stop being true the moment the +// layout changes, and this one outlived a strip that had since lost a row — +// leaving a band of empty window under the fields. The frontend measures what +// it actually rendered and says so. +// +// Ignored unless compact is on, so nothing can shrink the normal window. +func (a *App) SetCompactHeight(h int) { + if a.ctx == nil || !a.compact { + return + } + // Bounded: a measurement of zero (a layout not yet painted) must not collapse + // the window to nothing, and no strip is ever 600px tall. + if h < 80 || h > 600 { + return + } + w, _ := wruntime.WindowGetSize(a.ctx) + if w <= 0 { + w = compactW + } + wruntime.WindowSetMinSize(a.ctx, 900, h) + wruntime.WindowSetSize(a.ctx, w, h) +} diff --git a/changelog.json b/changelog.json index 289be88..72679cb 100644 --- a/changelog.json +++ b/changelog.json @@ -7,14 +7,16 @@ "DX Cluster: a spot lifetime can be set — 5, 10, 15 minutes or your own value — after which spots leave the list and the band maps.", "Call lookup: QRZ.com now fills Name with the first name only, and optionally with the operator nickname instead.", "Entry form: a narrower left column, State beside the locator, and a new line with County, CQ, ITU and DXCC.", - "Station Control: the short and long path headings are repeated under the compass, at a readable size and clickable." + "Station Control: the short and long path headings are repeated under the compass, at a readable size and clickable.", + "Compact mode: the window now fits the entry strip instead of leaving a band of empty space below it." ], "fr": [ "Cluster : « Masquer les contactés » ne masque plus un spot qui est un nouveau préfixe, comté, carré ou parc dans une contrée déjà faite.", "Cluster DX : on peut fixer une durée de vie des spots — 5, 10, 15 minutes ou une valeur libre — au-delà de laquelle ils quittent la liste et les band maps.", "Recherche d indicatif : QRZ.com remplit désormais Nom avec le seul prénom, et au choix avec le surnom de l opérateur.", "Saisie : colonne de gauche plus étroite, État à côté du locator, et une nouvelle ligne Comté, CQ, ITU et DXCC.", - "Contrôle station : les azimuts court et long chemin sont repris sous la boussole, lisibles et cliquables." + "Contrôle station : les azimuts court et long chemin sont repris sous la boussole, lisibles et cliquables.", + "Mode compact : la fenêtre épouse la bande de saisie au lieu de laisser une bande vide en dessous." ] }, { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ae37c78..fd588f0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,7 +16,7 @@ import { GetStartupStatus, CheckForUpdate, DownloadAndApplyUpdate, GetLiveStations, GetWhatsNew, GetChangelog, SMTPConfigured, SendLogToDeveloper, WorkedBefore, - SetCompactMode, + SetCompactMode, SetCompactHeight, GetCATState, SetCATFrequency, SetCATMode, SwitchCATRig, FlexApplyBandAntenna, FlexApplyBandPower, GetSecretStatus, UnlockSecrets, RefreshCtyDat, DownloadAllReferenceLists, @@ -529,6 +529,28 @@ export default function App() { setCompact(next); SetCompactMode(next); } + // Fit the compact window to what was actually rendered. + // + // The height was a constant "tuned so the compact entry strip fits in a single + // row". A constant tuned against a layout stops being true the moment the + // layout changes, and this one outlived a strip that had since lost a row — + // leaving a band of empty window under the fields. Measure instead. + const compactRootRef = useRef(null); + useEffect(() => { + if (!compact) return; + const fit = () => { + const el = compactRootRef.current; + if (!el) return; + // Rounded, so a sub-pixel reflow cannot start a resize loop. + // + the compact topbar, which is a fixed h-8 (32px), + a couple of pixels + // for the border so the strip is never clipped by one row of anti-aliasing. + SetCompactHeight(Math.round(el.getBoundingClientRect().height) + 32 + 2); + }; + const id = window.setTimeout(fit, 60); // let the strip paint first + const ro = new ResizeObserver(fit); + if (compactRootRef.current) ro.observe(compactRootRef.current); + return () => { window.clearTimeout(id); ro.disconnect(); }; + }, [compact]); // CAT — receives live rig state via Wails events. const [catState, setCatState] = useState({ enabled: false, connected: false } as any); @@ -5892,7 +5914,7 @@ export default function App() { Enter from any inside the strip logs the QSO. Radix Selects render as