Keep Directions reachable and tested in the Utilities panel

Voice show_data_layers_menu opens the panel that holds the row, so asking
for Directions opens UTILITIES and reports that panel, not a hidden row
under Data Layers. In Cockpit the panel stays with Directions alone, as
Data Layers does; only Draw and Import step aside there.

Draw's options sit flat under its name instead of keeping the card that
hung under a DISPLAY button, and its Shape label no longer takes DISPLAY's
label column in other languages. Cyber marks Draw on with its red edge.

LayerPanel routing, the voice panel choice, and the panel's order, mode
and phone-sheet CSS gain tests that fail on the earlier code. The form-field
and segmented-control gates open Utilities for Draw's controls, measuring
Scenes and Utilities in turn since the left rail fits one tall panel.
README, CONTRIBUTING, the PR template, CURRENT-STATE and comments now say
UTILITIES for Draw and Import and list the Audio group, and two files
outside the format scope lose an unrelated reflow.
This commit is contained in:
Said Turksever committed 2026-10-29 11:53:15 +00:00
1 parent d730b0ce87
commit 8543b7be43
22 files changed
+319 -97

No files matched your search

+12 -7
View File
@@ -1046,15 +1046,18 @@ export function createGevActionRunner({
if (name === 'show_data_layers_menu') {
const layerId = normalizeLayerId(args.layerId || args.layer);
setPanelOpen(styleManager, 'data-panel', true);
const focusedLayer =
const row =
layerId && dataManager.layers.has(layerId)
? focusDataLayerRow(layerId)
? findDataLayerRow(layerId)
: null;
// Open the panel that holds the row: Directions lives in Utilities.
const panelId = row?.closest('[data-panel-id]')?.id || 'data-panel';
setPanelOpen(styleManager, panelId, true);
const focusedLayer = row ? focusDataLayerRow(row, layerId) : null;
return {
ok: true,
action: 'show_data_layers_menu',
panelId: 'data-panel',
panelId,
focusedLayer,
layers: dataManager
.getAll()
@@ -3190,12 +3193,14 @@ function normalizeCockpitAction(value) {
return null;
}
function focusDataLayerRow(layerId) {
function findDataLayerRow(layerId) {
const selector = `[data-layer-id="${CSS.escape(layerId)}"]`;
const row = document.querySelector(
return document.querySelector(
`#data-toggles ${selector}, #utilities-toggles ${selector}`,
);
if (!row) return null;
}
function focusDataLayerRow(row, layerId) {
row.scrollIntoView({ block: 'center', behavior: 'smooth' });
row.classList.remove('gev-voice-focus');
void row.offsetWidth;
+47
View File
@@ -743,6 +743,53 @@ test('Data Layers voice inventory hides the Context coordinator while current-vi
assert.deepEqual(current.cockpit, { active: false, entryAllowed: true });
});
test('showing a layer opens the panel that holds its row: Directions lives in Utilities', async () => {
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
const previous = { document: globalThis.document, CSS: globalThis.CSS };
const focused = [];
const rowIn = (panelId) => ({
dataset: { layerName: panelId === 'utilities-panel' ? 'Directions' : 'Live Flights' },
classList: { add: (name) => focused.push(name), remove() {} },
scrollIntoView() {},
closest: (selector) => (selector === '[data-panel-id]' ? { id: panelId } : null),
});
const rows = { directions: rowIn('utilities-panel'), flights: rowIn('data-panel') };
globalThis.CSS = { escape: (value) => value };
globalThis.document = {
querySelector: (selector) => rows[/data-layer-id="([^"]+)"/.exec(selector)?.[1]] || null,
};
try {
const opened = [];
const layers = [
{ id: 'flights', name: 'Live Flights', enabled: false, showInTogglePanel: true, stats: {} },
{ id: 'directions', name: 'Directions', enabled: false, showInTogglePanel: true, stats: {} },
];
const dataManager = {
layers: new Map(layers.map((layer) => [layer.id, { module: {} }])),
getAll: () => layers,
};
const styleManager = {
setPanelCollapsed: (panelId, collapsed) => opened.push([panelId, collapsed]),
};
const viewer = {
clock: { onTick: { addEventListener: () => () => {} } },
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: { moveEnd: { addEventListener() {} } },
};
const runner = createGevActionRunner({ viewer, styleManager, dataManager });
const directions = await runner('show_data_layers_menu', { layerId: 'directions' });
assert.equal(directions.panelId, 'utilities-panel');
assert.deepEqual(directions.focusedLayer, { id: 'directions', name: 'Directions' });
const flights = await runner('show_data_layers_menu', { layerId: 'flights' });
assert.equal(flights.panelId, 'data-panel');
assert.deepEqual(opened, [['utilities-panel', false], ['data-panel', false]]);
assert.deepEqual(focused, ['gev-voice-focus', 'gev-voice-focus']);
} finally {
globalThis.document = previous.document;
globalThis.CSS = previous.CSS;
}
});
test('generic layer visibility forwards cancellation and reports semantic failure', async () => {
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
const viewer = {