diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css index d8a4b834..adb8716f 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/style.css @@ -2085,9 +2085,15 @@ small { color: var(--text-muted); } display: grid; grid-template-columns: minmax(8.5rem, 11rem) minmax(0, 1fr); gap: 1rem; - align-items: start; + /* The panel takes the full height of the tab, which is what the decode + lists inside it size against: FT8, FT4, FT2 and WSPR fill their panel + with flex, so a panel sized to its own content collapsed them to their + 120px minimum however much room was going spare. */ + align-items: stretch; } #tab-digital-modes > .sub-tab-bar { + /* The list keeps its own height while the panel stretches. */ + align-self: start; flex-direction: column; align-items: stretch; gap: 0.12rem; @@ -2130,6 +2136,7 @@ small { color: var(--text-muted); } grid-column: 2; grid-row: 1; min-width: 0; + min-height: 0; } .sub-tab { flex-shrink: 0; background: transparent; border: none; border-bottom: 2px solid transparent; border-radius: 0; padding: 0.35rem 0.75rem; color: var(--text-muted); cursor: pointer; font-size: 0.85rem; height: auto; } .sub-tab.active { border-bottom-color: var(--accent-green); color: var(--accent-green); font-weight: 600; } @@ -2816,23 +2823,26 @@ body.map-fake-fullscreen-active { #aprs-packets, #ais-messages, #vdes-messages { max-height: 360px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } -#aprs-packets { - flex: 0 1 auto; - height: calc(100vh - 28rem); - min-height: 16rem; - max-height: calc(100vh - 28rem); +/* Fill the panel, the way the FT8, FT4, FT2 and WSPR lists do. These were + sized by formula against the viewport — 100vh minus a guess at everything + above them — which stopped matching the moment the panel changed shape, and + left a few hundred pixels of the page empty under a scrolling list. */ +#aprs-packets, +#ais-messages, +#vdes-messages, +#hf-aprs-packets { + flex: 1 1 0; + min-height: 12rem; + max-height: none; } -#ais-messages { - flex: 0 1 auto; - height: calc(100vh - 24rem); - min-height: 16rem; - max-height: calc(100vh - 24rem); -} -#vdes-messages { - flex: 0 1 auto; - height: calc(100vh - 24rem); - min-height: 16rem; - max-height: calc(100vh - 24rem); +/* HF APRS had no container styling at all: no scroller, no frame, no height — + its packets simply ran down the page. */ +#hf-aprs-packets { + overflow-y: auto; + border: 1px solid var(--border-light); + border-radius: 6px; + background: var(--input-bg); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .aprs-packet { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.82rem; padding: 0.45rem 0.55rem; border-bottom: 1px solid var(--border); line-height: 1.35; } .aprs-packet:last-child { border-bottom: none; } @@ -3387,7 +3397,7 @@ body.map-fake-fullscreen-active { .cw-tone-picker-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.35rem; color: var(--text-muted); font-size: 0.78rem; } #cw-tone-waterfall { width: 100%; height: 56px; display: block; border-radius: 6px; background: linear-gradient(180deg, rgba(8, 14, 18, 0.92), rgba(18, 28, 36, 0.98)); cursor: crosshair; } .cw-tone-picker.is-auto #cw-tone-waterfall { cursor: not-allowed; } -#cw-output { max-height: 360px; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.85rem; padding: 0.4rem 0.5rem; min-height: 60px; white-space: pre-wrap; word-break: break-all; } +#cw-output { flex: 1 1 0; max-height: none; overflow-y: auto; border: 1px solid var(--border-light); border-radius: 6px; background: var(--input-bg); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 0.85rem; padding: 0.4rem 0.5rem; min-height: 60px; white-space: pre-wrap; word-break: break-all; } .cw-line { line-height: 1.5; } .cw-signal-on { width: 10px; height: 10px; border-radius: 50%; background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green); flex-shrink: 0; } .cw-signal-off { width: 10px; height: 10px; border-radius: 50%; background: var(--border-light); flex-shrink: 0; } @@ -4006,14 +4016,10 @@ body[data-operator-layout="broadcast"] #cw-bar-overlay { #subtab-aprs { min-height: calc(100vh - 14rem); } - #aprs-packets { - min-height: calc(100vh - 26rem); - } - #ais-messages { - min-height: calc(100vh - 22rem); - } + #aprs-packets, + #ais-messages, #vdes-messages { - min-height: calc(100vh - 22rem); + min-height: 60vh; } .aprs-details-grid { grid-template-columns: minmax(0, 1fr); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs index cabaa179..6b6fcff7 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs @@ -47,6 +47,29 @@ try { assert.ok(digital.panelBesideSidebar, "the decoder panel does not sit beside the sidebar"); assert.deepEqual(digital.panelsShown, ["subtab-ft8"], `panels shown: ${JSON.stringify(digital.panelsShown)}`); assert.ok(digital.decoders >= 10, `only ${digital.decoders} decoders in the sidebar`); + + // Each decoder's list fills its panel. FT8, FT4, FT2 and WSPR size against + // the panel with flex, so a panel sized to its own content collapsed them to + // their 120px minimum with the rest of the page left empty; the marine lists + // were sized by a viewport formula that stopped matching when the panel + // changed shape. + for (const [subtab, list] of [["ft8", "ft8-messages"], ["wspr", "wspr-messages"], + ["ais", "ais-messages"], ["aprs", "aprs-packets"], ["hf-aprs", "hf-aprs-packets"]]) { + await page.locator(`.sub-tab[data-subtab="${subtab}"]`).click(); + await page.waitForTimeout(150); + const filled = await page.evaluate((id) => { + const element = document.getElementById(id); + const panel = element.closest(".sub-tab-panel"); + return { + list: Math.round(element.getBoundingClientRect().height), + panel: Math.round(panel.getBoundingClientRect().height), + scrolls: getComputedStyle(element).overflowY, + }; + }, list); + assert.ok(filled.list > filled.panel * 0.6, + `${subtab}: the list is ${filled.list}px in a ${filled.panel}px panel`); + assert.equal(filled.scrolls, "auto", `${subtab}: the list does not scroll on its own`); + } await page.locator('.tab[data-tab="main"]').click(); await page.waitForTimeout(200);