Replace the semantic keys with the English source text, so code reads as the
UI does and English needs no catalog: t('Save keys'), tn(count, one, other)
for plurals, msg() for text stored in data, and bare data-i18n* markup that
translates the element's own English. Other languages are one flat JSON each.
- Simplify src/i18n: drop namespaces, the configurable locale pair
(GEV_DEFAULT_LOCALE / GEV_SECONDARY_LOCALE) and its build defines. Locale
comes from ?lang=, the stored choice, then browser languages.
- Add scripts/i18n.mjs (npm run i18n): extracts every message from t/tn/msg
and markup, and fails on missing, unused, placeholder-mismatched or
wrongly-shaped plural entries. npm test runs it via catalog.test.mjs.
- Add Russian (src/i18n/locales/ru.json, 833 messages), started from the
community catalog in #312 and reviewed against the current English.
- Fix data-i18n placed on parents that would wipe their children (cockpit
LEVEL and local weather cells, first-run EXPLORE MANUALLY tile), and a
doubled degree sign in the TRACK ACQUIRED signal.
- Rewrite docs/TRANSLATORS.md for the new workflow; drop sections the earlier
merge resurrected in docs/CURRENT-STATE.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The panel connected before the shell had its data manager, so it never
heard switch-on requests and a user switch-on left it collapsed.
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]>
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]>
- 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]>
From gekh's review of #768, each with a test that fails before:
- P2: the panel no longer reopens on every page load. The shell reports
who asked for Street Level to switch on; only a user, voice or tool
request (isExplicitLayerStateOrigin) or an opening photo opens it, and
those automatic opens are never stored. Without the hook, a stored or
shared "collapsed" choice wins during the restore window.
- P2: the expanded photo hides in Clean View, recording and the cockpit,
and holds neither Esc nor Tab while hidden.
- Esc/Tab are taken only from the viewer, body or globe, not app-wide;
FIT/FILL show the right choice while expanded; a 0x0 viewer is never
resized (no z=NaN tile request); the expanded viewer keeps the panel's
CSS tokens; KEY REQUIRED hides the raw "no_key" code.
- Share links carry the panel's collapsed state (ui token t); the Cyber
theme frames the panel like its rail peers (the pinned Cyber selector
tests now list it).
- Accessibility: radiogroups move with arrow keys and are one tab stop,
their focus ring is no longer clipped, and the provider chips sit
outside the disabled fieldset so the "how to add the key" hint stays
reachable (a gated chip explains instead of toggling).
- Renders write only changed attributes (the rail's MutationObserver
relaid out on every write); one coalesced resize per open; the legacy
render-mode key migration is gone.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Brings in 123 commits from main, including the v0.2.0 and v0.2.1 releases
(MCP server and tool catalog, server security hardening, build hygiene).
Conflicts:
- package.json: kept main's script order and added this PR's
qa:panel-resize and qa:street-level; exports keep both sides (main's
tools/view entries and this PR's street-level and Mapillary entries).
- src/tooling/viteBuild.test.mjs: kept this PR's Mapillary-token define
assertion and main's new wildcard-host allowedHosts assertions.
- CHANGELOG.md: main now has release sections; this PR's entries go under
[Unreleased].
Checks after the merge: doctor, format:check, check:boundaries,
layer-token:check (28 published, 1 new: street-level 0), npm test (5726
pass), build, the Windows job's focused tests (70 pass), qa:panel-resize,
and the keyless street-level gate. The new CSP allows Mapillary (https:
connect/img, blob workers). The keyed gate stops at coverage locally
because the dev .env Mapillary token is invalid (upstream 403), not because
of the merge.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
P0
- tileMath: lonToTileX(180) wrapped to column 0, so a whole-world view at
z1 loaded only the western column; 180 is now the last column.
- coverage.loadTile: a superseded request (after a retire or clear) still
deleted the newer request's pending entry and loading count in `finally`,
letting a duplicate load attach lines that nothing could remove. Only the
request that still owns the key attaches its tile or settles the key.
P1
- view/tileMath: a view across the date line read as a ~358 deg box and
loaded tiles on the far side of the globe. visibleBbox now returns
west > east for such views and tilesForBbox splits them at +/-180;
viewCentre averages across the line.
- decode: the parts of a multi-line sequence were joined into one line,
drawing straight segments over capture gaps. Each part is now its own
line (`parts`); later parts carry `~n` in their pick id, and picking or
recolouring any part acts on the whole sequence.
- viewerHost: a prewarmed MapillaryJS viewer was never destroyed, leaking
its WebGL context. Prewarmed adapters are tracked and released on
unmount, at once when the layer went off mid-load, and per provider when
only that provider is switched off.
- nearestImage took the first record the API returned; it now takes the
closest one that passes the filter.
P2
- groundCast: the cache was cleared after a request counted its corners,
dropping ones it relied on; it is now cleared first.
- streetLevelControls: the viewer was resized on every render (every frame
while a panorama is dragged); the ResizeObserver handles size changes and
the viewer is resized once when it opens. The legend now rebuilds when its
entries change, not only their count.
- panelPositionControls: the portable-panel paths this PR added called the
CCTV viewport sync directly; they go through onPanelResized, which the
shell routes to CCTV. The "Compose camera panel controls" JSDoc is back on
_initCctvPanel.
New tests: zoom-1 columns, date-line tiles and bbox, multi-part decode and
part pick ids, nearest image, prewarmed viewer release, and the cache
ordering (fails on the old order).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Route every application-owned presentation string through the i18n
core: t() call sites across the app/layers/ui/data/voice surfaces,
data-i18n* attributes in the src/ui/templates/*.html markup (applied at
boot by applyDocumentTranslations from src/standalone/application.js),
and the dock locale selector (runtime-rendered from the offered pair —
a single EN button while only English ships).
Rebased onto current main, where the extraction follows the module
splits: the shell wiring now lands in the owner modules
(panelChrome, visualSettings, locationNavigation, shareRestoration,
shellFeedback, applicationShell) and the voice status corpus in the
realtime* modules; scene status lives in director.js +
playbackAdapter.js.
Modules shared with the node runtime (loadingFeedback,
installationFeedback, regionalModel, keySetupCore, vessels ingestion)
stay environment-neutral English and localize at their browser render
sites (chip maps in shellFeedback/installations controls, the weather
table in cockpitBriefing, keySetupRequirementEnvVars compositions), so
the package-boundary graph stays clean; the browser boundary groups
now own the i18n modules they reach (scripts/package-boundaries.json).
The panel changed the imagery filter and provider switches by calling the
layer directly, which bypasses the data manager's params request, the only
path saved state and share links record. A link copied after picking 360°
or a SINCE window came back with the defaults.
The controls now send those changes through
dataManager.setLayerParams('street-level', ..., { origin: 'user' }), as
the CCTV panel does; the layer applies them through its own setParams.
The feature gate's 360° step now asserts the share hash carries `0.p.p`;
it times out on the previous controls.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The viewer opens at the top of the panel, under the header, so the photo
is on screen without scrolling; its tools (EXPAND, FIT/FILL, FOLLOW, close)
sit in a bar above the image, clear of the provider's own overlays, and the
caption wraps instead of truncating.
- No separate ON/OFF or OPEN NEAREST PHOTO buttons: the header pill is the
layer switch, and a provider chip is lit only while the layer is on (the
last lit chip switches the layer off).
- SINCE is a stepped slider (any date to the last month) whose readout names
the cut-off date and is announced via aria-valuetext.
- The panel floats and resizes (portable spec); in a resized window the
viewer takes the spare height and only the settings scroll. SHRINK after
EXPAND, collapsing, or a header double-click docks it at its default size.
- FIT/FILL tooltips say what they do.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bring in portable, resizable rail panels. The Street Level rail rules get
the same :not(.panel-floating) guard as CCTV's so the panel can float too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Pure move: the layer id, panel id, CSS prefix, classes, exports and config
paths become provider-neutral (street-level, #street-level-panel, sl-);
the Mapillary-specific modules move under providers/mapillary/. Behaviour
is unchanged. The two sprite collections now register under distinct ids
instead of overwriting each other under 'mapillary'.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Upstream took share token '2' for fire perimeters, so Street Level moves to
'3'. mapillary-js joins upstream's optimizeDeps include list instead of
overriding it; catalog and registry tests count the new upstream layers.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The panel no longer shows a separate keyless call-to-action; the fieldset
gate and the layer-row tooltip already cover the missing-key case. The
planner flag is tri-state (null until status answers).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Adds a Local ADS-B layer fed by your own receivers: a browser WebUSB
RTL-SDR (with FM listening) and local 1090/978 MHz decoder feeds
(LOCAL_RECEIVER_FEEDS, local and private addresses only). Local aircraft
draw in magenta with class, silhouettes, 3D models, trails and a position
sanity filter; the click card names the band and input that heard them.
Builds on Sameh Khamis's SDR prototype and the initial GEV port by
Mazeyar Moeini Feizabadi (#134); the decoder-feed design follows the
notes on #57. Closes#57.
Co-authored-by: Sameh Khamis <[email protected]>
Co-authored-by: Mazeyar Moeini Feizabadi <[email protected]>
Twenty layers now (weather, wind, cyclones and recent imagery landed
upstream); Street Level sits after Recent Imagery in the right rail and the
Cameras group, and the rail CSS names it beside the other panels.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
An ordinary collapsible GEV panel between CCTV and Context, starting
collapsed with the Mapillary mark in its strip. Body: a gated empty state
when no client token is present, plain-English query box (ASK becomes STOP
while running), streamed result chips, imagery filters with a colour
legend, and the embedded viewer whose expanded mode is a focus-trapped
dialog. All wording lives in a pure presentation module.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
A drag spec can now be `portable`: dragging the panel by its header lifts
it out of the right rail into a fixed window, resizable from all four
edges and corners, that remembers its place and size under the existing
godsEyeView.v8.panelPos.<id> key ({ left, top, width?, height?, floating })
and snaps back on a header double-click. Only CCTV opts in here; the rail
skips .panel-floating panels when measuring and allocating, and its
placement rules in layers.css are guarded so no !important is needed.
PanelPositionControls gains an optional onPanelResized hook, threaded
through PanelChrome to a no-op on the application shell.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Wind (NOAA GFS and ECMWF IFS: animated particles, colour field, a reading at map centre), observed weather (NOAA MRMS radar, GOES and NESDIS infrared clouds, lightning density) on one shared history timeline, NHC and CPHC cyclone advisories with tracks and cones, and a WEATHER panel in the right rail. On 3D Tiles the observed products render as raised shells with a sharper window around the view.
Co-authored-by: Gustavo Beneduzi <[email protected]>
Co-authored-by: Daniel Slay <[email protected]>
Co-authored-by: Rehaan Delmotra <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
Supersedes #218 by @spcpza, ported onto the current component layout with fixes from review.
Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.
Contributor: @spcpza. His original commit is the base of this branch, and his own re-port onto main (2026-09-15, `5b69011`) was consulted while finishing the height work. That port reads every vehicle's surface from the app's shared ground-floor system (`cachedGroundFloor` / `warmGroundFloor` / `GROUND_FLOOR_LIFT_M`) rather than sampling the scene per layer, keeps a vehicle hidden until its cell answers, warms a bounded batch per poll on a timer instead of per frame, and renders at the ellipsoid above the altitude gate. That is the sounder primitive — one floor system for the app, with the rooftop-versus-street question already settled against a datum-correct DEM prior — and this branch's height work was rebuilt on it, replacing a bespoke cell cache, ground prior and rooftop rule written here. Credit for that direction is his.
Future work (not in this PR), both written up in docs/CURRENT-STATE.md:
- Readable route names. The realtime feeds carry only `route_id`; `route_short_name` / `route_long_name` live in each operator's static GTFS `routes.txt`, and headsigns in `trips.txt`. Doing it properly wants a server-side metadata loader beside the transit proxy with fixed registered static sources, bounded indexes built once and persisted, a slow conditional refresh, and a versioned route dictionary the browser caches. Shipped here: ids are never rewritten, and a cut one carries an ellipsis instead of reading as a typo.
- Below-ground rendering. Subway vehicles are drawn on the surface directly above themselves, because the feeds carry no depth and a train nobody can see is not worth drawing; the card says so plainly. Real treatment wants OSM tunnel geometry per city and a ghosted sprite tethered to the surface. The owner recorded it and left it.
Round four and after (2026-09-15). The owner tested round three under FLIR and NVG, and then asked for the design to be taken further: elegant in performance, beautiful on camera, trails with a cache for public transport, Cesium-native, and a core other moving layers can adopt. Codex Astra reviewed the plan (it confirmed the speed fault: a glide started from the drawn position at receipt time but spanned the gap between report times, so with MBTA reporting every ~10 s against a 15 s poll every second glide ran at 1.5x; it rejected a queued-segment patch), then wrote the design, then implemented it in commit-sized passes with adversarial review between them and real-browser harness runs by the integrator.
What this PR now contains beyond the contributor's original port:
- Delayed playback in a contact-agnostic core (`src/data/contactPlayback.js`): each vehicle keeps a typed, bounded fix history and a display clock that runs at one second per second through it, a feed-derived lag behind real time, holding at its newest fix rather than extrapolating and never sprinting to catch up. Marker, course, motion word, card and trail all read the same displayed sample. The clock has an explicit time-offset and seek API so a rewind is a later feature, not a rewrite; flights and vessels are untouched.
- A selected-vehicle trail (`src/data/contactTrailRenderer.js`, `src/layers/transit/trails.js`): the retained history drawn with batched Cesium primitives, clipped at display time so the head is exactly the sprite, faded by age, in the mode colour (white under sensor presets), on the same surface path as the marker.
- Bounded proxy-side history (`server/providers/transitHistory.js`): fifteen minutes and at most 128 fixes per vehicle with hard byte ceilings, MBTA only for now (opt-in per feed), served by a small endpoint that never fetches upstream, so a selected trail survives a browser reload while the server keeps running.
- Scene work bounded to what is visible and moving, an occluder, cached DETECT candidates, holds on the render loop only while something visible moves.
- Sensor presets: white-hot haloed sprites under NVG/FLIR/noir (black-hot inverts), saturated mode colours under CRT, per-mode bracket tiers in every theme, the selected card keeping its mode colour.
- A discriminating browser harness (`scripts/qa-transit.mjs`) that samples postRender frames, reads the real card anchor, and measures fleet budgets at 800 and 3,000 vehicles.
Not built, recorded as follow-ups: curated route display names (lowest priority per the owner), fleet-wide tails, route snapping, a rewind UI, adoption of the playback core by flights and vessels.
Verified at merge (2026-09-15 morning): unit suite 3,896 passing, build clean, format and boundary gates clean, tracking regression 109/109, browser harness keyless 200 pass / 0 fail (13 sensor checks unexercised because the live view offered fewer than six isolated sprites; every judged sprite passed), keyed on Google 3D tiles 198 pass with the per-frame allocation gate as the only failure. Follow-ups recorded: the keyed build's playback sampler still allocates per frame (CPU and frame time stay within budget; the 3,000-vehicle metro view sits at a 27.6 ms frame interval against a 25 ms budget), the shared detection overlay's per-paint allocation, friendlier route names, fleet-wide tails, a rewind UI, and adopting the playback core for vessels and flights.
Co-authored-by: spcpza <[email protected]>
## Explore the map without losing your target
Tilt and north-up used to stop tracking an aircraft or satellite. Both controls now keep the selected target centered while easing into the new angle over 650 ms, preserving camera range. Tilt switches between top-down and oblique views; north-up takes the shortest rotation. Rapid presses reverse a pending tilt, and pressing north-up during a tilt completes both changes.
New navigation, target selection, map gestures, and teardown cancel the transition. Cockpit keeps its existing camera guard.
## A data panel that follows how you explore
The panel now has five clearly labeled sections:
| Section | Layer order |
| --- | --- |
| Movement | Satellites → Live Flights → Military Flights → Live Vessels → Street Traffic → Bike Share |
| Cameras | Cameras → Mapped ALPR Cameras |
| Infrastructure | Mapped Installations → Data Centers → Submarine Cables → Dams |
| Events | Space Missions → Earthquakes → Active Fires |
| Utilities | Directions → Radio |
Readable panel labels replace CCTV, Bikeshare, Datacenters, and provider-heavy names. Source attribution remains in the subtitles, and toggle accessibility labels match the visible names. Presentation order is independent of layer registration; hidden layers stay hidden and unlisted layers remain available under Other layers.
## Validation
- Camera changes: full Node 24 suite, including allocation gates — 3,699 passing and one skipped.
- Final panel changes: 42 focused tests passed; browser inspection confirmed all sections, ordering, and labels.
- Real Cesium Camera/EntityView tests cover moving aircraft, low/high satellites, cancellation, navigation ownership, and rapid button presses.
- Live browser: UITMSAT-2 remained centered and selected through tilt and north-up, including combined commands.
- Final formatting, package-boundary checks, and production build pass.
- GitHub CI verifies the final head on Node 24, Node 26, and Windows onboarding before merge.
Supersedes #442 by @yashveeeeeeer, ported onto the current component layout with fixes from review.
Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.
Co-authored-by: Yashveer Singh <[email protected]>
Supersedes #219 by @spcpza, ported onto the current component layout with fixes from review.
Verified before merge: format, package boundaries, unit suite, build, and the browser tracking suite; a browser harness for the change where one applies; adversarial review applied.
Co-authored-by: spcpza <[email protected]>