feat(street-level): one colour per imagery source

As more sources arrive, each one reads the same everywhere it appears: its
PROVIDERS chip, coverage lines, overview points, image cones and a single
legend swatch. The palette lives in one place, PROVIDER_COLORS in
src/layers/streetLevel/policy.js: Mapillary green (#05cb63), Panoramax
purple, Google Street View blue.

Mapillary drops its age dimming and magenta panoramas: lines and overview
points are Mapillary green, cones are green too and 360° images keep their
ring shape; the 360°/FLAT filter still separates them, and the selected
sequence stays GEV cyan.

The provider contract loses `legend`: `colors.coverage` is the source's one
colour, and the core composes the legend as one swatch per active source
(its name, its colour) followed by Selected. The chip group takes an
optional per-chip colour as a `--chip-color` CSS variable, written only
when it changes; Street Level chips show it as a swatch dot and, while on,
as the chip's border, text and a faint fill.

The feature gate checks the legend has one swatch per source plus Selected
and that the Mapillary chip carries #05cb63.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-09-29 00:03:00 +01:00
1 parent d5da45eacb
commit 0f2cac2ca2
21 files changed
+188 -99

No files matched your search

+25 -17
View File
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { composeUIState, summarizeCoverage } from './uiState.js';
import { COLORS } from './policy.js';
const provider = (overrides = {}) => ({
id: 'mapillary',
@@ -14,7 +15,7 @@ const provider = (overrides = {}) => ({
count: 10,
hint: '',
error: null,
legend: [{ key: 'recent', label: 'Recent', color: '#0f0' }],
color: '#05cb63',
...overrides,
});
const base = {
@@ -58,30 +59,37 @@ test('the layer is key-gated only when every switched-on provider lacks its key'
assert.equal(offOnly.keyRequired, false, 'nothing switched on to gate');
});
test('the legend lists active providers, prefixed once more than one is registered', () => {
test('the legend is one swatch per active source, in its colour, then Selected', () => {
const single = composeUIState({ ...base, providers: [provider()] });
assert.deepEqual(
single.legend.map((entry) => [entry.key, entry.label]),
[
['mapillary:recent', 'Recent'],
['selected', 'Selected'],
],
);
assert.deepEqual(single.legend, [
{ key: 'mapillary', label: 'Mapillary', color: '#05cb63' },
{ key: 'selected', label: 'Selected', color: COLORS.selected },
]);
const two = composeUIState({
...base,
providers: [
provider(),
provider({
id: 'panoramax',
name: 'Panoramax',
on: false,
legend: [{ key: 'coverage', label: 'Coverage', color: '#00f' }],
}),
provider({ id: 'panoramax', name: 'Panoramax', color: '#a66bff' }),
],
});
assert.deepEqual(
two.legend.map((entry) => entry.label),
['Mapillary Recent', 'Selected'],
two.legend.map((entry) => [entry.label, entry.color]),
[
['Mapillary', '#05cb63'],
['Panoramax', '#a66bff'],
['Selected', COLORS.selected],
],
);
const oneOff = composeUIState({
...base,
providers: [
provider(),
provider({ id: 'panoramax', name: 'Panoramax', on: false }),
],
});
assert.deepEqual(
oneOff.legend.map((entry) => entry.key),
['mapillary', 'selected'],
);
assert.deepEqual(
composeUIState({ ...base, providers: [provider({ on: false })] }).legend,