- No pointer cursor over coverage lines (no other layer has one); a click
test replaces the hover tests.
- No legend: one source, one colour.
- The on-globe credit lives in index.js; credits.js is gone.
- Billboard glyphs are SVG data URLs with the same geometry, so tests need
no canvas stub.
- The tile cache keeps the 120 most recently used tiles, same 24 h TTL,
instead of a byte budget; listTileLayers was test-only.
- providers/mapillary/ is flattened into the layer and its policy merged
into one policy.js; the source's method list is a literal in the catalog.
- One fakeCesiumViewer for the layer tests.
- The fixture gate keeps the steps the unit tests do not cover: coverage and
credit, a rejected key, line -> cone -> photo, close, a keyless page.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Resolve the source-pack conflicts with Statens vegvesen (keep both packs).
The Norway pack's 900 default pushed the per-pack defaults to 5,204, past
the 4,500 catalog cap this PR enforces, so raise DEFAULT_CCTV_MAX_SOURCES
to 5,500 and CCTV_MAX_SOURCES_CEILING to 6,000.
A client drops the tiles a camera move leaves behind; their fetches no
longer run on or hold the six upstream slots ahead of the tiles in view.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- OPEN STREET VIEW in the PROVIDERS row opens Street View's nearest
panorama at the ground under the middle of the screen, without moving
the camera: Street View no longer needs a pointer. It is disabled (and
says so) until the camera is at street zoom.
- Off the Google 3D map Street View does not appear at all: no chip,
legend swatch, globe credit, layer source or button, and Google's script
is not loaded (the viewer has no prewarm; the script loads on first use).
The share option keeps the user's choice for when Google 3D returns.
- A ground click measures the camera height at the click rather than
reading the cached flag.
- A chip without a usable key explains it instead of lighting; it darkens
beside a lit chip, and the other chips keep switching, so a refused
Street View key cannot lock Mapillary out.
- The gate checks that nothing of Street View shows off Google 3D even when
a share link switches it on, presses OPEN STREET VIEW on Google 3D, and
asserts that each chip it switches really switched.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Google's Maps Platform terms forbid Street View imagery beside non-Google
maps, so the provider declares requiresMapStack: 'photoreal': off Google
3D its chip is dashed with the reason, lookups and opens skip it, and
leaving Google 3D closes its panorama.
- Ground clicks: only a click on the map itself (scenePick.isSurfacePick:
nothing picked or Google 3D tile content) at street zoom opens Street
View, and the camera stays put; higher up the hint says to zoom in.
scenePick.pickGroundPosition replaces the copy in Directions.
- Viewer: poses go out at once and again when the date and address
arrive, so an open never hangs on that lookup; one lookup per panorama,
seeded by the nearest lookup; a close settles a pending open; failed
lookups are asked again; poses follow position changes, not pano_changed.
- Loader: the gm_authFailure handler is installed once, so a retried load
reports a refusal once.
- Viewer host: a prewarm that lands after another provider took the host
is released; FIT/FILL availability is set on every open.
- Provider: a refusal while switched off reports no error over another
photo; SINCE compares against the end of Google's month; messages name
the referrer and network causes; FLAT says Street View is 360° only.
- The layer row names the key of the providers that are on; the MCP panel
CSP admits Street View's tiles, control images and photos.
- Tests for each, against fakes that now follow Google's event order; the
browser gate restores its state, waits instead of sleeping, aims its
panel clicks and corner drags, and asserts the camera stays put.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Google Street View registers beside Mapillary as provider `google`. It runs
on the Maps JavaScript API with the browser GOOGLE_MAPS_API_KEY, loaded on
first use (mapsLoader.js), and uses Google's own StreetViewPanorama as its
viewer adapter, emitting provider-neutral poses (address and month in the
caption, a link to Google Maps, steps along Google's arrows, FOLLOW on
Google 3D). Its chip starts off because every panorama is billed to the
key; the share option is `g`.
Google offers no coverage layer for other maps, so the provider draws
none: with it on, a click on bare ground at street zoom opens its nearest
Google-collected outdoor panorama (groundClick.js). A key Google refuses
reads KEY REJECTED and fails a pending open at once; a lookup Google never
answers fails after 10 s.
Core: providers may declare `groundClick` and `defaultOn`; poses may carry
`capturedAtPrecision` and `title`; the coverage line keeps every
provider's hint; opening one provider's image releases another's
prewarmed viewer; FIT/FILL hides for viewers without render modes. CSP
script-src admits https://maps.googleapis.com. The browser gate gains two
Street View steps (the keyed one skips as 'no Google key' in CI).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Three regressions from the slimming, found in review:
- Clean View, recording and the cockpit hid the old expanded viewer with
CSS; native fullscreen sits above all of that, so the photo stayed over the
globe (and in recordings). While expanded, the panel now watches the
<body> classes of those modes and leaves full screen when one starts.
- iPhone Safari has no element fullscreen, so EXPAND had disappeared there.
Where requestFullscreen is missing but popovers exist, EXPAND opens the
viewer as a top-layer popover sized to the viewport (it stays in the
panel's DOM; the popover attribute is set only while open, since a closed
popover is hidden). Checked in Chrome: the popover fills the viewport over
the panel's backdrop filter.
- Without the provider chips, a layer that was on with Mapillary switched off
by a share link or tool (`0.m.0`) showed ON and drew nothing. The pill now
reads OFF with a hint, and pressing it brings Mapillary back without
switching the layer off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The browser gate is one pass over the remaining flows: rail strip and
expand, coverage with fixtures, the ON/OFF pill, 360° and SINCE filters,
KEY REJECTED, nearest photo with caption, × close, phone fit, keyless
KEY REQUIRED on a second page and no page errors. Panel controls are
pressed through the DOM; pick-point search, line/cone clicks, the
framing-cancel, reload/share and status-route probes, the second
viewport and pointer-settling retries are gone (unit tests cover them).
Tests that pinned constants, copy, CSS or source text, or repeated
another test, are removed or merged. CSS loses an unused variable and
duplicate rules. Docs describe the slimmed feature; unused exports and
the layer's dead setCoverageFilter/refreshCoverage surface are removed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Drop the disk tile cache (.gev-cache/mapillary/tiles, background sweep,
size cap, legacy retrim), the server-side 401/403/429 holds, the upstream
concurrency semaphore and per-waiter abort bookkeeping. The client already
pauses on 429 and stops on a rejected key.
Keep: server-side token, {configured}-only status, strict coverage path
validation (z11-14), image-layer trimming, same-site gate and per-IP limit,
HTTPS redirect pinning to the tile origin, the streamed size cap, the error
mapping the client reads, a 24 h byte-budget LRU memory cache, in-flight
coalescing via the shared coalesceProxyRequest, and browser Cache-Control.
Routes and constants fold into mapillary.js and mapillary/tiles.js.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layer now owns its Mapillary parts directly: the provider registry,
pick router, multi-provider UI summary, params codec helper and provider
context are gone. createStreetLevelLayer({ source }) builds coverage,
sequences, the nearest-image lookup and the MapillaryJS viewer itself; a
single providerOn switch keeps the share link's `mapillary` option (off
draws nothing), and getParams/setParams keep { mapillary, pano, sinceDays }.
No z0-5 overview points: above 60 km nothing is drawn and the panel says to
zoom in; the proxy now serves only coverage z11-14. Tiles come from the
visible ground within range of the camera, ranked from the ground under it
(no line-of-sight ranking or near-range padding). MapillaryJS loads on the
first photo; viewer prewarm is removed. Key status, a rejected key and a
rate limit share one `blocked` state on coverage.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
FOLLOW is gone: no setFollow, follow state, map-stack availability or
handoff listener. Opening a photo still frames it with one flight through
the application's deferred navigation (begin/reassert), so a slow photo
never overrides newer navigation and tracking is released as before.
Ground casting is gone: groundCast.js, meshSampler.js, the surface mode,
the draped-to-cast swap and remesh. Coverage lines are always draped
GroundPolylinePrimitives (ClassificationType.BOTH); cones and the marker
clamp to the ground and keep the horizon cull. The terrain/meshFloor/ground
services and meshFloor.visibleTilesetLoaded are no longer needed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EXPAND now uses the browser's Fullscreen API on the viewer element, which
stays in the panel; the hand-built modal (re-parenting to <body>, inert
siblings, focus trap, document-level Esc/Tab, Clean View/recording/cockpit
hide rules) is gone. The button is hidden where the API is missing.
The FOLLOW button and the PROVIDERS chip row are removed. The header pill is
the only layer switch and re-enables a provider switched off elsewhere; under
KEY REQUIRED the error line says which key to add. Chip colour support in
chipGroup, dead CSS and tests pinning the removed behaviour go with them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PR #768 bundled a portable-panel windowing system with the Street Level
layer. Remove it so CCTV and Street Level are ordinary docked right-rail
panels again, as on main before the merge.
Removed:
- header drag to float, 8-way edge/corner resize, double-press snap-back,
dockOnCollapse, command-dock clearance, float hint toast, floating z floor,
persisted floating geometry and ResizeObserver re-clamping
(panelPositionControls.js restored to its pre-#768 version)
- src/ui/panelResize.js and its tests, panelDock.test.mjs
- onPanelResized/dockPanel wiring in PanelChrome, the application shell and
StreetLevelControls (EXPAND/SHRINK no longer docks)
- :not(.panel-floating) guards and .panel-floating/.panel-portable/resize CSS
- qa:panel-resize, qa-panel-resize.mjs, qa-panelDrag.mjs, their tests, the
CI "Portable panel gate" step, QA_FAIL_ON_RETRY, and the Street Level
gate's float/resize/dock steps
- floating-panel docs in README, CHANGELOG, CURRENT-STATE, KNOWN-ISSUES
Kept: the Street Level panel as a docked rail panel (share/cockpit/rail
lists, layout controller, rail and Cyber CSS, EXPAND viewer), and the
independent right-rail alignment fix (settledLeftRailTop, 9dde7b4).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Imported metadata stays inert: descriptions, native labels, properties and
KML extended data are dropped from every entity; names are kept as short
plain text. GeoJSON loads with a describe that builds no HTML, and
simplestyle (including marker-symbol) is dropped, so no icon asset is
fetched for a file and GeoJSON draws in its row's color.
- Bound geometry before loading: features and vertices are counted in the
parsed GeoJSON, the KML document and while reading GPX, and a file over
20,000 features or 250,000 vertices is refused before Cesium builds it.
- Imports run one batch at a time, so concurrent picks or drops cannot pass
the 12-file cap.
- Fly-to framing walks long tracks by index instead of spreading them into a
call.
- Fly-to is an immediate navigation through the application shell; the fly
after an import is a deferred navigation stamped when the batch starts and
re-asserted just before flying.
- Dated imports never change the shared clock: imported sources expose no
clock, entities are available for all time, and a KML gx:Track is drawn as
its line.
- GPX is read into GeoJSON with one line per track segment, without times.
- KML/KMZ rows show a hatched swatch (they keep their own styles); keyboard
focus is kept on the same control after hide/show and moves to the next
row after remove.
- Unit regressions for each, and the rendered QA now imports hostile
metadata, dated KML/GPX and a two-segment track, and fails on any request
for a file's contents or icon assets, a clock change, or surviving
metadata.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
viewer.flyTo frames ground-clamped billboards before the destination's
terrain has loaded, and their bounding sphere then sits kilometers below
the surface: flying to a points-only GeoJSON from another city left the
camera about 2.3 km underground (a black screen). Frame the features'
longitudes and latitudes at the surface instead, and let the existing
camera ground guard lift the eye once the terrain can be measured
(src/ui/flyToDataSource.js, with tests).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DISPLAY ▸ Import (and drop onto the globe) puts the person's own files on
the map as Cesium data sources, clamped to the ground, one color per file,
with a row to show/hide, fly to or remove each. Files stay in the browser
for the session.
KML/KMZ are sanitized before Cesium reads them (network links, remote
icons/styles, models, photo/screen overlays and balloon HTML removed; KMZ
images served from blob URLs), so an imported file cannot make the page
fetch anything. KMZ is read with a small dependency-free zip reader on the
platform DecompressionStream, with a zip-bomb limit. Bounded file size,
entity count and number of files.
Tests: src/data/geoFileImport.test.mjs, src/ui/geoFileImport.test.mjs.
Gate: scripts/qa-geo-import.mjs. The import half of #890.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- A photo takes a deferred navigation ticket when it starts opening
(begin/reassert, the application's _beginDeferredNavigation and
_reassertNavigationHandoff) and frames only if that ticket still owns
the camera once loaded, so a slow photo no longer flies the camera back
over newer navigation or clears newer aircraft tracking.
- A failed mesh refresh keeps the last good sample and the distance it was
taken from, and is retried after the miss cooldown, instead of recording
the failed distance and never refreshing again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Photo framing and FOLLOW claim the camera through the application's
navigation, injected with attachNavigation({ run, subscribeHandoff }):
aircraft and satellite tracking is released first, the cockpit refuses,
and FOLLOW stops when another feature takes the camera. No UI imports
in the layer.
- The mesh sampler follows the mesh floor's rules: it waits for the
visible tileset to finish streaming (visibleTilesetLoaded, now exposed
by the mesh floor service), needs a real bare-earth prior and keeps to
the shared meshFloorSampleWithinPrior window, which cameraFollow now
uses too. A cell is probed again once the camera is half as far from
it (down to 40 m), so a coarse early sample is replaced and redrawn.
- The MCP globe panel's CSP allows the Mapillary Graph API and its image
CDN (https://*.xx.fbcdn.net).
- The tile proxy reads bodies with the shared readResponseBytesCapped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Trim the doc text this PR adds (README, CHANGELOG, CURRENT-STATE,
KNOWN-ISSUES, SECURITY, DATA_SOURCES, .env.example, the CI job comment),
drop code comments that restate the code, cut design-note JSDoc to a line
or two, and drop review tags from test names. Comments only; no code
changes.
Also: README key count (seven keys, two metered), CURRENT-STATE's gate
paragraph (CI runs a production build with a dummy token), the stacked
doc above meshBoxInRange and the orphaned inMeshRange line in
coverage.js, and a duplicate ALPR row in DATA_SOURCES that a merge had
brought back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The client lease id came from crypto.randomUUID, which browsers only
expose in secure contexts. Opened over http://<lan-ip>:4173 (the
HOST=0.0.0.0 opt-in), starting a live camera feed threw before playback.
Fall back to a v4 UUID from crypto.getRandomValues, which the server's
lease check accepts.
Cover every Norwegian station-based city-bike system with an open GBFS
feed: Kolumbus Bysykkel (Stavanger) and Farte Bysykkel (Skien/Porsgrunn)
through Entur Mobility, and Bysykkel Lillestrøm through JCDecaux
Cyclocity.
api.entur.io and api.cyclocity.fr join the GBFS allowlist, each kept to
its GBFS path prefix since both hosts front other APIs. Entur serves its
feeds without a .json suffix, so the endpoint checks accept either form,
and requests to Entur carry the ET-Client-Name header it requires.
Attributed under NLOD (Entur) and Licence Ouverte (JCDecaux).
Co-Authored-By: Claude Opus 5.5 <[email protected]>