Keep the context id in Core's entity records instead of on picked objects

registerEntityContext stamped __gevContextId on the entities, primitives and
carriers it registered, and selection and pick resolution read it back. It
is now the contextId field of the entity's Core record.
This commit is contained in:
Sameh Khamis committed 2026-11-03 09:40:15 -08:00
1 parent 07c8d7a17c
commit 42b17ffab6
5 files changed
+18 -16

No files matched your search

+5 -3
View File
@@ -1,3 +1,5 @@
import { entityRecord, readEntityRecord } from './entityRecords.js';
const STORE_KEY = '__gevContextStore';
function createStore() {
@@ -36,14 +38,14 @@ export function registerEntityContext(entity, metadata) {
entity,
updatedAt: Date.now(),
};
entity.__gevContextId = metadata.id;
entityRecord(entity).contextId = metadata.id;
store.entities.set(metadata.id, record);
return record;
}
export function selectEntityContext(entity) {
const store = getContextStore();
const contextId = entity?.__gevContextId;
const contextId = readEntityRecord(entity)?.contextId;
if (!contextId || !store.entities.has(contextId)) return null;
store.selectedEntityId = contextId;
store.selectedAt = Date.now();
@@ -84,7 +86,7 @@ export function selectTrackedSubjectContext(metadata) {
store.entities.delete(key);
}
// Reuse the existing carrier so a per-poll refresh does not churn identity.
const carrier = store.entities.get(id)?.entity || { __gevContextId: id };
const carrier = store.entities.get(id)?.entity || {};
const record = registerEntityContext(carrier, { ...metadata, id });
if (!record) return null;
store.selectedEntityId = id;
+2 -2
View File
@@ -96,7 +96,7 @@ test('a sibling tracking layer keeps its own subject slot', () => {
test('the per-poll refresh updates values without stealing the selection', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
const other = registerEntityContext({ __gevContextId: 'dc-7' }, {
const other = registerEntityContext({}, {
id: 'dc-7', layerId: 'local-datacenters', label: 'Datacenter 7',
});
selectEntityContext(other.entity);
@@ -130,7 +130,7 @@ test('deselecting a contact releases the shared slot', () => {
test('clearing one tracking layer leaves another layer selection alone', () => {
withWindow(() => {
const other = registerEntityContext({ __gevContextId: 'dam-3' }, {
const other = registerEntityContext({}, {
id: 'dam-3', layerId: 'local-dams', label: 'Dam 3',
});
selectEntityContext(other.entity);
+4 -4
View File
@@ -1,4 +1,5 @@
import { getContextStore, isContextRecordActive } from './contextStore.js';
import { readEntityRecord } from './entityRecords.js';
import { findPickOwner, resolvePickId } from './pickRegistry.js';
/**
@@ -7,8 +8,8 @@ import { findPickOwner, resolvePickId } from './pickRegistry.js';
*
* Two lanes, in order:
* 1. Context-store carriers. Entity-backed layers (infrastructure GeoJSON,
* selected vessels, fires, installations) stamp `__gevContextId` on the
* picked object, and the store record already carries the label and
* selected vessels, fires, installations) write a `contextId` into the
* picked object's entity record, and the store record already carries the label and
* position voice uses everywhere else.
* 2. Pick owners. Primitive-backed layers (aircraft, satellites, vessels)
* register an ownership predicate in `pickRegistry`; the owning layer's
@@ -52,8 +53,7 @@ function contextCarrierRecord(picked, dataManager) {
picked?.primitive?.id,
picked?.primitive,
]) {
const contextId =
carrier && typeof carrier === 'object' ? carrier.__gevContextId : null;
const contextId = readEntityRecord(carrier)?.contextId;
if (!contextId) continue;
const record = store.entities.get(contextId);
if (record && isContextRecordActive(record, dataManager)) return record;
+3 -3
View File
@@ -12,7 +12,7 @@ import {
localAdsbReceiverLine,
localAdsbStatus,
} from './index.js';
import { readEntityRecord } from '../../data/entityRecords.js';
import { entityRecord, readEntityRecord } from '../../data/entityRecords.js';
function fakeReceiver(initial = {}) {
const state = {
@@ -56,11 +56,11 @@ function fakeServices() {
render: { governorRequestRender() {} },
context: {
registerEntityContext(entity, metadata) {
entity.__gevContextId = metadata.id;
entityRecord(entity).contextId = metadata.id;
calls.registered.push(metadata);
},
selectEntityContext(entity) {
selected = { id: entity.__gevContextId, entity };
selected = { id: readEntityRecord(entity)?.contextId, entity };
calls.selected.push(selected.id);
return selected;
},
+4 -4
View File
@@ -1303,7 +1303,7 @@ test('control_cockpit delegates selected-flight adoption to the canonical cockpi
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: { moveEnd: { addEventListener() {} } },
};
const recordCarrier = { __gevContextId: 'selected-flight-for-cockpit-enter' };
const recordCarrier = {};
const record = registerEntityContext(recordCarrier, {
id: 'abc123',
layerId: 'flights',
@@ -1444,10 +1444,10 @@ test('control_cockpit rolls Contacts back when the turn becomes stale at commit'
test('control_cockpit adopts the newest selection after Contacts settles', async () => {
globalThis.window = globalThis.window || { clearTimeout, setTimeout, requestIdleCallback: null };
const contextStore = getContextStore();
const first = registerEntityContext({ __gevContextId: 'cockpit-selection-first' }, {
const first = registerEntityContext({}, {
id: 'first', layerId: 'flights', label: 'FIRST',
});
const second = registerEntityContext({ __gevContextId: 'cockpit-selection-second' }, {
const second = registerEntityContext({}, {
id: 'second', layerId: 'flights', label: 'SECOND',
});
contextStore.selectedEntityId = first.id;
@@ -1713,7 +1713,7 @@ test('control_cockpit enter skips selected non-flight context', async () => {
scene: { canvas: { addEventListener() {}, removeEventListener() {} } },
camera: { moveEnd: { addEventListener() {} } },
};
const recordCarrier = { __gevContextId: 'selected-non-flight-cockpit-enter' };
const recordCarrier = {};
const record = registerEntityContext(recordCarrier, {
id: 'poi-001',
layerId: 'local-datacenters',