Merge origin/main: keep the provider registry, slim it

main grew the Street Level provider registry while this branch removed it
(#982–#985: injected providers, an empty composition, the `r` switch field).
The registry, params, uiState and pick router come back as main has them and
the slimmed layer sits behind the provider contract:

- The core (index.js) drives providers as on main, without surface modes,
  ground casting, FOLLOW, prewarm and nearest-image lookups. A provider's key
  status is asked with a 10 s timeout and asked again on a switch-on while no
  key is known.
- Mapillary is mapillaryProvider.js over the slim coverage, sequences and
  viewer modules, which read the filter and the active flag from the provider
  context.
- The viewer host mounts one provider adapter at a time, no prewarm.
- The panel has a chip per provider again (the pill switches the layer);
  no legend, EXPAND stays the Fullscreen API.
- The contract drops what no provider implements: groundCaster, meshSampler,
  getSurface/setSurface, nearestImage, prewarm, reportError, the legend and
  surface fields of the UI snapshot.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Fable 5.1 committed 2026-10-13 13:24:25 +04:00
commit c29ccd55d6
103 files changed
+5469 -522

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { composeUIState, summarizeCoverage } from './uiState.js';
import { providerSnapshot } from '../../testSupport/streetLevelFakes.mjs';
const provider = (overrides = {}) =>
providerSnapshot({ count: 10, ...overrides });
const base = {
enabled: true,
filter: { pano: 'all', sinceDays: 0 },
street: { open: false },
sequence: { selectedId: null, images: 0, loading: false },
};
test('counts add up across active providers only', () => {
const ui = composeUIState({
...base,
providers: [
provider(),
provider({ id: 'panoramax', name: 'Panoramax', count: 5, on: false }),
provider({ id: 'kartaview', name: 'KartaView', count: 7, loading: true }),
],
});
assert.equal(ui.coverage.count, 17);
assert.equal(ui.coverage.loading, true);
});
test('the layer is key-gated only when every switched-on provider lacks its key', () => {
const gated = composeUIState({
...base,
providers: [provider({ keyRequired: true })],
});
assert.equal(gated.keyRequired, true);
const mixed = composeUIState({
...base,
providers: [
provider({ keyRequired: true }),
provider({ id: 'panoramax', name: 'Panoramax', requiresKeyId: null }),
],
});
assert.equal(mixed.keyRequired, false, 'a keyless provider still draws');
const offOnly = composeUIState({
...base,
providers: [provider({ keyRequired: true, on: false })],
});
assert.equal(offOnly.keyRequired, false, 'nothing switched on to gate');
});
test('hint and error come from the first active provider that has one', () => {
const ui = composeUIState({
...base,
providers: [
provider({ hint: '', error: null }),
provider({ id: 'b', name: 'B', hint: 'Look down', error: 'boom' }),
],
});
assert.equal(ui.coverage.hint, 'Look down');
assert.equal(ui.coverage.error, 'boom');
assert.deepEqual(ui.filter, base.filter);
assert.notEqual(ui.filter, base.filter, 'snapshot copies the filter');
});
test('summarizeCoverage is what getStats reports, without building a snapshot', () => {
assert.deepEqual(
summarizeCoverage([
provider({ count: 4, hint: 'Look down' }),
provider({ id: 'b', name: 'B', count: 6, loading: true, error: 'boom' }),
provider({ id: 'c', name: 'C', count: 99, on: false, keyRequired: true }),
]),
{
count: 10,
loading: true,
hint: 'Look down',
error: 'boom',
keyRequired: false,
keyRejected: false,
},
);
assert.equal(summarizeCoverage([]).keyRequired, false);
});
test('a rejected key gates like a missing one and is named as rejected', () => {
const rejected = provider({
keyRequired: true,
keyRejected: true,
error: 'Mapillary rejected MAPILLARY_CLIENT_TOKEN',
});
const summary = summarizeCoverage([rejected]);
assert.equal(summary.keyRequired, true);
assert.equal(summary.keyRejected, true);
assert.equal(summary.error, 'Mapillary rejected MAPILLARY_CLIENT_TOKEN');
const state = composeUIState({ ...base, providers: [rejected] });
assert.equal(state.keyRequired, true);
assert.equal(state.keyRejected, true);
// A provider that is merely missing its key is not "rejected".
assert.equal(
summarizeCoverage([provider({ keyRequired: true })]).keyRejected,
false,
);
});