fix(ui): the "kept in the link" notice is readable on Navara

On Navara the engine's persistent notice came up right after the share
restore's NOT ON THIS ENGINE notice and replaced it at once, so the names
of the linked layers this engine lacks were never seen (and a later finite
notice would in turn have wiped the engine notice for good).

- ShellFeedback keeps a persistent notice that a finite one covers and
  brings it back when the finite one ends; a persistent notice that arrives
  under a live finite one waits for it.
- Notices take a dwellMs; "Kept in the link: …" stays 15 s.
- scripts/qa-engine-notice.mjs (headless): a Cesium share link with a layer
  Navara lacks, opened with ?engine=navara: notice shown with the layer
  name, still up 14 s later, then the engine notice is back. 4/4 (before
  the fix the notice never appeared).

T8-T9 review item 2.
This commit is contained in:
German Khokhlov committed 2026-10-16 23:27:47 +04:00
1 parent c493a8303b
commit 9f1a0611fa
5 files changed
+103 -5

No files matched your search

+2 -1
View File
@@ -1109,5 +1109,6 @@
"src/app/layers/qaSources.test.mjs",
"scripts/qa-w1-layers.mjs",
"src/bench/w1Feeds.js",
"scripts/qa-map-switch.mjs"
"scripts/qa-map-switch.mjs",
"scripts/qa-engine-notice.mjs"
]
+6 -2
View File
@@ -130,7 +130,7 @@ export function createLoadingFeedbackState() {
export function createGlobalStatusNotice(
message,
nowMs = 0,
{ state = 'error', detail = '', persistent = false } = {},
{ state = 'error', detail = '', persistent = false, dwellMs } = {},
) {
const label = String(message || '').trim();
if (!label) return null;
@@ -139,7 +139,11 @@ export function createGlobalStatusNotice(
label,
detail: String(detail || '').trim(),
persistent: !!persistent,
dwellMs: persistent ? null : LOADING_FAILURE_DWELL_MS,
dwellMs: persistent
? null
: Number.isFinite(dwellMs) && dwellMs > 0
? dwellMs
: LOADING_FAILURE_DWELL_MS,
// A finite notice starts its dwell only when it first wins presentation.
// Otherwise a higher-priority manager failure could consume the whole
// deadline while this notice remained queued and invisible.
+6
View File
@@ -1,6 +1,9 @@
import { LayerStateCoordinator } from '../data/layerStateCoordinator.js';
import { APPLICATION_LAYER_DIRECTORY } from '../app/layers/directory.js';
/** How long the "kept in the link" notice stays up (ms). */
export const NOT_ON_THIS_ENGINE_DWELL_MS = 15_000;
/** Names of the layers a link turned on that this engine does not have. */
export function layersNotOnThisEngine(results) {
return results
@@ -189,6 +192,9 @@ export class ShareRestoration {
this.showStatus('NOT ON THIS ENGINE', {
state: 'retry',
detail: `Kept in the link: ${names.join(', ')}`,
// Long enough to read a list of layer names; the engine's own
// persistent notice comes back afterwards (ShellFeedback).
dwellMs: NOT_ON_THIS_ENGINE_DWELL_MS,
});
}
cancelSelection() {
+20 -2
View File
@@ -17,6 +17,9 @@ export class ShellFeedback {
this._trafficSyncFeedbackState = createTrafficSyncFeedbackState();
this._loadingFeedbackEvent = null;
this._globalStatusNotice = null;
// A persistent notice (the Navara engine notice) that a finite one
// covers for its dwell; it comes back when the finite one ends.
this._coveredPersistentNotice = null;
this._loadingFeedbackTicker =
this._trafficChipTicker =
this._toastTimer =
@@ -111,7 +114,8 @@ export class ShellFeedback {
Number.isFinite(this._globalStatusNotice?.hideAt) &&
now >= this._globalStatusNotice.hideAt
) {
this._globalStatusNotice = null;
this._globalStatusNotice = this._coveredPersistentNotice;
this._coveredPersistentNotice = null;
}
// Loading phases and universal notices both have time-driven transitions.
// Compute this after arbitration: a queued finite notice starts its dwell
@@ -138,7 +142,21 @@ export class ShellFeedback {
_showGlobalStatusNotice(message, options = {}) {
if (this.destroyed) return;
const now = performance.now();
this._globalStatusNotice = createGlobalStatusNotice(message, now, options);
const next = createGlobalStatusNotice(message, now, options);
const current = this._globalStatusNotice;
if (next?.persistent && current && !current.persistent) {
// A persistent notice arriving under a live finite one (the engine
// notice after a share link's "kept in the link") waits for it.
this._coveredPersistentNotice = next;
this._updateGlobalLoadingFeedback(now);
return;
}
if (next && !next.persistent) {
if (current?.persistent) this._coveredPersistentNotice = current;
} else {
this._coveredPersistentNotice = null;
}
this._globalStatusNotice = next;
this._updateGlobalLoadingFeedback(now);
}
+69
View File
@@ -404,3 +404,72 @@ test('busy traffic completion is polled promptly without accelerating idle or gl
f.restore();
}
});
test('a finite notice covers the persistent engine notice only for its dwell', () => {
const f = fixture();
const realNow = performance.now;
let now = 1_000;
performance.now = () => now;
try {
const owner = new ShellFeedback({ readLayers: () => [] });
const detail = () => f.nodes.get('global-loading-detail').textContent;
owner._showGlobalStatusNotice('Experimental Navara engine', {
state: 'retry',
detail: 'No data layers on this engine yet',
persistent: true,
});
assert.equal(detail(), 'No data layers on this engine yet');
owner._showGlobalStatusNotice('NOT ON THIS ENGINE', {
state: 'retry',
detail: 'Kept in the link: CCTV',
dwellMs: 15_000,
});
assert.equal(detail(), 'Kept in the link: CCTV');
now += 14_000;
owner._updateGlobalLoadingFeedback(now);
assert.equal(detail(), 'Kept in the link: CCTV', 'still up after 14 s');
now += 2_000;
owner._updateGlobalLoadingFeedback(now);
owner._updateGlobalLoadingFeedback(now);
assert.equal(
detail(),
'No data layers on this engine yet',
'the engine notice comes back',
);
assert.equal(owner._globalStatusNotice?.persistent, true);
owner.destroy();
} finally {
performance.now = realNow;
f.restore();
}
});
test('a persistent notice that arrives under a finite one waits for it', () => {
const f = fixture();
const realNow = performance.now;
let now = 1_000;
performance.now = () => now;
try {
const owner = new ShellFeedback({ readLayers: () => [] });
const detail = () => f.nodes.get('global-loading-detail').textContent;
owner._showGlobalStatusNotice('NOT ON THIS ENGINE', {
state: 'retry',
detail: 'Kept in the link: Earthquakes',
dwellMs: 15_000,
});
owner._showGlobalStatusNotice('Experimental Navara engine', {
state: 'retry',
detail: 'No data layers on this engine yet',
persistent: true,
});
assert.equal(detail(), 'Kept in the link: Earthquakes');
now += 16_000;
owner._updateGlobalLoadingFeedback(now);
owner._updateGlobalLoadingFeedback(now);
assert.equal(detail(), 'No data layers on this engine yet');
owner.destroy();
} finally {
performance.now = realNow;
f.restore();
}
});