A 429 now starts the credit cooldown from its headers and drops the body
unread, so a stalled or oversized 429 cannot skip the governor; an oversized
snapshot is not retried. A 429 without retry-after now waits the intended
2 minutes (headers.get() returns null, which Number() read as 0 s). Adds
oversized-stream tests for OpenSky, adsb.lol, CelesTrak and TomTom that fail
on main, names the six reads in the changelog, and corrects the docs on
what was already bounded.
CHANGELOG: keep both new [Unreleased] entries (Panoramax, and the
emergency-transponder highlight from #856).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
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 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 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]>
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]>
The fixture gate answers api.kartaview.org, cdn.kartaview.org and
*.openstreetcam.org from fixtures (a drive with a 360° photo, stored on
storage2 while the API names storage1) and adds three steps: keyless
coverage with its credit and hidden filters, a line click opening the
nearest photo with cones and ›, and a 360° photo on the panorama without
FIT / FILL. The Street View share-link check reads the switch field instead
of one exact entry, since a chip click records every provider's switch.
README, DATA_SOURCES, CHANGELOG and CURRENT-STATE describe KartaView.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Bins airborne ADS-B v1/v2 aircraft that report degraded navigation
integrity (NIC < 7, NACp < 8, or readsb's GPS-loss flag) into 0.5 degree
cells over a rolling 30-minute window, coloured with the gpsjam.org
bands. Keyless: reads the capped adsb.lol point snapshot around the view
through /api/gnss-integrity, which returns integrity fields only, caches
60 s per whole-degree anchor, backs off on upstream 429 and serves the
last snapshot on failure. Adds scripts/qa-gnss.mjs.
Built by the Hex project (Crew Leo Agile dev team).
Co-Authored-By: Claude <[email protected]>
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]>
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]>
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]>
A mutation review left 18 earlier survivors; each now has a test that
fails with the mutant applied:
- the expanded viewer returns into the panel on shrink, close and destroy
(the fake DOM had no parentNode, so reinsertion was never exercised); the
fake DOM gains offsetParent and a MutationObserver that honours its
options, and a <body> child added while expanded goes inert;
- each pose moves the marker, drives FOLLOW and selects a new sequence;
close() retires an open still mounting or opening; a provider toggled
while the layer is off activates nothing; the adapter drops late and
superseded poses;
- share-link defaults: "any date" (0 days), the codec max pinned to the
filter's MAX_SINCE_DAYS (layerState cannot import it across package
boundaries, so a test ties the two), Mapillary on when a link omits it;
- Esc that something else handled, or typed in a field, keeps the
selection; a key problem outranks LOADING on the pill;
- the marker's terrain height (mesh, DEM, clamp then re-place);
- proxy: LRU memory cache and its half-budget rule, Retry-After capped,
a request after a cancelled fetch starts a fresh one;
- date-line tile ranking with and without focus; the horizon cull idles
while the camera stands still; the old zoom goes as soon as the new one
lands; a rate limit holds every refresh inside its wait.
One real bug found and fixed: Esc inside a contenteditable cleared the
selected sequence (selection.js now ignores isContentEditable targets).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
From the IC8 review of #768, each with tests that fail before:
- 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]>
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 the IC8 review of #768 (P2s), each with a test that fails before:
- A tile read from disk keeps its real age in memory, and the trim rewrite
keeps the file's mtime, so nothing is served past the 24 h TTL.
- The disk cache is swept in the background at most every 15 min: expired
tiles go, then the oldest past 1 GiB. Coverage zooms the app never
requests (z6-10) are refused with a 400, and layer names are matched as
own keys only.
- The tests run against a temporary cache directory (a test seam) instead
of writing into and deleting from the developer's real .gev-cache.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tilted views: the visible box came from screen rays against the bare
ellipsoid and the 9 tiles kept were the ones nearest its centre, so a view
over high ground (Denver, 1,600 m up) or near the horizon dropped the
streets in front of the camera. At street zooms the rays now meet an
ellipsoid raised to the ground under the camera, hits beyond a range tied
to camera height are ignored, tilted views also keep the ground around the
camera, and tiles are ranked along the line of sight (nadir to screen
centre). Simulated with real Cesium ray maths over Sacramento and Denver
from 2 m to 1,000 m: the nearest visible ground and the screen centre are
always loaded.
Rejected token: Mapillary's 401/403 reached the panel as a raw "Mapillary
tiles HTTP 403" while the pill said ON, and every pan asked Mapillary again.
The proxy now answers a refusal as {keyRejected: true} and holds it for that
token (5 min), and a 429 with its Retry-After (held until then). The panel
reads KEY REJECTED, names MAPILLARY_CLIENT_TOKEN and Provider Settings,
gates its controls like KEY REQUIRED, requests nothing more, and clears the
message when the layer goes off. A rate limit keeps the drawn tiles and
refreshes once the wait is over.
Found in the IC8 review of #768 (P1 tile selection, P2 rejected token).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes from gekh's re-review of 061d269, each with a regression test that
fails on the old code:
- A pan that drops a tile still loading now settles the loading count
(the aborted request no longer owns its key, so its `finally` never
did), and purges the previous zoom's tiles once nothing is pending.
LOADING no longer sticks on the status pill and chip.
- Zoom 0 (above 15,000 km) is a zoom: `zoom == null` instead of `!zoom`.
- A pose that arrives after a photo was closed is dropped, in the
MapillaryJS adapter and in the viewer host, so the marker and sequence
selection do not come back.
- Ground casting passes the tile's signal to the terrain request, so a
cancelled tile stops its height batch instead of holding the queue.
- The server's in-memory tile cache honours the 24 h TTL like the disk.
- The viewer's one-time resize runs for every photo, not just the first.
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]>
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 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.
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 Content-Security-Policy blocked the YouTube player API, the Facebook
video SDK and X's post widget that src/data/bhoteKoshiEmbeddedMedia.js
loads, along with their players and posts in frames. script-src now allows
exactly those three script origins and a new frame-src allows YouTube,
youtube-nocookie, Facebook and X embeds. A test ties the policy to the
frames the media module builds and pins the allowed script origins.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
/api/google/text-search spends the same Google quota as nearby-places but
was not behind the cross-site gate. It now refuses cross-site requests
before calling Google, and a test covers both paid Google routes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Vite installs its Host check after the middleware plugins add in
configureServer, so the provider and /api routes answered any Host,
including a DNS-rebinding name. hostCheckPlugin runs first on the dev and
preview servers and applies Vite's rule with the same allowed hosts: IP
addresses, localhost and *.localhost, the configured server host, and the
listed names. A test drives a real Vite server with an unlisted Host.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The voice debug log writes the server's timestamp after the client's record,
so a record can no longer replace loggedAt. The dev and preview servers also
send X-Content-Type-Options: nosniff with their shared browser headers.
From the findings in #710.
Co-authored-by: Sunil <[email protected]>
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 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]>
Embed-mode documents now keep the server's framing protection unless
GEV_EMBED_FRAME_ANCESTORS names the pages that may frame them (or * for any
page). The MCP Apps panel loads the app into its own page and needs no
framing. View answers are posted to the origin that sent the view rather
than to any origin.
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.
panel_request resolved paths with a check for a leading //, but URL
parsing also reads /\host as another host, so it could request other
servers. It now resolves the path against the app's address, refuses
any other origin, matches refused routes decoded as well, and does not
follow redirects. Responses are capped at 64 MiB while reading, with a
deadline; held responses make room on arrival and expire by time only,
so a response being read keeps its parts.
The panel keeps converted images only for its build files, so live
camera frames no longer accumulate, and failed build loads are retried.
The dev server answers a malformed /panel/ path with 400 instead of
crashing.
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.
Codex serves panels from codex-sandbox:// pages, where origins are
opaque: the panel recognizes the app's own addresses by scheme and host,
the vessels feed resolves against the document address, and the dev
server answers Codex's sandbox origins with CORS.
A panel page loads the app with <base> pointing at the app's server, so
addresses the app builds resolve against the document base instead of
the page location, and hash updates stay on the page's own address.
The dev server answers panel origins with CORS on the API routes too.