[fix](trx-frontend-http): let the decode lists fill their panel
CI / test (push) Successful in 8m16s
CI / lint (push) Successful in 2m18s
CI / frontend (push) Successful in 3m40s
CI / reuse (push) Successful in 3s

FT8, FT4, FT2 and WSPR size their list against the panel with flex, and
the sidebar layout made the panel a grid item aligned to the start of
its row — sized to its own content.  The lists collapsed to their 120px
minimum with several hundred pixels of the page empty underneath.  The
panel stretches to the row now and the sidebar keeps its own height.

The marine lists were sized a different way, by formula: 100vh minus a
guess at everything above them.  That guess stopped matching the moment
the panel changed shape, so they left a few hundred pixels unused as
well.  They fill the panel like the rest now, and so does CW, which had
a 360px ceiling.

HF APRS had no container styling at all — no scroller, no frame, no
height — so its packets ran down the page.  It gets what the other
packet lists have.

The smoke test measures each list against its panel and requires it to
scroll on its own.

Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
sjg
2026-08-04 22:49:28 +02:00
parent b48cc23d6e
commit 67a7bace4e
2 changed files with 54 additions and 25 deletions
@@ -2085,9 +2085,15 @@ small { color: var(--text-muted); }
display: grid; display: grid;
grid-template-columns: minmax(8.5rem, 11rem) minmax(0, 1fr); grid-template-columns: minmax(8.5rem, 11rem) minmax(0, 1fr);
gap: 1rem; 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 { #tab-digital-modes > .sub-tab-bar {
/* The list keeps its own height while the panel stretches. */
align-self: start;
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
gap: 0.12rem; gap: 0.12rem;
@@ -2130,6 +2136,7 @@ small { color: var(--text-muted); }
grid-column: 2; grid-column: 2;
grid-row: 1; grid-row: 1;
min-width: 0; 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 { 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; } .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, #aprs-packets,
#ais-messages, #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; } #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 { /* Fill the panel, the way the FT8, FT4, FT2 and WSPR lists do. These were
flex: 0 1 auto; sized by formula against the viewport — 100vh minus a guess at everything
height: calc(100vh - 28rem); above them — which stopped matching the moment the panel changed shape, and
min-height: 16rem; left a few hundred pixels of the page empty under a scrolling list. */
max-height: calc(100vh - 28rem); #aprs-packets,
#ais-messages,
#vdes-messages,
#hf-aprs-packets {
flex: 1 1 0;
min-height: 12rem;
max-height: none;
} }
#ais-messages { /* HF APRS had no container styling at all: no scroller, no frame, no height —
flex: 0 1 auto; its packets simply ran down the page. */
height: calc(100vh - 24rem); #hf-aprs-packets {
min-height: 16rem; overflow-y: auto;
max-height: calc(100vh - 24rem); border: 1px solid var(--border-light);
} border-radius: 6px;
#vdes-messages { background: var(--input-bg);
flex: 0 1 auto; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
height: calc(100vh - 24rem);
min-height: 16rem;
max-height: calc(100vh - 24rem);
} }
.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 { 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; } .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-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-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-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-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-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; } .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 { #subtab-aprs {
min-height: calc(100vh - 14rem); min-height: calc(100vh - 14rem);
} }
#aprs-packets { #aprs-packets,
min-height: calc(100vh - 26rem); #ais-messages,
}
#ais-messages {
min-height: calc(100vh - 22rem);
}
#vdes-messages { #vdes-messages {
min-height: calc(100vh - 22rem); min-height: 60vh;
} }
.aprs-details-grid { .aprs-details-grid {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
@@ -47,6 +47,29 @@ try {
assert.ok(digital.panelBesideSidebar, "the decoder panel does not sit beside the sidebar"); 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.deepEqual(digital.panelsShown, ["subtab-ft8"], `panels shown: ${JSON.stringify(digital.panelsShown)}`);
assert.ok(digital.decoders >= 10, `only ${digital.decoders} decoders in the sidebar`); 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.locator('.tab[data-tab="main"]').click();
await page.waitForTimeout(200); await page.waitForTimeout(200);