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 <[email protected]>
This commit is contained in:
Bilawal SidhuandBilawal Sidhu authored and GitHub committed 2026-09-23 19:51:56 -05:00
1 parent 1fc795577a
commit 4b56d0e917
5 files changed
+35 -4

No files matched your search

+7
View File
@@ -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 /
+9 -3
View File
@@ -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(() => {
+8
View File
@@ -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 {
+9
View File
@@ -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']);
});
+2 -1
View File
@@ -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;
}