Review on #856 (kvnloo): the analyst projection flattened a 7700 squawk and
the readsb ADS-B emergency field into the same emergency/emergencyKind pair.
- mapAnalystRecord (flights, military) adds emergencySource ('squawk' |
'ads-b' | null, from aircraftEmergency().source), emergencyBroadcast (new
pure emergencyBroadcastPhrase: "broadcasting squawk 7700 (general
emergency)" vs "ADS-B reports general emergency") and observedAtMs (the
record's lastContactEpochMs: last transponder message, epoch ms).
- emergencySource is a queryable analyst text field; the voice payload passes
it and emergencyBroadcast through, and observedAtMs only for aircraft with
a status. Tool description asks the model to narrate with the phrase.
- Source-to-analyst fixture test: OpenSky 7700 vs adsb.lol emergency=general
on squawk 1200 differ in provenance and timestamp; filterable by source.
- Voice tool-payload pins re-derived for the additive description change.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- aircraftEmergency: `source` is 'squawk' only when the Mode A code itself
names the status; the Cockpit HUD no longer quotes an unrelated squawk
(e.g. SQUAWK 1200 on an ADS-B `downed` status).
- military: new billboards and the untrack restore use the fleet tint, so an
emergency stays red instead of snapping back to amber; tint helpers take the
record already in hand instead of a second lookup.
- cockpit/cyber CSS: emergency colour via a custom property / theme token,
meta line bolded so the status does not rely on colour alone in the HUD.
- tests: revert Prettier churn in unscoped test files; only semantic edits.
- docs/CURRENT-STATE.md: transponder emergency highlighting section.
- qa-emergency-squawk: (iv) RELEASE check; new files added to format scope.
Co-Authored-By: Claude <[email protected]>
Flights and Military Flights tint aircraft red while the latest poll
carries squawk 7500/7600/7700 or an adsb.lol ADS-B emergency status
(general/unlawful/nordo/downed). Minimum-fuel and lifeguard priorities
are reported but not tinted. The Cockpit HUD aircraft-meta line names
the broadcast code in red. The status is not sticky: a cleared code
stops highlighting on the next refresh.
The analyst/voice query engine gains squawk, emergency and
emergencyKind fields. No new source or request: OpenSky state vectors
already carry the squawk and adsb.lol/readsb carries both fields.
Built by the Hex project (Crew Leo Agile dev team).
Co-Authored-By: Claude <[email protected]>
The layer compared the server's fetchedAt with the browser's clock, so a
skewed client clock could shift or void the 30-minute window. The proxy now
returns ageMs (its own now minus fetchedAt, at serve time, on fresh, cached
and stale answers); the source refuses a snapshot without a finite,
non-negative ageMs, and the layer dates rows as its now() - ageMs. fetchedAt
stays in the payload for display.
Tests: proxy cached/stale answers report ageMs on the server clock; the
source rejects missing/invalid/negative ageMs; the layer replay test drives
ageMs. qa-gnss stubs send ageMs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Re-enabling after a disable longer than the window no longer shows the
old cells until the next refresh. A healthy report arriving 31 minutes
after a degraded one, with no prune in between, starts clean; exactly
30 minutes still counts.
Maintainer review on #855 (mazy1998, 2026-10-06):
- records: presence (seenAt) and degraded evidence (degradedAt) age
separately, so continuing healthy reports no longer keep an older
degraded flag alive past 30 minutes; an aircraft returning after the
window starts fresh. pruneGnssObservations ages the store with no new
rows.
- Rows are dated by the snapshot's fetchedAt, which the proxy keeps on
cached and stale answers: a replay never restarts the window, the
proxy stops serving a stale snapshot older than the window, the source
refuses an undated one, and cells expire during fetch failures and
while there is no view anchor.
- Normalization accepts ADS-B versions 1 and 2 only; a missing, invalid
or negative seen_pos is not a confirmed position from the last minute.
- qa-gnss: keyboard, 390x844 viewport (fresh page: isMobile reloads) and
replay-age sections; the live cell assertion bins the returned rows
with the layer's own rules instead of a raw row count.
Review on #855: the per-aircraft rule (NIC < 7 || NACp < 8, borrowed from
the US 14 CFR 91.227 ADS-B Out minima) is GEV-defined, not gpsjam's, and
gpsjam's map shows low navigation accuracy, not necessarily jamming.
- Primary cell state is now `navigation-integrity-anomaly` (medium/high) or
`nominal`; `suspected-gnss-interference` is a secondary `interpretation`
marked validated: false.
- GNSS_PROVENANCE / gnssProvenance({ windowMs }) records the classifier
(gev-nic-nacp-v1, GEV, unvalidated), the gpsjam aggregation (formula,
bands [0.02, 0.1], FAQ source) and the GEV 30-minute visited-view window,
built from the constants the code applies. Exposed as layer.provenance
and getStats().provenance; the proxy tags responses with `classifier`.
- Visible name "GNSS Integrity", legend "... low accuracy", blurb leads with
low navigation accuracy and states the threshold is a GEV choice.
- Layer id `gnss-interference` and share token `0` unchanged.
- Voice aliases add "gnss integrity", "navigation integrity",
"low navigation accuracy"; the interference aliases stay.
- Docs describe the method as GEV threshold + gpsjam aggregation.
- Tests: provenance object, renamed state/label, proxy classifier tag;
qa-gnss asserts provenance and wording in the real app.
Hex project - Crew Leo Agile dev team.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- Route the adsb.lol GNSS source through createReferenceSources and the
constructCatalog contract; app layer module is a thin pass-through.
- Describe the method honestly: gpsjam.org's published cell formula and
bands; per-aircraft thresholds are the 14 CFR 91.227(c) ADS-B Out minima
(NIC < 7, NACp < 8) plus readsb gpsOkBefore.
- Proxy: reuse the adsb.lol anchor parser, honour upstream Retry-After
(clamped 5-120 s), serve stale during cooldown, cap the body size.
- Layer: redraw only when a cell id or band changes, treat a settling
camera as a retry rather than a rejected enable, keep low cells visible,
skip rows without finite coordinates, state the 3-aircraft rule in the
legend blurb.
- Voice: gnss-interference in set_layer_visibility / show_data_layers_menu
plus common aliases; voice pins re-derived.
- qa-gnss: stub the route in-page (request interception stalls Cesium's
geometry workers), assert every cell is built, cover all bands over land
and sea, screenshot every state with the Data Layers row open.
Hex project - Crew Leo Agile dev team.
Co-Authored-By: Claude <[email protected]>
Bins airborne ADS-B v1/v2 aircraft that report degraded navigation
integrity (NIC < 7, NACp < 8, or readsb's GPS-loss flag) into 0.5 degree
cells over a rolling 30-minute window, coloured with the gpsjam.org
bands. Keyless: reads the capped adsb.lol point snapshot around the view
through /api/gnss-integrity, which returns integrity fields only, caches
60 s per whole-degree anchor, backs off on upstream 429 and serves the
last snapshot on failure. Adds scripts/qa-gnss.mjs.
Built by the Hex project (Crew Leo Agile dev team).
Co-Authored-By: Claude <[email protected]>
- One provider is lit at a time: lighting a chip darkens the others, and a
chip click records every registered provider's switch in one update so
the share link keeps the swap (also off Google 3D, where Street View is
not listed). The layer starts with the first default-on provider
(Mapillary, free); params that light several keep the one already on,
so reapplying them never flips (litByParams). The now-unreachable
"several lit" chip paths are gone.
- Leaving Google 3D during a Street View lookup ends it without an error
or a stuck loading state (openImage's early return resets it; the
lookup loop rechecks availability).
- A source swap during a lookup no longer reports "no imagery".
- Street View lit off Google 3D says the panel needs the Google 3D map.
- Comments trimmed to the why; keySetupCore.test.mjs back to upstream's
formatting plus the one new test (was a 551-line reformat).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The chip reads GOOGLE STREET VIEW (and its caption link likewise).
- Providers declare the imagery filters they honour (`filters`, default
pano + since). Street View declares none: every panorama is 360° and it
draws no coverage to thin, so its lookups ignore the filters and the
panel hides IMAGERY and SINCE while it is the only lit provider.
- The legend lists only providers that draw coverage lines, with
Selected only beside one: Street View alone shows no legend.
- Providers may pick their on-globe marker (`marker`); Street View's is a
Google-blue field-of-view cone from a white-ringed dot, in place of
GEV's amber pin, and switches back for a Mapillary photo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- An open after a failed one is no longer failed by the old panorama's
status: only status_changed can reject; position_changed only resolves.
- A nearest lookup aborted, timed out or refused while Google's script
loads no longer sends its getPanorama request once the script arrives.
- A panorama-details lookup that never answers times out after 10 s,
cools down and retries; an unmount releases lookups in flight, and
their late answers are dropped.
- Every Maps loader on a page shares one script load and ready callback,
and either can retry a failed script.
- OPEN STREET VIEW aimed at the sky opens nothing and says to aim at a
street, instead of looking up the camera footprint.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflicts with upstream's Street Level provider composition and share-state
codec, resolved:
- Street View's switch moves from its own share option (`g`) to upstream's
provider-switch field (`r`, e.g. `0.r.google-1`); a link without it keeps
Street View off. Docs and the browser gate follow.
- createApplicationStreetLevel keeps upstream's injectable provider list;
its default, once the application supplies its Street Level source, is
Mapillary plus Google Street View. Without that source there are none, as
upstream intends.
- The coverage hint keeps upstream's empty-composition message and joins
every switched-on provider's hint otherwise.
- CHANGELOG, README and SECURITY keep both sides' entries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #966: with a Google Maps key but no Mapillary token, switching
Street Level on read KEY REQUIRED / "Needs MAPILLARY_CLIENT_TOKEN" with
nothing about Street View, which stayed off (billing), so the layer looked
broken.
- While the switched-on providers lack their keys, the layer reports the
switched-off ones that would draw with a key already set
(keyAlternatives: Street View on Google 3D with a Google key). The
panel's KEY REQUIRED tooltip, its coverage line and the layer row's
tooltip then read "Needs MAPILLARY_CLIENT_TOKEN — add it in Provider
Settings, or light STREET VIEW to use your Google Maps key"
(keySetupWithAlternatives). Off Google 3D the plain KEY REQUIRED stays.
Street View stays off by default.
- OPEN STREET VIEW moves beside the coverage hint: at the default panel
width the PROVIDERS row has no room for it, so it wrapped onto a line of
its own.
- The panel gate's corner drag names what lay under the pointer when it
fails.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Maintainer follow-up to #966 review:
- A failed panorama-details lookup now waits DETAILS_RETRY_COOLDOWN_MS
(5 s) per panorama before a view event can retry it, so dragging the
view after a failure no longer issues a lookup on every pov change. A
success clears the pause; one panorama's failure does not delay another.
- SECURITY.md notes that allowing maps.googleapis.com in script-src also
trusts its JSONP endpoints, an accepted limitation alongside the
existing 'unsafe-eval' allowance.
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]>
A tile whose terrain lookup cast some lines kept castRequested set, so a
later refresh skipped it and the lines that failed stayed draped over
trees and roofs until the tile was rebuilt. A refresh now retries any tile
with lines still draped, and redraws it only when more of its lines can be
cast, so a tile that cannot be cast fully costs a cache check, not a
rebuild, per refresh. castRequested still stops the redraw from casting
again at once.
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]>
- A floating portable panel saved its measured box, so dragging a
collapsed window stored its 50 px header strip and a reload clamped it
to the minimum. It now saves the inline size the user chose. A unit
test and a collapse -> drag -> reload -> expand step in the panel gate
pin it.
- Coverage awaits the tile fetch and the one-time terrain table load
together, so a tile that fails before the table loads is reported at
once instead of surfacing as an unhandled rejection.
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 fleet draws a grounded contact at its delayed position lifted onto the floor under it, but a new billboard was created at the delayed position without that lift. A taxiing aircraft whose reported fix sat lower than the ground under its delayed position started on the fix's floor and then rose on the next fleet tick. Creation now applies the same floor the fleet pass does.
The military fleet also renders behind real time and projects a fresh contact's first fix back to that delayed time, but its billboard was created at the raw fix. A new contact therefore appeared ahead of where the next fleet tick put it and jumped back. The snapshot renderer now receives the layer's motion and creates the billboard at the dead-reckoned delayed position.
The fleet renders behind real time, so a newly seen aircraft is displayed at its fix projected back to that delayed time. Its billboard was created at the raw fix instead, so it appeared ahead of where the next fleet tick put it and then jumped back about one render delay of travel before moving forward.
From gekh's review of db79027, each with a test that fails before:
- P2: image cones and the photo marker no longer disappear where the
ground lies below the WGS84 ellipsoid (NYC -22 m, Colombo -95 m). The
horizon cull's occluder is WGS84 shrunk by 1 km (HORIZON_CULL_DEPTH_M),
below any real surface, so a camera near or under the ellipsoid no
longer treats every lower point as behind the horizon; the far side of
the Earth is still hidden from orbit (tested).
- Mesh range across the date line: the camera's longitude is taken to the
box's side of +-180 before clamping (meshBoxInRange), so a tile just
across the line is not measured from its far edge.
- MapillaryJS no longer tracks window resizes (trackResize: false): the
panel's ResizeObserver resizes the viewer and skips it while hidden, so
a window resize after closing a photo cannot ask for a tile at z=NaN.
- A key-less install sends no tile requests before the key status is
known: coverage waits for the first status answer and the provider
refreshes it once the status lands.
- constructCatalog takes the source method list from MAPILLARY_SOURCE_
METHODS; sequences.js imports view.js once; the provider's unused
re-exports are gone.
Verified: unit 5970 pass; strict fixture gate on a production build 28 ok
+ 2 "no Google 3D" skips and the panel gate 6/6 (QA_FAIL_ON_RETRY=1);
live gate with a real token 25 ok + 3 fixture-only skips.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mutation review left 18 earlier survivors; each now has a test that
fails with the mutant applied:
- the expanded viewer returns into the panel on shrink, close and destroy
(the fake DOM had no parentNode, so reinsertion was never exercised); the
fake DOM gains offsetParent and a MutationObserver that honours its
options, and a <body> child added while expanded goes inert;
- each pose moves the marker, drives FOLLOW and selects a new sequence;
close() retires an open still mounting or opening; a provider toggled
while the layer is off activates nothing; the adapter drops late and
superseded poses;
- share-link defaults: "any date" (0 days), the codec max pinned to the
filter's MAX_SINCE_DAYS (layerState cannot import it across package
boundaries, so a test ties the two), Mapillary on when a link omits it;
- Esc that something else handled, or typed in a field, keeps the
selection; a key problem outranks LOADING on the pill;
- the marker's terrain height (mesh, DEM, clamp then re-place);
- proxy: LRU memory cache and its half-budget rule, Retry-After capped,
a request after a cancelled fetch starts a fresh one;
- date-line tile ranking with and without focus; the horizon cull idles
while the camera stands still; the old zoom goes as soon as the new one
lands; a rate limit holds every refresh inside its wait.
One real bug found and fixed: Esc inside a contenteditable cleared the
selected sequence (selection.js now ignores isContentEditable targets).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768, each with tests that fail before:
- P1: a mesh batch no longer rebuilds the selected sequence's cones. The
drawn cones are indexed by mesh cell; a batch that touches none of them
is ignored and the affected cones are moved in place (no removeAll/add).
- P2: mesh sampling keeps to its 6 ms slices. Requests are taken in inside
the budget, only cells within range of the camera are queued, the queue
is a persistent min-heap re-ranked in one pass only after a 100 m camera
move, and the deadline starts before any work. Coverage keeps a bounding
box per line and densifies a line only once it comes within range. On a
synthetic dense tile (600 x 40 points): slice median ~10 ms -> 5 ms, max
18-30 ms -> 6.7 ms, refresh cost 11-24 ms -> 1.5-3 ms.
- P2: image cones and the photo marker are horizon-culled like the
overview dots (one createHorizonCull in view.js, shared by all three).
- P2: a selection change recolours the previous zoom's lines still on
screen and the old lines a cast swap keeps showing.
- P2: distances (metresBetween), line densification and ground-corner
requests go the short way round +-180 deg (wrapLon in tileMath.js), so
nearest ordering, cone thinning, mesh range and cast lines are right
across the date line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768, each with tests that fail before:
- P1: closing a photo, switching the layer off or destroying it stops the
1.6 s framing flight toward that photo. The flight is claimed with a
token before flyToBoundingSphere and released by its complete/cancel
callbacks, so cancelFraming() calls camera.cancelFlight() only while
the flight is still Street Level's: a newer navigation flight (a search
result, a scene) is never cancelled. Turning FOLLOW on also stops it, so
the tween cannot overwrite the follow view.
- P2: an older nearest-photo lookup can no longer replace a newer choice.
The core owns one AbortController per lookup, aborted by a newer lookup,
a direct open or cone click, closing the viewer, disable and destroy;
the signal reaches source.nearestImages through the provider, and an
aborted lookup never surfaces as an error.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A duplicate scan of every module the PR adds (repeated 4-line blocks,
normalised) after gekh's dedup round left these:
- glyphs.js: the cone, ring and position glyphs share cachedGlyph(), wedge(),
ring() and dot(). Checked with a recording canvas: all 20 draw calls keep
the same path and fill/stroke state, so the glyphs are pixel-identical.
- panelPositionControls.js: one placeAt() for the three places that pin a
panel's top-left corner as a fixed window.
- tiles.js: one warn() for the three background-failure logs.
- Tests: src/testSupport/streetLevelFakes.mjs holds the provider snapshot
(uiState + presentation tests), the stand-in provider (core + controls
tests) and one real-ray pinhole camera (view + coverage tests, which had
each written their own). The coverage line-of-sight test still fails when
refresh() stops passing its focus.
- qa-street-level.mjs: one waitForCoverage() for the five copies of the
"on, drawn, settled, not key-gated" wait.
No behaviour change: unit 5862 pass, fixture gate 16/16, panel gate 5/5.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From gekh's review of #768, each bug with a test that fails before:
- P2: a failed sequence load clears the previous sequence's cones, resets
the selection and can be retried; a second click while loading starts no
second lookup. sequences.js had no tests: sequences.test.mjs now covers
failure and retry, a superseded late answer, the cache and clearSelection.
- P2: closing a photo stops MapillaryJS sequence playback, which kept the
hidden viewer stepping and downloading images.
- openNearest without the panel reports it instead of loading forever, and
stops if the layer (or its provider) went off during the lookup; the
mesh sampler's misses are bounded and forgotten with its samples; the
sequence count is the union of ids across tiles; a provider withdraws
only its own error, so a sequence error cannot mask KEY REJECTED; hover
stays on (throttled) under an orbiting camera and re-picks when it
stops; "since N days" follows the clock in a long-open tab.
- A prewarm waits for one in flight and then builds if it gave up.
- New coverage for the layer core (credits, KEY REJECTED vs REQUIRED,
switching a provider off), the refresh wiring of the tilted-view fix and
the draped-to-cast swap completion.
- Cleanup: loading is derived from pending; freshStreet(); tile ranking
computes distances once and shares one path for the date line; shared
metresBetween / whenIdle / redrawTile; one NEAREST_RADIUS_M and one
source method list; unused contract fields, exports, getImage and the
decoder's image branch removed; PROVIDER_COLORS lists only Mapillary.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768 (P2s), each with a test that fails before:
- Switching the layer off and on during the first photo download no longer
leaves the viewer stuck on "not mounted": an outdated mount only unmounts
an adapter nobody else holds and subscribes poses only once current, and
the MapillaryJS adapter never builds a viewer after it was unmounted.
- Hover picks at most every 120 ms (the CCTV convention), not while the
camera moves or a button is held, and not after the layer went off.
- Esc shrinks the expanded viewer wherever focus is (document capture), so
the map keeps its selection, and Tab from outside returns into it.
Closing a photo moves focus to the panel instead of dropping it on body.
- Portable panels lift only after a mouse or pen moves past the drag
threshold: a click on a docked panel's edge or a touch swipe on its
header no longer turns it into a floating window, and pointercancel
saves nothing and puts the panel back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768 (P2s), each with a test that fails before:
- Far-side overview dots no longer show through the globe. They skip the
depth test so they stay above Google 3D terrain, so points behind the
horizon are now hidden with an EllipsoidalOccluder (as the cyclones layer
does), re-culled only when the camera moves.
- The per-tile cap applies after the imagery filter: a dense tile of newer
flat captures kept no 360° lines at all. Lookup, picking and counts use
the drawn set.
- Lines whose ground heights were evicted, or whose terrain lookup failed,
are cast again on the next refresh instead of staying draped for good
(castRequested no longer latches; castAbort marks a cast in flight). The
height cache now drops its oldest corners instead of all of them.
- A selection made or cleared while a tile's lines rebuild is applied once
they are ready (each primitive remembers the selection it was built with).
Co-Authored-By: Claude Opus 5.5 <[email protected]>