EidosNodeStore (client-side node state)¶
Verified against the code · 421c63ba3fab5894cdb9e707eed8ef694881d60d · 2026-10-20
956 D1b-1 (2026-10-20): 1 finding(s) fixed — the EidosCore.nodeCache getter was removed in 5.2f (#906); only app.js's const alias remains. Re-verified against main 421c63ba.¶
Layer: frontend
EidosNodeStore (frontend/eidos-node-store.js) is the ONE canonical owner of
client-side node state. Before trin 5.2b (#891) the module-level nodeCache
map in app.js held the answer to "what do we know about node uid"
alongside the _navGen nav-generation counter. That ownership moved into
one module: the store replaces the nodeCache map and now owns _nodes
(the backing map) plus _navGen. Spec SSOT:
docs/specs/eidos-nodestore-tree.md §3, §4.1, §5.2b, §8.
What it owns¶
_nodes—uid -> JSON-LD node, the backing map._navGen— a nav-generation counter plugins use to drop fetches started before the user navigated (the formerapp.js _navGenlives here now).
Nothing else may hold a parallel map keyed by uid.
The live-alias contract¶
app.js holds const nodeCache = window.EidosNodeStore._nodes — the SAME
object. This is by design: existing index call-sites inside app.js
(nodeCache[uid]) stay unchanged. It is the only alias left: 5.2f (#906)
removed the EidosCore.nodeCache getter (it is absent from the EidosCore
literal and the export snapshot, and the B4 ratchet freezes any
EidosCore.nodeCache reference at 0). The
contract on the store is therefore:
invalidate('all')deletes every key IN PLACE — it never reassigns_nodes. A reassignment would break the alias and leave stale references inapp.js.upsert(uid, node)writes_nodes[uid] = nodein place.patch(uid, delta)mutates_nodes[uid]in place (no-op with a warning ifuidis unknown — never auto-creates).
Reads (get, getBatch, snapshot) return the same live references the
alias returns — callers who need a defensive copy make one.
The B3 ratchet¶
tests/test_frontend_node_tree_ratchet.py freezes the number of
cross-file reads of window.EidosCore.nodeCache or core.nodeCache outside
app.js and the stores. The baseline was 31 at 5.2b; 5.2c migrated every
one to a call through EidosNodeStore.get/getBatch/snapshot, driving the
counter to 0 (frozen at equality — not <=). A new plugin read raises the
count and trips the ratchet.
B1 (WRITE-bypass — EidosCore.{nodeCache}[x] = y and .push(...) in
frontend/** outside the stores) is also frozen at 0 since 5.2b.
Nav generation guard¶
EidosNodeStore.nav.generation() and .bump() replace the old app.js
_navGen. The pattern: a long-running fetch captures const g = nav.generation()
at start and bails on completion if nav.generation() !== g. bump()
runs at every navigation. The seam lives in the store because the nav gate
is a property of the nodes-we-care-about-right-now, not of app.js wiring.
Load order¶
<script src="frontend/eidos-action.js">
<script src="frontend/eidos-node-store.js"> <!-- defines window.EidosNodeStore -->
<script src="frontend/eidos-tree.js"> <!-- consumes it in _ingest -->
<script src="frontend/app.js"> <!-- const nodeCache = window.EidosNodeStore._nodes -->