docs(street-level): shorten comments and docs, drop audit references

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-08 17:44:32 +04:00
1 parent d59b4d05bb
commit 48b889590d
12 files changed
+41 -76

No files matched your search

+6 -8
View File
@@ -71,16 +71,14 @@
proposed diagnosis in #927, and [kvnloo](https://github.com/kvnloo) for identifying proposed diagnosis in #927, and [kvnloo](https://github.com/kvnloo) for identifying
the repair race. Windows/Claude Desktop Cowork confirmation remains outstanding. the repair race. Windows/Claude Desktop Cowork confirmation remains outstanding.
- Street Level: Mapillary street-level imagery modelled on the iD editor's - Street Level: Mapillary street-level imagery (free client token, CC BY-SA
photo overlay (free client token, CC BY-SA 4.0 imagery). A docked right-rail 4.0). A right-rail panel holds an ON/OFF pill, 360°/flat and captured-since
panel holds an ON/OFF pill, 360°/flat and captured-since filters, a legend filters, a legend and an embedded viewer with EXPAND and FIT/FILL. Coverage
and an embedded viewer with full-screen EXPAND and FIT/FILL. Coverage lines lines (below 60 km) and image cones drape on terrain and Google 3D tiles;
(below 60 km) and image cones drape on the terrain and Google 3D tiles;
opening a photo frames it with one camera flight. Share links carry the opening a photo frames it with one camera flight. Share links carry the
switch, the filters and the panel's collapsed state. A server proxy adds the switch, the filters and the panel's collapsed state. A server proxy adds the
token to coverage-tile requests, trims the tiles and caches them in memory token to coverage tiles, trims and caches them for 24 h, refuses cross-site
for 24 h; it refuses cross-site requests and upstream redirects, rate-limits requests and redirects, and limits load per IP and upstream. The panel reads
per IP and runs at most six Mapillary fetches at once. The panel reads
KEY REQUIRED without a token and KEY REJECTED when Mapillary refuses it. KEY REQUIRED without a token and KEY REJECTED when Mapillary refuses it.
- Add Norway's road cameras from Statens vegvesen as a CCTV pack. The camera - Add Norway's road cameras from Statens vegvesen as a CCTV pack. The camera
+3 -4
View File
@@ -66,10 +66,9 @@ gate for the feature you changed.
> **CI does not cover this for you.** The workflow runs the setup policy > **CI does not cover this for you.** The workflow runs the setup policy
> checks, the formatting and package-boundary checks, the unit suite and the > checks, the formatting and package-boundary checks, the unit suite and the
> production build, plus a Windows onboarding job. It runs neither > production build, plus a Windows onboarding job. It runs neither
> `npm run test:track` nor any other `qa-*.mjs` gate — both need a live dev > `npm run test:track` nor any `qa-*.mjs` gate except the Street Level fixture
> server and a browser. The one exception is the hermetic Street Level fixture > gate (`street-level-browser` job) — they need a live dev server and a
> gate (`street-level-browser` job). Include the applicable local run in your PR's validation > browser. Include the applicable local run in your PR's validation evidence.
> evidence.
## Share-link layer tokens ## Share-link layer tokens
+8 -12
View File
@@ -3028,9 +3028,8 @@ Street Level is a docked, collapsible right-rail panel (`#street-level-panel`,
layer token `0`, option owner `street-level`, panel `ui` token `t`) that starts layer token `0`, option owner `street-level`, panel `ui` token `t`) that starts
collapsed. It opens itself only when the user, voice or a tool switches the collapsed. It opens itself only when the user, voice or a tool switches the
layer on or a photo opens, never on a restore, and those opens are not stored. layer on or a photo opens, never on a restore, and those opens are not stored.
Like the iD editor's photo overlay it shows Mapillary only: 360°/flat and It has 360°/flat and captured-since filters (relative days, so a link keeps
captured-since filters (relative days, so a link keeps its meaning), one its meaning), one embedded viewer and an on-globe credit while the layer is on. Share options:
embedded viewer and an on-globe credit while the layer is on. Share options:
`m` (Mapillary on/off; off draws nothing), `p` (`a`/`p`/`f` panoramas), `s` `m` (Mapillary on/off; off draws nothing), `p` (`a`/`p`/`f` panoramas), `s`
(since, days). The header pill is the only layer switch. (since, days). The header pill is the only layer switch.
@@ -3047,9 +3046,8 @@ pauses them for its Retry-After.
The server proxy (`server/providers/mapillary.js`) adds the token to The server proxy (`server/providers/mapillary.js`) adds the token to
coverage-tile requests (the browser holds it too, for MapillaryJS and the Graph coverage-tile requests (the browser holds it too, for MapillaryJS and the Graph
API), serves only z11–14 coverage, refuses upstream redirects, strips the API), serves only z11–14 coverage, refuses upstream redirects, strips the
unused `image` layer, coalesces concurrent requests, runs at most six Mapillary unused `image` layer, shares one fetch per tile, runs at most six at once
fetches at once (a fetch every client has left is cancelled, or never started) (cancelling those every client has left) and keeps tiles in memory for 24 h.
and keeps tiles in a 24 h memory cache.
MapillaryJS loads on the first photo. Opening one frames it with a single MapillaryJS loads on the first photo. Opening one frames it with a single
camera flight through the application's deferred navigation (`begin` / camera flight through the application's deferred navigation (`begin` /
@@ -3064,12 +3062,10 @@ Mapillary back on. At phone width the viewer height comes from the rail band
so the whole photo fits. so the whole photo fits.
Browser gates: `npm run qa:street-level -- --url <server>` runs against real Browser gates: `npm run qa:street-level -- --url <server>` runs against real
Mapillary with a token; `npm run qa:street-level:fixtures` answers every Mapillary; `npm run qa:street-level:fixtures` answers every Mapillary request
Mapillary request from fixtures (`providers/mapillary/coverageFixture.mjs`, from fixtures (`providers/mapillary/coverageFixture.mjs`,
`scripts/fixtures/street-level/`); its server (default `:4173`) still needs a `scripts/fixtures/street-level/`), but its server (default `:4173`) still needs
`MAPILLARY_CLIENT_TOKEN`, and any value will do. CI runs the fixture gate with some `MAPILLARY_CLIENT_TOKEN`. CI runs it `--strict` against `vite preview`.
`--strict` against a production build served by `vite preview` with a dummy
token.
Directions is a keyless front end to the routing the voice agent already Directions is a keyless front end to the routing the voice agent already
uses. Its row chips are the whole interface: DRIVE / WALK / BIKE pick the uses. Its row chips are the whole interface: DRIVE / WALK / BIKE pick the
+2 -3
View File
@@ -57,9 +57,8 @@ Related keys (current versions):
- Panel collapsed state: `godsEyeView.v6.panelCollapsed.<panel-id>` — `'0'` open, - Panel collapsed state: `godsEyeView.v6.panelCollapsed.<panel-id>` — `'0'` open,
`'1'` closed, absent means the panel's own default. A view opened from a share `'1'` closed, absent means the panel's own default. A view opened from a share
link ignores the stored value entirely. link ignores the stored value entirely.
- Panel positions: `godsEyeView.v8.panelPos.<panel-id>` — the versioned name for a - Panel positions: `godsEyeView.v8.panelPos.<panel-id>` — the current layout
stored position. The current layout writes none, so deleting one changes writes none.
nothing.
- CCTV calibration: `godsEyeView.cctv.calibration.v2` - CCTV calibration: `godsEyeView.cctv.calibration.v2`
--- ---
+1 -4
View File
@@ -14,10 +14,7 @@ const DROP_LAYERS = ['image'];
/** A z14 tile with its image layer is ~11 MB; anything past this is wrong. */ /** A z14 tile with its image layer is ~11 MB; anything past this is wrong. */
export const TILE_MAX_BYTES = 48 * 1024 * 1024; export const TILE_MAX_BYTES = 48 * 1024 * 1024;
const FETCH_TIMEOUT_MS = 60_000; const FETCH_TIMEOUT_MS = 60_000;
/** /** Upstream fetches at once; more wait their turn. */
* Upstream fetches at once; more wait their turn. A fetch every client has
* left is cancelled, or never started, so the queue holds live tiles only.
*/
export const TILE_UPSTREAM_CONCURRENCY = 6; export const TILE_UPSTREAM_CONCURRENCY = 6;
/** Tiles change only when new imagery is processed. */ /** Tiles change only when new imagery is processed. */
export const TILE_TTL_MS = 24 * 60 * 60 * 1000; export const TILE_TTL_MS = 24 * 60 * 60 * 1000;
+2 -4
View File
@@ -4,10 +4,8 @@ import { plausibleSurfaceHeight, sampledSurfaceHeight } from './view.js';
/** Fly the globe camera once to frame a photo after it opens. */ /** Fly the globe camera once to frame a photo after it opens. */
export function createCameraFraming({ state }) { export function createCameraFraming({ state }) {
/** /**
* The application's camera authority (`attachNavigation`): `begin(noun)` * The application's camera authority: `reassert(begin())` succeeds only if
* takes a deferred ticket (a generation, or false when refused) that * nothing newer took the camera. Without it, framing flies directly.
* `reassert(generation)` honours, releasing tracking, only if nothing newer
* took the camera. Without it, framing flies directly.
*/ */
let navigation = null; let navigation = null;
/** Our framing flight while it is current; Cesium calls `cancel` when another flight starts. */ /** Our framing flight while it is current; Cesium calls `cancel` when another flight starts. */
+1 -5
View File
@@ -218,11 +218,7 @@ export function createStreetLevelLayer({
}; };
}, },
/** /** A photo frames itself only if it still owns the camera once loaded. */
* The application's camera authority, the deferred `begin(noun)` /
* `reassert(generation)` pair: a photo claims the camera when opening
* starts and frames only if it still owns it once loaded.
*/
attachNavigation(navigation) { attachNavigation(navigation) {
parts.framing.attachNavigation(navigation); parts.framing.attachNavigation(navigation);
}, },
+8 -13
View File
@@ -31,10 +31,9 @@ export function sampledSurfaceHeight(scene, carto) {
} }
/** /**
* Ellipsoidal height (m) of the ground under the camera, or null. The globe * Ellipsoidal height (m) of the ground under the camera, or null. Under
* answers where shown; Google 3D hides it, so the rendered surface does * Google 3D (globe hidden) the rendered surface counts, minus any roof above
* (roofs count: a building's height at most). A sample above the camera is * the camera.
* ignored: a raised ground would put the camera underground.
*/ */
export function groundUnderCamera(viewer) { export function groundUnderCamera(viewer) {
const carto = viewer?.camera?.positionCartographic; const carto = viewer?.camera?.positionCartographic;
@@ -81,11 +80,9 @@ function groundHit(camera, point, ellipsoid, maxRange) {
const BBOX_GRID = 5; const BBOX_GRID = 5;
/** /**
* Visible [west, south, east, north] degrees, or null when fewer than four * Visible [west, south, east, north] degrees from the rays that hit the
* rays hit. Only hits count, so a horizon cannot inflate the box to the world. * ground, or null when fewer than four do. `groundHeight` raises the ellipsoid
* `groundHeight` raises the ellipsoid to the ground (rays to the bare * to the ground (Denver is 1,600 m up); `maxRange` (m) drops far hits.
* ellipsoid under Denver land kilometres too far out) and `maxRange` (m)
* drops near-horizon hits.
*/ */
export function visibleBbox( export function visibleBbox(
viewer, viewer,
@@ -158,10 +155,8 @@ export function cameraNadir(viewer) {
} }
/** /**
* Where coverage tiles are ranked from: halfway between the ground under the * Where coverage tiles are ranked from: halfway between the nadir and the
* camera and the ground at the centre of the screen, so a tilted view loads * screen centre's ground, or the nadir when that is out of `maxRange`.
* what it looks at as well as what is near. The nadir when the centre ray
* misses the ground within `maxRange`. Options as `visibleBbox`.
*/ */
export function viewFocus( export function viewFocus(
viewer, viewer,
+1 -5
View File
@@ -1,10 +1,6 @@
import { freshStreet } from './state.js'; import { freshStreet } from './state.js';
/** /** The panel's photo viewer: its poses update `state.street` and the marker. */
* Owns the panel's viewer element and the photo viewer mounted in it (the
* MapillaryJS adapter, or a stand-in). Its poses update `state.street` and
* the marker; MapillaryJS loads on the first open.
*/
export function createViewerHost({ state, parts, adapter }) { export function createViewerHost({ state, parts, adapter }) {
/** Set once mounted, until `unmount`. */ /** Set once mounted, until `unmount`. */
let unsubscribe = null; let unsubscribe = null;
+1 -1
View File
@@ -45,7 +45,7 @@ const UNTRIMMED = tile([
{ name: 'image', payload: Buffer.alloc(4000, 1) }, { name: 'image', payload: Buffer.alloc(4000, 1) },
]); ]);
/** Mount the plugin and return `call(route, url, method, headers)`. */ /** Mount the plugin and return `call(route, url, method, headers, onResponse)`. */
function install(mode = 'configureServer') { function install(mode = 'configureServer') {
const routes = new Map(); const routes = new Map();
mapillaryProxy()[mode]({ mapillaryProxy()[mode]({
+3 -5
View File
@@ -79,9 +79,8 @@ export class PanelPositionControls {
this._restorePanelPosition(spec.id, spec.panel); this._restorePanelPosition(spec.id, spec.panel);
this._makePanelDraggable(spec.id, spec.panel, spec.handle); this._makePanelDraggable(spec.id, spec.panel, spec.handle);
} }
// Keep a positioned panel on-screen when its HEIGHT changes after restore — it expands to its // A restored panel grows a frame or two later, so the restore-time clamp
// full row set a frame or two later, so the restore-time clamp used a stale (shorter) height and // is stale: re-clamp on every size change.
// the panel could still hang off the bottom (audit U2). Re-clamp on every size change.
if (this._ppToggles && typeof ResizeObserver !== 'undefined') { if (this._ppToggles && typeof ResizeObserver !== 'undefined') {
this._draggableResizeObserver = new ResizeObserver(() => this._draggableResizeObserver = new ResizeObserver(() =>
this._reclampDraggablePanels(), this._reclampDraggablePanels(),
@@ -159,8 +158,7 @@ export class PanelPositionControls {
const pos = JSON.parse(raw); const pos = JSON.parse(raw);
if (!pos || typeof pos.left !== 'number' || typeof pos.top !== 'number') if (!pos || typeof pos.left !== 'number' || typeof pos.top !== 'number')
return; return;
// Clamp to the viewport: a position saved at one window size would otherwise land off-screen at // A position saved at another window size can land off-screen.
// another (audit U2 — observed a panel at x:-192). The drag handler clamps; restore must too.
const { left, top } = this._clampToViewport( const { left, top } = this._clampToViewport(
Math.round(pos.left), Math.round(pos.left),
Math.round(pos.top), Math.round(pos.top),
+5 -12
View File
@@ -105,10 +105,8 @@ export class StreetLevelControls {
this._setParams({ sinceDays: sinceDays() }), this._setParams({ sinceDays: sinceDays() }),
); );
this.listen(el.viewerClose, 'click', () => this.layer.closeViewer?.()); this.listen(el.viewerClose, 'click', () => this.layer.closeViewer?.());
// EXPAND is the browser's own fullscreen: it handles Esc, focus and the // EXPAND is the browser's fullscreen. iPhone Safari has no element
// rest of the page. iPhone Safari has no element fullscreen, so there the // fullscreen, so there it is a top-layer popover; with neither, no button.
// viewer opens as a top-layer popover over the page instead (Esc and the
// button close it). With neither, the button is not offered.
this._expandMode = this._expandMode =
typeof el.viewerWrap?.requestFullscreen === 'function' typeof el.viewerWrap?.requestFullscreen === 'function'
? 'fullscreen' ? 'fullscreen'
@@ -195,9 +193,8 @@ export class StreetLevelControls {
async _toggleEnabled() { async _toggleEnabled() {
const enabled = this.actions.isEnabled?.() === true; const enabled = this.actions.isEnabled?.() === true;
// The pill is the only switch. A share link or tool can switch Mapillary // The pill is the only switch: it also brings back Mapillary that a link
// off under a layer that is on; the pill then reads OFF, and switching on // or tool switched off under a layer that is on.
// brings Mapillary back (and the layer, if it is off too).
if (this._state?.providerOn === false) { if (this._state?.providerOn === false) {
this._setParams({ mapillary: true }); this._setParams({ mapillary: true });
if (enabled) return; if (enabled) return;
@@ -252,11 +249,7 @@ export class StreetLevelControls {
this._syncExpanded(); this._syncExpanded();
} }
/** /** Clean View, recording and the cockpit hide every panel, so they end EXPAND too. */
* While expanded, leave when Clean View, recording or the cockpit starts:
* they hide every panel, and the photo would stay over the globe (and in a
* recording).
*/
_watchPanelHidingModes(on) { _watchPanelHidingModes(on) {
const body = document.body; const body = document.body;
if (!on || !body) { if (!on || !body) {