Merge upstream main into feat/tools-area-breakdowns

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-15 09:00:37 -07:00
commit 38b95fb3b1
7 files changed
+104 -9

No files matched your search

+1 -1
View File
@@ -42,7 +42,7 @@
- Director timeline seeks cross the dateline along the short longitude arc.
- Conflicting CCTV direction tokens remain unknown within the highest matching tier, preserving travel-token precedence over street quadrants and bare words.
- Recent Imagery thumbnails require explicit presence evidence before reporting available pixels.
- Treat explicitly current, complete empty vessel coverage as a successful update,
clearing obsolete contacts while retaining stale or incomplete snapshots.
+4 -4
View File
@@ -1100,6 +1100,9 @@ collapse can still be undone.
## Surface keyboard lifecycle
Surface Tab cycling excludes controls with a negative tabindex, native disabled
state or inert ancestry; ordinary visible enabled controls retain their order.
`ui/surfaces` owns the capture-phase keyboard listener, Tab cycling and return
focus shared by the first-run launcher and Provider Settings. Each caller
activates it while open and deactivates it on dismissal; destruction releases
@@ -4545,10 +4548,7 @@ are omitted rather than framing the wrong part of the globe.
### Recent Imagery (September 2026)
Programmatic boxes passed to the exported `setBox` and `searchHls` operations
reject missing, blank or boolean edges instead of interpreting them as zero
coordinates. Valid finite numeric edges remain accepted; the app’s drag tool,
pins and share links already supply complete boxes.
Thumbnail decoding requires explicit `Data-Present: true` evidence; false remains empty, while absent or malformed evidence reports an error. Injected `getThumbnail` sources must return an OK Response with readable `Data-Present: true` to admit image bytes, or `false` for confirmed empty coverage. Cross-origin sources must expose that header to browser JavaScript.
- The layer sits in the Data Layers panel's Cameras group beside Cameras (CCTV); Mapped ALPR Cameras moved to the Infrastructure group to make room.
- DATA ▸ **Recent Imagery** (`recent-imagery`, `src/layers/recentImagery/`) lists the last 30 UTC days of imagery for a selected box and drapes the days you pick: HLS S30 (Sentinel-2) and L30 (Landsat 8/9) at 30 m, and the VIIRS NOAA-21 daily overview at 250 m. Keyless and browser-direct: NASA CMR for which days have HLS granules in the box (`catalog.js`, both collections concurrently, paged with `CMR-Search-After` up to 2,000 records each, `truncated` against CMR `hits`), GIBS WMTS for tiles, Worldview Snapshots for thumbnails and exports. Worldview's `Data-Present` header is the only availability signal: GIBS answers an empty tile with HTTP 200, and the JPEG overview would drape black, so a day confirmed empty never drapes.
+5 -1
View File
@@ -1,7 +1,11 @@
import { searchHls } from './catalog.js';
import { gibsTemplate, wvsSnapshotUrl } from './model.js';
/** NASA-compatible imagery acquisition; transport and URL builders are replaceable. */
/**
* NASA-compatible imagery acquisition; transport and URL builders are replaceable.
* Thumbnail responses must expose Data-Present: true for image admission, or
* false for confirmed empty coverage, including injected getThumbnail sources.
*/
export function createRecentImagerySource({
fetchImpl = (...args) => globalThis.fetch(...args),
search = searchHls,
@@ -0,0 +1,30 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createThumbnailLoader } from './thumbnails.js';
test('missing or malformed presence evidence cannot prove thumbnail pixels', async () => {
for (const header of [null, 'unknown', '', 'true', 'false']) {
let decodes = 0;
const loader = createThumbnailLoader({
requestThumbnail: async () => ({
ok: true,
headers: { get: (key) => (key === 'Data-Present' ? header : null) },
blob: async () => {
decodes++;
return new Blob(['fixture']);
},
}),
createObjectUrl: () => 'blob:fixture',
});
loader.request(
{ key: 'fixture', product: 'S30', day: '2026-09-18' },
{ west: 0, east: 1, south: 0, north: 1 },
);
await new Promise((resolve) => setImmediate(resolve));
assert.equal(
loader.get('fixture').status,
header === 'true' ? 'present' : header === 'false' ? 'empty' : 'error',
);
assert.equal(decodes, header === 'true' ? 1 : 0);
loader.destroy();
}
});
+2 -2
View File
@@ -100,12 +100,12 @@ export function createThumbnailLoader({
cancelled = true;
} else if (response?.ok) {
const present = String(
response.headers?.get?.('Data-Present') ?? 'true',
response.headers?.get?.('Data-Present') ?? '',
).toLowerCase();
acquisitionTime = response.headers?.get?.('Acquisition-Time') || null;
if (present === 'false') {
status = 'empty';
} else {
} else if (present === 'true') {
const blob = await response.blob();
if (controller.signal.aborted) cancelled = true;
else {
+57
View File
@@ -0,0 +1,57 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createSurfaceKeyboard } from './surfaceKeyboard.js';
test('surface cycling never targets deliberately excluded controls', () => {
let handler;
const documentRef = {
activeElement: {},
addEventListener: (_, fn) => (handler = fn),
removeEventListener() {},
};
const node = ({
attributes = {},
fieldsetDisabled = false,
inert = false,
} = {}) => ({
hasAttribute: (name) => Object.hasOwn(attributes, name),
getClientRects: () => [{}],
getAttribute: (name) => attributes[name] ?? null,
matches: (selector) => selector === ':disabled' && fieldsetDisabled,
closest: (selector) =>
selector === '[inert]' && inert ? { inert: true } : null,
focus() {
documentRef.activeElement = this;
},
});
const ignored = [
node({ attributes: { tabindex: '-1' } }),
node({ fieldsetDisabled: true }),
node({ inert: true }),
];
const first = node({}),
last = node({});
const controls = [...ignored, first, last];
const root = {
ownerDocument: documentRef,
querySelectorAll: () => controls,
contains: (n) => controls.includes(n),
};
const surface = createSurfaceKeyboard({
root,
isActive: () => true,
onEscape() {},
});
surface.activate();
const press = (shiftKey) =>
handler({
key: 'Tab',
shiftKey,
preventDefault() {},
stopPropagation() {},
});
press(false);
assert.equal(documentRef.activeElement, first);
press(true);
assert.equal(documentRef.activeElement, last);
surface.destroy();
});
+5 -1
View File
@@ -39,7 +39,11 @@ export function createSurfaceKeyboard({
),
].filter(
(node) =>
!node.hasAttribute('disabled') && node.getClientRects().length > 0,
!node.hasAttribute('disabled') &&
!node.matches?.(':disabled') &&
!(Number(node.getAttribute?.('tabindex')) < 0) &&
!node.closest?.('[inert]') &&
node.getClientRects().length > 0,
);
if (!order.length) return;
const first = order[0];