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; }