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]>
Fixesbilawalsidhu/gods-eye-view#927. Each stdio MCP server made its own
random panel key, and panel_request accepts only the key in the panel
page. A client that starts two server processes for one connection and
reads the page from one while sending the page's requests to the other
(seen in Claude Desktop on Windows) therefore always got "Only the God's
Eye View panel may make this request".
The stdio entry now passes a key shared by the install, created once in
.gev-cache/mcp-panel-key. The file is staged 0600 and published with
link(), so processes starting together agree on one key and never read a
half-written one. If it cannot be stored, the process keeps a key of its
own, as before. createLocalMcpServer still makes a fresh key unless given
one, so in-process servers (the /mcp HTTP plugin, tests) are unchanged.
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]>
CI's fixture gate timed out on the header ON/OFF pill: page.click reads a
point and then presses it, and the rail can still be resizing the panel
after coverage arrives, so the press missed. Panel clicks now wait for
the panel to hold still and check that the control is what lies under the
point before pressing. Also correct the header note on --fail-on-retry.
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]>
CI on fb024d5 failed step 24 with QA_FAIL_ON_RETRY=1: the press after a
double-click dock landed on the globe because the panel, showing a photo
that was still settling, moved 116 px up and lost 46 px of height between
the pre-press hit check and the press (box 1058,382 330x471 -> 1058,266
330x425, from the new diagnostics and the uploaded screenshot). That is
the panel's own content changing its layout, not the rail race fixed in
9dde7b4.
- Fail-on-retry now fails a miss only when the panel's box was the same
at the hit check and at the press (the pointer missed a header that
stood still); a miss the panel moving explains is retried with a
::warning:: naming the move. New harness test with a moving panel.
- Step 24 waits for the open photo and its sequence to finish loading
before pressing the header again.
Verified: strict fixture gate on a production build 28 ok + 2 "no Google
3D" skips and the panel gate 6/6, both with QA_FAIL_ON_RETRY=1.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The CI gate builds the app and serves it with `vite preview`, with a
dummy non-secret client token (a fork PR never gets secrets), and
answers the Graph API, image tiles and thumbnails from fixtures: 41
photos on a line under the parked camera and one generated 640x320 JPEG
(scripts/fixtures/street-level). All 7 photo steps now run in CI, plus a
real click on a coverage line and on a cone, the framing flight stopping
on close, the inert modal, the viewer returning into the panel, and a
reload keeping a collapsed panel collapsed (share ui token t).
- Fixture interception is limited to Mapillary hosts via CDP
Fetch.enable: page-wide request interception stalled Cesium's workers,
so the old fixture mode never drew anything on the globe.
- Failure evidence (screenshots, UI state, console, failed requests,
render errors) is written to qa-artifacts/ with keys and tokens redacted
and uploaded on failure; qa-artifacts/ is gitignored.
- Cesium render-loop errors fail the gate; Google 3D branches that cannot
run print as skips, and --strict rejects any other skip.
- Panel presses log their diagnostics, warn on a retry, and CI now fails
on any retry (QA_FAIL_ON_RETRY=1) since the rail settles; a press with
no pointerdown is a failure, not a retry.
- Assertion-gating sleeps 27 -> 1; the rejected-key "no requests" check
has a positive control.
Verified locally: strict fixture gate on the production build 28 ok + 2
"no Google 3D" skips (twice, fail-on-retry on); panel gate 5/5 plus no
render errors; live gate with a real token 25 ok + 3 fixture-only skips.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768, each with tests that fail before:
- P2: the expanded viewer is a real modal: while it is on screen every
other <body> child is inert (only the ones it set are restored, on
shrink, close and destroy, and when Clean View/recording/cockpit hide
it). The voice pill and toasts are not clickable while it is expanded.
- P2: the tile proxy follows redirects by hand, at most 3 hops, each one
HTTPS on exactly https://tiles.mapillary.com without credentials;
anything else is a 502 and the token never leaves that origin.
- P2: the real dev and preview server smoke tests request
/api/mapillary/status and a tile, and the browser gate first checks the
server's real status route before any interception, so CI fails if the
Mapillary plugin is not registered.
- P2: both gates press panel headers through one shared helper
(scripts/qa-panelDrag.mjs): re-resolve the header until it is under the
pointer, record what the press hit, wait on the real floating/docked
state, and retry only a press that provably missed the header.
Verified: unit 5910 pass; fixture gate 17/17; panel gate 5/5; live gate
with a real Mapillary token 22/22.
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 with a test that fails before:
- The routes refuse cross-site requests (sameSiteGated, as the paid
proxies do) and the tile route has a generous per-IP limit (600/min), so
another page cannot burn the token's quota or trip the shared 429 hold.
- At most six upstream fetches run at once; a queued request that is
abandoned never fetches, nor does one abandoned during the disk read.
- A body past the 48 MB cap aborts the upstream request as it streams.
- Empty tiles are charged against the memory and disk budgets.
- Each disk write uses its own temp file and a retrim is skipped while a
write of that tile is pending, so concurrent hits cannot extend a tile's
life or fail a rename.
- Upstream refusals other than 401/403/429 answer 502.
- The tile route's success path is tested end to end (200 with the image
layer stripped and the right headers, 204 when empty, an abandoned
response aborts upstream); the teardown race that made this file flaky
is gone (240 parallel runs, no failure); the QA-constant tautologies are
replaced by a check against the real provider registry; the boundary
group lists the modules the provider actually imports.
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]>
The diagnostics from d9b13c7 named the CI failure: the press landed on the
globe canvas, not the header, because the CCTV panel had moved 32 px down
between the hit check and the press (docked at 234, 266 at the press) -
something above it in the rail grows on the runner after load. A press that
provably missed the header (the page records the pointerdown target) says
nothing about dragging, so it is retried up to twice, logging what sat
above CCTV at dock and at the retry; a press that landed in the header and
did not lift still fails at once.
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]>
On the CI runner the header drag stopped lifting CCTV (it did not float),
right after a run where its box never held still: the rail is shifting
there, so the press can land beside the header. It does not reproduce
locally (both gates pass on a fresh key-less dev server). The gate now
waits, bounded at 10 s, until the title point is on the CCTV header before
pressing, and a failed lift reports the element pressed, whether it was in
the header, and where the panel was when docked and at the press.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On the CI runner the CCTV box never held pixel-identical for ten animation
frames within 15 s, so settle() timed out and failed the gate although the
street-level gate before it passed. The wait now rounds the box to whole
pixels, polls every 100 ms for half a second of stillness, and only logs if
the rail keeps moving: the lift is checked against the box recorded at
pointerdown, so settling is only there to make that box the docked one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The fixture gate always reported a configured key, so the KEY REQUIRED path
most installs see never ran in CI, nothing checked that the lazily imported
MapillaryJS viewer loads, and the FOLLOW step (which needs an open photo)
failed for anyone with a Google key. Fixture runs now:
- check the viewer is stood up in the panel ahead of the first photo;
- serve a rejected key and assert KEY REJECTED, no further tile requests on
a pan, and no stale message once the layer is off;
- open a second page whose server has no key and assert KEY REQUIRED;
- treat FOLLOW as a photo step.
Passes 16/16 locally against a dev server with Google 3D available.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The panel gate read the docked box after a fixed 600 ms sleep and expected
the lifted window 100 px above it. On the Linux CI runner the rail was still
moving and the panel sat 8 px lower when the drag began, so the gate failed
("lifted top: expected 156.5, got 164.8") although the window followed the
pointer exactly. Wait for the box to hold still for ten frames, record it at
pointerdown, and compare the lifted window against that.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
preserve Bilawal's merge history, OpenAI schema-combinator startup fix, and Contacts viewport note
keep the validated voice and analyst behavior while adopting the current provider-neutral feed routes and Google token fallback
The Street Level and panel gates never ran in CI, and the live gate needs a
Mapillary token, the Mapillary network and real coverage at its test site,
so everything users see was guarded only by manual runs (which a bad token
and expired tile caches blocked twice this week).
`qa-street-level.mjs --fixtures` (npm run qa:street-level:fixtures) answers
the page's /api/mapillary/status and coverage-tile requests from generated
tiles (a street grid with flat and 360 deg sequences, half captured three
years ago; overview points from orbit) and blocks mapillary.com. Only the
six steps that open a photo are skipped. Because the data is known, the 360
deg and SINCE steps also assert that the filters remove sequences, not just
that counts do not grow. A unit test pins the fixture contents.
New CI job "Street Level browser gate": install with headless Chrome, start
a key-less dev server, run the fixture gate and qa:panel-resize. Locally,
against a dev server with every key blanked: 14/14 on three consecutive
runs, panel gate all pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the test-engineering review of #768: the rendering and panel code had
thin unit coverage (coverage.js 29% of lines, sequences/viewer/selection
~30%, cameraFollow 50%, streetLevelControls only source-text checks), and
two shipped bugs were caught only by the manual browser gate.
- coverage.test.mjs drives the real createCoverage against a fake scene and
a source whose tile requests the test resolves. It reproduces gekh's P0 #2:
a request superseded by retire() finishes after a newer one for the same
tile. Without the ownership checks it drops the newer request's key and a
refresh starts a duplicate whose lines nothing can remove (fails on the old
loadTile, passes now). It also covers the draped-to-cast swap keeping both
line sets on the globe until the cast one is built and clear() removing
both mid-swap, and a tile with nothing castable not being rebuilt.
- coverageFixture.mjs encodes Mapillary-shaped coverage tiles (sequence
lines, overview points) from lon/lat; the tests and the hermetic gate use it.
- cameraFollow.test.mjs pins the underground-camera fix: a -14,886 m sample
is ignored for bare earth, a plausible sample wins, and following stands at
eye height. Three of the four fail on the pre-fix module (the first reports
"centre at -14884 m").
- streetLevelControls.test.mjs is a contract test through the real
DataLayerManager and LayerStateCoordinator: a 360 deg click and a SINCE
release reach the durable layer state and the encoded share link under
Street Level's token. Against a stand-in layer it also covers the chip rule
(darkening the last lit chip turns the layer off, provider kept on; lighting
a dark chip is a user params request, then enable), the legend rebuilding
on a same-count swap (review #9) and the viewer resizing only on open
(review #8). With the old direct-to-layer routing, three of these fail,
including both share-link tests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Norway's ~850 working road cameras load from one keyless GeoJSON request
to the agency's OGC API view of its DATEX 3.1 CCTV table, so no DATEX
account is needed. Faulty cameras are dropped, every camera sits under a
"Norway" category, and the ~135 that publish HLS play as live video
through the existing in-memory relay with the still as fallback. Frames
and manifests are pinned to each camera's own path on the agency hosts.
CCTV_VEGVESEN_ENABLED=0 disables the pack, CCTV_VEGVESEN_MAX_SOURCES
caps it (nearest the largest cities first) and CCTV_VEGVESEN_VIDEO=0
keeps stills only. Attributed under NLOD 2.0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keeps the voice-core prompt rules and adds main's world-query and
vessels-vs-visuals rules; the tool catalog wraps the deixis-aware runner.
Also fixes two things the branch had before the merge:
- Live voice could not start: OpenAI rejects track_entity's top-level
anyOf. Model-facing tools now omit top-level combinators; the runner
and benchmark still require a query or a referent.
- The Contacts-centred analyst answer dropped the viewport coverage note.
The fixture answers /api/vessels without X-Feed-Source, so the source
fell back to the feed's name and the attribution check failed. Send
AISStream as the bundled server does.
The flight, military and vessel feeds were named after the providers the
bundled server uses (/api/opensky, /api/adsblol/mil, /api/ais-live and
their track routes; createOpenSkySource, createAdsbLolSource,
createAisStreamSource). Any server should be able to answer a feed from
its own provider, so the feeds are now:
/api/flights /api/flights/track?icao24=
/api/military /api/military/track?hex=
/api/vessels /api/vessels/track?mmsi=
with createFlightSource, createMilitarySource and createVesselSource. The
old routes are removed. Each server names its provider in X-Feed-Source
(the bundled server sends OpenSky Network, adsb.lol and AISStream, so its
attribution is unchanged) and the military feed's cache state and age in
X-Feed-Cache and X-Feed-Age-Ms. Sources report the provider the server
names and fall back to the feed's name.
The vessel source accepts an optional area and sends it as lat, lon and
radius_km. The vessel layer asks for the area the camera shows (centered
on the middle of the screen, sized by camera height), or every vessel
from high enough up, and asks again when the view moves or zooms by a
quarter of that radius. vessels_in_area names its area too. Servers that
hold every vessel may ignore the area; callers still keep only what is
inside it.
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]>
The Cockpit utility gate still expected five modes and an inherited NOIR
entry. Cycle all seven modes, check each drives only its own shader and that
Normal clears them all, enter on a Noir map preset, and restore it through
Exit Cockpit and Reset.
Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
localRequestGate.mjs owns the reverse-proxy header list and hasProxySignals;
Provider Settings, the cross-site gate and /mcp all use it instead of two
copies of the same nine names. The changelog credits the cross-site gate.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Binding HOST=0.0.0.0 no longer sets Vite's allowedHosts to true. The dev and
preview servers accept IP addresses, localhost and the LAN hostnames listed
in GEV_ALLOWED_HOSTS; suffix and wildcard entries are ignored. The
configuration factory takes the resolved list as an input, so build/vite.js
still reads no environment.
Ported from #97 to the current build/vite.js layout. Fixes#21.
Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The OpenAI endpoints (/api/realtime/token, /api/openai/hud-summary) now
allow 30 requests per minute per client IP and the Google Places endpoints
120, the caps the Pinokio build already ships. GEV_RATELIMIT_OPENAI_PER_MIN
and GEV_RATELIMIT_GOOGLE_PER_MIN still override them and exactly 0 disables
them; an unreadable value falls back to the default rather than to
unlimited.
Salvaged from #683 onto current main.
Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The cost-bearing endpoints (/api/realtime/token, /api/openai/hud-summary,
/api/google/nearby-places) and the voice debug log refuse cross-site browser
requests: a foreign or opaque Origin, a Sec-Fetch-Site other than
same-origin/none, or proxy forwarding headers. Loopback tools without an
Origin and the LAN opt-in keep working. The dev and preview servers send one
shared Content-Security-Policy, and embed-mode framing rewrites only the
frame-ancestors directive of it.
Salvaged from #242 onto current main.
Co-authored-by: Sameh Khamis <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Cesium's script sets CESIUM_WORKERS itself when it loads, so the panel's
combined workers script was never used, and neither was the embedded
terrain-heights file: the worker still requested it from the panel
page's site. The panel build now writes only the prelude, and the panel
puts it ahead of whatever Cesium assigns to CESIUM_WORKERS. The separate
esbuild bundle of Cesium's workers is gone.
The ground-polyline worker loads approximateTerrainHeights.json itself,
and a worker's requests go to the panel page's own site, where they
failed with 404 and stopped rendering once ground-clamped lines such as
military installations appeared. The panel build now embeds that file in
the workers script, which answers the worker's request from memory.
Hosts serve panels from their own sites and may refuse other addresses;
Codex refuses any address on the user's machine. The panel now never
requests the app's server: it loads the app's panel build and data
through panel_request, a tool only the panel may call, which requests
the path from the app's server. One path in every host; only map
providers load directly.
npm run build:panel writes the panel build to dist/panel, served at
/panel/: one app script, one stylesheet, Cesium's script, and Cesium's
workers as one script Cesium runs from memory. Tools may now be marked
app-only. The panel CORS rules are gone.
show_on_globe replaces open_in_gods_eye_view and names an MCP Apps view, a
ui:// panel that loads the app in embed mode and posts each later view to it,
so the globe in the conversation changes without reloading. It also takes a
view another answer returned. Tools can declare a UI resource, and the MCP
adapter serves resources/list and resources/read and lists each tool's
resource in _meta.ui.resourceUri.
?embed=1 shows the globe alone: clean view with the HUD, panels, welcome and
setup prompts hidden, and provider attribution kept. The page that frames it
sends views with postMessage; the app applies each through its own actions
and reports every step. Annotations in any link are drawn once it restores.
Only embed-mode documents may be framed; every other page keeps
X-Frame-Options DENY and frame-ancestors 'none', and
GEV_EMBED_FRAME_ANCESTORS can restrict who frames them.
Tool answers with something to show now include data.view: the view with
the matching layers on, the area framed from above or a single aircraft or
satellite followed, and the link that opens it. Area framing moves to
src/tools/views.js so links and answers frame alike.
A view is a camera, data layers, visual style, map imagery and an aircraft,
military aircraft or satellite to follow, written in the share-link format
the app already restores. gods-eye-view/view builds, encodes and decodes one,
and share links take their style names from it. open_in_gods_eye_view now
builds its link from a full view: an area or camera, layers, style, map and
something to follow.
Brief sections received a named place as its bounding box, so they centered
on the box's middle: Tokyo's aircraft were sorted from a point in the
Pacific. Sections now receive the place name and resolve it like a direct
query, and place lookups are cached briefly so a brief resolves it once.
The caption request declared every answered section nominal, so a stale
aircraft snapshot reached the caption as live. Each section now reports
stale or unavailable from its own result, and the provenance envelope is
built with the app's feedProvenanceEnvelope.
get_transit_vehicles returned positions from a feed the proxy served from
its error cache, and hour-old fixes, as current. It now applies the transit
layer's rules: a feed is stale when the proxy says so or the operator has
not answered recently, and fixes older than the vehicle limit are dropped
and counted.