mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge branch 'main' into prereq-10-osm-overlays
This commit is contained in:
424 files changed
+25311
-956
No files matched your search
@@ -2227,7 +2227,11 @@ export async function controlRadio(
|
||||
if (!radioActionIsCurrent(options)) return cancelled(summarize);
|
||||
const prepared = state.selected
|
||||
? true
|
||||
: radio.cycleStation?.(1, { focus: false, autoplay: false });
|
||||
: radio.cycleStation?.(1, {
|
||||
focus: false,
|
||||
autoplay: false,
|
||||
fallback: true,
|
||||
});
|
||||
return {
|
||||
ok: Boolean(prepared),
|
||||
action: 'control_radio',
|
||||
@@ -2251,6 +2255,7 @@ export async function controlRadio(
|
||||
const selected = radio.cycleStation?.(action === 'next' ? 1 : -1, {
|
||||
focus: false,
|
||||
autoplay: false,
|
||||
fallback: true,
|
||||
});
|
||||
return {
|
||||
ok: Boolean(selected),
|
||||
|
||||
@@ -1905,7 +1905,7 @@ test('voice Radio resolves Austin and exposes semantic selection, volume, pause,
|
||||
assert.equal(result.radioAction, 'play');
|
||||
assert.equal(result.stationId, 'nearest');
|
||||
assert.equal('station' in result, false);
|
||||
assert.deepEqual(calls.at(-1), ['cycle', 1, { focus: false, autoplay: false }]);
|
||||
assert.deepEqual(calls.at(-1), ['cycle', 1, { focus: false, autoplay: false, fallback: true }]);
|
||||
|
||||
for (const invalidCoordinates of [
|
||||
{ latitude: null, longitude: null },
|
||||
@@ -4032,3 +4032,20 @@ test('set_panel_open reaches the Utilities panel by its name, Draw or Import', a
|
||||
}
|
||||
assert.deepEqual(calls.at(-1), ['utilities-panel', false, { explicit: true }]);
|
||||
});
|
||||
|
||||
test('a partial analyst card shows one partial chip', () => {
|
||||
const card = presentResult('analyst_query', {
|
||||
ok: true,
|
||||
action: 'analyst_query',
|
||||
count: 3,
|
||||
feedState: 'partial',
|
||||
partial: true,
|
||||
unanswered: ['vessels'],
|
||||
summary: '3 contacts',
|
||||
scopeLabel: 'within 5 km of X',
|
||||
display: { scope: 'within 5 km of X' },
|
||||
coverage: {},
|
||||
});
|
||||
const labels = card.display.chips.map((chip) => chip.label);
|
||||
assert.equal(new Set(labels).size, labels.length, JSON.stringify(labels));
|
||||
});
|
||||
@@ -591,41 +591,50 @@ test('replacement input invalidates an in-flight Radio preflight immediately', (
|
||||
assert.equal(controller.radioHandoffEpoch, priorEpoch + 1);
|
||||
});
|
||||
|
||||
test('direct Radio pause or stop cancels a pending voice handoff', async () => {
|
||||
const order = [];
|
||||
let playbackControl = null;
|
||||
const ui = createTestVoiceUi();
|
||||
const controller = new GevRealtimeController({
|
||||
ui,
|
||||
runner: async () => ({ ok: true }),
|
||||
radioLayer: {
|
||||
subscribePlaybackControls(listener) {
|
||||
playbackControl = listener;
|
||||
return () => { playbackControl = null; };
|
||||
for (const [name, control, message] of [
|
||||
['direct Radio pause or stop cancels a pending voice handoff', 'stop', 'the explicit stop remains authoritative'],
|
||||
[
|
||||
'a direct Radio Next that does not play cancels a pending voice handoff',
|
||||
{ action: 'select', origin: 'user', attemptId: null },
|
||||
'the user selection is not overridden by voice playback',
|
||||
],
|
||||
]) {
|
||||
test(name, async () => {
|
||||
const order = [];
|
||||
let playbackControl = null;
|
||||
const ui = createTestVoiceUi();
|
||||
const controller = new GevRealtimeController({
|
||||
ui,
|
||||
runner: async () => ({ ok: true }),
|
||||
radioLayer: {
|
||||
subscribePlaybackControls(listener) {
|
||||
playbackControl = listener;
|
||||
return () => { playbackControl = null; };
|
||||
},
|
||||
playForVoice: async () => {
|
||||
order.push('radio-play');
|
||||
return true;
|
||||
},
|
||||
stopPlayback: () => order.push('radio-stop'),
|
||||
},
|
||||
playForVoice: async () => {
|
||||
order.push('radio-play');
|
||||
return true;
|
||||
},
|
||||
stopPlayback: () => order.push('radio-stop'),
|
||||
},
|
||||
});
|
||||
controller.debugLog = () => {};
|
||||
controller.dc = {
|
||||
readyState: 'open',
|
||||
send() {},
|
||||
close() { order.push('voice-stop'); },
|
||||
};
|
||||
controller.pendingRadioPlaybackResult = { ok: true, radioPlaybackRequested: true };
|
||||
});
|
||||
controller.debugLog = () => {};
|
||||
controller.dc = {
|
||||
readyState: 'open',
|
||||
send() {},
|
||||
close() { order.push('voice-stop'); },
|
||||
};
|
||||
controller.pendingRadioPlaybackResult = { ok: true, radioPlaybackRequested: true };
|
||||
|
||||
playbackControl('stop');
|
||||
await controller.handleRealtimeEvent({
|
||||
data: JSON.stringify({ type: 'response.done', response: { status: 'completed' } }),
|
||||
});
|
||||
playbackControl(control);
|
||||
await controller.handleRealtimeEvent({
|
||||
data: JSON.stringify({ type: 'response.done', response: { status: 'completed' } }),
|
||||
});
|
||||
|
||||
assert.equal(controller.pendingRadioPlaybackResult, null);
|
||||
assert.deepEqual(order, [], 'the explicit stop remains authoritative');
|
||||
});
|
||||
assert.equal(controller.pendingRadioPlaybackResult, null);
|
||||
assert.deepEqual(order, [], message);
|
||||
});
|
||||
}
|
||||
|
||||
test('confirmed manual Radio playback closes active voice without stopping Radio', () => {
|
||||
const order = [];
|
||||
|
||||
@@ -376,6 +376,10 @@ export class RealtimeConnection {
|
||||
this.stream = null;
|
||||
}
|
||||
if (this.audioEl) {
|
||||
// This session owns the element's remote media; peer closure is not track release.
|
||||
const remoteStream = this.audioEl.srcObject;
|
||||
this.audioEl.srcObject = null;
|
||||
remoteStream?.getTracks?.().forEach((track) => track.stop());
|
||||
this.audioEl.remove();
|
||||
this.audioEl = null;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ function browser(t) {
|
||||
test('retained peer and channel callbacks cannot act after stop or enter a replacement session', async (t) => {
|
||||
const { peers, streams } = browser(t);
|
||||
const actions = [];
|
||||
const remote = () => {
|
||||
const track = { stops: 0, readyState: 'live', stop() { this.stops++; this.readyState = 'ended'; } };
|
||||
return { getTracks: () => [track], track };
|
||||
};
|
||||
const controller = new GevRealtimeController({
|
||||
runner: async (name) => { actions.push(name); return { ok: true }; },
|
||||
backend: {
|
||||
@@ -67,28 +71,38 @@ test('retained peer and channel callbacks cannot act after stop or enter a repla
|
||||
const old = peers[0];
|
||||
old.channel.handlers.get('open')();
|
||||
assert.equal(controller.status, 'listening');
|
||||
const oldAudio = controller.audioEl;
|
||||
const oldRemote = remote();
|
||||
old.ontrack({ streams: [oldRemote] });
|
||||
const message = { data: JSON.stringify({ type: 'response.function_call_arguments.done', name: 'get_entity_context', call_id: 'stale', arguments: '{}' }) };
|
||||
controller.stop();
|
||||
assert.equal(oldRemote.track.stops, 1, 'Stop releases its owned remote track');
|
||||
assert.equal(oldRemote.track.readyState, 'ended');
|
||||
assert.equal(oldAudio.srcObject, null);
|
||||
assert.doesNotThrow(() => old.ontrack({ streams: [{}] }));
|
||||
await old.channel.handlers.get('message')(message);
|
||||
await controller.start();
|
||||
const current = peers[1];
|
||||
current.channel.handlers.get('open')();
|
||||
const audio = controller.audioEl;
|
||||
const incoming = remote();
|
||||
current.ontrack({ streams: [incoming] });
|
||||
old.channel.readyState = 'open'; // Model an already queued callback from the old transport.
|
||||
old.channel.handlers.get('open')();
|
||||
await old.channel.handlers.get('message')(message);
|
||||
old.ontrack({ streams: [{}] });
|
||||
old.ontrack({ streams: [oldRemote] });
|
||||
old.onicecandidateerror({ errorText: 'stale error' });
|
||||
assert.equal(controller.status, 'listening');
|
||||
assert.equal(controller.pc, current);
|
||||
assert.equal(controller.audioEl, audio);
|
||||
assert.equal(audio.srcObject, undefined);
|
||||
assert.equal(audio.srcObject, incoming, 'a stale track cannot replace the current remote stream');
|
||||
assert.equal(incoming.track.stops, 0, 'a stale callback cannot stop the current remote track');
|
||||
assert.deepEqual(actions, []);
|
||||
const incoming = {};
|
||||
current.ontrack({ streams: [incoming] });
|
||||
assert.equal(audio.srcObject, incoming, 'the current peer still delivers audio');
|
||||
controller.stop();
|
||||
controller.stop();
|
||||
assert.equal(audio.srcObject, null);
|
||||
assert.deepEqual([oldRemote, incoming].map(s => s.track.stops), [1, 1], 'repeated Stop does not restop remote tracks');
|
||||
assert.equal(incoming.track.readyState, 'ended');
|
||||
assert.deepEqual(streams.map(s => s.track.stops), [1, 1]);
|
||||
});
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ export class RealtimeRadio {
|
||||
: control || {};
|
||||
if (
|
||||
event.origin === 'user' &&
|
||||
(event.action === 'pause' || event.action === 'stop')
|
||||
['pause', 'stop', 'select'].includes(event.action)
|
||||
) {
|
||||
this.cancelRadioHandoff();
|
||||
} else if (
|
||||
|
||||
@@ -422,7 +422,10 @@ function analystCard(result) {
|
||||
...(result.feedState && result.feedState !== 'nominal'
|
||||
? [{ label: shownFeedState(result.feedState) }]
|
||||
: []),
|
||||
...(result.partial ? [{ label: t('partial') }] : []),
|
||||
// A partial feed state already says it.
|
||||
...(result.partial && result.feedState !== 'partial'
|
||||
? [{ label: t('partial') }]
|
||||
: []),
|
||||
],
|
||||
// Coverage qualifications lead the feed detail. The bounded card groups
|
||||
// overflow in its existing Notes disclosure rather than dropping them.
|
||||
|
||||
Reference in new issue
Block a user