From 4b56d0e91775e95baf1169f027cc793e401aa812 Mon Sep 17 00:00:00 2001 From: Bilawal Sidhu <106619546+bilawalsidhu@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:51:56 -0500 Subject: [PATCH] fix(radio): restore the Radio QA harness and the row refresh it depended on (#736) The harness called the compatibility facade's private _refreshTogglePanel, which the layer-lifecycle refactor removed; Transit and Directions call refreshLayerStats() on the bare lifecycle, which lacked it, so their rows only repainted on the next tick. Add refreshLayerStats() to the lifecycle and use it from the harness. With the crash gone, two further checks ran: the selected-entity paint wait now also waits for Radio's own reveal, and on phones the title bar sits 16 px from the top so both broadcast waves stay on-screen. Co-authored-by: Bilawal Sidhu --- CHANGELOG.md | 7 +++++++ scripts/qa-radio.mjs | 12 +++++++++--- src/data/lifecycle.js | 8 ++++++++ src/data/manager.test.mjs | 9 +++++++++ src/ui/styles/responsive.css | 3 ++- 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 544f3780b..0a7b13888 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +- Transit and Directions rows repaint as soon as their data lands again: + `refreshLayerStats()` now lives on the layer lifecycle, not only on the + compatibility facade. `scripts/qa-radio.mjs` uses it instead of a private + panel method. +- On phones the title bar sits 16 px from the top so both Radio broadcast + waves stay on-screen. + - Add a **Recent Imagery** data layer (NASA GIBS ยท HLS + VIIRS, keyless). Select a box (drag, the current view, or around a pin; up to 1,000 km a side) and the right-rail panel lists the last 30 days of Sentinel-2 / diff --git a/scripts/qa-radio.mjs b/scripts/qa-radio.mjs index c5629d2f4..c30a17ee0 100644 --- a/scripts/qa-radio.mjs +++ b/scripts/qa-radio.mjs @@ -374,7 +374,7 @@ async function main() { enabled: true, uncertain: true, }); - gev.dataManager._refreshTogglePanel(); + gev.dataManager.refreshLayerStats(); const staticResult = module.setTuningStatic(true); const filterBefore = module.getUIState().filter; const filterControl = document.getElementById('radio-filter'); @@ -447,7 +447,7 @@ async function main() { enabled: true, uncertain: false, }); - gev.dataManager._refreshTogglePanel(); + gev.dataManager.refreshLayerStats(); module.cancelTuning(); camera.flyTo = originalFlyTo; return result; @@ -4048,7 +4048,13 @@ async function main() { const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js'); const expectedEntryId = `selected:${selected.id}`; let painted = null; - for (let attempt = 0; attempt < 20 && !painted?.rect; attempt += 1) { + // Radio's horizon pass reveals the selected entity on its own timer + // after the camera jump, so a stationary view waits for both to settle. + for ( + let attempt = 0; + attempt < 20 && !(painted?.rect && selectedEntity?.show === true); + attempt += 1 + ) { painted = await new Promise((resolve) => { let removePostRender = null; const timeout = setTimeout(() => { diff --git a/src/data/lifecycle.js b/src/data/lifecycle.js index e083f1677..e501d04df 100644 --- a/src/data/lifecycle.js +++ b/src/data/lifecycle.js @@ -2304,6 +2304,14 @@ export class LayerLifecycle { return () => this._activityListeners.delete(callback); } + /** + * Ask presentation to repaint layer rows now, for data that lands outside + * the manager tick (Transit's proximity polls, Directions' route steps). + */ + refreshLayerStats() { + this._publishActivity({ type: 'status' }); + } + _publishActivity(change) { for (const callback of this._activityListeners) { try { diff --git a/src/data/manager.test.mjs b/src/data/manager.test.mjs index 5c9edc107..fce64ae8f 100644 --- a/src/data/manager.test.mjs +++ b/src/data/manager.test.mjs @@ -3477,3 +3477,12 @@ test('row info and legends reconcile in place while chips retain focus and order else globalThis.document = originalDocument; } }); + +test('refreshLayerStats reaches presentation through the bare lifecycle', async () => { + const { LayerLifecycle } = await import('./lifecycle.js'); + const lifecycle = new LayerLifecycle({}); + const changes = []; + lifecycle.subscribeActivity((change) => changes.push(change.type)); + lifecycle.refreshLayerStats(); + assert.deepEqual(changes, ['status']); +}); diff --git a/src/ui/styles/responsive.css b/src/ui/styles/responsive.css index 16817ac10..78faa0073 100644 --- a/src/ui/styles/responsive.css +++ b/src/ui/styles/responsive.css @@ -255,8 +255,9 @@ } @media (max-width: 520px) { + /* 16px keeps the Radio broadcast waves (15px above the logo) on-screen. */ #title-bar { - top: 14px; + top: 16px; left: 16px; }