# How to Manage a Store for Scene Context in a CesiumJS Application

> Learn to manage a scene context store in CesiumJS applications. This article explains how to create a shared view of selected entities without tight coupling, perfect for voice commands, data layers, and UI panels.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-04

---

**The Gods-Eye-View codebase maintains a single, window-scoped store that holds all scene context records, enabling any module—whether voice commands, data layers, or UI panels—to share a common view of selected entities without tight coupling.**

Managing scene context in a CesiumJS application requires a centralized state solution that persists across asynchronous updates and module boundaries. The `bilawalsidhu/gods-eye-view` repository solves this by implementing a global context store in [`src/data/contextStore.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/contextStore.js) that lives on `window.__gevContextStore`, providing a unified API for registering entities, handling selections, and managing tracked subjects across dynamic data layers.

## Architecture of the Scene Context Store

The store acts as a singleton container attached to the global `window` object, ensuring consistent state across the entire application lifecycle.

### Store Creation and Host Guard

At lines 3‑9 in [`src/data/contextStore.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/contextStore.js), the `createStore()` function initializes the store object containing a `Map` of entity records, a `selectedEntityId` field, and a `selectedAt` timestamp. The store is lazily instantiated and assigned to `window.__gevContextStore` to guarantee a single instance across all modules.

To support unit testing environments where `window` may be undefined, the code implements `hasContextHost()` (lines 3‑9), which checks `typeof window !== 'undefined'` before attempting to access the global scope. This guard enables the store logic to be tested in Node.js environments without modification.

```javascript
// Access the shared store singleton
import { getContextStore } from './data/contextStore.js';
const store = getContextStore();   
// Returns: { entities: Map, selectedEntityId: null, selectedAt: null, ... }

```

## Working with Entity Contexts

Entities from Cesium data sources must be registered before they can participate in selection and tracking workflows.

### Registering New Entities

The `registerEntityContext(entity, metadata)` function (lines 31‑42) adds or updates records in `store.entities`. It accepts a Cesium entity object and a metadata object containing `id`, `layerId`, and coordinate properties. The function merges metadata, timestamps the entry, and stores it using the unique `id` as the Map key.

```javascript
import { registerEntityContext } from './data/contextStore.js';

const fireEntity = { /* Cesium Entity instance */ };
const metadata = { 
  id: 'fire-123', 
  layerId: 'fire-anchors', 
  latitude: 38.9, 
  longitude: -77.0 
};

registerEntityContext(fireEntity, metadata);
// store.entities now contains the record keyed by 'fire-123'

```

### Handling User Selection

When a user interacts with an entity—such as clicking a marker in the 3D view—`selectEntityContext(entity)` (lines 44‑53) updates the store state and notifies the rest of the application. This function sets `store.selectedEntityId` and `store.selectedAt`, then dispatches the `gev:entity-selected` custom event at line 51, allowing UI panels and voice command systems to react to selection changes.

```javascript
import { selectEntityContext } from './data/contextStore.js';

selectEntityContext(fireEntity);
// Updates store.selectedEntityId to 'fire-123'
// Dispatches window.dispatchEvent(new CustomEvent('gev:entity-selected'))

```

## Managing Tracked Subjects Separately

Dynamic tracking layers—such as live aircraft or satellite feeds—require a specialized API that updates positional data without triggering UI selection events.

### The Tracked Subject API

The `selectTrackedSubjectContext(metadata)` function (lines 73‑89) stores volatile subject data separately from the main UI selection. Unlike `selectEntityContext`, this method does not emit selection events, ensuring that polling updates from tracking layers do not steal focus from the user's current selection.

For refresh cycles, `refreshTrackedSubjectContext(metadata)` (lines 99‑107) updates the metadata for an existing tracked subject while maintaining its identity. When a tracking layer is disabled, `clearTrackedSubjectContext(layerId)` (lines 117‑127) removes the subject from the store.

```javascript
import { 
  selectTrackedSubjectContext, 
  refreshTrackedSubjectContext 
} from './data/contextStore.js';

// Initial tracking registration (no UI event emitted)
selectTrackedSubjectContext({ 
  id: 'ac-456', 
  layerId: 'aircraft', 
  latitude: 40.0, 
  longitude: -73.0 
});

// Poll update (lines 99-107)
refreshTrackedSubjectContext({ 
  id: 'ac-456', 
  layerId: 'aircraft', 
  latitude: 40.1, 
  longitude: -73.1 
});

```

## Querying and Validating State

Modules consume the store through query functions that validate record freshness and visibility.

### Retrieving Selected Entities

The `getSelectedEntityContext({ dataManager })` function (lines 29‑38) returns the currently selected record only if it passes activity validation. It accepts an optional `dataManager` parameter to filter records by their parent data source state.

```javascript
import { getSelectedEntityContext } from './data/contextStore.js';

const selected = getSelectedEntityContext({ dataManager });
if (selected) {
  console.log('Active selection:', selected.id);
}

```

### Validating Active Records

Internally, the store uses `isContextRecordActive(record, dataManager)` (lines 79‑85) to determine if a stored record should be considered valid. This function inspects `entity.show`, `dataSource.show`, and `dataManager.isEnabled` properties, ensuring that hidden or disabled entities are not returned as active selections.

## Cleanup and Event Lifecycle

Viewport-scoped layers must clean up their context records when refreshing or unloading to prevent memory leaks and stale selections.

### Removing Layer-Specific Contexts

The `removeEntityContextsForLayer(layerId)` function purges all entities associated with a specific layer ID from the store's Map. When a selection is cleared—either manually or due to layer removal—`clearSelectedEntityContextForLayer(layerId, options)` (referenced in cleanup workflows) resets `store.selectedEntityId` to `null` and emits the `gev:entity-selection-cleared` event.

```javascript
import { clearSelectedEntityContextForLayer } from './data/contextStore.js';

// Clear selection when aircraft layer is removed
clearSelectedEntityContextForLayer('aircraft', { evicted: false });
// Emits: gev:entity-selection-cleared

```

## Real-World Integration Patterns

The context store serves as the backbone for cross-module communication in the Gods-Eye-View application.

- **Data layers**: Files like [`src/data/militaryInstallations.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/militaryInstallations.js) and [`src/data/aisLiveVessels.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/aisLiveVessels.js) call `registerEntityContext` when loading new features, binding Cesium entities to metadata records.
- **Voice commands**: The [`src/voice/gevActions.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/voice/gevActions.js) module queries `getSelectedEntityContext` to resolve pronouns like "this target" or "that aircraft," enabling natural language interaction with the 3D scene.
- **CCTV focus management**: [`src/data/cctvFocusPolicy.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/cctvFocusPolicy.js) utilizes the tracked-subject API to maintain camera focus on moving subjects without interfering with the user's manual entity selection.

## Summary

- **Single window-scoped store**: The `createStore()` function in [`src/data/contextStore.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/contextStore.js) attaches the state container to `window.__gevContextStore`, ensuring one consistent instance across the CesiumJS application.
- **Dual selection model**: Use `selectEntityContext` for UI-driven selections that emit events, and `selectTrackedSubjectContext` for background tracking layers that update silently.
- **Activity validation**: The `getSelectedEntityContext` function filters results through `isContextRecordActive`, checking entity visibility and data source state before returning records.
- **Event-driven updates**: The store dispatches `gev:entity-selected` and `gev:entity-selection-cleared` events, decoupling state management from UI components.
- **Layer-scoped cleanup**: Functions like `removeEntityContextsForLayer` and `clearTrackedSubjectContext` prevent memory leaks by purging stale records when data layers refresh or unload.

## Frequently Asked Questions

### How does the store handle server-side rendering or unit testing?

The `hasContextHost()` guard checks `typeof window !== 'undefined'` before accessing the global scope, allowing the store logic to execute in Node.js testing environments without throwing reference errors. When `window` is absent, the store creation is safely skipped or mocked.

### What is the difference between `selectEntityContext` and `selectTrackedSubjectContext`?

`selectEntityContext` designates an entity as the user's active selection, updates `store.selectedEntityId`, and fires the `gev:entity-selected` event for UI consumption. `selectTrackedSubjectContext` stores volatile tracking data—such as live aircraft positions—without changing the UI selection state or emitting events, ensuring polling updates do not disrupt the user experience.

### How can I check if a stored entity is still visible in the Cesium scene?

Use `isContextRecordActive(record, dataManager)`, which validates that `entity.show !== false`, `dataSource.show !== false`, and `dataManager.isEnabled !== false`. This function is automatically invoked by `getSelectedEntityContext` to ensure only visible, enabled entities are returned as active selections.

### When should I use `refreshTrackedSubjectContext` versus re-registering the entity?

Call `refreshTrackedSubjectContext` when updating positional coordinates or metadata for an existing tracked subject during polling cycles. This maintains the same record identity and timestamp continuity. Only use `registerEntityContext` when initially creating the entity record or when the entity's fundamental identity (such as its `id` or `layerId`) changes.