diff --git a/.gitignore b/.gitignore index 14e8d320b..d2f0107c0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ qa-artifacts/ 3d-models/ pinokio/ENVIRONMENT pinokio/.installed + +# Transient: a release interrupted mid-assembly (npm run changelog recovers it) +changelog.d/.assembling/ diff --git a/CHANGELOG.md b/CHANGELOG.md index da4967218..98d32365f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,10 @@ # Changelog -- Add Bogotá (Tembici, 250 stations) and Santiago de Chile (Bike Itaú, 200 - stations) to the GBFS bikeshare registry, the layer's first stations in - Colombia and Chile. Both are PBSC feeds like the Brazilian Bike Itaú cities, - so no proxy change is needed. Both feeds also list depot stations on other - continents (São Paulo, Madrid) as in service; stations outside a city's load - radius are now dropped instead of drawn as live docks. - -- Add Barcelona's bike share to the GBFS registry: Bicing (543 stations, the - city's PBSC system) and AMBici (237 e-bike stations across the metropolitan - area, run by nextbike). Both are keyless GBFS 2.3 feeds listed in - MobilityData's systems catalogue; AMBici's is CC0. `gbfs.nextbike.net` joins - the proxy allowlist. These are the layer's first stations in Spain. +- The tracked-aircraft descriptor now publishes `verticalRateMps`, on both the + civil and military layers, so a consumer reading `getTrackedInfo()` no longer + has to reach past it into the raw poll record for climb/descent. Reported, + never derived: an aircraft that has not transmitted a rate reads as `null` + rather than a level-flight `0`. - Add nine Ontario transit feeds: TTC, MiWay, Hamilton HSR, Durham Region, YRT/Viva, Guelph, Burlington, Barrie and Kingston, each carrying its own @@ -161,6 +154,18 @@ the five borough cores load and `CCTV_NYC_MAX_SOURCES` changes the cap. The catalog-wide ceiling is unchanged; extra cameras share it round-robin with the other packs. +- Spanish interface (`es`), translated by @meirm (#602) and reviewed against the + current English. + +- Interface localization, starting with Russian. UI text is marked with + `t()`, `tn()` (plurals) and `msg()` in code and `data-i18n*` in markup, keyed + by the English text itself; each language is one flat JSON catalog + (`src/i18n/locales/ru.json`). The language comes from `?lang=`, the language + picker (the globe button in the bottom-right corner), then the browser + language; English stays the default and the fallback. `npm run i18n`, also + run by `npm test`, fails on missing, unused or broken translations. Built on + the i18n foundation by @meirm (#601), with the Russian catalog started + from #312. It also fails on UI text that is not marked at all. - Street Level counts unique tiles before ranking so overlapping dateline halves in polar views do not waste slots in the 16-tile overview budget. @@ -299,10 +304,9 @@ snapshot is not retried, and an OpenSky 429 starts its cooldown before the body is read. -- Keep later satellites when a TLE catalog entry is missing an orbital line or - pairs lines with mismatched NORAD catalog numbers. The parser skips corrupt sets - and resumes at the next complete named set, instead of losing the rest of the - group (#906, #936). +- Keep later satellites when a TLE catalog entry is missing an orbital line. + The parser skips the incomplete set and resumes at the next complete named + set, instead of losing the rest of the group (#906). - Satellite clouds keep Clouds only / Full as quick endpoints and add a continuous cloud-filter slider: 0% is Full, 100% is the existing brightness key, and diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cde8582f9..ba0d38768 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,8 +54,9 @@ head -40 scripts/qa-radio.mjs # what this runnable gate proves and how to run i Runnable gate entrypoints document what they assert and how to invoke them. Most need a dev server; some also need a specific provider key or port. Follow the entrypoint's header rather than assuming every matching file accepts the -same arguments. Only a couple (`qa:map-source-tray`, `qa:transit`) have an -`npm run` alias. +same arguments. Five have an `npm run` alias: `qa:map-source-tray`, +`qa:panel-resize`, `qa:street-level`, `qa:street-level:fixtures` and +`qa:transit`. If you aren't sure which gate covers your change, search `docs/CURRENT-STATE.md` for the feature: it names the gate for many of them, and it's the authoritative @@ -63,12 +64,13 @@ runtime reference either way. `scripts/qa-l9-matrix.mjs` aggregates the broader release-candidate checks and selected harnesses; it does not replace the focused gate for the feature you changed. -> **CI does not cover this for you.** The workflow runs the setup policy +> **CI covers only two of these for you.** The workflow runs the setup policy > checks, the formatting and package-boundary checks, the unit suite and the -> production build, plus a Windows onboarding job. It runs neither -> `npm run test:track` nor any `qa-*.mjs` gate — both need a live dev server -> and a browser. Include the applicable local run in your PR's validation -> evidence. +> production build, plus a Windows onboarding job, and against the built preview +> it runs `qa:street-level:fixtures` and `qa:panel-resize`. It does not run +> `npm run test:track` or any other `qa-*.mjs` gate — those need a live dev +> server and a browser. Include the applicable local run in your PR's +> validation evidence. ## Share-link layer tokens @@ -126,6 +128,7 @@ The highest-leverage places to jump in: - ES modules, **2-space indent, single quotes, semicolons.** - JSDoc on exported/public functions. - Match the surrounding code — comment density, naming, and idiom. +- User-visible text goes through `t()`, `tn()` or `msg()`, or a `data-i18n*` attribute in markup, and `npm run i18n` must pass. See [docs/I18N.md](docs/I18N.md). - Prefer small, reviewable commits. Conventional-commit-style prefixes (`feat:`, `fix:`, `perf:`, `docs:`) are appreciated but not required. ## Formatting and reusable components @@ -149,7 +152,7 @@ ownership and adoption process. 1. Branch off `main`. 2. Keep `npm run build`, `npm test`, and `npm run test:track` green and avoid new console errors, plus the [feature gate](#feature-regression-gates) for the area you touched. -3. If you change runtime behavior, update `docs/CURRENT-STATE.md` and `CHANGELOG.md` in the same PR. +3. If you change runtime behavior, update `docs/CURRENT-STATE.md` in the same PR, and add your changelog entry as `changelog.d/-.md` instead of editing `CHANGELOG.md` — see [changelog.d/README.md](changelog.d/README.md). Distinct filenames don't conflict; a release assembles the directory into `CHANGELOG.md`. A documentation claim that needs a regression test should assert against `docs/CURRENT-STATE.md` or `README.md`, which a PR still edits directly. 4. If you add or change a data source, update [DATA_SOURCES.md](DATA_SOURCES.md) with its license and attribution. **Don't add data you don't have the right to redistribute** — fetch it at runtime instead. 5. Describe what you changed and how you verified it (screenshots welcome for anything visual). diff --git a/README.md b/README.md index c5aeb4710..c49c53ece 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Start with the included data sources, then add your own. Each layer is a separat - **🔭 Satellite passes:** Ask by voice when any loaded satellite next rises over you: rise, peak and set times, and whether you can see it. - **🔎 Analyst answers:** Count, filter and rank satellites, datacenters and dams by voice, alongside flights, ships, fires and quakes; answers say when a feed is stale or on a fallback. - **🧭 Tilt and North Up:** Switch between straight-down and a 35° oblique, or put north at the top, while a tracked target stays centered. -- **🔍 Keyless search:** Type coordinates or a bundled city or landmark to fly there with no network request; other names use Google when configured, then Photon and Nominatim. +- **🔍 Keyless search:** Type decimal coordinates, a two-dimensional WGS84 `geo:` URI (for example, `geo:39.7392,-104.9903`), or a bundled city or landmark to fly there with no geocoder request; other names use Google when configured, then Photon and Nominatim. - **📦 Shareable scenes:** Import, preview and share Director scenes as files or bundles, with camera anchors, authored moves and data packs. - **🌊 Nepal flood scene:** Replay the Bhote Koshi flood: flood path, witness sources and before-and-after imagery (bundled data is non-commercial; see [DATA_SOURCES.md](DATA_SOURCES.md)). diff --git a/SECURITY.md b/SECURITY.md index cc3f27cfe..e53883548 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -73,7 +73,7 @@ The data proxies under `server/providers/` are written so the browser cannot tur - **Sanitized errors** — internal error details are not echoed back to clients. - **Coalesced OAuth refresh** and cached successful responses only (OpenSky). - **Redacted debug logging.** The voice debug log (`.gev-logs/`, gitignored) strips API keys, bearer tokens, client secrets, and image data URLs before writing. -- **Cross-site gate on cost/log endpoints.** The cost-bearing (`/api/realtime/token`, `/api/openai/hud-summary`, `/api/google/nearby-places`, `/api/google/text-search`), quota-bearing (`/api/mapillary/*`, with a per-IP limit on tiles) and log (`/api/realtime/debug-log`) endpoints refuse cross-site browser requests: a foreign or opaque `Origin`, a `Sec-Fetch-Site` other than `same-origin`/`none` (this is what blocks an ``/navigation that carries no `Origin`), or any reverse-proxy/CDN forwarding header returns `403`. Non-browser loopback tools (the repo QA harnesses POST the token endpoint with no `Origin`) and the explicit `HOST=0.0.0.0` LAN opt-in keep working — the gate deliberately does not require a loopback remote address; that stricter requirement belongs only to the credential panel (`admitKeySetupRequest`). The Host check (`allowedHosts`, applied by `hostCheckPlugin` ahead of every route, since Vite's own check runs only after plugin middleware) rejects a rebinding `Host` before any handler runs, in every mode, and the gate is defense in depth behind it: the LAN opt-in keeps that check and accepts only IP addresses, `localhost` and names listed in `GEV_ALLOWED_HOSTS`. **Limit:** a name you list there is trusted like the app's own address, so list only names you control. +- **Cross-site gate on cost/log endpoints.** The cost-bearing (`/api/realtime/token`, `/api/openai/hud-summary`, `/api/google/nearby-places`, `/api/google/text-search`), quota-bearing (`/api/mapillary/*`, with a per-IP limit on tiles, and `/api/tomtom/*`, backed by its persistent daily tile budget) and log (`/api/realtime/debug-log`) endpoints refuse cross-site browser requests: a foreign or opaque `Origin`, a `Sec-Fetch-Site` other than `same-origin`/`none` (this is what blocks an ``/navigation that carries no `Origin`), or any reverse-proxy/CDN forwarding header returns `403`. Non-browser loopback tools (the repo QA harnesses POST the token endpoint with no `Origin`) and the explicit `HOST=0.0.0.0` LAN opt-in keep working — the gate deliberately does not require a loopback remote address; that stricter requirement belongs only to the credential panel (`admitKeySetupRequest`). The Host check (`allowedHosts`, applied by `hostCheckPlugin` ahead of every route, since Vite's own check runs only after plugin middleware) rejects a rebinding `Host` before any handler runs, in every mode, and the gate is defense in depth behind it: the LAN opt-in keeps that check and accepts only IP addresses, `localhost` and names listed in `GEV_ALLOWED_HOSTS`. **Limit:** a name you list there is trusted like the app's own address, so list only names you control. - **Full Content-Security-Policy.** The dev and preview servers send a real CSP (`script-src 'self' 'unsafe-eval' blob:` plus the YouTube, Facebook and X scripts the Nepal event's embedded media loads and the Maps JavaScript API that Street View runs on, with a matching `frame-src` for their players and posts: no inline script and no other script origin; `blob:` covers the object URLs Cesium's bundled workers bootstrap through in the built app), alongside the existing `X-Frame-Options: DENY` and `frame-ancestors 'none'`. `'unsafe-eval'` stays because Knockout, bundled inside Cesium's widgets package, resolves the global object with `eval` at load time and the globe does not initialize without it; styles permit `'unsafe-inline'` and Google Fonts because Vite's dev client injects `