Merge feat/voice-geometry onto main

Both stack parents (#837, #842) are on main, so the voice geometry branch
now builds on main directly. Conflict resolutions keep main's behaviour and
add the geometry work on top: osm_query gating on every provider, restored
admin identity for Nominatim outlines, merged analyst follow-up memory, and
the cancelled Globe View reset instruction ahead of the generic cancellation.
This commit is contained in:
Manjunath committed 2026-11-03 14:33:34 +05:30
commit ecaddd838b
102 files changed
+12925 -1006

No files matched your search

+2
View File
@@ -159,6 +159,8 @@ export function createNominatimOutlineClient({
const outline = {
polygons,
name: body.outline.name || null,
adminArea: body.outline.adminArea || null,
adminLevel: body.outline.adminLevel || null,
class: body.outline.class || null,
type: body.outline.type || null,
osm: body.outline.osm || null,
+12 -3
View File
@@ -44,20 +44,29 @@ test('an accepted outline comes back with its polygons and is remembered', async
reply(200, {
status: 'OK',
outline: {
name: 'Paris',
name: 'Bagmati Province',
adminArea: 'Bagmati Province',
adminLevel: 'admin1',
class: 'boundary',
type: 'administrative',
polygons: [[ring, hole]],
},
}),
);
const ask = { query: 'Paris', kind: 'city', lat: 48.85, lon: 2.35 };
const ask = {
query: 'Bagmati Province',
kind: 'admin',
lat: 27.72,
lon: 85.32,
};
const outline = await lookup(ask);
assert.deepEqual(outline.polygons, [[ring, hole]]);
assert.equal(outline.class, 'boundary');
assert.equal(outline.adminArea, 'Bagmati Province');
assert.equal(outline.adminLevel, 'admin1');
assert.match(
calls[0],
/^\/api\/geocode\/outline\?q=Paris&kind=city&lat=48\.850&lon=2\.350$/,
/^\/api\/geocode\/outline\?q=Bagmati\+Province&kind=admin&lat=27\.720&lon=85\.320$/,
);
await lookup(ask);
assert.equal(calls.length, 1, 'one request per ask in a session');
+54
View File
@@ -0,0 +1,54 @@
/**
* Browser client for `/api/osm/features`: one curated preset inside a box,
* as a count or as features. The server writes and bounds the Overpass
* query and answers only when an operator configured Overpass; this only
* names the preset, box, mode and limit.
* @module sources/osmFeatures
*/
/**
* @param {{fetchImpl?: typeof fetch, endpoint?: string}} [options]
* @returns {(request: {preset: string, bbox: number[], mode?: 'count'|'features', limit?: number}, options?: {signal?: AbortSignal}) => Promise<object>}
*/
export function createOsmFeatureSource({
fetchImpl = (...args) => globalThis.fetch(...args),
endpoint = '/api/osm/features',
} = {}) {
return async function searchOsmFeatures(
{ preset, bbox, mode = 'features', limit },
{ signal } = {},
) {
const params = new URLSearchParams({
preset,
bbox: bbox.map((v) => Number(v).toFixed(5)).join(','),
mode,
});
if (Number.isFinite(limit)) params.set('limit', String(limit));
let response;
try {
response = await fetchImpl(`${endpoint}?${params}`, { signal });
} catch (error) {
if (signal?.aborted) throw error;
return {
ok: false,
status: 0,
error: 'OpenStreetMap search is unreachable.',
};
}
let body = null;
try {
body = await response.json();
} catch {
body = null;
}
if (!response.ok)
return {
ok: false,
status: response.status,
code: body?.code || null,
error:
body?.error || `OpenStreetMap search failed (${response.status}).`,
};
return { ok: true, ...body };
};
}