Share-link integers and Cesium radians were converted before normalizeBox
checked them, so a blank edge still became 0°. Edges are now accepted only as
numbers or non-blank numeric strings, which also refuses [] and {}. Tests cover
all four edges through validateBox, dequantizeBox, boxFromRectangle and
searchHls.
Adds a case that fails before de-duplication (a 24-slot overview held repeats
and reported itself truncated) and says why the date-line halves overlap.
KartaView's server filters by upload date only, so SINCE goes by it
throughout for KartaView: the lines (as before), the photo a click opens
and the cones, the cut-off taken as its UTC day as KartaView's startDate
(passesKartaViewFilter). Providers may declare sinceBasis: 'upload'
(validated); while one is lit the panel shows "KartaView: by upload date"
under the SINCE slider, read out with it. The gate checks the note, and
step 13's picks get 60 s on a loaded machine.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
KartaView filters coverage on its server only in its PNG tiles (its JSON
tiles come back empty with a filter), so a filtered tile's PNG is a mask
over the cached JSON segments: a segment stays when at least half the
points along it lie on drawn pixels. A filter change reloads the lines
(lineCoverage.reload keeps the drawn ones until the new ones are in);
masks are cached per filter and tile, and a filtered tile KartaView is
still drawing (504 or a timeout) is asked again, four tries in all.
The nearest lookup asks the photo API for the projection and date, and
the cones follow the filter like Mapillary's. The gate fixture draws
filtered PNG tiles and adds an older flat-only street; a new gate step
checks 360 and LAST YEAR thin KartaView's lines and ALL brings them back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Viewer: resizes while a sharper photo loads start no second load (the
last size is looked at once it lands); decoded bitmaps nobody will draw
(an overtaken preview or upgrade) are closed.
- Cones: a photo opened while the drive's first page loads is the one
they centre on.
- A sequence page that lands while a lost WebGL context recovers keeps
its cones: no pose is emitted without a renderer, and a viewer error
never rejects page data.
- Source: an answer turned away unread (HTTP error, 429, length past the
cap) aborts its request and cancels its body, freeing the connection.
- Gate: findPickPoint no longer picks each centre five times at radius 0,
aims step 13's cone pick at the drawn billboards, and caps each
evaluation at 4 s, resuming from a cursor on the next poll, so a slow
machine cannot spend a whole wait in one evaluation.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Marker and direction (reported on the PR):
- KartaView has its own marker, a purple view cone (Street View's is
blue), not GEV's default pin that Mapillary uses.
- The camera direction (marker, cones, FOLLOW, a panorama's centre) is
the direction of travel to the next photo a few metres on. KartaView's
heading is the device compass: whole drives read 0 (no compass) and
others sit a steady ~110° off. Until the drive loads the compass stands
in (0 = unknown), and the pose is sent again once the course is known.
Remaining P4s from the review of #1121:
- Segment chaining is linear (a per-point cursor) and capped at 20,000
segments a tile and 5,000 points a line: a hostile tile of 60k segments
sharing one point took 1.5 s, now tens of ms.
- Decoded tiles are dropped when KartaView is switched off.
- No network / a refused request says so and is not retried as a timeout;
an unreadable answer says so.
- A docked flat photo skips the 2,560 px download until the viewer grows.
- The panel counts the drive's photos (countActivePhotos), not the thinned
page of cones (2,250, not 217).
- After a GPU reset the viewer redraws the photo itself, so a lookup the
user started is not cancelled; a failure is still reported.
- The gate's keyless page lights KartaView and checks it draws with no key;
the fixture answers the sequence count; the line-click step checks the
camera looks along the drive when the compass says 200°.
- README/CHANGELOG show the share-link form the app writes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #1121 (2 P2, 7 P3; each with a test that fails on
the old code):
P2
- A WebGL context lost while a newer panorama loaded left a black viewer
and a 'successful' open: the panorama now builds on the new canvas (or
falls back to its front view).
- The streaming body read every real answer takes was tested with one
chunk only: answers split mid-character are reassembled; the shared test
fake now streams, so every source test runs the production path.
P3
- Lighting another source by its chip (params) now stops a running lookup
and clears its spinner and error, as setProviderEnabled did; a restore
that changes nothing leaves them alone.
- Panoramas decode off the main thread (ImageBitmap, RGBA upload: ~90 ms →
~15 ms at 4,096 px on a phone-class CPU) and are 2,048 px docked, 4,096
once the viewer grows; a closed viewer keeps a 1×1 texture.
- The lit KARTAVIEW chip label is #8f8aff (~6:1; #635bff was 3.7:1).
- The viewer's keyboard focus ring is drawn above the photo.
- Tests: a second context loss and a drag on the new canvas; the look
direction kept across a loss; a page's 30 s first try.
- The browser gate loses a real WebGL context in the 360° step and checks
the 429 lookup was asked again (the fixture counter is reset first).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the second IC8 pass (launch ready; P3s closed here):
- A lost WebGL context is redrawn on a new canvas (the old one only gives
back its lost context), through the core, and only if no newer photo is
opening; verified live with WEBGL_lose_context, twice.
- The 16 MB answer cap counts bytes while reading: KartaView sends
chunked answers with no Content-Length, so the header check never fired.
- A lookup gets 8 s, then 15 s once more: KartaView sometimes sits on a
lookup for 11 s and answers the same one in 3 s.
Left as follow-ups: cancelling abandoned sequence pages; finding a page
by binary search on the index when thousands of photos are missing.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A mutation review (91, then 130 mutants) found tests that could not fail
(quick presses with synchronous fakes, a no-flight check with a provider
that never posed), and untested paths. New and rewritten tests kill 123 of
130 mutants; the 7 survivors are equivalent or harmless redraws.
- viewer: held-back loads for quick presses, stale sharp/flat loads,
close mid-load, ends checks for an older photo, WebGL retried per mount,
focus handed back, look direction kept on ›.
- provider: id-ordered pages with out-of-order indexes, page and probe
failures retried, overtaken lookups, cones re-centred on ‹ ›, leftover
imagery filters hide no line or cone, coverage hints, tile cache.
- source: 429 backoff and Retry-After under mock timers, hang timeouts,
the nearest query's parameters. policy: storage-host injection, FOV 360,
longitudes past ±180. panorama: a stub WebGL context (angles, lost
context, re-measuring). lineCoverage.test.mjs: tooHigh hint, passes().
- core: openImage({frame:false}) and openNearest leave the camera
(with a posing provider and a default-open control), KartaView's legend
and wording end to end; viewerHost supportsRenderModes.
- Fixed tick-count waits replaced by an until() poll.
- Browser gate fixture: odd photos archived (409 at storage2, served by
the proxy), the first lookup rate-limited (429, Retry-After: 0); the
line-click step asserts the camera stays and › skips the archived photo.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
P1s from the correctness review, each with a regression test:
- Cones showed the wrong page of a long sequence (cache keyed by
sequence; no re-centre after a close or a same-drive line click):
KartaView always reselects, and re-centres whenever a photo of the
selected sequence opens on another page.
- A 360° photo's first frame was one colour: the canvas was sized while
hidden (1×1). It is shown first, and re-measured every frame.
P2/P3s: a storage timeout or 5xx is not remembered as archived (only
403/404/409/410 are); dense z13 tiles draw up to 1,600 lines (a third of
downtown Detroit was cut); decoded tiles are kept 10 min; a timed-out tile
is asked once more; pages are 250 photos and the newest page request goes
first; a photo far past deleted ones, or unlisted at a page edge, still
finds its neighbours; a step that gets no answer is not the end; a lost
WebGL context redraws the photo, and a failed one is retried on the next
mount; the first flat image comes from the proxy (KartaView's own can be
1.7 MB); fileurl fallbacks must be https storage hosts; oversized answers
are refused; keyboard (Shift+← →, ↑ ↓, + −) and pinch look around a
panorama; focus stays in the viewer when a step button disables. Docs:
share links with 0.m.0, keyless count, CURRENT-STATE matched to the code.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Six candidates at once on photo storage (not the API host): the nearest
showable one wins. Jakarta line clicks opened 5/8 (0/5 before the QA
round; the rest are archived and say so), Detroit 8/8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From two agentic QA passes (UX and failure modes):
- A line click opens the nearest photo that can be shown: archived photos
(409; a HEAD says 200) and records without files are skipped, a few at
most, else 'KartaView's photos here are archived'. ‹ › skip them too.
- api.kartaview.org is HTTP/1.1 (six connections): tiles take three,
lookups two, sequence pages one, so a lookup never times out behind
tiles; a timed-out lookup is asked once more; waits are capped at 10 s.
- Sequence pages follow photo ids, not the index: a photo's page is found
by search, a neighbour page joins near an edge, so ‹ › neither skip at
page edges nor loop past gaps. Pages are 500 photos (~1.2 MB).
- ‹ › open through the core without framing: loading, errors and cones
behave as for any open; presses step on from the photo on its way; the
ends disable up front.
- Cones ignore the hidden IMAGERY / SINCE filters; no 'Selected' swatch
for a source whose lines never highlight; 'no KartaView photos in view';
an unreadable tile is not cached as empty; a 0,0 fix is no photo; a
failed lookup's error goes when another source is lit; the link goes
through the image proxy; a stalled panorama shows its front view after
15 s instead of 60.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Coverage lines from KartaView's z13-14 JSON tiles (OSM ways with photos,
chained end to start), the photo nearest a clicked line, cones along its
sequence, and a viewer for flat photos and 360° panoramas (a small WebGL
equirectangular renderer, no new dependency) with ‹ › along the sequence.
Mapillary's camera-driven line coverage and sequence cones move to the
provider-neutral core (lineCoverage.js, sequenceCones.js) with the tile
fetch, zoom ladder, colours and pick prefixes injected; Mapillary keeps
thin wrappers and its tests pass unchanged.
KartaView quirks handled: api.kartaview.org (the documented
api.openstreetcam.org serves empty tiles), photo URLs that name storage1
for photos stored elsewhere, and a photo API that answers 429 to more than
about three requests at once (two in flight, retried with backoff).
Co-Authored-By: Claude Opus 5.5 <[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 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.
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]>