Keep both: Catalonia CCTV alongside the NYC DOT TMC pack in the source
imports, README layer table and CURRENT-STATE data table, and both
CHANGELOG entries.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Normalize GeoJSON ground footprints, preserve explicit KML track heights,
and tessellate ground paths before native loading. Validate coordinates,
geometry types and decoded image budgets before allocating native resources;
strip loader-sensitive metadata and settle deferred failures and cancellation.
Render static pins in owned billboard collections instead of walking tens
of thousands of point entities each frame. Refine nearby ground heights in
batches, isolate imported pins from height picks, and release owned resources
and point selections on removal. Use safe ray origins above a hidden coarse
globe and retain valid mesh heights outside the current footprint.
Frame complete geometry with viewport-aware nadir arrivals, including walls
and rotated overlays; respect camera gestures and accept document file drops.
Strengthen real-loader, native 3D Tiles, paint, clock and lifecycle coverage.
Addresses both P1 and all 25 P2 findings in the follow-up review of #1122.
Preserve upstream changes, restore the reviewed Radio ownership regression,
and retain reporter raysung-2026 credit and #804 provenance.
Reconciles upstream main 81cf437 and PR #816 head 702ecc3; no force push.
Keep this branch's CHANGELOG entry above upstream's; upstream main no longer lists the GBFS station-name entry an earlier merge carried here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Seven QA scripts hard-coded macOS Google Chrome. They now share chromeExecutablePath(): PUPPETEER_EXECUTABLE_PATH when set, then macOS Chrome when installed, otherwise Puppeteer's managed browser. The cables screenshot path is built with fileURLToPath so it works on Windows. qa-overlay-baseline keeps its fixed Chrome because its baselines record the executable.
Co-authored-by: Sameh Khamis <[email protected]>
process.exit(2) after a successful fetch trips a libuv assertion on Windows and turns the exit code into 127. The four gates set process.exitCode and return instead, and discard the unread reachability response so an early exit does not wait for the server to finish the body.
Co-authored-by: Sameh Khamis <[email protected]>
This branch moved twice at once: upstream merged KartaView (#1121, a
third keyless street-level provider sharing a new lineCoverage.js), and
the remote i18n-spanish gained the whole-interface translation work
(1993 catalog entries, translateKnown, data-i18n-runtime, the unmarked-
string detector in npm run i18n) built on the Street View merge.
Integration keeps the remote's mechanisms and re-adds KartaView on top:
- presentMeta: remote's translateHint for coverage hints, plus the
KartaView count line as its own literal tn pair (Russian plurals need
the noun inside the template; keyed off coverageLabel.noun with the
sequences pair as the default).
- uiState: upstream's highlightsSequence legend gate (KartaView's lines
do not highlight) with t('Selected').
- layer-panels.html: remote's data-i18n/data-i18n-runtime hooks plus the
sl-since-note wiring KartaView's upload-date SINCE note needs.
- kartaview viewer: the photo-navigation aria-label and img alt go
through t(), as the remote's unmarked-string detector requires.
- catalogs: remote's 1987-entry base (its refined keys supersede this
branch's earlier Street View wording) plus the KartaView entries:
coverage hints, the upload-date note, the streets count line (with
Russian one/few/many/other forms) and the viewer strings.
npm run i18n: complete, no unmarked strings. npm test: 6520/6522 (the
one failure is the pre-existing environmental OPENAI_API_KEY leak in
devFreshDotenv). Build passes.
Upstream's KartaView PR (#1121) extracted the Mapillary coverage engine
into a shared lineCoverage.js and made the panel wording
provider-specific (coverage noun/action, SINCE upload-date note,
highlightsSequence legend gate), again in the regions this branch
localizes.
- mapillary/coverage.js: taken as upstream's thin re-export of the new
lineCoverage.js; the generic too-high hint stays msg()-marked, now in
lineCoverage.js where the default lives.
- kartaview/policy.js: coverage hints (tooHigh, noCoverage) marked as
msg() data so they translate like every other coverage hint; thrown
error messages stay English, matching the Mapillary precedent.
- presentMeta: KartaView counts streets, and Russian plurals need the
noun inside the template, so the count line gets its own literal tn
pair keyed off coverageLabel.noun, falling back to the sequences pair;
upstream's noun/action data contract is untouched.
- uiState legend: upstream highlightsSequence gate with t('Selected');
sinceNote localized with a {names} placeholder.
- layer-panels.html: sl-since-note wiring kept, data-i18n hooks kept.
- CHANGELOG: both sides kept. es/ru: 4 new keys each.
npm run i18n complete (953 messages each); npm test 6504/6506 (the one
failure is the pre-existing environmental OPENAI_API_KEY leak in
devFreshDotenv, unrelated); build passes.
KartaView's later viewer changes are carried into the shared
photoViewer.js: the camera bearing (`bearingOf`), redrawing the same
photo after a GPU reset through `onReopen`, no pose while a lost context
recovers, the 2,560 px flat version only once the viewer outgrows
1,280 px, `refreshPose`, and one sharper-panorama load at a time with a
second look once it lands. Its tile cache's `keyOf` and `clear` move to
the shared tileCache.js. KartaView gets `onEscape` beside `onReopen`.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A final review of both PDFs found text the earlier checks missed:
- Map cards: fire confidence (high/nominal/low) and age (24h), vessel
types (TOWING, TANKER) and installation classes (BASE, AIRFIELD) showed
in English. They now go through the catalog; the data stays English.
- The wind readout kept "10-14 14:00 UTC" and "29.93°N 97.74°W"; it now
uses the locale's date, decimal separator and hemisphere letters.
- Catalog: one term per concept (label/подпись, basemap/подложка,
PROVIDER CONTROLS, HEARD, zoom in, REGIONAL NEWS), calques and broken
grammar in the scene editor, provider settings and Bhote Koshi notes,
and Spanish wording that read wrong ("restringidas por el proveedor",
"Incluye … no se incluyen", "justo ahora", "Sal").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Esc moved to the viewer's box (`.sl-viewer-wrap`) in the last commit, but
without one it was a second keydown listener on the root; Esc is now taken
by the root's own handler there. format-scope.json listed
panorama.test.mjs twice, which made the formatter refuse the scope; the
files it skipped are formatted.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From a five-lens IC8 review (correctness, security, tests with mutation
testing, performance, UX):
- P0: over Paris at 12–60 km the unsimplified z10–z12 lines sent ~2M
points to Cesium's ground-polyline builder (gigabytes of geometry; the
tab hung). decode.js now simplifies lines (Douglas–Peucker, 4 tile
units: 6–10% of the points), and lineCoverage gains an opt-in point
budget (`maxPoints`, 10,000 a tile for Panoramax).
- Decoded coverage tiles are kept (KartaView's tile cache moves to the
shared tileCache.js); a sequence's z15 picture lists are kept and load
on their own connection; cones know their tiles from the start of a
load, so a picture opening meanwhile re-centres them and ‹ › no longer
restarts the load.
- The HD panorama loads only for the expanded viewer (a docked one on a
dense screen asked for 3 MB originals) and is aborted when another
photo opens; picture files over 48 MB or 16,384 px are refused; a
bitmap no renderer took after a context loss is closed.
- A nearest lookup survives one of its two searches failing; only a
timeout is retried; offline reads as unreachable; Retry-After is
capped at five minutes; the search box stays on the map at ±180°;
cones near both edges of a narrow (high-latitude) tile read both.
- Every producer is credited (app and photographer); with none to name,
the caption says "via <instance>" rather than crediting the host.
- Esc anywhere in the docked viewer (toolbar, caption) clears the
sequence before the panel takes it. A filter that hides every density
dot says so; wording aligned ("coverage", "photo").
- Tests for every surviving mutant (Esc consumed, keepOrder and dot-size
wiring, licence in the core, limiter aborts, radius floor, bbox corner,
first-producer-only, byte and size caps), a limiter test, and a gate
fixture that answers box searches and leaves out pictures facing away;
the gate now clicks over the other sequence's pictures, so only the
line click's own-sequence lookup can pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
From a UX QA round in the browser against the live API:
- A line click could time out while coverage loaded: the meta-catalogue
speaks HTTP/1.1 only (six connections), and six tiles plus four
searches queued the lookup behind megabyte tiles. Tiles now take
three connections and searches two; a search that timed out is asked
once more.
- Between 30 and ~100 km Paris read as empty: the density grid is ~10 km
a cell. Lines now draw to 60 km (z10), as Mapillary's do; above, dots
grow with the pictures they stand for.
- Esc in the docked viewer collapsed the whole panel instead of clearing
the sequence the panel says it clears (shared photo viewer: KartaView
too). The viewer now offers Esc to the provider first; expanded, the
dialog keeps it.
- A picture whose producer is an email address is credited to the
instance holding it (its `via` host), so Licence Ouverte pictures name
their source.
- Empty states say why: "no Panoramax photos here match the filters"
when the filter hid every line, and nothing to zoom into over empty
sea. Wording follows KartaView's ("photos", "click a line for its
nearest photo").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolve the CHANGELOG.md conflict by keeping both new entries: this
branch's group_by/stats entry above main's GBFS v3 station-name entry.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From an adversarial functional QA round against the live API:
- The nearest lookup missed pictures looking away from the click
(place_position leaves them out at any tolerance): it now runs a
bounding-box search beside it and ranks both by distance. A line
clicked from high up searches its sequence within height / 100
(50–400 m).
- The per-tile line cap kept the newest sequences, not the longest
(lineCoverage re-sorted them): providers can now keep their decoded
order (`keepOrder`).
- A long producer name froze the tab (quadratic email regex): linear
check. A malformed link escape no longer fails the lookup; null or
string coordinates no longer land at 0,0.
- A line with no camera type or day stays drawn under the filters.
- A sky click on a line changes nothing (as KartaView), instead of
selecting a sequence with no cones.
- An unreadable tile reads as such; a failed neighbour z15 tile only
thins the cones; unknown or refused ids say the picture is gone.
- Shared viewer: the sharp-panorama guard is per open (a late download
of the previous photo no longer lets a second one start); an HD-only
360° picture is shrunk or skipped like any original; panorama.js read
the bitmap size after closing it, so 2:1 power-of-two panoramas
clamped instead of wrapping.
- Tests for each, and for the gaps mutation testing found (preferred
point, cone order, no reload within drawn tiles, newest-first gate,
stale bitmap release, flat upgrade retry).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Panoramax, the open federated street-level commons, registers beside
Mapillary, Google Street View and KartaView. Its meta-catalogue
(api.panoramax.xyz) is keyless and CORS-open, so it runs in the browser:
- coverage from its vector tiles: sequence lines at z11–14 below 30 km
(longest first under the per-tile cap), and above that the grid layer's
density cells as binned points, not counted as sequences;
- a click on a line opens that sequence's picture nearest the click
(STAC search, any direction), without moving the camera, then any
sequence's if it has none within 50 m; the 360° and since filters go to
the lines and to the API;
- cones from the z15 tiles' pictures layer around the open picture
(~110 bytes a picture, against ~7.5 kB for a STAC item), re-centred when
a picture opens outside them; ‹ › follow the picture's prev/next links;
- the caption names the author (not an account named by an email
address) and the picture's licence (StreetPose `license`), and links to
the picture in the Panoramax viewer; the globe credits Panoramax.
KartaView's viewer becomes the shared photoViewer.js (flat <img>, 360° on
panorama.js, ‹ ›, keyboard, pinch, context loss), driven by a per-provider
PhotoSources table; the request limiter moves to limiter.js. Panoramax shows
the 2,048 px SD derivative and fetches the HD original only when the viewer
is wide enough, once, shrunk while it decodes (never above 8,192 px).
Orange #ff6f00 (Panoramax's own), off by default, share option
0.m.0_0.r.panoramax-1. The MCP panel allows the meta-catalogue and the
biggest instances. Tests: policy, decode, source, viewer, provider, and a
Panoramax fixture in the hermetic browser gate.
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]>
Adding the app to a phone home screen produced a blank tile: index.html
linked only an SVG favicon, and Safari ignores SVG for apple-touch-icon,
substituting a screenshot of the page. There was no manifest at all, so
Android had nothing to install against either.
Generate the rasters from public/logo.svg with sharp, which the repo
already carries. The output is committed so a plain `vite build` needs no
image toolchain; re-run scripts/generate-icons.mjs after changing the
logo. The logo's viewBox is wider than it is tall, so each icon centres
the mark on a square #0a0a0f canvas rather than stretching it, inset to
78% normally and 54% for the maskable variant to survive Android's
circular crop.
The SVG favicon stays as the browser tab icon.