Each overlay is now its own Infrastructure row instead of a chip on one
shared row: sea-marks (token 6) and ski-pistes (token 7), both
enabled-only, built by createOsmOverlayLayers() from one descriptor
list. Drops the option group and its share-codec defaults, gives each
layer its own name, voice entry and CURRENT-STATE row, and shifts the
token-ledger fixtures to the next free digit 8.
Main moved on since this PR opened; the overlay layer is ported to the
current contracts:
- Layer-state token 6 (j belongs to transit; new single-character
tokens are digits) with a reservation-ledger row, and the option owner
renamed to the layer id so the compact share codec can encode it.
- Constructed per catalog by createOsmOverlaysLayer() in
constructCatalog.js; src/standalone/data.js no longer registers layers.
- Drapes through maps/imageryHost.js, so the overlays show on Google 3D
(tileset imagery) as well as the globe stacks, and follow map-stack
switches. Imagery layers are created on enable and destroyed on
disable/destroy.
- Translated chip labels and layer name in every locale, LAYER_NAME_KEYS,
Infrastructure panel group, voice manifest entry.
- Attribution: OpenSeaMap tiles CC BY-SA 2.0, OpenSnowMap credit as
required, OSM ODbL credit for both; bulk-download note in DATA_SOURCES.
- CURRENT-STATE row, changelog fragment, share-codec absent defaults,
updated token-ledger fixtures and voice schema pins.
Chat sits next to the mic in the command dock. Replies are silent and never
request the microphone; the first message connects with the selected provider
and runs the same map actions as voice through each provider's text API
(OpenAI OPENAI_CHAT_MODEL, Gemini GEMINI_CHAT_MODEL via generateContent, which
never starts Live). Only one mode runs at a time.
- Chat panel rendered by the voice control presenter: conversation, recent and
quick-command suggestions, Up/Down history, attachments (map-layer
references or small UTF-8 text files, sent only with the message)
- Chat and voice panels sit above the dock or dock into the right rail's
column; minimized bars sit centred above the dock, and height animates on
swaps
- A centred panel steps down to its bar for the Location and Visual Presets
trays without moving focus, rests above an open tray, and docks right while
expanded over one; it returns to the centre once the trays close
- Minimized voice previews the last sentence from each side; the empty Voice
Preview suggests things to say
- Settings opened from chat keep chat's frame and show the shared AI provider
and quick commands; voice Settings show Back to voice
- Closing chat ends a typed session; the voice panel's close turns voice off
- The dock widens its voice control for the chat button and restyles its
wings, which show icons only at 900px and below; assistant panels share the
left panels' glass, and Cyber keeps its own surfaces
- Server text-chat handlers with body, time and per-IP limits
(GEV_RATELIMIT_*_CHAT_PER_MIN), documented in SECURITY.md; tests and all
eleven locales
Co-authored-by: Darren Dignam <[email protected]>
- The proxy keeps combining marks, in-word apostrophes and name dots
(ar vowel signs, fr d'Orléans, ja ・), normalizes to NFC, caps every
summary in characters and cuts at a word boundary.
- feedState is nullable instead of a `none` token, and the summary has at
most five words: "exactly five" made the model pad short views with
"no data" or "unavailable".
- The HUD caches only the model's words and adds the current warning on
every paint; an RTL locale gets a leading right-to-left mark.
- The echo check also rejects an all-Latin reply in a non-Latin language,
translated layer ids and, with every feed healthy, the UNAVAILABLE word;
places are matched by word and one-word layer names are skipped in
Latin-script locales.
- The warning tag names the worst layers first; a partial analyst card
shows one partial chip.
Releasing the tuning dial always started the chosen station, plus tuning
static, even when nothing was playing. It now follows the Previous/Next
rule: the release plays only if a stream was loading, buffering or playing
when the drag began; otherwise it just selects the station.
The privacy note no longer lists finishing tuning as a way to connect and
now names voice instead, in all locales. Docs, changelog and comments are
tightened to match.
main moved the Radio markup into Vue views and rebuilt the UI tests on
stores. Resolved:
- src/ui/templates/context.html: deleted in main; the new privacy note now
lives in src/ui/views/panels/RadioPanel.vue.
- src/ui/radioControls.test.mjs: main's store fixture; the Next/Previous
autoplay test drives store.act('cycleRadio').
- src/voice/gevRealtime.test.mjs: main's createTestVoiceUi; the stop and
select handoff cases stay one table-driven test.
- src/data/radioNextState.test.mjs reads the published gev-radio view.
- src/i18n/locales/ar.json: the new note translated for the Arabic locale.
- _relocalize() now drops the received reply and aborts one in flight, so
the next tick asks in the new language instead of keeping old-language
text under the new-language state tag.
- The English-echo check also allows state words inside a place label
(Estación Loading Dock).
- Reword the non-English instruction so it reads for every language name.
- Add the two new test files to the format scope.
Directions, the Contacts marker and panel, placed annotation labels, the
drawn-line measure, the Cockpit and formatRouteDistance each chose the unit
and precision on their own, and most chose before rounding: #1450, #1433 and
formatDistanceText or kilometreDigits from src/data/distanceFormat.js, which
cut after rounding. Each keeps its own metre step and rendering.
That also fixes the 10 km cut the Contacts marker and panel, the drawn-line
measure and the Cockpit still had (9,960 m read 10.0 km). The helper returns
kilometres already rounded, so the plain marker and the localized panel no
longer disagree on half-tenths such as 1,450 m.
A negative annotation distance now leaves the label without a distance, and
a drawn line with a coordinate that is not a number shows no measure, rather
than printing 0 m or NaN km. The HUD altitude tag and the spoken view height
keep their own unit choice.
UiRail takes `collapsible`: while no panel on its side is open, each item
shows its icon and label as a pill, and once one opens the labels fold away
to the icon rail, making room for the panel. The tip is not shown while the
label is on screen; the button keeps its accessible name and the visible
label is hidden from assistive technology. The side panel rails turn it on.
Each rail is now placed by its screen-side edge, at the rail clearance, so a
rail that widens grows toward the middle of the screen; collapsed, it sits
6px outside its stack as before.
The transit, ALPR, vessel, CCTV and flight/military context fixes each
wrapped 359.5° to north with an inline Math.round(x) % 360. They now call
roundHeadingDegrees in src/data/headingDegrees.js, which also wraps negative
and multi-turn bearings. normalizeHeading moves there with it, and
cockpitMath re-exports it so existing imports keep working. It now adds a
turn only to a negative remainder, so a bearing just under half a degree
keeps its precision.
The awareness, Local ADS-B, cockpit wind, cockpit heading and Street Level
readouts use the same helper. The Cockpit's TRACK ACQUIRED signal rounded
without wrapping and could read COURSE 360°; it now uses
formatHeadingReadout, which shows --- for a track that is not finite.