Commit Graph
394 Commits
Author SHA1 Message Date
manjunath c5aa0d28c3 merge main 07094d9 into feat/voice-outlines 2026-11-02 11:05:08 +05:30
Sameh Khamis 5605fdc60a Merge Core main into sameh/core-vue
railCardBlocks.js is the Vue wrapper on this branch, so main's verbatim
legend units move into RailCardBlocks.vue.
2026-11-01 17:37:56 -08:00
Sameh Khamis 3a750c9021 feat(recording): render the safe frame from a recording store
RecordingControls publishes the safe frame a recording composes in, 16:9
or 9:16 (null while none records), to the new recording store, and the
scene chrome renders the overlay's active and ratio classes from it
instead of the controls writing them. Any frame but 9:16, even none named,
records in 16:9 as before.
2026-11-01 14:15:36 -08:00
Sameh Khamis 7edf8a577e feat(globe): answer the clear and share actions through the globe actions store
The top-center bar is now GlobeActions.vue. The shell publishes whether a
clear is under way and answers the clear and share buttons through the
globe actions store, instead of binding listeners and writing attributes on
the buttons. A shell disposed mid-clear resets the busy state, so the next
shell's bar is not left busy. bindClearLayersControl stays exported and is
deprecated.
2026-11-01 13:23:51 -08:00
Sameh Khamis b9ecb9b9f5 feat(kit): render the status badges and empty states with UiBadge and UiEmpty
UiBadge takes a tone and carries Core's gev-status-badge class, with
the density variants and colours left to its callers; the CCTV source and
calibration badges and the rail card badges render through it. UiEmpty takes
a title and hint, with the title as the line itself, since Core's
empty states are one line; the Scene shot list, mission roster and imagery
strip use it.
2026-11-01 12:31:09 -08:00
Sameh Khamis ecaf723035 feat(kit): render the selects with UiSelect
UiSelect renders the bare native select with v-model, options and
disabled. Options that carry their own attributes come through the slot, a
caller's own change handler still reaches the select, and hold never
rewrites a focused select and takes the model back on blur. Without hold, a
select takes its model back once its change has rendered, so a pick its
owner refuses goes back, as the raw value binding did.

The HUD layout, draw colour, RTL-SDR gain, radio station tag, voice
provider, voice timeout, scene and CCTV camera selects now render through
it.
2026-11-01 12:22:49 -08:00
Sameh Khamis 28ca784d49 feat(kit): render the chips with UiChip and UiButton
UiChip is a non-interactive label chip and renders the voice
card's chips. The pressable chips and pills are pressed toggles, so the
rail card chips, map source chips and city and place pills render through
UiButton with pressedClass, keeping their classes, attributes and the
classes other code adds. The pills now report aria-pressed.
2026-11-01 12:09:38 -08:00
Sameh Khamis d98a7c81be feat(kit): render the pressed toggles with UiButton
UiButton gains pressedClass, Core's class on a pressed button. The pressed
classes toggle through classList, so classes other code adds survive a
press, a caller's class change keeps them, a renamed pressedClass leaves
the button, and server-rendered markup carries them.

The Display, SDR, Radio and Cockpit enable, Cockpit weather and briefing,
mission replay, Street Level, TR-3B, overlay, voice and CCTV toggles now
render through it with the same ids, classes and aria-pressed values.
2026-11-01 11:59:30 -08:00
Sameh Khamis 5019bd9e8a feat(kit): render the sliders with UiSlider
UiSlider takes v-model, min, max, step and disabled
over Core's native range input, emits numbers, and with `hold` never
rewrites a focused handle and takes the model back on blur. It is a
functional render function, so the portable style parameter rows use it
too. The Display, Radio, RTL-SDR, Cockpit, mission replay and style
parameter sliders render with it; server-rendered markup keeps their
default values.
2026-11-01 11:27:52 -08:00
Sameh Khamis 7f7e66e793 feat(radio): answer the internet radio controls through the radio store
The Radio panel's and Cockpit's Enable, launcher, station tag,
transport and volume controls call RadioControls through the radio
store instead of DOM listeners. A refused station tag gets the kept tag
back from act(), which now returns what the answering action returned.
The Cockpit's station steps keep rotate: false. The unused element
fields go from the shell.
2026-11-01 11:02:39 -08:00
Sameh Khamis 61d11ce369 feat(radio): answer the RTL-SDR card through the radio store
SdrCard calls LocalSdrControls through radio store actions instead of
DOM listeners, and renders its gain options from the receiver's own
R820T gain steps with page-language labels. The display and radio
stores share one owner list (createOwners): the newest connected owner
with an action answers it.
2026-11-01 10:52:50 -08:00
Sameh Khamis e5fa31d01e feat(controls): render every segmented group with UiSegmented
Street Level's fit and panorama filters and Recent Imagery's mode render
through the kit's UiSegmented, and their owners answer the choice
through the Street Level store (setPano, setRenderMode) and the
readout's onSetMode. The imperative segmentedControl.js adapter and its
test go. UiSegmented moves focus at once on a key, a radio group from
the option holding focus and an arrows group from the selection, and
asks for a change only when the key lands somewhere new.
2026-11-01 10:38:25 -08:00
Sameh Khamis 3c3f8e3e67 feat(display): answer the Display controls through the store with UiSegmented
The Display panel, the dock's style buttons and the clean-view exit call
the display store's actions, and DisplayBindings connects their handlers
to it, replacing the bindDisplayControls listeners. The detection
allocation and 3D model coverage choices render through a new kit
UiSegmented, which takes v-model, options, label,
stacked and disabled, and adds a keyboard policy, item classes and
per-option attributes.

Shell element fields and facade accessors that only the old listeners
read are removed. The package boundaries now own UiSegmented and the
UiPanel kit files, and the format scope drops the deleted test.
2026-11-01 09:56:47 -08:00
Sameh Khamis 0adcd61184 feat(first-run): render the mission launcher from a store
initFirstRunExperience no longer writes the launcher card's classes,
attributes and text, wires its listeners or removes it: it presents the
card's visibility, busy state, status line, copy and scroll fade to the
gev-first-run store and connects the controls the welcome view's choices,
DISMISS and "Don't show this again" call. A refused save hands the tick
back to the view to revert. The launcher still reads the rendered card for
its hit test, keyboard trap, focus and overflow measurement.

The owner-chosen copy moves to src/firstRunCopy.js, shared by both. The
source-text tests give way to component tests, and the first-run mutation
QA follows the new code (67/67 caught).
2026-10-31 22:53:03 -07:00
Sameh Khamis 039b0828b7 Let a weather source describe its own products
A weather source can describe its own products (credit, units, coverage,
legend) through an optional `display` object in each product manifest:
source, credit, units, coverage, info, legend and extent. The weather
layer uses each valid field in the row summary, legend, info text,
coverage navigation, outside-coverage status, layer stats and imagery
credit, and keeps today's built-in value for any field that is absent or
invalid. Units and legend describe one scale, so they apply only as a
valid pair; otherwise both keep their built-in values, and the radar
info heading names the units in use.

Text is single-line and length-capped. It rejects control characters
(U+0000-U+001F, U+007F-U+009F), line and paragraph separators, and
invisible or format characters: soft hyphen (U+00AD), combining grapheme
joiner (U+034F), Arabic letter mark (U+061C), Mongolian vowel separator
(U+180E), zero-width and direction marks (U+200B-U+200F), bidi embedding
and override (U+202A-U+202E), word joiner, invisible operators and bidi
isolates (U+2060-U+2069), variation selectors (U+FE00-U+FE0F), the
byte-order mark (U+FEFF) and tag characters (U+E0000-U+E007F).

Legends need 2-16 stops whose labels are plain decimal numbers or short
text and whose colours are #rrggbb. A number label must read back
exactly as the legend shows it: within the safe integer range, with no
leading zeros and no digits lost to rounding. Extents must be finite, in
range and non-empty. Supplied source and units text is data: the layer
marks it so the panels show it untranslated, while built-in values are
translated as before.

The imagery credit is escaped as plain text and follows a refreshed
manifest even when the shown frame stays the same. A refresh changes
only the credit: the shown frame keeps the snapshot it was staged from,
so a later host switch restages it with its own time and bounds and the
refreshed credit. Manifests without `display`, including the bundled
nowCOAST ones, render exactly as before.
2026-10-31 22:42:01 -07:00
Sameh Khamis c03b4a7643 feat(hud): render the Intel HUD from a store
IntelHUD no longer builds #intel-hud with innerHTML or writes its readouts
by id: it publishes its readouts, typing summary, REC blink, visibility,
layout variant and style colors through its publish option to the gev-hud
store, and the intel-hud application view renders them. The corners render
from mount so the panel layout controllers register them as obstacles.

The vessel layer presents the selected vessel's readout through
src/hudReadouts.js, which IntelHUD follows, instead of writing into the
HUD. The Cyber sonar state answers a HUD toggle from IntelHUD's visibility
rather than the class the view renders on its next update.
2026-10-31 22:18:50 -07:00
Sameh Khamis b96cc37ab5 feat(awareness): render the Global Context compass from a store
The awareness layer presents the compass ring, heading, cardinal labels and
contact bearing markers through subscribeDirectionOverlay(); the application
shell publishes them to the gev-awareness-direction store and the scene
chrome renders them in the viewer's container.
2026-10-31 22:01:56 -07:00
Sameh Khamis b22e138c55 feat(world-overlay): render the visible map target list from a store
The world overlay presents its focusable mirror of painted cards through
subscribeWorldOverlayActions() and answers activateWorldOverlayAction();
the application shell publishes the list to a store and the scene chrome
renders the buttons and the status, announcing a repeated focus again.
The module stays free of Vue for the portable transit entry, and its
canvases remain painting surfaces.
2026-10-31 21:52:32 -07:00
Sameh Khamis 662f51fdf3 feat(annotations): render the screen-space annotation board from a store
The screen annotation renderer keeps each mark's SVG parts as attribute
records and presents the board to a store after every rendered frame; a
view teleports it to the page and hands back the callout text and mark
groups the renderer measures. The tracked-entity fade is measured once
the frame is in the document, removed marks fade out before they leave,
and the styles it injected move to annotations.css.
2026-10-31 21:39:34 -07:00
Sameh Khamis 265aa605bd feat(celestial-ring): render the full-globe ring from a store
The celestial ring presents its overlay to a store and the scene chrome
renders it in the globe container, handing back the two canvases the
ring paints. The application shell passes the store in so the keyhole
helpers other packages import stay free of Vue. Without its canvases the
ring still tracks whether the globe is framed and turns itself off.
2026-10-31 21:20:27 -07:00
Sameh Khamis fd0d51c450 feat(bhote-koshi): render the reconstruction panel from a store
The Bhote Koshi layer keeps its panel controls as plain records and
presents them to a store; the context rail renders the panel as its last
child and marks itself while it is on. Controls are bound to the enable
that published them, and an unchanged frame is not sent again.
2026-10-31 21:12:08 -07:00
Sameh Khamis 9ea6d7e8aa feat(launches): render the replay vehicle overlay from a store
The launches layer keeps the replay state, projection and wording and
presents the replay vehicle overlay through subscribeReplayOverlay(),
sending nothing for an unchanged frame, so the portable launches entry
stays free of Vue. The application shell publishes each presentation to
the gev-mission-replay store and the scene chrome teleports the vehicle,
orbit dot and callout into the globe container with the same markup,
classes, mode, transform, rotation and sonar opacities. Hiding keeps the
mode, place and text, as before.
2026-10-31 20:53:54 -07:00
Sameh Khamis e0bfa471ac feat(wind): render the inspection marker from a store
createWindInspectionMarker() moves to src/layers/wind/inspectionMarker.js,
so the portable ./tools entry that reads wind/inspection.js stays free of
Vue. It keeps the frame listener and projection and presents where the
inspected point falls to the gev-wind-inspection store after each
rendered frame, publishing nothing for an unchanged frame; the scene
chrome teleports the marker into the wind layer's container with the
same class, inline style, hidden state and position. The application
guide lists everything leaving out the scene chrome now leaves out.
2026-10-31 20:47:19 -07:00
Sameh Khamis 0926cbc105 feat(voice): render the pointer reticle beside the voice control
PointerReticle keeps the turn's timers and tool tracking and presents
where the user pointed to the gev-pointer-reticle store, one entry per
reticle; the command dock teleports each to the body with the same
class, aria-hidden, hidden state, target and position. The voice QA
reads the card and reticle after Vue's flush, and the application guide
says leaving out the command dock leaves out the voice control and its
reticle.
2026-10-31 20:36:39 -07:00
Sameh Khamis f9bf782b9c feat(ui): render the FPS readout in the title bar from the status store
createFrameRateMonitor() keeps the key, frame count and interval and
publishes the readout to the gev-status store; the scene chrome renders
it after the subtitle with the same class, hidden state and title. A
page without the title bar still gets no monitor, and a monitor only
updates or withdraws the readout the store shows.
2026-10-31 20:28:24 -07:00
Sameh Khamis e0abf74308 feat(scenes): render the data-pack attribution cards from a store
The data-pack presentations keep the Cesium resources, object URLs and
pick targets and present each pack's attribution card, with its media
player, to the gev-data-packs store; the DataPacks view teleports one
panel per set of presentations into the viewer's container with the same
markup and inline styles, and stops a removed card's player as it goes.
2026-10-31 20:22:22 -07:00
Sameh Khamis 59e3aaa271 feat(scenes): render the Scene actions panel from a store
The scene interactions owner keeps the pick handler and the interaction
session and presents the panel to the gev-scene-interactions store; the
SceneInteractions view teleports it into the viewer's container with the
same markup and inline styles. Each activation keys its own panel, each
button carries the owner it was rendered for, and only the owner the
store shows may change or remove it.
2026-10-31 20:14:35 -07:00
Sameh Khamis f846aaed4c feat(ui): render the page toast from the status store
ShellFeedback and the scene director show toasts through createToast(),
each on its own timer as before, and the scene chrome renders the toast.
Every show rewrites the text, so a repeated message is announced again.
2026-10-31 19:58:59 -07:00
Sameh Khamis 3c391482f6 feat(ui): render the status chips and their split-flap labels from a store
ShellFeedback publishes the loading status and the road network chip, and
CctvControls the camera grid chip, to the gev-status store; the scene
chrome renders them. SplitFlapText plays the departure-board cascade that
splitFlap.js plans, writing only the label's one text node, and rests when
its owner is destroyed. setSplitFlapText and disposeSplitFlap are gone.
2026-10-31 19:51:39 -07:00
Sameh Khamis 492b719f19 feat(ui): render the imagery split dividers from a store
createImagerySplit keeps the divider's value, drag, keyboard, CSS position
and scene split, and publishes its line to the imagery split store.
ImagerySplitView, in the scene chrome, teleports each line to the body, so
recent imagery and the Bhote Koshi event no longer build divider DOM.
2026-10-31 19:35:27 -07:00
Sameh Khamis 1de4f635ca feat(voice): render the voice control from a presenter
createVoiceControl now returns a presenter instead of building the
control with innerHTML: the session, cost, input, card and panel owners
write its state by part and listen for interactions, and VoiceControl.vue
renders it in the command dock, between the location bar and the visual
presets as before. Focus moves wait for the render; the waveform's bars
publish apart from the control and only when they change; the minutes
field keeps a draft until its owner writes it. Owner tests use a real
presenter on their own store, and view tests render the control.
2026-10-31 19:22:03 -07:00
Sameh Khamis 2ffd7a4afd refactor(ui): remove the action button and status badge builders
The Vue views apply the gev-action-button and gev-status-badge classes
themselves, so the DOM builders have no users left. The docs describe the
views instead.
2026-10-31 18:40:25 -07:00
Sameh Khamis 5bf797ef71 refactor(build): place the views in the page instead of expanding templates
index.html now lists each view's host and the loading screen itself, so
the gev:template expansion plugin, its view list, the loading-screen
template and the ./build/html export are gone. createApplication takes
omitViews for views an application leaves out; their hosts are removed,
and a view an earlier call rendered is unmounted.
2026-10-31 17:52:47 -07:00
Sameh Khamis 6802c9c8b0 feat(ui): render every application template except the loading screen as a Vue view
The scene chrome, cockpit, Display controls, command dock, layer panels,
Context rail, welcome card and Provider Settings move verbatim into
src/ui/views and mount into their hosts before the page is translated and
wired. Each view renders the same elements, attributes and text as its
template; Vue keeps whitespace between elements as one space, and no view
markup sits under a white-space: pre rule.

Template asset URLs stay literal, view files are left out of Prettier so
the markup is not reflowed, and the Node component loader moves to build/
and is exported as build/register-vue for host test runners. Hosts that do
not initialize Core's locale pass localePicker: false instead of editing
the markup.
2026-10-30 17:47:52 -07:00
Sameh Khamis 3d5d3ef014 feat(ui): add a desktop panel rail to each sidebar
Each side stack hosts a Vue rail listing its panels; selecting one opens it
through PanelChrome. The shell owns one Vue runtime and Pinia instance, and a
panel store mirrors each side's panels from the DOM. The rail hides with its
stack, in Cockpit, clean view and recording, and on phones. Panels without a
catalog entry get a generic item, and Data Layers leaves the rail while the
f shortcut hides it.
2026-10-30 16:41:29 -07:00
Sameh Khamis 7307c11c74 feat(ui): add the Vue component kit with UiRail, UiTooltip and UiIcon 2026-10-30 16:11:48 -07:00
manjunath 3def533d8c merge main a16eb07 into feat/voice-outlines 2026-10-30 20:54:54 +05:30
Said Turksever 38302a27a4 Merge upstream/main into feat/cctv-a22-autobrennero
Resolve conflicts with the NYC, Toronto, Lithuania, Florida, Taiwan and
Kartverket work on main. The CHANGELOG entry moves to
changelog.d/956-a22-brennero-webcams.md, as main now asks.
2026-10-30 14:50:41 +00:00
manjunath 578ebdc030 merge main f13c60c into feat/voice-outlines
reconcile voice outlines with main f13c60c, retaining German/Japanese,
Operator HUD retirement and Context/Radio cleanup, named-building
containment, courtyard-hole, non-Latin identity, attribution, cancellation
and antimeridian repairs

keep outline callouts above the open voice preview when an above-panel
slot fits without colliding with another callout,
and clip the compact dock location label on narrow screens
2026-10-30 19:02:47 +05:30
Said Turksever a370742e25 Merge remote-tracking branch 'origin/main' into ux/context-panel-cleanup 2026-10-29 16:42:17 +00:00
Said Turksever 97b7307b14 Fix the format gate and the Cyber Context styling, and pin the redesign
CI failed at format:check: the format scope still listed the deleted
panelChrome.radioOwnership test, and panelRails.test.mjs needed Prettier
after its glow list shrank.

The new Context title colour outranked Cyber's red panel titles, and the
collapsed Context launcher came out brighter than its siblings; the rule
now applies only to the expanded panel outside Cyber. Cyber's layout-focus
standby card had relied on the old dashed border and centring, so it
drew a 3px frame with its text off the radar art; it now names its own
1px frame, padding and centring. Cyber also drops the shared segmented
track, which drew a third frame around its already framed mode buttons.

Tests now pin the Context header to its title and collapse control, the
segmented control, 10px type, the missing halo and the scoped title
colour, plus the Cyber standby and track overrides. Each fails on the
previous CSS or markup.

qa-radio enables Radio and starts the first Play from the visible
controls, expanding Context and Radio the way a person must, then puts
the panels back. The Escape test and its mutation describe what they
still protect. CURRENT-STATE no longer describes the removed Radio icon
or compact player.
2026-10-29 12:55:47 +00:00
Said Turksever 39a60b4034 Remove the Snow visual preset
Snow is no longer offered anywhere a style can be chosen: the Visual
Presets tray, keyboard shortcut 7, the Cockpit vision carousel, and the
set_visual_style voice action. The shader and its labels go with it;
weather snowfall and the SNOW weather condition are unaffected.

Share links keep working. Snow's enum code (6) stays reserved, so a
compact or long-form link that names it opens on Normal and drops the
Snow shader parameters without reporting them as unknown. A Scene
Director shot saved with Snow plays on Normal too. Snow's frozen share
defaults matched Normal's apart from those parameters, so no other
setting in an old link changes meaning.

The legacy voice-schema pins restore Snow before hashing, so the
unchanged pins still prove nothing else moved; the complete Realtime
payload digest is re-derived.
2026-10-29 11:20:49 +00:00
Sameh Khamis c1f60cb7e7 Cut the Google requests the HUD summary makes on its own
The HUD builds place context for its AI summary whenever the camera
settles, and each build paid for up to four reverse geocodes plus a Nearby
Places search, even when no OpenAI key was configured to use them.

- Ask GET /api/openai/hud-summary/status once; with no summary configured,
  or after an unconfigured reply, make no place lookups for the page.
- Reverse-geocode at most two points: the view center and one viewport
  point.
- Reuse a lookup until the view center moves a quarter of the visible
  width, the camera's distance to it doubles or halves, or the camera
  crosses a lookup height. The reused points are asked again, so only a
  timeout, failure or empty answer spends.
- Repaint the last AI reply when a small pan leaves the context unchanged.

Cached-only hosts and voice context lookups are unchanged.
2026-10-28 14:59:33 -07:00
manjunath d4434aa6d7 feat(outlines): restore voice outlines without public Overpass
use bundled place boundaries and shared OpenFreeMap tiles before a guarded Nominatim fallback

preserve multipart shapes, holes and cancellation,
and keep pending annotations visible while ground sampling finishes across frames

keep non-Latin name distinctions when selecting provider results,
and retain an outline anchor only when it lies in filled geometry
2026-10-27 13:24:02 +00:00
Sameh Khamis da02fd8645 feat(share): short /share/<code> links with a compact versioned view format (#1418)
* feat(share): add compact versioned share-view codec

Add a standalone codec for `#s=<base64url>` share links: a version byte
followed by protobuf-style tag-length-value fields. Settings equal to
their default are omitted and refilled from the same table on decode;
unknown fields are skipped by wire type, so new fields never break
older apps. Views are a place (camera) or a thing (an aircraft,
military aircraft, vessel or satellite to follow, with camera offset
and last-known fix); an unknown kind falls back to the camera.

The decoder bounds its input and rejects malformed payloads with a
reason instead of throwing. A converter maps the long form's parsed
state to and from the codec state; the long form is unchanged.

* fix(share): tighten codec decoding after review

Unknown detection mode and allocation names now fall back to their
defaults instead of reaching the lenient long-form migration. Future
panel values and style-parameter tokens are skipped rather than applied
or rejected, and layer options are accepted only under their owner
layer. Wrap longitudes before scaling, normalize negative zero, reuse
the layer-state tracking ID grammar, and align the format doc with the
decoder.

* feat(share): let a host share the link its own way

ShareLinkManager.buildLink() returns the current share link without
copying it, and copyLink() now copies exactly that. The share button
passes the link to an optional services.shareLink(url), which resolves
to 'shared', 'copied', 'cancelled' or false; 'cancelled' shows no toast
and 'shared' shows the new "Link shared" (es, ru, zh-CN). Without the
service the button copies as before.

* feat(share): open and make short /share/<code> links

The share button now makes `<app>/share/<code>` links with the compact
share-view code, and the app opens them at start-up through the same
restore pipeline as long links. Once the app next writes the address, the
share path is replaced (no new history entry) by the app's own address and
the live long-form hash, so a reload follows the live view. A host can pass
the code as `initialShareCode` instead. A code that fails to decode restores
nothing and shows "This share link can't be opened".

The code gains a trailing CRC-8 check byte, so any single mistyped
character is rejected instead of opening a different view. Thing views no
longer store a camera: it is derived from the entity's last-known fix and
the follow offset on a sphere, the entity kind is implied by the id's
field, and the followed entity's layer bit is implied; an aircraft followed
from the app's own camera is a 42-character code. The `#s=` hash form is
gone.

While the camera follows an aircraft, military aircraft or satellite, the
link is a thing view with the camera's offset and the entity's position.
Opening one turns the layer on and follows the entity through the existing
shared-follow restore, then swings the camera round to the shared offset; a
vessel view selects the vessel once the vessel feed has it. If the entity
is not reporting within its window, the view stays at its last-known fix
and says so. The panel and style-parameter tables now come from the codec,
and a host share hook that returns a plain value or throws still gets a
toast.

* fix(share): encode style settings against the style's own look

Settings a style applies when selected (bloom, sharpen, HUD, detection,
celestial ring and the style's shader parameters) now default, per field,
to what that style applies in a fresh session, read from the tables the
app applies (the first-load baseline, the style presets and the shader
defaults). The encoder compares against the view's style and the decoder
fills omitted settings from the decoded style, so a view that keeps its
style's look costs only its camera and style. The previous defaults did not
match a fresh session (sharpen, HUD and detection were off), so every link
paid for them, and a style's own shader parameters were always written.

A test pins the per-style defaults as frozen v1 meanings. A followed
aircraft in Noir is now 44 characters and the real long-link example 39.

* docs: changelog for #1418
2026-10-26 15:31:51 -07:00
Sameh Khamis c533b1994c Merge main and preserve voice panel and GhostMaps schema coverage 2026-10-26 08:52:03 -07:00
Sameh Khamis 074e272356 fix(voice): recover idle timers and retire panel audio observers 2026-10-26 08:42:41 -07:00
Mazeyar Moeini Feizabadi 4da002bd42 Merge current main into S2 GhostMaps and preserve updated import and voice behavior 2026-10-26 16:25:49 +01:00
Mazeyar Moeini Feizabadi 4a18af5426 fix: address GhostMaps resource and source ownership review findings 2026-10-26 16:24:34 +01:00
Bilawal SidhuandBilawal Sidhu a3a19883b4 feat: add Scope controls and conditional HUD text backplates (#1410)
* feat: add scope controls and conditional HUD backplates

* docs: assign scope changelog to PR 1410

* fix: stabilize title backplate and brighten secondary labels

---------

Co-authored-by: Bilawal Sidhu <[email protected]>
2026-10-26 00:24:27 -05:00