@modernrelay/orbit-core 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2995 @@
1
+ import { G as GraphSnapshot, A as AcceptedGraph, R as ResolvedGroup, a as GraphDiagnostic, b as GraphNode, c as AcceptedEdge, N as NodeId, M as MetaEdge, S as SceneFold, d as GroupSpec, e as GroupBySpec, f as SceneGroups, g as RenderScene, h as SceneLinkRef, i as ScenePointRef, j as Accessor, k as RevisionAwareService, l as RequestContext, m as SearchResult, V as ViewportState, n as SelectionState, o as SubgraphSpec, J as JsonValue, E as EngineFactory, p as ExpansionService, P as PathService, q as GraphTheme, r as GraphStoreState, L as LabelPlacement, s as GraphHostUpdate, B as BeginIngestOptions, I as IngestSession, t as GraphEventName, u as GraphEventMap, v as GraphListenerControl, w as EdgeId, x as ResolvedCluster, y as PathOptions, z as PathResult, C as SearchActivation, D as CrossfilterSession, T as TimelinePlayback, F as Scale, H as MetricName, K as Revisions, O as AccessibilityConfig, Q as ThemeInput, U as LabelConfig, W as IngestBatch, X as RevisionDimension, Y as FilterSpec, Z as FilterExpr, _ as DimensionSpec, $ as BrushState, a0 as DimensionSummary, a1 as DomainPolicy, a2 as EngineBufferChannel, a3 as GraphEngine, a4 as EngineCommit, a5 as EngineCapabilities, a6 as MetricColumn } from './index-BPjuELfY.js';
2
+ export { a7 as AppendReceipt, a8 as CategoryBin, a9 as ClusterSpec, aa as DIAGNOSTIC_SAMPLE_CAP, ab as DiagnosticCode, ac as DiagnosticSeverity, ad as DimensionKind, ae as ErrorPhase, af as ExpansionBatch, ag as ExpansionResponse, ah as FilterMode, ai as FilterValue, aj as GraphEdge, ak as GraphError, al as GraphOperationError, am as HistogramBin, an as IngestCommitReceipt, ao as IngestSessionState, ap as InstanceStatus, aq as LayoutKind, ar as NodeEventPayload, as as OrbitOperationError, at as ResourceAdmissionReport, au as SearchUnavailableReason, av as SimulationConfig, aw as graphErrorToError, ax as isFatalGraphError, ay as resourceLimitFatal } from './index-BPjuELfY.js';
3
+ export { C as ClusterDerivation, D as DEFAULT_CLUSTER_CENTER_RADIUS, a as DEFAULT_LAYOUT_SEED, c as clusterCentroids, d as deriveClusters, g as generateClusterCenters, r as resolveClusterCenters } from './clusters-ExqnvobT.js';
4
+ import { StoreApi } from 'zustand/vanilla';
5
+
6
+ /**
7
+ * §5.1 snapshot validation — deterministic malformed-input resolution.
8
+ *
9
+ * Pure function: same input → structurally identical output; never throws on
10
+ * malformed input. Diagnostics are batched — at most one GraphDiagnostic per
11
+ * code per pass, with a total count and at most DIAGNOSTIC_SAMPLE_CAP samples
12
+ * (O(categories) work/allocation, never O(bad rows)).
13
+ */
14
+
15
+ declare function validateSnapshot<N = Record<string, unknown>, E = Record<string, unknown>>(snapshot: GraphSnapshot<N, E>): AcceptedGraph<N, E>;
16
+
17
+ /**
18
+ * §16.3 stage-3 containment rewrite (S12-T01) — pure derivation, no engine,
19
+ * no DOM.
20
+ *
21
+ * ## The representative forest
22
+ *
23
+ * ONE structure drives both group collapse and node fold. Every entity — a
24
+ * physical node id, or a synthetic group scene key — has AT MOST ONE
25
+ * representative parent, and:
26
+ *
27
+ * > An entity is drawn iff no ancestor is collapsed. Each edge endpoint
28
+ * > reroutes to its OUTERMOST COLLAPSED ancestor; edges whose endpoints
29
+ * > rewrite to the same key are dropped; the rest merge per directed pair
30
+ * > into one meta-edge carrying the underlying count. A SYNTHETIC
31
+ * > representative materializes only while collapsed; a REAL one is always
32
+ * > drawn.
33
+ *
34
+ * `collapsed` is a property of the REPRESENTATIVE, not of the node — which
35
+ * is exactly why a folded anchor stays visible while its children hide, and
36
+ * why nesting needs no special case (a fold inside a collapsed group hides
37
+ * with it, and its members' edges route to the group).
38
+ *
39
+ * Contract summary (spec §16.3/§7.6 stage 3, invariants E5):
40
+ * - Groups are HIERARCHICAL but singly-parented: a member id may name another
41
+ * group (nesting), but no entity has two parents. `validateGroupSpecs`
42
+ * rejects a violating array with ONE batched §5.1 'config-error' diagnostic
43
+ * BEFORE any scene rewrite — a rejected array changes nothing.
44
+ * - `rewriteGroups` runs over the HARD-SCOPED model (§7.6 stage 2 output):
45
+ * collapsed representatives replace their in-scope descendants with one row
46
+ * (a synthetic super-node, or the fold anchor's existing physical row);
47
+ * descendant edges re-route into meta-edge rows carrying the underlying-edge
48
+ * count (the badge datum). The post-rewrite graph feeds the EXISTING §7.2
49
+ * structural diff, so collapse/expand is a diff, never a reload.
50
+ * - Group ids occupy a distinct PUBLIC namespace (§7.4): synthetic rows carry
51
+ * INTERNAL scene keys (NUL-prefixed — outside the documented caller id
52
+ * contract) that never escape public payloads. A group id equal to a node
53
+ * id coexists without collision. A FOLD anchor is a real node, so its
54
+ * entity key IS its node id and it needs no synthetic slot at all.
55
+ * - Synthetic rows are never cast to N/E: they carry only `id`, and consumers
56
+ * identify them by slot position — synthetics are always a CONTIGUOUS
57
+ * SUFFIX of the node/edge lists (physical prefix ordering is preserved), so
58
+ * `slot >= physicalCount` is the discrimination rule.
59
+ */
60
+
61
+ /** Internal scene key of a collapsed group's super-node row. NEVER public. */
62
+ declare function groupSceneKey(groupId: string): string;
63
+ /** Internal scene key of a meta-edge row (directed rewritten endpoint pair). */
64
+ declare function metaEdgeSceneKey(sourceKey: string, targetKey: string): string;
65
+ /** §16.3 PUBLIC meta-edge id: the collision-safe ordered endpoint tuple
66
+ * `JSON.stringify(['meta-edge', source.kind, source.id, target.kind,
67
+ * target.id])` — shared with the parallel-edge grouping toggle (R-16.3-24). */
68
+ declare function metaEdgePublicId(sourceKind: 'node' | 'group', sourceId: string, targetKind: 'node' | 'group', targetId: string): string;
69
+ /**
70
+ * Entity key: a physical node id, or a synthetic group scene key. A fold
71
+ * anchor IS its node id — that is the whole point of a real representative.
72
+ */
73
+ type EntityKey = string;
74
+ interface RepForest {
75
+ /** entity → its representative parent (at most one, by construction). */
76
+ readonly parent: ReadonlyMap<EntityKey, EntityKey>;
77
+ /** Representatives whose CHILDREN are hidden — never themselves. */
78
+ readonly collapsed: ReadonlySet<EntityKey>;
79
+ /** Synthetic representatives: scene key → the group it stands for. */
80
+ readonly groupOf: ReadonlyMap<EntityKey, ResolvedGroup>;
81
+ /** Fold anchors in declaration order (deterministic scene output). */
82
+ readonly anchors: readonly NodeId[];
83
+ /** Groups in array order (deterministic super-node suffix order). */
84
+ readonly groups: readonly ResolvedGroup[];
85
+ }
86
+ /** Physical default size when no nodeSize accessor is configured but the
87
+ * scene needs a full buffer for the synthetic suffix. */
88
+ declare const PHYSICAL_DEFAULT_POINT_SIZE = 4;
89
+ /** Physical default width when no linkWidth accessor is configured. */
90
+ declare const PHYSICAL_DEFAULT_LINK_WIDTH = 1;
91
+ declare const SUPER_NODE_MAX_SIZE = 36;
92
+ declare const META_EDGE_MAX_WIDTH = 8;
93
+ /** Aggregate super-node size: sublinear in member count, bounded. */
94
+ declare function superNodeSizeFor(memberCount: number): number;
95
+ /** Aggregate meta-edge width: sublinear in underlying-edge count, bounded. */
96
+ declare function metaEdgeWidthFor(count: number): number;
97
+ interface GroupValidationResult {
98
+ /** Null when the array is valid; otherwise ONE batched 'config-error'. */
99
+ diagnostic: GraphDiagnostic | null;
100
+ }
101
+ /**
102
+ * Validates a manual `groups` array against the FULL accepted model (members
103
+ * may live outside the current hard scope). Violations, all collected into
104
+ * one diagnostic (§5.1 delivery contract — never one event per bad row):
105
+ * - duplicate group id in the array;
106
+ * - self-membership (a group naming its own id — rejected even when a node
107
+ * with that id exists: the ambiguity itself is the error);
108
+ * - duplicate membership (the same node twice in one group);
109
+ * - overlapping membership (the same node in two groups);
110
+ * - CYCLIC nesting (groups that contain each other, directly or through a
111
+ * chain — the containment forest must stay acyclic);
112
+ * - unknown members (ids absent from the accepted model AND not naming
113
+ * another group; with no accepted model every non-group member is unknown).
114
+ *
115
+ * NESTING IS LEGAL: a member id naming another group id nests that group.
116
+ * Containment is a forest, not a partition — but it stays SINGLY PARENTED,
117
+ * so overlap remains an error.
118
+ */
119
+ declare function validateGroupSpecs(specs: readonly GroupSpec[], nodeIndex: ReadonlyMap<NodeId, number> | null): GroupValidationResult;
120
+ /**
121
+ * Resolves a VALIDATED manual groups array against the accepted model.
122
+ * Tolerant by design: members that later departed the model drop out of the
123
+ * resolved membership (model drift is data, not a config error — mirrors
124
+ * selection pruning). Spec order and member order are preserved.
125
+ */
126
+ declare function resolveManualGroups(specs: readonly GroupSpec[], nodeIndex: ReadonlyMap<NodeId, number>): ResolvedGroup[];
127
+ /** Canonical structural equality for the groups HOST LANE (GroupSpec is a
128
+ * plain descriptor like Scale — equal literals never re-rewrite, §6.1). */
129
+ declare function sameGroupSpecArrays(a: readonly GroupSpec[] | null, b: readonly GroupSpec[] | null): boolean;
130
+ /**
131
+ * R-16.3-14 collision-safe derived-group id codec: `JSON.stringify(['group',
132
+ * key])`. Injective over keys (JSON string encoding), never equal to the raw
133
+ * key itself (every output starts with `["group",`), and round-trippable via
134
+ * JSON.parse. Labels stay separate — the derived group's `label` is the raw
135
+ * key. Group ids remain a distinct public namespace (§7.4), so a NODE id that
136
+ * happens to equal a derived id still coexists without collision.
137
+ */
138
+ declare function groupByDerivedId(key: string): string;
139
+ /** Canonical identity for the groupBy HOST LANE: `by` compares by function
140
+ * reference (a new inline lambda re-derives), semanticZoom structurally. */
141
+ declare function sameGroupBySpec<N>(a: GroupBySpec<N> | null, b: GroupBySpec<N> | null): boolean;
142
+ /**
143
+ * D4 boundary validation for a groupBy spec: `by` must be a function and
144
+ * `semanticZoom.expandAbove` must be STRICTLY greater than `collapseBelow`
145
+ * (the §16.3 hysteresis contract T05 builds on). Returns ONE batched
146
+ * 'config-error' diagnostic (the rejected spec never lands — the previous
147
+ * groupBy configuration stays active) or null when valid.
148
+ */
149
+ declare function validateGroupBySpec<N>(spec: GroupBySpec<N>): GraphDiagnostic | null;
150
+ interface GroupByDerivation {
151
+ /** One derived group per distinct key, first-encounter order over the
152
+ * model's node list. `collapsed` comes from the caller's residue lookup. */
153
+ groups: readonly ResolvedGroup[];
154
+ /** Derived PUBLIC id → derived key (the setGroupCollapsed reverse map —
155
+ * the residue is keyed by KEY, never by id, R-16.3-16). */
156
+ keyById: ReadonlyMap<string, string>;
157
+ /** ONE aggregated 'accessor-error' warning when `by` threw (the affected
158
+ * nodes derive as ungrouped — never silent loss, I3), else null. */
159
+ diagnostic: GraphDiagnostic | null;
160
+ }
161
+ /**
162
+ * §16.3 groupBy derivation: one GroupSpec-shaped ResolvedGroup per distinct
163
+ * string key returned by `by` (first-encounter order, matching §11
164
+ * categorical-domain conventions); `null` — and any non-string value — means
165
+ * ungrouped. Derived ids use {@link groupByDerivedId}; `label` is the raw
166
+ * key; `derived: true` marks membership read-only. `collapsed` defaults to
167
+ * whatever `isCollapsedKey` reports — with an empty residue everything is
168
+ * expanded, so adding groupBy alone changes no rendering (R-16.3-13).
169
+ */
170
+ declare function deriveGroupsByKey<N>(nodes: readonly GraphNode<N>[], by: (node: GraphNode<N>) => string | null, isCollapsedKey: (key: string) => boolean): GroupByDerivation;
171
+ interface SuperNodeRecord {
172
+ /** Internal scene key of the super-node row (never public). */
173
+ sceneKey: string;
174
+ group: ResolvedGroup;
175
+ /** TRANSITIVE descendants present in the REWRITTEN model's input
176
+ * (hard-scoped set) — the stage-5 mask and aggregate size read this, not
177
+ * the full declared membership. Transitive and direct coincide unless
178
+ * something is nested underneath. */
179
+ presentMemberIds: readonly NodeId[];
180
+ }
181
+ interface MetaEdgeRecord {
182
+ /** Internal scene key of the meta-edge row (never public). */
183
+ sceneKey: string;
184
+ /** Public record (§7.4 payload surface): endpoints are PUBLIC ids — a
185
+ * group id or a node id — and `count` is the badge datum. */
186
+ metaEdge: MetaEdge;
187
+ /** Indices of the rerouted edges in the PRE-rewrite model's edge list —
188
+ * the stage-5 "any underlying edge passes" rule evaluates these. */
189
+ underlying: readonly number[];
190
+ }
191
+ interface GroupRewrite<N = Record<string, unknown>, E = Record<string, unknown>> {
192
+ /** The post-rewrite scene model — what feeds the §7.2 structural diff. */
193
+ graph: AcceptedGraph<N, E>;
194
+ /** Physical rows (same objects as the input model's, members removed). */
195
+ physicalNodes: readonly GraphNode<N>[];
196
+ physicalEdges: readonly AcceptedEdge<E>[];
197
+ /** Scene slots >= these counts are synthetic (contiguous-suffix rule). */
198
+ physicalNodeCount: number;
199
+ physicalEdgeCount: number;
200
+ /** Aligned to point slots physicalNodeCount..count-1, in groups order. */
201
+ superNodes: readonly SuperNodeRecord[];
202
+ /** Aligned to link slots physicalEdgeCount..linkCount-1, first-encounter
203
+ * order over the input edge scan. */
204
+ metaEdges: readonly MetaEdgeRecord[];
205
+ /** Drawn fold anchors and the descendant count each currently hides. These
206
+ * are PHYSICAL rows — they occupy no synthetic slot. */
207
+ folds: readonly SceneFold[];
208
+ /** hidden node id → the entity key of the row standing for it (a super-node
209
+ * scene key, or a fold anchor's node id). */
210
+ hiddenOwner: ReadonlyMap<NodeId, EntityKey>;
211
+ }
212
+ /**
213
+ * Rewrites the collapsed representatives of `forest` over the hard-scoped
214
+ * model (§7.6 stage 3). Returns null when nothing collapsed intersects the
215
+ * model — an uncollapsed group exists only in store.groups and never
216
+ * rewrites the scene.
217
+ *
218
+ * Deterministic: physical rows keep model order; super-nodes append in
219
+ * groups-array order; meta-edges append in first-encounter order. Edges whose
220
+ * endpoints land on the SAME representative drop — that covers both a
221
+ * same-group internal edge and an anchor's edge to its own folded member,
222
+ * which would otherwise emit a self-loop. Dropped edges are internal state,
223
+ * cached by construction: they re-derive from the unchanged accepted model on
224
+ * expand. Synthetic rows carry no positions; the reconciler's departed cache
225
+ * restores a re-collapsed super-node (stable scene key) and returning members
226
+ * exactly like any other leave-and-return (§7.3).
227
+ */
228
+ declare function rewriteGroups<N, E>(model: AcceptedGraph<N, E>, forest: RepForest): GroupRewrite<N, E> | null;
229
+ /**
230
+ * Collapses same-DIRECTED-endpoint-pair physical edges into ONE meta-edge
231
+ * per pair, composing with an existing group rewrite (or synthesizing a
232
+ * groups-empty rewrite when none is active). Returns `base` unchanged when
233
+ * no pair has multiplicity > 1 — the toggle is then a scene no-op.
234
+ *
235
+ * Meta-edge identity reuses the T01 codecs: public id
236
+ * `metaEdgePublicId('node', source, 'node', target)` (the R-16.3-24 tuple),
237
+ * internal scene key `metaEdgeSceneKey(source, target)`. `count` (the badge
238
+ * datum) is the collapsed multiplicity and drives the T01 aggregate width
239
+ * channel; `underlying` indexes the PRE-rewrite model's edge list so the
240
+ * stage-5 "any underlying edge passes" mask rule applies unchanged.
241
+ */
242
+ declare function collapseParallelEdges<N, E>(model: AcceptedGraph<N, E>, base: GroupRewrite<N, E> | null): GroupRewrite<N, E> | null;
243
+ /** The RenderScene.groups descriptor for a rewrite. */
244
+ declare function sceneGroupsOf(rewrite: GroupRewrite<unknown, unknown>): SceneGroups;
245
+ /** Discriminated point ref: physical node id or the ResolvedGroup — never an
246
+ * internal scene key. Null when out of range. */
247
+ declare function scenePointRefAt(scene: RenderScene, index: number): ScenePointRef | null;
248
+ /** Discriminated link ref: physical edge id or the MetaEdge record. */
249
+ declare function sceneLinkRefAt(scene: RenderScene, linkIndex: number): SceneLinkRef | null;
250
+
251
+ /**
252
+ * §7 reconciler — id↔index model, structural diff, position cache.
253
+ *
254
+ * v0.1 implements the 'rebuild' index policy only (§7.3): every reconcile
255
+ * rebuilds the flat buffers from scratch in accepted-base order, preserving
256
+ * positions by id via the live/departed caches. Pure data-structure work: no
257
+ * DOM, no engine import.
258
+ */
259
+
260
+ interface ReconcileResult {
261
+ scene: RenderScene;
262
+ /**
263
+ * True when the node id sequence OR the edge endpoint/id sequence differs
264
+ * from the previous reconcile. The first reconcile is always structural.
265
+ */
266
+ structuralChange: boolean;
267
+ /**
268
+ * True when the resolved position buffer differs from the previous live
269
+ * position mirror. NaN pairs compare equal so attr-only updates on
270
+ * unseeded nodes do not spuriously dirty the engine structure.
271
+ */
272
+ positionChange: boolean;
273
+ /** Nodes whose position was restored from the live or departed cache. */
274
+ reusedPositions: number;
275
+ }
276
+ declare class Reconciler {
277
+ private datasetKey;
278
+ private hasScene;
279
+ private prevIdByIndex;
280
+ private prevIndexById;
281
+ private prevEdgeIdByIndex;
282
+ private prevLinks;
283
+ /**
284
+ * Declared node.x/y as of the previous reconcile, for nodes that declared
285
+ * them. Lets Priority 1 distinguish a CHANGED declaration (caller intent —
286
+ * wins over any cache) from an unchanged one (defers to live drift). Fresh
287
+ * per pass, so departed ids drop and a re-add treats its declaration as new.
288
+ */
289
+ private prevDeclared;
290
+ /**
291
+ * CPU position mirror for the CURRENT scene, indexed by slot (§7.1 posBuf).
292
+ * Owned copy — never aliases a published scene's positions array, so
293
+ * noteEnginePositions never mutates an already-published RenderScene.
294
+ */
295
+ private livePositions;
296
+ /**
297
+ * Ids removed from the scene → last known finite position (§7.1/§7.3
298
+ * leave-and-return guarantee). Map insertion order doubles as LRU order;
299
+ * invariant: never overlaps the current scene's id set.
300
+ */
301
+ private readonly departed;
302
+ reconcile<N, E>(accepted: AcceptedGraph<N, E>): ReconcileResult;
303
+ /**
304
+ * Copy engine-read positions into the live cache for the CURRENT scene's
305
+ * slots (§7.1 per-event readback — simulation end, pre-structural-swap).
306
+ * Extra trailing floats are ignored; a short buffer updates a prefix.
307
+ */
308
+ noteEnginePositions(positions: Float32Array): void;
309
+ private resetDatasetState;
310
+ }
311
+
312
+ /**
313
+ * §8 projection (v0.1 subset): styling accessors → engine-ready typed buffers.
314
+ *
315
+ * Pure and DOM-free. Color parsing covers the CSS subset orbit documents
316
+ * (hex, rgb()/rgba(), hsl()/hsla(), small named map); anything else is a
317
+ * caller error surfaced as a batched 'accessor-error' diagnostic, never a
318
+ * throw and never a NaN in a GPU buffer (§8 numeric hygiene).
319
+ */
320
+
321
+ type RGBA = [number, number, number, number];
322
+ /**
323
+ * Pure, DOM-free CSS color parse → RGBA floats in [0,1], or null when the
324
+ * string is not a recognized color. Successful parses are memoized (cache
325
+ * capped at 4096 entries; cleared wholesale on overflow). The returned tuple
326
+ * is shared across calls — treat it as immutable.
327
+ */
328
+ declare function parseColor(css: string): RGBA | null;
329
+ declare function projectColors<T>(items: readonly T[], accessor: Accessor<T, string>, fallback?: [number, number, number, number]): {
330
+ buffer: Float32Array;
331
+ diagnostics: GraphDiagnostic[];
332
+ };
333
+ declare function projectSizes<T>(items: readonly T[], accessor: Accessor<T, number>, fallbackSize?: number): {
334
+ buffer: Float32Array;
335
+ diagnostics: GraphDiagnostic[];
336
+ };
337
+
338
+ /**
339
+ * §16.5 search — the SearchService contract plus the built-in LOCAL indexed
340
+ * service (S11-T06).
341
+ *
342
+ * The default service is client-side and field-scoped: it indexes the node id
343
+ * ALWAYS plus `attrs[field]` (String()-coerced) for each field the host
344
+ * declared via `searchIndex` — it never guesses privileged attr names, so a
345
+ * missing declaration leaves the service id-only. Matching is a
346
+ * case-insensitive SUBSTRING scan over ONE precomputed lowercase haystack per
347
+ * node (id + indexed field values joined with unit separators) — NOT a
348
+ * per-keystroke re-tokenization: the index builds once per model revision
349
+ * (lazily, on the first search that sees the new revision) and every query
350
+ * against that revision reuses it.
351
+ *
352
+ * Scoring: exact-id match {@link SEARCH_SCORE_EXACT_ID}, id-prefix
353
+ * {@link SEARCH_SCORE_ID_PREFIX}, field/id substring
354
+ * {@link SEARCH_SCORE_SUBSTRING} (+{@link SEARCH_SCORE_TOKEN_START_BONUS}
355
+ * when the match starts a token). Results sort score-desc then accepted-base
356
+ * order; `label` is the first matching indexed field's ORIGINAL value (the id
357
+ * when only the id matched).
358
+ *
359
+ * `ctx.signal` is honored between scan chunks (an awaited microtask every
360
+ * {@link SEARCH_SCAN_CHUNK} nodes) — abort is an optimization; the instance's
361
+ * admission gate is the correctness gate (§9.2). Nothing here touches the
362
+ * engine, the store, or the DOM.
363
+ */
364
+
365
+ /** §16.5 search resolver: custom services plug in server-side search
366
+ * (Omnigraph B.7); the instance owns RequestContext creation, revision-keyed
367
+ * caching, supersede cancellation, and stale-result rejection at admission. */
368
+ interface SearchService<N = Record<string, unknown>> extends RevisionAwareService {
369
+ search(q: string, options: {
370
+ limit: number;
371
+ }, ctx: RequestContext): Promise<readonly SearchResult<N>[]>;
372
+ }
373
+ /** Accepted-base view the local service indexes (thunked for lazy wiring —
374
+ * the instance re-reads it on every call, so the service can be constructed
375
+ * before any data arrives). */
376
+ interface LocalSearchBase<N = Record<string, unknown>> {
377
+ /** Accepted-model nodes in accepted-base order (the tie-break order). */
378
+ nodes: readonly GraphNode<N>[];
379
+ /** §16.5 declared attr fields; undefined = id-only search (the service
380
+ * never guesses attr names). */
381
+ searchIndex: readonly string[] | undefined;
382
+ }
383
+ /** The built-in service plus its test seam: how many times the index was
384
+ * (re)built — pins "one build per model revision, not one per keystroke". */
385
+ interface LocalSearchService<N = Record<string, unknown>> extends SearchService<N> {
386
+ readonly buildCount: number;
387
+ }
388
+ declare const SEARCH_SCORE_EXACT_ID = 3;
389
+ declare const SEARCH_SCORE_ID_PREFIX = 2;
390
+ declare const SEARCH_SCORE_SUBSTRING = 1;
391
+ declare const SEARCH_SCORE_TOKEN_START_BONUS = 0.25;
392
+ /** Nodes scanned between cooperative yields (awaited microtask + signal check). */
393
+ declare const SEARCH_SCAN_CHUNK = 4096;
394
+ /**
395
+ * Creates the §16.5 default indexed search service over the accepted model.
396
+ * Declares `revisionDependencies: ['source', 'model']` — the index keys on
397
+ * `ctx.modelRevision` (plus the declared field list), so it builds at most
398
+ * once per model revision and a stale-model result is discarded by the
399
+ * instance's admission gate. `getBase` is a thunk re-read on every call so
400
+ * the instance can wire the service before data arrives.
401
+ */
402
+ declare function createLocalSearchService<N = Record<string, unknown>>(getBase: () => LocalSearchBase<N>): LocalSearchService<N>;
403
+
404
+ /**
405
+ * §8 image-atlas pipeline (S10-T09) — pure core side.
406
+ *
407
+ * Turns per-point image refs (stable strings from the synchronous `nodeImage`
408
+ * accessor) into engine-ready atlas resources: resolved ImageBitmaps with
409
+ * slot assignments plus an index-aligned per-point slot buffer. The engine
410
+ * only ever sees the output shape (`EngineCommit.resources`); everything
411
+ * async — resolve, fetch, decode, retry, abort — stays here.
412
+ *
413
+ * Invariants (spec §8):
414
+ * - Loads are deduplicated by ref: one in-flight resolve per unique ref.
415
+ * - Slots are allocated per unique ref from a free-list capped at
416
+ * `maxEntries`; over-cap refs stay placeholder and are diagnosed once per
417
+ * generation.
418
+ * - Results are admission-checked against the current request state before
419
+ * atlas admission; the abort signal is an optimization, admission is the
420
+ * gate (a resolver that ignores its signal cannot poison a newer
421
+ * generation).
422
+ * - Transient resolver/fetch failures retry up to `maxRetries`; decode
423
+ * failures are final.
424
+ * - Failures are cadence-batched: ONE `image-resolve-failed` diagnostic per
425
+ * flush with a count and sampled refs, each ref counted once per
426
+ * generation. Failures never poison the engine commit.
427
+ * - A resolver-returned string is a final URL/data URI fetched + decoded
428
+ * exactly once — never recursively passed back to the resolver.
429
+ */
430
+
431
+ /**
432
+ * Owns authenticated fetch / caching / redirect handling for one ref
433
+ * (spec §8, Appendix B.6). Returning a Blob hands the bytes straight to
434
+ * decode; returning a string names a FINAL URL or data URI that the pipeline
435
+ * fetches + decodes once (it is never re-resolved).
436
+ */
437
+ type ImageResolver = (ref: string, signal: AbortSignal) => Promise<Blob | string>;
438
+ /** Minimal structural slice of `fetch` the pipeline needs (Node-test injectable). */
439
+ type FetchLike = (url: string, init: {
440
+ signal: AbortSignal;
441
+ }) => Promise<{
442
+ ok: boolean;
443
+ status: number;
444
+ blob(): Promise<Blob>;
445
+ }>;
446
+ /** Blob → ImageBitmap. Injectable so Node tests never touch createImageBitmap. */
447
+ type ImageDecode = (blob: Blob) => Promise<ImageBitmap>;
448
+ /** One coalesced flush of atlas work — mirrors `EngineCommit.resources` (§13). */
449
+ interface ImageAtlasBatch {
450
+ /** Newly resolved bitmaps with their slot assignments. */
451
+ upserts: readonly {
452
+ slot: number;
453
+ bitmap: ImageBitmap;
454
+ }[];
455
+ /** Slots freed by eviction that the engine previously received. */
456
+ removeSlots: readonly number[];
457
+ /** Per-point atlas slot indices, aligned to the last requestRefs call
458
+ * (-1 = placeholder shape). */
459
+ pointImageIndex: Float32Array;
460
+ /** At most one batched `image-resolve-failed` diagnostic per flush. */
461
+ diagnostics: readonly GraphDiagnostic[];
462
+ }
463
+ interface ImageAtlasPipelineOptions {
464
+ /** Ref → bytes. Default: plain `fetch(ref)` → blob for public URLs. */
465
+ resolver?: ImageResolver;
466
+ /** Concurrent resolve bound. Default 4. */
467
+ maxConcurrent?: number;
468
+ /** Transient-failure retries per ref lifecycle (decode failures are final).
469
+ * Default 2 (three attempts total). */
470
+ maxRetries?: number;
471
+ /** Atlas slot capacity; refs beyond it stay placeholder. Default 512. */
472
+ maxEntries?: number;
473
+ /** Blob → ImageBitmap. Default `createImageBitmap`. */
474
+ decode?: ImageDecode;
475
+ /** Fetch used by the default resolver AND for resolver-returned strings. */
476
+ fetchImpl?: FetchLike;
477
+ /** Batch scheduler: called with a flush thunk when work is pending; each
478
+ * scheduled thunk must run at most once. Default: queueMicrotask. */
479
+ schedule?: (flush: () => void) => void;
480
+ }
481
+ declare const ATLAS_MAX_CONCURRENT_DEFAULT = 4;
482
+ declare const ATLAS_MAX_RETRIES_DEFAULT = 2;
483
+ declare const ATLAS_MAX_ENTRIES_DEFAULT = 512;
484
+ declare class ImageAtlasPipeline {
485
+ private readonly resolver;
486
+ private readonly maxConcurrent;
487
+ private readonly maxRetries;
488
+ private readonly maxEntries;
489
+ private readonly decode;
490
+ private readonly fetchImpl;
491
+ private readonly schedule;
492
+ /** ref → live entry. Identity of the mapped entry is the admission gate. */
493
+ private readonly entries;
494
+ private readonly freeSlots;
495
+ private nextSlot;
496
+ private readonly queue;
497
+ private active;
498
+ /** Slots the engine has received via a flushed upsert. */
499
+ private readonly deliveredSlots;
500
+ private readonly pendingUpserts;
501
+ private readonly pendingRemoveSlots;
502
+ /** Failed refs awaiting the next flush's single batched diagnostic. */
503
+ private pendingFailureRefs;
504
+ /** ref → generation it was last counted in (once-per-generation gate). */
505
+ private readonly countedFailures;
506
+ private lastRefs;
507
+ private generation;
508
+ private flushScheduled;
509
+ private disposed;
510
+ /** D5/F11-06: evicted-entry bitmaps awaiting close — closed AFTER the
511
+ * flush that carries their removeSlots, so the instance's recovery-replay
512
+ * map (pruned synchronously in the batch callback) can never re-send a
513
+ * closed bitmap. */
514
+ private pendingCloseBitmaps;
515
+ private readonly batchCallbacks;
516
+ constructor(options?: ImageAtlasPipelineOptions);
517
+ /** Subscribe to coalesced flushes. Returns an unsubscribe thunk. */
518
+ onBatch(cb: (batch: ImageAtlasBatch) => void): () => void;
519
+ /**
520
+ * Declare the full index-aligned per-point ref list for `generation`.
521
+ * Refs that drop out are evicted (slot freed → removeSlots); new unique
522
+ * refs are resolved once each; calls with a stale (lower) generation are
523
+ * discarded.
524
+ */
525
+ requestRefs(refs: readonly (string | null)[], generation: number): void;
526
+ /** Abort all in-flight work and drop all state. Terminal. */
527
+ dispose(): void;
528
+ private allocSlot;
529
+ private evict;
530
+ /** Admission gate: entry must still be the live entry for its ref. */
531
+ private isCurrent;
532
+ private pump;
533
+ /** One attempt: resolve → (fetch if string) → decode. Never throws. */
534
+ private runAttempt;
535
+ private fetchBlob;
536
+ private finalFailure;
537
+ /** Queue a ref for the next flush's single diagnostic, once per generation. */
538
+ private recordFailure;
539
+ private scheduleFlush;
540
+ private flush;
541
+ /** -1 (placeholder) until the ref's bitmap is delivered or in this batch. */
542
+ private buildPointIndex;
543
+ /**
544
+ * I2 (roster-atomic resource mappings, F11-03): the SYNCHRONOUS point→slot
545
+ * mapping for the last requested roster — a slot appears only when its ref
546
+ * is resolved AND the engine has already received the bitmap (delivered);
547
+ * pending, failed, evicted, and reused-but-undelivered refs are the −1
548
+ * placeholder. The instance pairs every STRUCTURAL commit with this index
549
+ * in the same commit, so the engine never renders a new roster against the
550
+ * previous roster's (wrong-length, stale-slot) mapping; the scheduled
551
+ * async flush then only ever PROMOTES placeholders to resolved slots.
552
+ */
553
+ currentPointIndex(): Float32Array | null;
554
+ }
555
+
556
+ /**
557
+ * §16.14 view state (S15 view-state lane) — pure module: the serialized
558
+ * schema, the canonical-JSON encoder, the structural validator, and the
559
+ * version gate. No engine, no DOM, no store access; the instance wires
560
+ * `getViewState`/`setViewState` on top.
561
+ *
562
+ * ## Compatibility contract
563
+ *
564
+ * Deep-links outlive library versions, so everything in this file is a
565
+ * COMMITMENT, not an internal:
566
+ * - The v1 wire shape below never changes incompatibly; breaking shape
567
+ * changes bump `v` and ship an in-code migration from every prior version
568
+ * in the same release (the registry seam is here from day one).
569
+ * - Additive fields never bump `v` — an equal-`v` payload with unknown
570
+ * fields applies cleanly with the unknowns ignored.
571
+ * - A payload with `v` HIGHER than this library knows, or failing structural
572
+ * validation (hand-edited URL, truncated paste), is rejected whole: a
573
+ * half-restored view misrepresents what the sender saw (§16.14).
574
+ *
575
+ * ## v1 shape notes
576
+ *
577
+ * - `folds` extends the spec's v1 schema (amendment logged with this slice):
578
+ * §16.3 node folds are exploration state over stable public ids — a
579
+ * deep-link that silently dropped them would misrepresent the sender's
580
+ * view, the exact failure this feature exists to prevent. Session-local
581
+ * expansion records, by contrast, are NEVER serialized (R-16.3-08).
582
+ * - Brushes are TAGGED on the wire (`kind`) even though the runtime
583
+ * BrushState is untagged: the tag comes from `DimensionSpec.kind` at
584
+ * serialize time and lets restore validate shape-vs-dimension without
585
+ * consulting the live spec first. Categorical stores EXCLUSIONS, so
586
+ * categories that appear after the view was saved stay visible.
587
+ * - `layout` is the normalized OBJECT form (`{ kind }`) even though the
588
+ * runtime today carries only the bare kind string — the object is what
589
+ * makes a future static seed or a layouts package additive.
590
+ */
591
+
592
+ /** Tagged wire form of a crossfilter brush (§16.6 → §16.14). */
593
+ type ViewBrushState = {
594
+ kind: 'numeric' | 'temporal';
595
+ range: readonly [number, number];
596
+ } | {
597
+ kind: 'categorical';
598
+ excluded: readonly string[];
599
+ };
600
+ /** Normalized layout descriptor — object form reserves room for a static
601
+ * seed / structured layouts without a `v` bump. */
602
+ interface ViewLayoutSpec {
603
+ kind: 'force' | 'fixed';
604
+ }
605
+ /** §11 Scale subset that is data by construction (no functions). */
606
+ type SerializableScale = {
607
+ kind: 'sequential' | 'diverging';
608
+ metric: string;
609
+ range: readonly string[] | readonly number[];
610
+ domain?: readonly number[];
611
+ mid?: number;
612
+ } | {
613
+ kind: 'categorical';
614
+ /** Field-descriptor form only — a function `by` is omitted upstream. */
615
+ by: string;
616
+ palette?: readonly string[] | readonly number[];
617
+ domain?: readonly string[];
618
+ };
619
+ interface ViewStyling {
620
+ nodeColor?: SerializableScale;
621
+ nodeSize?: SerializableScale;
622
+ showLinks?: boolean;
623
+ edgeArrows?: boolean;
624
+ /** Named themes only; a custom GraphTheme object is omitted upstream. */
625
+ theme?: 'light' | 'dark';
626
+ }
627
+ interface GraphViewState {
628
+ v: 1;
629
+ camera: ViewportState | null;
630
+ selection: SelectionState;
631
+ hiddenNodeIds: readonly NodeId[];
632
+ /** §9.2 isolation; null = full scope. */
633
+ subgraph: SubgraphSpec | null;
634
+ /** Manual groups verbatim, or — under `groupBy` — `{key, collapsed}` pairs
635
+ * only (membership recomputes from current data on restore, R-16.3-16). */
636
+ groups: readonly GroupSpec[] | ReadonlyArray<{
637
+ key: string;
638
+ collapsed: boolean;
639
+ }>;
640
+ pinnedNodeIds: readonly NodeId[];
641
+ /** §16.3 node folds: anchor → declared members (v1 extension, see header). */
642
+ folds?: ReadonlyArray<readonly [NodeId, readonly NodeId[]]>;
643
+ layout: ViewLayoutSpec;
644
+ /** Declaration order; a key absent here has no brush. */
645
+ crossfilter: ReadonlyArray<{
646
+ key: string;
647
+ state: ViewBrushState;
648
+ }>;
649
+ /** Opt-in frozen layout (quantized, visible set); tuple form keeps opaque
650
+ * ids safe. Restores as a fixed-equivalent regardless of engine
651
+ * nondeterminism. */
652
+ positions?: ReadonlyArray<readonly [string, number, number]>;
653
+ styling?: ViewStyling;
654
+ /** Host-owned durable source coordinate — stored verbatim, NEVER
655
+ * interpreted; compared canonically on restore (§16.14). */
656
+ dataRef?: JsonValue;
657
+ }
658
+ declare const VIEW_STATE_VERSION: 1;
659
+ /**
660
+ * Canonical encoding of a JSON value: object keys recursively sorted, arrays
661
+ * order-preserving, JSON semantics for scalars (so `undefined` members are
662
+ * dropped exactly as JSON.stringify drops them). Two values compare equal iff
663
+ * their canonical strings are identical.
664
+ */
665
+ declare function canonicalJson(value: JsonValue | undefined): string | undefined;
666
+ /** Canonical equality for dataRef values (§16.14): key order never matters.
667
+ * A non-JSON value (cycles included) is never equal to anything — the §5
668
+ * "compared, never interpreted" rule extended to malformed input. */
669
+ declare function sameDataRef(a: JsonValue | undefined, b: JsonValue | undefined): boolean;
670
+ type ViewStateVerdict = {
671
+ ok: true;
672
+ state: GraphViewState;
673
+ } | {
674
+ ok: false;
675
+ code: 'invalid-view-state' | 'unsupported-version';
676
+ problems: readonly string[];
677
+ };
678
+ /**
679
+ * Full structural validation + version gate. Order per §16.14: version first
680
+ * (higher-than-known and non-numeric reject as `unsupported-version` /
681
+ * `invalid-view-state` BEFORE field checks), then per-field structure. Lower
682
+ * versions run the migration registry, then re-validate at the current shape.
683
+ */
684
+ declare function validateViewState(raw: unknown): ViewStateVerdict;
685
+ type SetViewStateResult = {
686
+ status: 'applied';
687
+ }
688
+ /** dataRef differed: `viewStateMismatch` fired INSTEAD of applying.
689
+ * Re-call with `ignoreMismatch: true` to opt in. */
690
+ | {
691
+ status: 'mismatch';
692
+ } | {
693
+ status: 'rejected';
694
+ code: 'invalid-view-state' | 'unsupported-version'
695
+ /** The state touches a §6.4 controlled slice or carries styling the
696
+ * host must reflect, and no aggregate restore callback exists. */
697
+ | 'missing-restore-callback'
698
+ /** Another restore/history transaction is awaiting acknowledgement. */
699
+ | 'restore-pending'
700
+ /** The host never reflected the intent within the window. */
701
+ | 'restore-timeout'
702
+ /** The host reflected DIFFERENT values than the intent asked for. */
703
+ | 'restore-diverged';
704
+ problems: readonly string[];
705
+ };
706
+
707
+ /**
708
+ * §6 GraphInstance — the public headless core instance (v0.3 subset).
709
+ *
710
+ * One `applyHostUpdate` call is the atomic host boundary: it validates,
711
+ * reconciles, re-projects only dirty channels, and publishes EXACTLY ONE store
712
+ * `set()` and AT MOST ONE engine commit, so a simultaneous data + style +
713
+ * controlled-state change can never tear across frames (§6).
714
+ *
715
+ * Vanilla zustand only — no React, no DOM access at module scope (§18).
716
+ */
717
+
718
+ /** §8 dark base theme (the default when no base is named). */
719
+ declare const GRAPH_THEME_DARK: GraphTheme;
720
+ /** §8 light base theme. */
721
+ declare const GRAPH_THEME_LIGHT: GraphTheme;
722
+ /**
723
+ * Resolve a ThemeInput to a full GraphTheme (§8): pick the named base
724
+ * (default dark), then merge every defined token over it. A full GraphTheme
725
+ * input resolves to exactly its own tokens; `undefined` resolves to the dark
726
+ * base; the v0.1 `{background}` compat shorthand merges as a partial.
727
+ */
728
+ declare function resolveTheme(input?: ThemeInput): GraphTheme;
729
+ /** One categorical legend row (§11): declared-domain rows first (including
730
+ * currently-empty categories), then extra seen values sorted. */
731
+ interface ScaleInfoRow {
732
+ value: string;
733
+ /** TOTAL occurrences over the accepted model (v0.8 tier: not mask-aware). */
734
+ count: number;
735
+ /** Palette slot from `categoricalIndex` (-1 = empty palette). */
736
+ colorIndex: number;
737
+ }
738
+ /** `getScaleInfo` payload: the active Scale descriptor plus its resolved
739
+ * numeric domain (sequential/diverging) or legend rows (categorical). */
740
+ interface ScaleChannelInfo<N = Record<string, unknown>> {
741
+ scale: Scale<string, N> | Scale<number, N>;
742
+ /** Resolved numeric domain; omitted while unresolvable (no data/metric). */
743
+ domain?: readonly [number, number];
744
+ /** Categorical rows; omitted for sequential/diverging scales. */
745
+ rows?: readonly ScaleInfoRow[];
746
+ }
747
+ /** §9.2 revision-aware service seam (expansion since v0.5, search since S11). */
748
+ interface GraphServices<N = Record<string, unknown>, E = Record<string, unknown>> {
749
+ /**
750
+ * Ego-expansion resolver for `expandNode` / `SubgraphSpec.hops`. Default:
751
+ * the built-in LOCAL service — it walks the core's §7.1 adjacency over the
752
+ * accepted model, INCLUDING currently out-of-scope nodes (zero config,
753
+ * zero network; the core still never fetches).
754
+ */
755
+ expansion?: ExpansionService<N, E>;
756
+ /**
757
+ * §16.2 path resolver for `findPath`. Default: the built-in LOCAL
758
+ * unweighted BFS over the loaded VISIBLE edge list, respecting
759
+ * PathOptions.direction. Revision-aware: a result arriving after a
760
+ * dataset replacement is discarded at admission (S12-T08).
761
+ */
762
+ path?: PathService;
763
+ /**
764
+ * §16.5 search resolver for `instance.search`. Default: the built-in LOCAL
765
+ * indexed service over the accepted model plus the host's declared
766
+ * `searchIndex` fields (id-only when never declared — it never guesses
767
+ * attr names; zero config, zero network). Custom services plug in
768
+ * server-side search (Omnigraph B.7) with the same instance-side
769
+ * correctness: RequestContext, revision-keyed caching, supersede
770
+ * cancellation, stale rejection at admission.
771
+ */
772
+ search?: SearchService<N>;
773
+ }
774
+ interface CreateGraphInstanceOptions<N = Record<string, unknown>, E = Record<string, unknown>> {
775
+ /** Called once per mount; a re-attach constructs a fresh engine (§6). */
776
+ engine: EngineFactory;
777
+ /** Fit the camera once when the first data-bearing commit reaches a fresh engine. Default true. */
778
+ fitViewOnFirstData?: boolean;
779
+ /** §9.2 revision-aware services (expansion + §16.5 search). */
780
+ services?: GraphServices<N, E>;
781
+ /**
782
+ * §16.5 node attr fields the DEFAULT search service indexes (ids always;
783
+ * absent = id-only — the service never guesses attr names). CONSTRUCTION-
784
+ * ONLY per the spec's host construction options (D7): read once here;
785
+ * changing it requires a keyed remount / replacement instance. A runtime
786
+ * `applyHostUpdate` attempt is ignored with a one-shot warning.
787
+ */
788
+ searchIndex?: readonly string[];
789
+ /**
790
+ * §16.14 undo/redo (S9-T20). Default true. `false` makes the history
791
+ * surface inert (record/undo/redo no-ops, depths stay 0); an object sets
792
+ * the stack bound (default {@link HISTORY_LIMIT_DEFAULT} entries).
793
+ */
794
+ history?: boolean | {
795
+ limit?: number;
796
+ };
797
+ /**
798
+ * §8 image-atlas resolver seam (S10): owns authenticated fetch/caching for
799
+ * one `nodeImage` ref. Default: plain `fetch(ref)` for public URLs.
800
+ * Injectable for tests and authenticated hosts.
801
+ */
802
+ imageResolver?: ImageResolver;
803
+ }
804
+ /**
805
+ * `expandNode` outcome (§9.2/§16.3):
806
+ * - `{ added }` — the admitted result merged; `added` counts the nodes it
807
+ * made newly visible in the current scope.
808
+ * - `{ noop: true }` — every returned neighbor was already visible in the
809
+ * current scope (T17); no session was opened, nothing changed.
810
+ * - `{ coalesced: true }` — reserved. v0.5 same-id coalescing hands the
811
+ * SECOND caller the IDENTICAL in-flight promise, so both callers observe
812
+ * the primary call's `{added}`/`{noop}` result instead of this marker.
813
+ */
814
+ type ExpandNodeResult = {
815
+ added: number;
816
+ } | {
817
+ coalesced: true;
818
+ } | {
819
+ noop: true;
820
+ };
821
+ /**
822
+ * §9.2 expansion bookkeeping: one committed expansion overlay. Data-merging
823
+ * batches carry the request id (overlayId + batch ids) and provenance into
824
+ * ingestion so abort, rollback, and removeOverlay remove the exact
825
+ * contribution they own.
826
+ */
827
+ interface ExpansionOverlayRecord {
828
+ overlayId: string;
829
+ requestId: string;
830
+ /** Node ids this expansion revealed into the visible scope. */
831
+ revealedIds: readonly NodeId[];
832
+ /** Service-supplied provenance (single-response or stream header). */
833
+ provenance?: unknown;
834
+ }
835
+ /**
836
+ * §14 overlay label lane subscriptions. Two channels with distinct cadences:
837
+ * - `subscribeCandidates` fires ONLY when the candidate SET (ids/text/forced)
838
+ * changes — the throttled re-rank. React re-renders label content here.
839
+ * - `subscribePositions` fires on scheduler ticks (host `onFrame`) with fresh
840
+ * x/y for the SAME set — imperative transform writes, NO React re-render.
841
+ * Both replay the current state synchronously on subscribe. The emitted array
842
+ * and its placement objects are REUSED across position ticks — copy if you
843
+ * need a snapshot.
844
+ */
845
+ interface LabelSubscriptions {
846
+ subscribeCandidates(cb: (list: readonly LabelPlacement[]) => void): () => void;
847
+ subscribePositions(cb: (list: readonly LabelPlacement[]) => void): () => void;
848
+ }
849
+ interface GraphInstance<N = Record<string, unknown>, E = Record<string, unknown>> {
850
+ /** Vanilla zustand store — the single observable state surface (§6.3). */
851
+ readonly store: StoreApi<GraphStoreState>;
852
+ /** §14 DOM label lane (overlay scheduler output). */
853
+ readonly labels: LabelSubscriptions;
854
+ /** Atomic host transaction: one store publication, at most one engine commit (§6). */
855
+ applyHostUpdate(update: GraphHostUpdate<N, E>): void;
856
+ /**
857
+ * §7.5 revisioned ingestion: begin a bounded, cancellable session against an
858
+ * explicit `datasetKey` and `baseModelRevision` (compare-and-set; mismatch
859
+ * throws 'stale-revision'). Overlay sessions must name the CURRENT
860
+ * datasetKey; replace sessions may establish a new one and are always
861
+ * atomic.
862
+ *
863
+ * T16 rule: while a declarative data source is actively driving (a snapshot
864
+ * was applied through `applyHostUpdate` and has not been superseded by a
865
+ * committed replace session), `purpose:'replace'` is rejected at begin with
866
+ * a TypeError — two writers may not race for the base. Overlay ingestion
867
+ * stays allowed alongside a declarative base.
868
+ *
869
+ * Every session admission/publication is serialized through the
870
+ * instance-local acceptance queue; arrival there is the global admission
871
+ * order (§7.5).
872
+ */
873
+ beginIngest(opts: BeginIngestOptions): IngestSession<N, E>;
874
+ /**
875
+ * §7.5: atomically remove exactly one committed overlay — re-runs collision
876
+ * and endpoint resolution, promotes formerly shadowed rows from surviving
877
+ * overlays, advances model/render revisions, and releases the overlayId for
878
+ * deliberate reuse. Unknown ids are an idempotent `{ removed: false }`.
879
+ */
880
+ removeOverlay(overlayId: string): {
881
+ removed: boolean;
882
+ };
883
+ /** Committed overlay ids for the current dataset (§7.5). */
884
+ getOverlayIds(): readonly string[];
885
+ attach(container: HTMLElement): Promise<void>;
886
+ detach(): void;
887
+ destroy(): void;
888
+ /**
889
+ * §15 typed events: listeners run SYNCHRONOUSLY in registration order; the
890
+ * control's preventDefault() cancels ONLY the built-in follow-up (click
891
+ * selection, drag pin), never other listeners.
892
+ */
893
+ on<K extends GraphEventName>(name: K, cb: (payload: GraphEventMap<N, E>[K], control: GraphListenerControl) => void): () => void;
894
+ fitView(): void;
895
+ zoomIn(): void;
896
+ zoomOut(): void;
897
+ setViewport(v: Partial<ViewportState>): void;
898
+ /**
899
+ * §16 focus neighborhood (T10): keep the v0.1 camera behavior
900
+ * (setFocusedIndex + zoomToIndex) and RETURN the 1-hop neighbor ids
901
+ * (engine adjacency when available, else the core CSR adjacency).
902
+ *
903
+ * Documented compromise: the engine exposes ONE highlight channel until S10
904
+ * styling lands, so the neighbor ring is pushed through setSelectedIndices
905
+ * ONLY when that cannot lie about real selection state — selection empty
906
+ * and uncontrolled. The ring is a visual, never a store write; the next
907
+ * selection push overwrites it. Opt out via `highlightNeighbors: false`.
908
+ * `hops` is reserved at 1 until multi-hop lands.
909
+ */
910
+ focusNode(id: NodeId, opts?: {
911
+ highlightNeighbors?: boolean;
912
+ hops?: 1;
913
+ }): readonly NodeId[];
914
+ /**
915
+ * §14/§15 typed context-menu channel, opened from a DOM presenter. Label
916
+ * divs are `pointerEvents: 'auto'` by design (click-to-focus), so a
917
+ * right-click on one never reaches the engine canvas — without this seam
918
+ * the nodes prominent enough to carry labels are exactly the ones whose
919
+ * right-click falls through to the browser's native menu. Emits the SAME
920
+ * 'contextMenu' event the canvas gesture produces; `screen` is
921
+ * container-relative CSS px (the §15 payload contract). Unknown ids are a
922
+ * silent no-op (a stale label racing a model swap is data, not an error).
923
+ */
924
+ requestNodeContextMenu(id: NodeId, screen: readonly [number, number]): void;
925
+ setSelection(ids: readonly NodeId[] | SelectionState): void;
926
+ selectNodes(ids: readonly NodeId[]): void;
927
+ selectEdges(ids: readonly EdgeId[]): void;
928
+ /** §16.2 group namespace (S12-T02): validate against the CURRENT resolved
929
+ * groups (unknown ids dropped, duplicates collapse) and store in
930
+ * groups-array order — the group analog of accepted-base ordering. Never
931
+ * touches the node/edge namespaces; the group namespace is always
932
+ * instance-owned (§6.4 controlled selection covers nodes only). */
933
+ selectGroups(ids: readonly string[]): void;
934
+ /** Expand to the 1-hop neighborhood of `id` (or of the current selection). */
935
+ selectNeighbors(id?: NodeId): void;
936
+ selectAll(): void;
937
+ invertSelection(): void;
938
+ clearSelection(): void;
939
+ /**
940
+ * §16.2 lasso (S6-T05): resolve the SCREEN-coordinate polygon to node ids
941
+ * via `engine.pointsInPolygon`, drop hidden ids, then replace (default) or
942
+ * union (`additive`) the node selection through the same §6.4 ownership
943
+ * path as every other mutator (controlled → intent only). Returns the
944
+ * resolved lasso ids (accepted-base order) regardless of ownership; empty
945
+ * when the engine is not ready or lacks `pointsInPolygon`.
946
+ */
947
+ selectWithinPolygon(screenPolygon: readonly [number, number][], opts?: {
948
+ additive?: boolean;
949
+ }): readonly NodeId[];
950
+ /** Pure fallback-route query: the accepted edge within pick tolerance of a
951
+ * SCREEN point. Null on the native route, while picking is disarmed (sim
952
+ * hot), or when nothing is in range. */
953
+ pickEdgeAt(screen: readonly [number, number]): AcceptedEdge<E> | null;
954
+ /** Fallback-route hover sample: writes `hover.edgeId` and emits 'edgeHover'
955
+ * on transitions (identical payloads to the native route). No-op (null) on
956
+ * the native route. */
957
+ sampleEdgeHover(screen: readonly [number, number]): AcceptedEdge<E> | null;
958
+ /** Fallback-route click resolution: emits 'edgeClick' when a link is within
959
+ * tolerance. No-op (null) on the native route. */
960
+ sampleEdgeClick(screen: readonly [number, number]): AcceptedEdge<E> | null;
961
+ hideNodes(ids: readonly NodeId[]): void;
962
+ showNodes(ids: readonly NodeId[]): void;
963
+ showAll(): void;
964
+ pinNode(id: NodeId, xy?: readonly [number, number]): void;
965
+ unpinNode(id: NodeId): void;
966
+ clearPins(): void;
967
+ /** §16.3 PERSISTENT pins (S12-T09): pin ids AT THEIR CURRENT POSITION via
968
+ * engine.setPinnedIndices — no position payload in v0.10. Independent of
969
+ * transient drag pinning (`pins`): the engine receives the UNION of both
970
+ * slices, so releasing a drag pin leaves a persistent pin held. §6.4
971
+ * ownership mirrors groups: once the host supplies `pinnedNodeIds` (null
972
+ * included) the ops fire the 'pinnedChange' intent instead of writing.
973
+ * Unknown ids drop; departed ids prune through the ownership path on
974
+ * model changes (R-16.3-25). */
975
+ pinNodes(ids: readonly NodeId[]): void;
976
+ /** Release persistent pins (see {@link pinNodes}); unpinned ids no-op. */
977
+ unpinNodes(ids: readonly NodeId[]): void;
978
+ /** Add one group definition (same §16.3 acyclic/singly-parented validation as the
979
+ * `groups` prop — a violating spec is ONE 'config-error' and a no-op). */
980
+ groupNodes(spec: GroupSpec): void;
981
+ /** Remove one group definition; its id prunes from SelectionState.groupIds
982
+ * through the ownership path. Unknown ids no-op (dev-mode warning). */
983
+ ungroup(groupId: string): void;
984
+ /** Collapse/expand one group as a §7.2 structural diff. Works on manual
985
+ * groups AND groupBy-derived groups (the residue toggle). Same-value
986
+ * calls are exact no-ops (zero publishes, zero commits). */
987
+ setGroupCollapsed(groupId: string, collapsed: boolean): void;
988
+ /**
989
+ * Folds `id`'s neighbourhood into `id`. Members default to the anchor's
990
+ * neighbours in the CURRENT render model that no representative has
991
+ * claimed yet — first fold wins, so folding two adjacent hubs never fights
992
+ * over a shared neighbour and never needs a leaf-only restriction. Pass
993
+ * `memberIds` to fold an explicit set instead (unknown ids and ids already
994
+ * claimed elsewhere drop; an id that is an ANCESTOR of the anchor is
995
+ * rejected, since that would close a containment cycle).
996
+ *
997
+ * One publish and at most one structural commit (E1). A no-member fold is
998
+ * an exact no-op. Records a §16.14 'folds' history step.
999
+ */
1000
+ foldNode(id: NodeId, opts?: {
1001
+ memberIds?: readonly NodeId[];
1002
+ }): void;
1003
+ /** Unfolds `id`, returning its members to the scene as a §7.2 structural
1004
+ * diff. Unknown or unfolded ids are exact no-ops. */
1005
+ unfoldNode(id: NodeId): void;
1006
+ /** The members `id` currently stands for, or null when it is not folded.
1007
+ * Membership is the DECLARED set — members that have since left the model
1008
+ * are reported but simply do not draw. */
1009
+ getFold(id: NodeId): {
1010
+ memberIds: readonly NodeId[];
1011
+ } | null;
1012
+ /** Current stage-4 clusters over the physical scene: ordered keys, member
1013
+ * ids, the force center labels anchor to while hot, and the settled
1014
+ * centroid (null until a §7.1 readback or a fixed-layout commit). Empty
1015
+ * when no `clusters` spec is active. Clusters synthesize nothing — the
1016
+ * scene is byte-identical with and without a spec (R-16.3-17/19). */
1017
+ getClusters(): readonly ResolvedCluster[];
1018
+ /** R-16.3-18: resolve a cluster (by key) to its MEMBER node ids and write
1019
+ * them into SelectionState.nodeIds through the standard §6.4 ownership
1020
+ * path — clusters have no id namespace of their own in selection.
1021
+ * `additive` unions with the current node selection. Unknown keys no-op. */
1022
+ selectCluster(key: string, opts?: {
1023
+ additive?: boolean;
1024
+ }): void;
1025
+ /**
1026
+ * §9.2 isolate: hard-scope the graph to the CURRENT node selection —
1027
+ * `subgraph: { seedIds: selection.nodeIds }` through the SAME path as the
1028
+ * host-update prop. No-op when nothing is selected. Ownership note (v0.5):
1029
+ * `subgraph` is UNCONTROLLED-ONLY — always instance-owned; the prop and
1030
+ * this method write the same state, last writer wins.
1031
+ */
1032
+ isolateSelection(): void;
1033
+ /** §9.2: clear the hard scope (`subgraph: null`) — the full accepted model
1034
+ * returns with cached positions (§9.4-style identity for kept ids). */
1035
+ resetIsolation(): void;
1036
+ /**
1037
+ * §9.2/§16.3 ego-expansion of `id` (default 1 hop) through the configured
1038
+ * ExpansionService. The result is gated by admission (declared revision
1039
+ * dependencies + dataset lineage + seed existence — abort is only an
1040
+ * optimization) and merges through ONE awaited atomic overlay
1041
+ * IngestSession carrying the request id; a discard/rejection leaves the
1042
+ * graph untouched ('service-aborted' info / 'service-error' error
1043
+ * diagnostic; the promise rejects, the 'error' event never fires). Within
1044
+ * one valid scope revision a second same-id call while one is in flight
1045
+ * returns the IDENTICAL promise (one service call serves both); distinct
1046
+ * ids run concurrently. Under an active hard scope, revealed neighbors
1047
+ * join the resolved scope (accretion) in the same commit.
1048
+ */
1049
+ expandNode(id: NodeId, opts?: {
1050
+ hops?: number;
1051
+ }): Promise<ExpandNodeResult>;
1052
+ /**
1053
+ * Undoes `id`'s own expansions — the navigation Back button, NOT a
1054
+ * containment operation. Aborts `id`'s pending expansion AND explicitly
1055
+ * removes the overlays its past expansions committed (plus their scope
1056
+ * accretion). Committed overlay DATA otherwise persists per §7.5 until
1057
+ * removeOverlay / a replacing snapshot; this IS that explicit removal for
1058
+ * expansion overlays.
1059
+ *
1060
+ * On a node that was never expanded from, this does nothing — there is no
1061
+ * record to pop. To hide a node's neighbourhood behind it on a freshly
1062
+ * loaded graph, that is {@link foldNode}: one word for containment, a
1063
+ * different word for navigation history.
1064
+ */
1065
+ retractExpansion(id: NodeId): void;
1066
+ /** §9.2 expansion bookkeeping for `id`: committed overlay records with
1067
+ * request id, provenance, and the ids each expansion revealed. */
1068
+ getExpansionOverlays(id: NodeId): readonly ExpansionOverlayRecord[];
1069
+ /**
1070
+ * Run the configured SearchService (default: the built-in local indexed
1071
+ * service). The instance creates the `RequestContext`, caches results by
1072
+ * `serviceCacheKey` over EXACTLY the service's declared revision
1073
+ * dimensions ({@link SEARCH_CACHE_LIMIT}-entry LRU; a second call with an
1074
+ * equal key while one is in flight coalesces onto the same service call),
1075
+ * cancels superseded work (a NEWER query aborts the older in-flight call —
1076
+ * the older promise rejects `OrbitOperationError {code:'aborted'}`), and
1077
+ * rejects stale results at admission (declared revision drift or dataset
1078
+ * lineage change → the same typed 'aborted' rejection with a distinct
1079
+ * staleness message; the store is untouched). A successful search
1080
+ * publishes `store.search = {query, results}` — `node` populated for
1081
+ * in-model ids — in ONE store publication. Search NEVER changes
1082
+ * scope/filter semantics and never fetches graph data (§16.5).
1083
+ */
1084
+ search(query: string, opts?: {
1085
+ limit?: number;
1086
+ }): Promise<readonly SearchResult<N>[]>;
1087
+ /** Clear `store.search` to null (e.g. the <GraphSearch> input emptied). */
1088
+ clearSearch(): void;
1089
+ /** §16.2 path query + atomic emphasis (S12-T08): resolves via the path
1090
+ * service (local BFS default); null = unreachable (a RESULT). Emphasis is
1091
+ * session-local — released by clearPath, any selection mutation, undo/
1092
+ * redo, or a scene rebuild; never a history step; never serialized. */
1093
+ findPath(sourceId: NodeId, targetId: NodeId, options?: PathOptions): Promise<PathResult | null>;
1094
+ clearPath(): void;
1095
+ getActivePath(): PathResult | null;
1096
+ /**
1097
+ * §16.5 result contract (T07): a result id in the current rendered scene
1098
+ * AND §9.1 mask-visible is focused (`focusNode`) → `{status:'focused'}`.
1099
+ * Otherwise classification ONLY — 'not-loaded' (absent from the accepted
1100
+ * model), 'out-of-scope' (in the model but outside the hard scope),
1101
+ * 'filtered' (in the scene but mask-hidden). Never mutates scope or
1102
+ * filters — the host reacts explicitly (§16.5).
1103
+ */
1104
+ activateSearchResult(result: SearchResult<N>): SearchActivation;
1105
+ pauseSimulation(): void;
1106
+ resumeSimulation(): void;
1107
+ isSimulationRunning(): boolean;
1108
+ /** §16.1 screenshot: delegates to the engine; null when unsupported/not ready. */
1109
+ captureScreenshot(): Promise<Blob | null>;
1110
+ /**
1111
+ * §15.1 binding-detected reduced-motion media preference. The EFFECTIVE
1112
+ * value is `accessibility.reducedMotion ?? v` — when reduced, camera
1113
+ * durations (fitView/setViewport/focusNode) coerce to 0.
1114
+ */
1115
+ setReducedMotion(v: boolean | undefined): void;
1116
+ /**
1117
+ * The crossfilter session facade, or null until the `crossfilter` prop has
1118
+ * configured dimensions over an accepted base. Delegates to the
1119
+ * typed-column engine; `setBrush` routes visibility deltas into the §9.1
1120
+ * soft mask (buffers-only commit, zero relayout) and resolves after the
1121
+ * publish. Brush slot deltas are BASE indices; under a hard scope
1122
+ * out-of-scope rows have no scene slot and simply do not mask anything.
1123
+ */
1124
+ getCrossfilterSession(): CrossfilterSession | null;
1125
+ /**
1126
+ * Play a brush window across a numeric/temporal dimension's domain through
1127
+ * the crossfilter mask fast path (zero relayout). One playing dimension at
1128
+ * a time — a second play supersedes; a USER `setBrush` on the playing key
1129
+ * pauses playback. The whole play session coalesces into ONE history entry.
1130
+ */
1131
+ playTimeline(key: string, playback?: Partial<TimelinePlayback>): void;
1132
+ pauseTimeline(): void;
1133
+ /** Undo the most recent uncontrolled mutation entry (selection / hidden /
1134
+ * pins / scope / brushes). Returns false when there is nothing to undo. */
1135
+ undo(): boolean;
1136
+ /** Re-apply the most recently undone entry. False when nothing to redo. */
1137
+ redo(): boolean;
1138
+ /**
1139
+ * Serialize the exploration state (§16.14): camera, selection, hidden ids,
1140
+ * isolation, groups (manual specs verbatim; under `groupBy` only collapsed
1141
+ * `{key, collapsed}` pairs — membership recomputes on restore), pins,
1142
+ * folds, layout, crossfilter brushes in declaration order, the Scale-valued
1143
+ * styling subset, and the host's `dataRef` verbatim. The predicate `filter`
1144
+ * and §16.3 expansion records are never serialized. The sync form carries
1145
+ * no positions: reproduction is best-effort via the layout descriptor.
1146
+ */
1147
+ getViewState(opts?: {
1148
+ includePositions?: false;
1149
+ }): GraphViewState;
1150
+ /**
1151
+ * Async form: additionally embeds quantized coordinates for the VISIBLE
1152
+ * (post-mask) set, read once from the engine (per-event readback, ADR-001).
1153
+ * Restores as a frozen fixed-equivalent — pixel-faithful regardless of
1154
+ * engine nondeterminism. Past `maxPositions` (default 100 000) the call
1155
+ * rejects `export-materialization-too-large`; persist the layout through
1156
+ * the export lane and reference it from `dataRef` instead of inlining.
1157
+ */
1158
+ getViewState(opts: {
1159
+ includePositions: true;
1160
+ maxPositions?: number;
1161
+ }): Promise<GraphViewState>;
1162
+ /**
1163
+ * Atomically restore a serialized view (§16.14). NEVER partially applies:
1164
+ * structural validation and the version gate run first (reject whole with
1165
+ * one 'invalid-view-state' diagnostic); then the dataRef canonical
1166
+ * comparison — a mismatch fires the 'viewStateMismatch' event INSTEAD of
1167
+ * applying, and restoration proceeds only on an `ignoreMismatch` re-call.
1168
+ * The apply is ONE history transaction through the same command appliers
1169
+ * undo/redo uses, so a restore is itself undoable. Embedded positions
1170
+ * apply as a frozen fixed-equivalent (one replay-style commit, then the
1171
+ * simulation pauses — a later explicit layout change or reheat unfreezes).
1172
+ * A state touching a §6.4 controlled slice (or styling, once a restore
1173
+ * callback exists) resolves 'missing-restore-callback' until the aggregate
1174
+ * protocol is registered.
1175
+ */
1176
+ setViewState(raw: unknown, opts?: {
1177
+ ignoreMismatch?: boolean;
1178
+ isDataRefEqual?: (stored: JsonValue | undefined, current: JsonValue | undefined) => boolean;
1179
+ }): Promise<SetViewStateResult>;
1180
+ /**
1181
+ * Picture exports. 'png' delegates to {@link captureScreenshot} (typed
1182
+ * rejection when the engine lacks the capability). 'svg' renders the
1183
+ * VISIBLE (post-mask) set through the engine-free exporter: one per-event
1184
+ * position readback, colors/sizes from the same projectors the commits
1185
+ * use, labels from the current candidate set, in space coordinates with a
1186
+ * padded viewBox. Above `maxSvgElements` (default 50 000) it rejects
1187
+ * `export-too-large` — pass `fallback: 'raster-hybrid'` to instead receive
1188
+ * a PNG base layer with a vector label overlay.
1189
+ */
1190
+ exportImage(format: 'png'): Promise<Blob>;
1191
+ exportImage(format: 'svg', opts?: {
1192
+ maxSvgElements?: number;
1193
+ fallback?: 'raster-hybrid';
1194
+ }): Promise<string>;
1195
+ /**
1196
+ * Bounded object export of the pinned model: 'visible' (default) is the
1197
+ * mask-visible roster with both-endpoint-visible edges; 'accepted' the
1198
+ * full model. Rejects `export-materialization-too-large` past `limit`
1199
+ * (default 100 000 rows) BEFORE allocating — the stream is the remedy.
1200
+ */
1201
+ exportData(scope?: 'visible' | 'accepted', opts?: {
1202
+ limit?: number;
1203
+ }): Promise<{
1204
+ nodes: readonly GraphNode<N>[];
1205
+ edges: readonly AcceptedEdge<E>[];
1206
+ }>;
1207
+ /** Memory-bounded JSONL: one `{"kind":"node"|"edge","value":…}` line per
1208
+ * entity over ONE pinned revision — a mid-stream commit never mixes
1209
+ * epochs. Closing the generator releases the pin. */
1210
+ exportDataStream(scope?: 'visible' | 'accepted'): AsyncGenerator<string, void, undefined>;
1211
+ /** Bounded id → [x, y] map from one position readback (§7.1 per-event). */
1212
+ exportLayout(opts?: {
1213
+ limit?: number;
1214
+ }): Promise<ReadonlyMap<NodeId, readonly [number, number]>>;
1215
+ /** Memory-bounded `{"id","x","y"}` JSONL over one pinned readback. */
1216
+ exportLayoutStream(): AsyncGenerator<string, void, undefined>;
1217
+ /**
1218
+ * §11 legend surface: the active Scale on a styling channel plus its
1219
+ * resolved domain (sequential/diverging — resolved through the SAME frozen
1220
+ * DomainStore coordinate the projection uses) or categorical legend rows
1221
+ * (declared-domain order first including empty categories, then extra seen
1222
+ * values sorted; counts are v0.8-tier TOTALS over the accepted model, not
1223
+ * mask-aware). Null when the channel is not scale-valued.
1224
+ */
1225
+ getScaleInfo(channel: 'nodeColor' | 'nodeSize'): ScaleChannelInfo<N> | null;
1226
+ /**
1227
+ * §12 metric read for one node id via the core id→index map (never a
1228
+ * public million-entry Map). Null for unknown ids/metrics and §8-null
1229
+ * values; lazily computes the degree family on first use.
1230
+ */
1231
+ getMetricValue(metric: MetricName, id: NodeId): number | null;
1232
+ getRevisions(): Revisions;
1233
+ getDiagnostics(): readonly GraphDiagnostic[];
1234
+ getNode(id: NodeId): GraphNode<N> | undefined;
1235
+ /** Accepted edge by id — the symmetric partner of {@link getNode}, and the
1236
+ * way a hover/selection consumer resolves `store.hover.edgeId` or
1237
+ * `selection.edgeIds` to real records. Undefined for unknown ids. */
1238
+ getEdge(id: EdgeId): AcceptedEdge<E> | undefined;
1239
+ /** Scene ids that are §9.1 mask-visible (scope ∧ mask), scene order. */
1240
+ getVisibleNodeIds(): readonly NodeId[];
1241
+ /** Scene roster: scope applied, mask NOT applied — the §15.1 navigator's
1242
+ * entry list, which must still LIST masked/hidden nodes and expose their
1243
+ * state in text rather than dropping them. */
1244
+ getSceneNodeIds(): readonly NodeId[];
1245
+ /** §15.1 accessibility config stash (navigator/live-region consumers). */
1246
+ getAccessibility(): AccessibilityConfig<N> | undefined;
1247
+ }
1248
+ /** §16.14: consecutive same-dimension brush moves within this window merge
1249
+ * into one history entry (scrub/drag coalescing). */
1250
+ declare const BRUSH_HISTORY_COALESCE_MS = 500;
1251
+ /** §16.6 timeline defaults (S9-T10). */
1252
+ declare const TIMELINE_TICK_MS_DEFAULT = 100;
1253
+ declare const TIMELINE_STEP_DEFAULT = 0.01;
1254
+ /** §16.5 search defaults (S11-T06). */
1255
+ declare const SEARCH_LIMIT_DEFAULT = 20;
1256
+ declare const SEARCH_CACHE_LIMIT = 32;
1257
+ declare function createGraphInstance<N = Record<string, unknown>, E = Record<string, unknown>>(opts: CreateGraphInstanceOptions<N, E>): GraphInstance<N, E>;
1258
+
1259
+ /**
1260
+ * §14 DOM label lane — pure candidate selection (S7-T01/T16).
1261
+ *
1262
+ * `selectLabelCandidates` is a pure, deterministic ranking function: no engine,
1263
+ * no DOM, no store. The instance-side overlay scheduler calls it on THROTTLED
1264
+ * re-rank triggers only (viewport idle, model change, config change, settle) —
1265
+ * NEVER per frame. Positions are the reconciler's CPU cache (space coords);
1266
+ * per-frame work elsewhere is a pure O(k) projection of the winners.
1267
+ *
1268
+ * Selection rules (§14):
1269
+ * - Zoom-LOD: below `minZoom` the lane is empty EXCEPT `showFor` ids, which
1270
+ * bypass the zoom gate but stay viewport-culled.
1271
+ * - `showFor` claims capacity FIRST in accepted-base order. When the
1272
+ * in-viewport `showFor` set alone exceeds capacity k, accepted-base order
1273
+ * wins deterministically and `overloadCount` reports the omissions (one
1274
+ * `label-overload` diagnostic upstream — no winner churn).
1275
+ * - Remaining capacity fills with viewport-visible nodes ranked by
1276
+ * `getWeight` (else degree), ties broken by accepted-base order.
1277
+ * - Visibility comes from the engine's `pointsInRect` when available, else a
1278
+ * CPU cull of cached positions through the viewport transform. Nodes with
1279
+ * unknown (NaN) cached positions are unplaceable on the CPU path.
1280
+ */
1281
+
1282
+ /** A capacity winner before per-frame projection assigns screen coordinates. */
1283
+ type LabelCandidate = Omit<LabelPlacement, 'x' | 'y'>;
1284
+ /** §14 default ranked-candidate cap. */
1285
+ declare const LABEL_MAX_VISIBLE_DEFAULT = 64;
1286
+ /** §14 policy maximum for `maxVisible`. */
1287
+ declare const LABEL_MAX_VISIBLE_CAP = 1024;
1288
+ interface LabelCandidateViewport {
1289
+ zoom: number;
1290
+ /** Visible screen rect `[x0, y0, x1, y1]` (CSS px). Omit = size unknown → no viewport cull. */
1291
+ screenRect?: readonly [number, number, number, number];
1292
+ /** space → screen projection for the CPU cull path (null = not projectable). */
1293
+ spaceToScreen?: (p: readonly [number, number]) => readonly [number, number] | null;
1294
+ }
1295
+ interface SelectLabelCandidatesArgs<N = Record<string, unknown>> {
1296
+ scene: RenderScene;
1297
+ /**
1298
+ * Accepted nodes in accepted-base order. Under the 'rebuild' index policy
1299
+ * (§7.3) scene index i IS accepted-base position i, so `nodes[i]` is the
1300
+ * node behind `scene.idByIndex[i]`.
1301
+ */
1302
+ nodes: readonly GraphNode<N>[];
1303
+ /** Space positions from the CPU cache (2*count floats; NaN pair = unknown). */
1304
+ positions: Float32Array;
1305
+ viewport: LabelCandidateViewport;
1306
+ config: LabelConfig<N>;
1307
+ /** Degree of scene index i — the default ranking weight. */
1308
+ degreeOf?: (index: number) => number;
1309
+ /**
1310
+ * Engine-accelerated visibility: point indices inside a screen rect. A null
1311
+ * return (engine not ready) falls back to the CPU cull.
1312
+ */
1313
+ pointsInRect?: (rect: readonly [number, number, number, number]) => number[] | null;
1314
+ }
1315
+ interface LabelCandidateResult {
1316
+ /** Capacity-ordered winners: forced (accepted-base order) then ranked fills. */
1317
+ placements: readonly LabelCandidate[];
1318
+ /** In-viewport `showFor` ids omitted because they alone exceed capacity. */
1319
+ overloadCount: number;
1320
+ }
1321
+ declare function selectLabelCandidates<N = Record<string, unknown>>(args: SelectLabelCandidatesArgs<N>): LabelCandidateResult;
1322
+
1323
+ /**
1324
+ * §7.1/§13 CSR adjacency over columnar link buffers.
1325
+ *
1326
+ * Pure, engine-free helpers shared by local expansion (§9.2), incident-edge
1327
+ * dirtying (§9.1), and the engine-facing `neighborIndices` interaction helper
1328
+ * (§13). Build is O(L) via two counting passes over the flat
1329
+ * `[src0, tgt0, src1, tgt1, …]` link buffer — no comparison sort and no
1330
+ * per-link allocation (the only allocations are the CSR arrays plus one
1331
+ * point-sized cursor array).
1332
+ *
1333
+ * The adjacency is UNDIRECTED: every link contributes one neighbor entry per
1334
+ * endpoint slot, so parallel links repeat, and a self-loop (a, a) lists `a`
1335
+ * twice under point a — once per endpoint slot.
1336
+ */
1337
+ interface Adjacency {
1338
+ /**
1339
+ * Length `pointCount + 1`; the neighbors of point `i` live at
1340
+ * `neighbors[offsets[i] … offsets[i + 1])`.
1341
+ */
1342
+ readonly offsets: Uint32Array;
1343
+ /** Length `links.length` (two entries per link — one per direction). */
1344
+ readonly neighbors: Uint32Array;
1345
+ }
1346
+ /**
1347
+ * Builds a CSR adjacency from a flat `[src, tgt]` pair buffer.
1348
+ *
1349
+ * @param links flat `[src0, tgt0, src1, tgt1, …]` point-index pairs
1350
+ * @param pointCount number of points; every endpoint must be `< pointCount`
1351
+ */
1352
+ declare function buildAdjacency(links: Uint32Array, pointCount: number): Adjacency;
1353
+ /**
1354
+ * Zero-copy neighbor list of one point: a `subarray` VIEW into
1355
+ * `adj.neighbors` — do not mutate, and do not hold across a rebuild.
1356
+ */
1357
+ declare function neighborsOf(adj: Adjacency, index: number): Uint32Array;
1358
+
1359
+ /**
1360
+ * §13/§15 CPU link-pick fallback: a uniform grid over ALL links in SPACE
1361
+ * coordinates (cell ≈ median link length), used when the engine lacks native
1362
+ * `linkAt` (`capabilities.linkPicking === false`). Backs
1363
+ * `onEdgeClick`/`onEdgeHover` via the §7.4 index→edge mapping.
1364
+ *
1365
+ * Coordinate contract: everything here is SPACE coordinates. Callers invert
1366
+ * the affine `Viewport` (space = (screen − center)/zoom + [x, y]) and pass a
1367
+ * space-unit tolerance (`tolerancePx / zoom`).
1368
+ *
1369
+ * Invalidation contract (normative — wave 2 enforces arming):
1370
+ * - The grid indexes a POSITION SNAPSHOT (the §7.1 `posBuf` CPU mirror as of
1371
+ * the build call). It NEVER observes live simulation positions: while the
1372
+ * simulation is hot (alpha above threshold) or an animated `setLayout()`
1373
+ * transition runs, picking is DISARMED by the caller; on settle one
1374
+ * per-event `getPositions` refreshes the mirror and the grid is rebuilt.
1375
+ * - Rebuild ONLY on structural change (link buffer changed) or position
1376
+ * sync. The grid is invariant under camera moves and visibility-mask
1377
+ * changes: the §9.1 mask is applied per candidate at QUERY time via the
1378
+ * optional `visible` callback (pure pass-through until S9 wires the real
1379
+ * mask through it).
1380
+ *
1381
+ * Build uses two counting-sort passes into CSR grid arrays (`cellOffsets`,
1382
+ * `cellLinkIds`). Each segment is inserted with a supercover grid traversal,
1383
+ * so a link contributes O(cols + rows) cells in the worst case instead of
1384
+ * every cell in its O(cols * rows) axis-aligned bounding box. There is no
1385
+ * comparison sort or per-link allocation beyond the CSR arrays (plus
1386
+ * O(cells) cursor and O(links) query stamps). Links with a non-finite endpoint
1387
+ * (NaN-tombstoned points, §13) are excluded and can never be returned. A hard
1388
+ * CSR-entry cap deterministically degrades pathological builds to exact O(L)
1389
+ * query scans with no cell-link payload.
1390
+ */
1391
+ /** §9.1 visibility mask applied per candidate at query time. */
1392
+ type LinkVisibilityMask = (linkIndex: number) => boolean;
1393
+ /**
1394
+ * Read-only view of the built grid (diagnostics/tests). The typed arrays are
1395
+ * the LIVE internal buffers — never mutate them.
1396
+ */
1397
+ interface LinkPickGridSnapshot {
1398
+ /** `scan` means the CSR cap tripped and queries use an exact full scan. */
1399
+ readonly mode: 'grid' | 'scan';
1400
+ readonly cellSize: number;
1401
+ readonly cols: number;
1402
+ readonly rows: number;
1403
+ readonly minX: number;
1404
+ readonly minY: number;
1405
+ /** CSR: length `cols * rows + 1`; `[0, 0]` in `scan` mode. */
1406
+ readonly cellOffsets: Uint32Array;
1407
+ /** CSR segment-supercover pairs; empty in `scan` mode. */
1408
+ readonly cellLinkIds: Uint32Array;
1409
+ }
1410
+ /**
1411
+ * Exact point→segment squared distance (shared with the query path so the
1412
+ * grid and any external oracle agree bit-for-bit).
1413
+ */
1414
+ declare function pointSegmentDistanceSquared(px: number, py: number, ax: number, ay: number, bx: number, by: number): number;
1415
+ declare class LinkPickIndex {
1416
+ private grid;
1417
+ /** Per-link visited stamps: dedupe across cells within one query. */
1418
+ private stamps;
1419
+ private queryStamp;
1420
+ get isBuilt(): boolean;
1421
+ /** Read-only grid/scan index view for diagnostics/tests; null before build. */
1422
+ gridSnapshot(): LinkPickGridSnapshot | null;
1423
+ /**
1424
+ * Drops the grid: `nearestLink` reports null until the next build. Wave 2
1425
+ * calls this on structural change / sim-hot disarm.
1426
+ */
1427
+ invalidate(): void;
1428
+ /**
1429
+ * One-shot output-sensitive build over a position snapshot: O(L + I), where
1430
+ * I is the emitted segment-cell pairs up to `MAX_CELL_LINK_ENTRIES`; above
1431
+ * that cap it commits exact-scan mode. Drains the chunked generator with an
1432
+ * infinite budget so both entry points share one code path (chunked and
1433
+ * one-shot builds are bit-identical).
1434
+ */
1435
+ build(positions: Float32Array, links: Uint32Array): void;
1436
+ /**
1437
+ * Incremental build: yields whenever `now() − sliceStart ≥ budgetMs` so a
1438
+ * scheduler can spread the work across idle frames (§17 long-task budget).
1439
+ * Pure generator — no rAF/timers here; the caller owns scheduling.
1440
+ *
1441
+ * The previous grid stays armed and queryable until the new one commits on
1442
+ * generator completion; abandoning the iterator leaves the old grid
1443
+ * intact. At most one build should be in flight per index — the wave-2
1444
+ * scheduler serializes rebuilds.
1445
+ */
1446
+ buildChunked(positions: Float32Array, links: Uint32Array, budgetMs: number, now: () => number): IterableIterator<void>;
1447
+ /**
1448
+ * Nearest link within `tolerance` (space units) of the space point
1449
+ * `(x, y)`, or null. Scans the candidate cells covering the tolerance
1450
+ * disc's bounding box (typically the 3×3 neighborhood; more when the
1451
+ * tolerance exceeds the cell size), applies the optional §9.1 visibility
1452
+ * mask per candidate, then runs the exact point→segment distance test.
1453
+ * Nearest wins; exact-distance ties break toward the LOWER link index.
1454
+ * Returns null while unbuilt/invalidated (picking disarmed).
1455
+ */
1456
+ nearestLink(x: number, y: number, tolerance: number, visible?: LinkVisibilityMask): number | null;
1457
+ }
1458
+
1459
+ /**
1460
+ * §13/§15 edge-picking facade (S6-T07).
1461
+ *
1462
+ * The core commits to ONE route per mount session, read from
1463
+ * `engine.capabilities.linkPicking` at engine-ready time — never method
1464
+ * sniffing, and never re-evaluated (a capability record mutated after ready
1465
+ * changes nothing):
1466
+ *
1467
+ * - 'native': the adapter delivers `onLinkClick`/`onLinkHover` host events
1468
+ * itself; the facade is inert (arm/disarm/queries no-op and
1469
+ * `pickLinkAt` returns null) and the instance maps link indices
1470
+ * to typed edges straight off the host events.
1471
+ * - 'fallback': the instance samples the pointer on the shared §15 throttle
1472
+ * cadence and resolves hits through a `LinkPickIndex` uniform
1473
+ * grid that is armed ONLY while the simulation is settled.
1474
+ *
1475
+ * Arming protocol (fallback route; §13):
1476
+ * - `arm(positions, links)` runs on simulation settle (fed by one per-event
1477
+ * `getPositions` readback) and on structural commits that do NOT restart
1478
+ * the simulation (fixed layout). The grid builds CHUNKED via
1479
+ * `LinkPickIndex.buildChunked` under a per-slice time budget so large link
1480
+ * sets never block (§17); small builds complete synchronously in the first
1481
+ * slice. While an initial build is in flight queries return null; a
1482
+ * position-sync REBUILD keeps answering from the previous grid until the
1483
+ * new one commits.
1484
+ * - `disarm()` runs on any commit that restarts the simulation: targets are
1485
+ * moving and the position mirror is stale, so queries return null until
1486
+ * the next settle re-arms.
1487
+ * - Rebuilds happen ONLY on structural change or position sync — the grid is
1488
+ * invariant under camera moves and mask changes (the §9.1 visibility mask
1489
+ * is applied per candidate at query time; pass-through stub until S9).
1490
+ *
1491
+ * Tolerance (§13): a query converts screen px to space units via
1492
+ * `screenToSpace` of two points `EDGE_PICK_TOLERANCE_PX` apart, then uses
1493
+ * `max(4px, half the median link width)` scaled by that factor.
1494
+ */
1495
+
1496
+ type EdgePickRoute = 'native' | 'fallback';
1497
+ /** Screen-space pick tolerance floor, and the probe distance used to measure
1498
+ * the screen→space scale (§13). */
1499
+ declare const EDGE_PICK_TOLERANCE_PX = 4;
1500
+ /**
1501
+ * Median link width in px from a projected linkWidth buffer (deterministic
1502
+ * stride sample, cap 1024). Returns 0 for null/empty/non-finite input — the
1503
+ * 4px floor then wins in the tolerance formula.
1504
+ */
1505
+ declare function medianLinkWidthPx(widths: Float32Array | null): number;
1506
+ interface EdgePickingFacadeOptions {
1507
+ /** Fixed for the facade's lifetime; read from capabilities at ready. */
1508
+ route: EdgePickRoute;
1509
+ /** Affine screen→space conversion; null = conversion unavailable. */
1510
+ screenToSpace: (p: readonly [number, number]) => readonly [number, number] | null;
1511
+ /** Median link width in px for the tolerance floor; default () => 0. */
1512
+ medianLinkWidthPx?: () => number;
1513
+ /** §9.1 visibility mask pass-through stub (mutable via the setter). */
1514
+ linkVisible?: LinkVisibilityMask;
1515
+ /** Chunked-build slice budget in ms. */
1516
+ buildBudgetMs?: number;
1517
+ /** Scheduler for build continuation slices (test seam). */
1518
+ schedule?: (continueBuild: () => void) => void;
1519
+ /** Clock for the build budget (test seam). */
1520
+ now?: () => number;
1521
+ }
1522
+ declare class EdgePickingFacade {
1523
+ /** The route this mount session committed to at ready. */
1524
+ readonly route: EdgePickRoute;
1525
+ private readonly screenToSpace;
1526
+ private readonly medianWidthPx;
1527
+ private readonly budgetMs;
1528
+ private readonly schedule;
1529
+ private readonly now;
1530
+ private readonly index;
1531
+ private mask;
1532
+ /** Bumped by disarm/arm/destroy; abandons any in-flight chunked build. */
1533
+ private generation;
1534
+ constructor(opts: EdgePickingFacadeOptions);
1535
+ /** True when the fallback grid is built and answering queries. */
1536
+ get armed(): boolean;
1537
+ /**
1538
+ * (Re-)arm from a settled position snapshot. `positions`/`links` must be
1539
+ * stable snapshots — the grid references them until the next rebuild.
1540
+ * No-op on the native route.
1541
+ */
1542
+ arm(positions: Float32Array, links: Uint32Array): void;
1543
+ /** Simulation went hot: drop the grid; queries null until the next settle. */
1544
+ disarm(): void;
1545
+ /** §9.1 mask pass-through stub: applied per candidate at query time. */
1546
+ setLinkVisibilityMask(mask: LinkVisibilityMask | null): void;
1547
+ /**
1548
+ * Nearest link index within tolerance of a SCREEN point, or null when: on
1549
+ * the native route, disarmed (sim hot / not yet settled), no conversion,
1550
+ * or nothing in range.
1551
+ */
1552
+ pickLinkAt(screen: readonly [number, number]): number | null;
1553
+ /** Abandon any in-flight build and drop the grid (session teardown). */
1554
+ destroy(): void;
1555
+ }
1556
+
1557
+ /**
1558
+ * Collision-safe identity helpers for synthesized edges.
1559
+ *
1560
+ * Simple ids retain the documented `source→target#k` shape. The three
1561
+ * framing characters are backslash-escaped inside endpoint components, making
1562
+ * the concatenation injective for arbitrary JavaScript strings (including
1563
+ * strings that themselves contain backslashes, arrows, hashes, or NULs).
1564
+ */
1565
+
1566
+ type EdgePairCounters = Map<NodeId, Map<NodeId, number>>;
1567
+
1568
+ /**
1569
+ * §7.5 revisioned ingestion — pure session/overlay bookkeeping plus the
1570
+ * instance-local acceptance queue (S8-T01..T07).
1571
+ *
1572
+ * Everything here is data-structure work: no store, no engine, no DOM. The
1573
+ * GraphInstance wires these helpers into its publication path (instance.ts).
1574
+ *
1575
+ * Ordering model: every admission takes a monotonically increasing ticket from
1576
+ * the instance's AcceptanceQueue. Overlay rows are stamped with their
1577
+ * admission ticket, and the merge folds base + overlays strictly in ticket
1578
+ * order — arrival at the queue IS the global admission order (§7.5).
1579
+ */
1580
+
1581
+ /** Byte backpressure budget default: 8 MiB of admitted-but-unflushed payload. */
1582
+ declare const INGEST_MAX_PENDING_BYTES_DEFAULT = 8388608;
1583
+ /** Progressive-overlay coalesced flush latency cap default (ms). */
1584
+ declare const INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT = 50;
1585
+ /** A single batch larger than `factor × maxPendingBytes` rejects outright. */
1586
+ declare const INGEST_OVERFLOW_FACTOR = 4;
1587
+ declare class AcceptanceQueue {
1588
+ private ticket;
1589
+ /** Depth guard: admissions made from inside a job run nested — arrival
1590
+ * order still equals call order under synchronous execution. */
1591
+ private depth;
1592
+ /** Total admissions so far (the global admission-order clock). */
1593
+ get admissions(): number;
1594
+ /** True while a job admitted by this queue is executing. */
1595
+ get active(): boolean;
1596
+ /** Take the next admission-order ticket without running a job (row stamps). */
1597
+ nextTicket(): number;
1598
+ /**
1599
+ * Admit a job at the tail of the global order and execute it synchronously.
1600
+ * Exceptions propagate to the admitter (v0.5 synchronous execution).
1601
+ */
1602
+ admit<T>(job: () => T): T;
1603
+ }
1604
+ /** `batch.bytes` when declared, else a rough JSON estimate of the payload. */
1605
+ declare function estimateBatchBytes(batch: IngestBatch<unknown, unknown>): number;
1606
+ interface RowTally {
1607
+ count: number;
1608
+ samples: string[];
1609
+ }
1610
+ /** Per-session staging tallies; surfaced as diagnostics ONLY at commit (§7.5). */
1611
+ interface StagingTallies {
1612
+ invalidNodes: RowTally;
1613
+ duplicateNodes: RowTally;
1614
+ invalidEdges: RowTally;
1615
+ duplicateEdges: RowTally;
1616
+ }
1617
+ declare function newStagingTallies(): StagingTallies;
1618
+ interface StampedNode<N> {
1619
+ /** Global admission-order ticket (queue arrival). */
1620
+ readonly order: number;
1621
+ readonly node: GraphNode<N>;
1622
+ }
1623
+ interface StampedEdge<E> {
1624
+ readonly order: number;
1625
+ readonly edge: AcceptedEdge<E>;
1626
+ }
1627
+ interface SessionContribution<N = Record<string, unknown>, E = Record<string, unknown>> {
1628
+ readonly overlayId: string;
1629
+ /** Session-deduped admitted node rows in admission order. */
1630
+ readonly nodes: StampedNode<N>[];
1631
+ /** Session-deduped admitted edge records (ids resolved) in admission order. */
1632
+ readonly edges: StampedEdge<E>[];
1633
+ readonly nodeIds: Set<NodeId>;
1634
+ readonly edgeIds: Set<string>;
1635
+ /** Exact ordered endpoint tuple → next synthesized-parallel-edge k (§5). */
1636
+ readonly pairCounters: EdgePairCounters;
1637
+ /** Public prefix lengths: rows before these counts have been published
1638
+ * (progressive flush / commit); appends only grow the arrays, so a prefix
1639
+ * is sufficient. Atomic sessions stay at 0 until commit (§7.5). */
1640
+ publicNodeCount: number;
1641
+ publicEdgeCount: number;
1642
+ }
1643
+ declare function newContribution<N, E>(overlayId: string): SessionContribution<N, E>;
1644
+ interface StageResult {
1645
+ admittedNodes: number;
1646
+ admittedEdges: number;
1647
+ }
1648
+ /**
1649
+ * Validate and stage one batch's rows into a session contribution.
1650
+ *
1651
+ * §5.1-equivalent row validation WITHOUT endpoint checks — edges may arrive
1652
+ * before nodes (§7.5): they occupy their eventual edge records here; endpoint
1653
+ * resolution happens at merge. Within a session, duplicate ids are
1654
+ * first-wins-dropped (tallied for commit diagnostics); cross-session
1655
+ * collisions are the merge's shadowing concern, not staging's.
1656
+ */
1657
+ declare function stageBatch<N, E>(contribution: SessionContribution<N, E>, batch: IngestBatch<N, E>, tallies: StagingTallies, nextOrder: () => number): StageResult;
1658
+ /** The accepted base (declarative snapshot or committed replace session). */
1659
+ interface MergeBase<N = Record<string, unknown>, E = Record<string, unknown>> {
1660
+ datasetKey: string;
1661
+ sourceRevision: number | string;
1662
+ nodes: readonly GraphNode<N>[];
1663
+ /** id → position in `nodes`. */
1664
+ nodeIndex: ReadonlyMap<NodeId, number>;
1665
+ /** Endpoint-resolved base edges. */
1666
+ edges: readonly AcceptedEdge<E>[];
1667
+ /** Replace-session bases retain edge records whose endpoints were missing
1668
+ * at commit; a later overlay node can still resolve them (§7.5). */
1669
+ pendingEdges: readonly StampedEdge<E>[];
1670
+ diagnostics: readonly GraphDiagnostic[];
1671
+ }
1672
+ /** Sentinel owner key for base pending edges in `pendingBySource`. */
1673
+ declare const BASE_PENDING_KEY = "";
1674
+ interface MergeResult<N = Record<string, unknown>, E = Record<string, unknown>> {
1675
+ accepted: AcceptedGraph<N, E>;
1676
+ /** Overlay node rows shadowed by an earlier admission (base included). */
1677
+ shadowedCount: number;
1678
+ shadowedSamples: readonly string[];
1679
+ /** Later same-id edge records dropped (first admission wins). */
1680
+ duplicateEdgeCount: number;
1681
+ duplicateEdgeSamples: readonly string[];
1682
+ /** overlayId (or BASE_PENDING_KEY) → edges still awaiting an endpoint. The
1683
+ * pending-endpoint index: these occupy edge records but never enter the
1684
+ * engine link buffer (§7.5). */
1685
+ pendingBySource: ReadonlyMap<string, number>;
1686
+ pendingEdgeCount: number;
1687
+ }
1688
+ declare function baseFromAccepted<N, E>(accepted: AcceptedGraph<N, E>): MergeBase<N, E>;
1689
+ /**
1690
+ * Build a MergeBase from a replace session's staged contribution. Edges whose
1691
+ * endpoints exist in the session's own node set resolve immediately; the rest
1692
+ * stay pending (they may resolve through later overlays).
1693
+ */
1694
+ declare function baseFromContribution<N, E>(datasetKey: string, sourceRevision: number | string, contribution: SessionContribution<N, E>, diagnostics: readonly GraphDiagnostic[]): MergeBase<N, E>;
1695
+ /**
1696
+ * Merge the accepted base with every overlay contribution's PUBLIC rows.
1697
+ *
1698
+ * - Node collisions: earliest admission (ticket order; base first) wins.
1699
+ * Later rows stay retained-but-shadowed in their contribution and are
1700
+ * tallied for the single count-aggregated 'overlay-node-shadowed' info
1701
+ * diagnostic (§7.5).
1702
+ * - Edge ids: first admission wins; later same-id records are dropped
1703
+ * (tallied). An edge whose endpoints are not all present in the merged
1704
+ * node set is pending — it occupies its record and the pending-endpoint
1705
+ * accounting but is excluded from `accepted.edges` (and therefore from the
1706
+ * engine link buffer). Endpoints arriving later resolve it on the next
1707
+ * merge (same or different batch/overlay).
1708
+ *
1709
+ * Deterministic: output depends only on the base and the stamped rows.
1710
+ */
1711
+ declare function mergeModel<N, E>(base: MergeBase<N, E>, overlays: readonly SessionContribution<N, E>[]): MergeResult<N, E>;
1712
+ /** Recomputed per merge: ONE count-aggregated shadow info + edge-dedupe note. */
1713
+ declare function mergeDiagnostics(merge: MergeResult<unknown, unknown>): GraphDiagnostic[];
1714
+ /**
1715
+ * Commit-time diagnostics for one session: staging tallies plus the dangling
1716
+ * (still-pending-endpoint) count — emitted ONLY at session commit (§7.5).
1717
+ */
1718
+ declare function sessionCommitDiagnostics(tallies: StagingTallies, danglingCount: number, danglingSamples?: readonly string[]): GraphDiagnostic[];
1719
+
1720
+ /**
1721
+ * §9.2 hard subgraph scope — pure resolution over the accepted base.
1722
+ *
1723
+ * `resolveScope` turns a `SubgraphSpec` into the exact node/edge subset the
1724
+ * reconciler should be fed: seeds validated against the accepted base
1725
+ * (unknown ids dropped, duplicates collapsed), optional `hops` expansion via
1726
+ * BFS over the §7.1 CSR adjacency of the ACCEPTED BASE (not the scene — the
1727
+ * default expansion service walks this same index, §9.2), and edges cascaded
1728
+ * through `cascadeEdges`, the single §9 edge-survival primitive (an edge
1729
+ * survives iff BOTH endpoints survive) that S9's soft masks reuse.
1730
+ *
1731
+ * Everything here is synchronous and engine-free. The async
1732
+ * `ExpansionService` seam (./services) exists for `expandNode`; `hops`
1733
+ * resolution inside a host update takes this local path directly.
1734
+ */
1735
+
1736
+ /**
1737
+ * THE edge-cascade primitive (§9): an edge survives iff BOTH of its
1738
+ * endpoints survive. O(E); the only allocation is the output array, which
1739
+ * holds references to the input edge objects (never copies).
1740
+ *
1741
+ * Exported for reuse by §9.1 soft masks (S9) and any other subset producer.
1742
+ */
1743
+ declare function cascadeEdges<E>(edges: readonly AcceptedEdge<E>[], survives: (id: NodeId) => boolean): AcceptedEdge<E>[];
1744
+ /**
1745
+ * Builds the §7.1 CSR adjacency of an accepted base: endpoints are positions
1746
+ * in `accepted.nodes` (accepted-base order). Every accepted edge has resolved
1747
+ * endpoints by contract (§5.1 drops dangling edges), so this cannot throw on
1748
+ * a well-formed `AcceptedGraph`.
1749
+ */
1750
+ declare function buildAcceptedAdjacency(accepted: AcceptedGraph<unknown, unknown>): Adjacency;
1751
+ /** Output of `resolveScope`: the exact subset to feed the reconciler (§9.2). */
1752
+ interface ResolvedScope<N = Record<string, unknown>, E = Record<string, unknown>> {
1753
+ /** Surviving node ids (seeds + hop expansion), membership-query form. */
1754
+ nodeIds: ReadonlySet<NodeId>;
1755
+ /** Surviving nodes in accepted-base order (same objects, never copies). */
1756
+ nodes: readonly GraphNode<N>[];
1757
+ /** Edges whose BOTH endpoints survive, in accepted-base order. */
1758
+ edges: readonly AcceptedEdge<E>[];
1759
+ }
1760
+ /**
1761
+ * Resolves a hard-scope spec against the accepted base (§9.2).
1762
+ *
1763
+ * - `spec.seedIds` are validated against `accepted.nodeIndex`: ids unknown to
1764
+ * the accepted base are dropped, duplicates collapse to one seed.
1765
+ * - `spec.hops` (default 0; negative/non-finite values clamp to 0, fractions
1766
+ * floor) expands the survivor set by BFS over the accepted-base adjacency.
1767
+ * This is the synchronous local path — the §9.2 default `ExpansionService`
1768
+ * walks the same index for `expandNode`.
1769
+ * - `adjacency` may be a caller-cached `buildAcceptedAdjacency(accepted)`
1770
+ * result; pass `null` to have one built on demand (only when `hops > 0`
1771
+ * and there are surviving seeds — hop-0 resolution never builds it).
1772
+ * - Output preserves accepted-base order for both nodes and edges; edges are
1773
+ * cascaded through {@link cascadeEdges}.
1774
+ */
1775
+ declare function resolveScope<N, E>(accepted: AcceptedGraph<N, E>, spec: SubgraphSpec, adjacency: Adjacency | null): ResolvedScope<N, E>;
1776
+
1777
+ /**
1778
+ * §9.2 revision-aware services — pure sequencing/admission/caching helpers
1779
+ * plus the built-in local expansion service.
1780
+ *
1781
+ * The correctness gate is `admitServiceResult`: a result is admitted only if
1782
+ * every revision dimension the service DECLARED is unchanged since the call
1783
+ * was issued; undeclared dimensions never invalidate (so e.g. the local
1784
+ * expansion service, declaring only 'source', survives unrelated model/scope
1785
+ * advances from other overlay publications). Abort is an optimization —
1786
+ * admission is the gate.
1787
+ *
1788
+ * Cache keys include service identity, canonical-JSON request parameters,
1789
+ * `datasetKey`, and EXACTLY the declared revision dimensions' current values.
1790
+ *
1791
+ * Nothing here touches the engine, the store, or the DOM; the instance wires
1792
+ * these primitives to its acceptance queue.
1793
+ */
1794
+
1795
+ /** Current value of each §9.2 revision dimension (a Revisions subset). */
1796
+ interface RevisionSnapshot {
1797
+ source: number | string | null;
1798
+ model: number;
1799
+ scope: number;
1800
+ }
1801
+ /** Monotonic instance-process-unique request id (deterministic prefix for tests). */
1802
+ declare function nextRequestId(prefix?: string): string;
1803
+ interface CreateRequestContextArgs {
1804
+ datasetKey: string;
1805
+ /** Revision values current at issue time (snapshotted into the context). */
1806
+ revisions: RevisionSnapshot;
1807
+ /** Explicit id (e.g. for coalescing bookkeeping); generated when omitted. */
1808
+ requestId?: string;
1809
+ /** Chain an upstream signal (e.g. instance teardown) into this request. */
1810
+ parentSignal?: AbortSignal;
1811
+ }
1812
+ /** A `RequestContext` plus its owning abort handle. */
1813
+ interface RequestContextHandle {
1814
+ readonly context: RequestContext;
1815
+ readonly controller: AbortController;
1816
+ abort(reason?: unknown): void;
1817
+ }
1818
+ /**
1819
+ * Builds the `RequestContext` a service call receives (§9.2): dataset,
1820
+ * the three revision dimensions at issue time, a request id, and a
1821
+ * cancellation signal owned by the returned controller.
1822
+ */
1823
+ declare function createRequestContext(args: CreateRequestContextArgs): RequestContextHandle;
1824
+ interface AdmitServiceResultArgs {
1825
+ /** The service's `revisionDependencies` (§9.2). */
1826
+ declared: readonly RevisionDimension[];
1827
+ /** Revision values when the request was issued. */
1828
+ at: RevisionSnapshot;
1829
+ /** Revision values at admission time. */
1830
+ now: RevisionSnapshot;
1831
+ }
1832
+ /**
1833
+ * §9.2 stale-result rule: admit a service result iff EVERY declared revision
1834
+ * dimension is unchanged between issue and admission. Undeclared dimensions
1835
+ * never invalidate. Declaring nothing means the result is admissible under
1836
+ * any drift; declaring all three restores strict point-in-model semantics.
1837
+ */
1838
+ declare function admitServiceResult(args: AdmitServiceResultArgs): boolean;
1839
+ interface ServiceCacheKeyArgs {
1840
+ serviceId: string;
1841
+ /** Request parameters (JSON-shaped); key order is canonicalized away. */
1842
+ params: unknown;
1843
+ datasetKey: string;
1844
+ /** The service's declared revision dependencies (§9.2). */
1845
+ declared: readonly RevisionDimension[];
1846
+ /** Current revision values; only declared dimensions enter the key. */
1847
+ revisions: RevisionSnapshot;
1848
+ }
1849
+ /**
1850
+ * §9.2 cache-key rule: service identity + canonical-JSON params +
1851
+ * `datasetKey` + EXACTLY the declared revision dimensions' current values.
1852
+ * Declaration-list order and params key order do not affect the key;
1853
+ * undeclared revision drift never changes it.
1854
+ */
1855
+ declare function serviceCacheKey(args: ServiceCacheKeyArgs): string;
1856
+ /** Accepted-base view the local service walks (thunked for lazy wiring). */
1857
+ interface LocalExpansionBase<N = Record<string, unknown>, E = Record<string, unknown>> {
1858
+ accepted: AcceptedGraph<N, E>;
1859
+ /** The §7.1 CSR adjacency of `accepted` (see buildAcceptedAdjacency). */
1860
+ adjacency: Adjacency;
1861
+ }
1862
+ /**
1863
+ * The built-in §9.2 expansion service: walks the core's accepted-base
1864
+ * adjacency — INCLUDING currently out-of-scope nodes (it reads the base, not
1865
+ * the scene) — with zero config and zero network. Declares
1866
+ * `revisionDependencies: ['source']`, so unrelated model/scope advances
1867
+ * (e.g. other overlay publications) never invalidate its results.
1868
+ *
1869
+ * `getBase` is a thunk so the instance can wire the service before data
1870
+ * arrives; it is re-read on every call. The returned promise is async but
1871
+ * effectively synchronous (resolves without I/O). `ctx.signal` is honored:
1872
+ * an aborted call rejects with `OrbitOperationError { code: 'aborted' }`
1873
+ * instead of returning a result (abort is still only an optimization — the
1874
+ * admission gate is authoritative).
1875
+ *
1876
+ * The response is the CLOSED N-hop neighborhood (seeds included, plus every
1877
+ * edge between returned nodes); per §9.2 the overlay merger dedupes rows
1878
+ * that already exist, so returning already-known seeds is correct.
1879
+ */
1880
+ declare function createLocalExpansionService<N = Record<string, unknown>, E = Record<string, unknown>>(getBase: () => LocalExpansionBase<N, E>): ExpansionService<N, E>;
1881
+ type RegisterExpansionResult = {
1882
+ kind: 'new';
1883
+ }
1884
+ /** A same-id expansion is already in flight; its result serves both. */
1885
+ | {
1886
+ kind: 'coalesced';
1887
+ onto: string;
1888
+ };
1889
+ /**
1890
+ * Pure in-flight-expansion ledger (§9.2): within one valid scope revision a
1891
+ * second `expandNode(id)` while one is in flight coalesces into the pending
1892
+ * call; expansions of DISTINCT ids run (and complete) concurrently and
1893
+ * independently. `retractExpansion(id)` uses `abort(id)` to drop that id's
1894
+ * pending expansion. Holds no timers, promises, or engine state — the
1895
+ * instance owns the actual requests.
1896
+ */
1897
+ declare class PendingExpansions {
1898
+ private readonly inFlight;
1899
+ /**
1900
+ * Registers an expansion of `id` under `requestId`. Returns
1901
+ * `{ kind: 'coalesced', onto }` when a same-id expansion is already in
1902
+ * flight (the caller must NOT issue a new request; `onto` is the request
1903
+ * id whose result serves both), else records the id and returns
1904
+ * `{ kind: 'new' }`.
1905
+ */
1906
+ register(id: NodeId, requestId: string): RegisterExpansionResult;
1907
+ /**
1908
+ * Marks `id`'s expansion complete. When `requestId` is given, clears only
1909
+ * if it still owns the slot (a stale completion after abort + re-expand
1910
+ * must not clear the newer request). Returns whether the slot was cleared.
1911
+ */
1912
+ resolve(id: NodeId, requestId?: string): boolean;
1913
+ /**
1914
+ * Drops `id`'s pending expansion (retractExpansion path). Returns the aborted
1915
+ * request id so the caller can cancel its controller, or null when nothing
1916
+ * was in flight.
1917
+ */
1918
+ abort(id: NodeId): string | null;
1919
+ /** Request id currently serving `id`, if any. */
1920
+ requestIdFor(id: NodeId): string | undefined;
1921
+ has(id: NodeId): boolean;
1922
+ get size(): number;
1923
+ /** Snapshot of pending ids (store's `pendingExpansions` shape). */
1924
+ ids(): ReadonlySet<NodeId>;
1925
+ }
1926
+
1927
+ /**
1928
+ * §16.14 history command-stack kernel (S9-T20; pulled forward from M7).
1929
+ *
1930
+ * v0.7 implements the KERNEL only: value-diff commands, transactions,
1931
+ * coalescing, and a bounded stack. The kernel never touches stores — it is a
1932
+ * pure application seam: `undo()`/`redo()` return the command list to apply
1933
+ * (already inverted, in application order) and move the cursor; the instance
1934
+ * (wired in S15) applies commands to its slices and publishes.
1935
+ * Ownership-mode acknowledgement walks and view-state integration are S15.
1936
+ *
1937
+ * Commands are `{ slice, before, after }` value diffs — never closures — so
1938
+ * they are serializable and invert by swapping. The kernel freezes and stores
1939
+ * exactly what it is given (no structuredClone): callers pass plain values /
1940
+ * arrays and must not mutate them afterward. A DEV-only serializability walk
1941
+ * (no functions, no Map/Set — callers convert) runs behind the `debug` flag.
1942
+ */
1943
+ /** One history-worthy mutation: a serializable value diff over a named slice. */
1944
+ interface HistoryCommand {
1945
+ readonly slice: string;
1946
+ readonly before: unknown;
1947
+ readonly after: unknown;
1948
+ }
1949
+ /** Depth snapshot published to subscribers (mirrors GraphStoreState.history). */
1950
+ interface HistoryDepths {
1951
+ readonly undoDepth: number;
1952
+ readonly redoDepth: number;
1953
+ }
1954
+ interface HistoryKernelOptions {
1955
+ /** Stack bound; oldest entry evicted past it (§16.14; default 50). */
1956
+ limit?: number;
1957
+ /** `false` = the `history: false` prop: everything is a no-op, depths stay 0. */
1958
+ enabled?: boolean;
1959
+ /** DEV-only: walk recorded payloads and throw on non-serializable values. */
1960
+ debug?: boolean;
1961
+ }
1962
+ declare const HISTORY_LIMIT_DEFAULT = 50;
1963
+ /**
1964
+ * Bounded undo/redo command stack. Store-agnostic: see module doc for the
1965
+ * application seam. All methods are synchronous; there is no async state.
1966
+ */
1967
+ declare class HistoryKernel {
1968
+ private readonly limit;
1969
+ private readonly enabled;
1970
+ private readonly debug;
1971
+ private readonly undoStack;
1972
+ private readonly redoStack;
1973
+ /** Nested begin() joins the outer transaction (depth-counted). */
1974
+ private txDepth;
1975
+ private txCommands;
1976
+ private txLabel;
1977
+ private txCoalesce;
1978
+ /**
1979
+ * Coalescing chain anchor: the tag of the most recently PUSHED entry, valid
1980
+ * only while that entry is still the top of the undo stack with nothing in
1981
+ * between — any undo/redo/clear or non-coalesced push breaks the chain.
1982
+ */
1983
+ private lastCoalesce;
1984
+ private readonly listeners;
1985
+ private lastDepths;
1986
+ constructor(options?: HistoryKernelOptions);
1987
+ /** Open a transaction; nested calls join the outer one (depth-counted). */
1988
+ begin(label?: string): void;
1989
+ /**
1990
+ * Open a coalescing transaction: consecutive transactions with the same
1991
+ * `key` within `windowMs` MERGE into the prior stack entry (original before
1992
+ * kept, after replaced per slice) — camera drags, timeline play sessions.
1993
+ * Nested inside an open transaction it joins the outer one unchanged.
1994
+ * `now` is injectable for tests; each merge refreshes the window anchor.
1995
+ */
1996
+ beginCoalesced(key: string, windowMs: number, now?: () => number): void;
1997
+ /**
1998
+ * Record one value-diff command. Inside a transaction it appends to the
1999
+ * pending entry; outside it wraps itself in an implicit single-command
2000
+ * transaction. The kernel freezes and stores what it is given.
2001
+ */
2002
+ record(slice: string, before: unknown, after: unknown): void;
2003
+ /** Close a transaction; the outermost end() pushes one stack entry. */
2004
+ end(): void;
2005
+ /**
2006
+ * Move the cursor back one entry and return its commands, already inverted
2007
+ * (before/after swapped) and in application order (reverse of recorded).
2008
+ * Returns null at the bottom of the stack. Does not touch any store.
2009
+ */
2010
+ undo(): readonly HistoryCommand[] | null;
2011
+ /**
2012
+ * Move the cursor forward one entry and return its commands in original
2013
+ * application order. Returns null when there is nothing to redo.
2014
+ */
2015
+ redo(): readonly HistoryCommand[] | null;
2016
+ peekUndoDepth(): number;
2017
+ peekRedoDepth(): number;
2018
+ /** Empty both stacks and any pending transaction (datasetKey swaps). */
2019
+ clear(): void;
2020
+ /** Subscribe to depth CHANGES (not every call). Returns an unsubscriber. */
2021
+ subscribe(cb: (depths: HistoryDepths) => void): () => void;
2022
+ /**
2023
+ * Push one entry: any new entry clears the redo branch; a coalesce-tagged
2024
+ * entry within the window of a still-anchored same-key predecessor merges
2025
+ * into it instead (original before kept, after replaced per slice); pushing
2026
+ * past the limit evicts the oldest entry.
2027
+ */
2028
+ private pushEntry;
2029
+ private notify;
2030
+ }
2031
+
2032
+ /**
2033
+ * §9.1 soft filtering (S9-T01) — pure, engine-free expr evaluation,
2034
+ * validation, compilation, and canonical keying. The instance feeds compiled
2035
+ * filters into the SoftMask kernel (./mask); nothing here touches the store.
2036
+ *
2037
+ * Semantics (orbit-spec.md §9.1):
2038
+ * - `field` addresses `attrs[field]`; the literal field 'id' addresses the
2039
+ * entity id (it wins even when `attrs.id` exists).
2040
+ * - The serializable expr path NEVER throws: junk shapes and §8-hygiene
2041
+ * failures (non-numeric / non-finite values under 'range') simply fail the
2042
+ * item. `validateFilterExpr` is the reporting channel — the instance turns
2043
+ * its findings into validation errors at applyHostUpdate.
2044
+ * - eq/neq/in compare numbers with Object.is semantics EXCEPT that NaN is
2045
+ * never equal to anything (so ±0 are distinct and NaN ≠ NaN); everything
2046
+ * else compares with plain ===. No coercion, ever ('5' never equals 5).
2047
+ * - Function predicates are black boxes: throws are caught and aggregated
2048
+ * into ONE {count, samples} result the caller converts to a single
2049
+ * 'filter-error' diagnostic (§5.1 batching — O(categories), never O(bad
2050
+ * rows)). A throwing predicate FAILS OPEN: the item stays visible, so a
2051
+ * buggy predicate can never blank the graph.
2052
+ * - Structural specs compare by canonical key (`canonicalFilterKey`) so
2053
+ * identity churn with equal structure never re-evaluates; function
2054
+ * predicates key by reference identity via a WeakMap-issued token.
2055
+ */
2056
+
2057
+ /**
2058
+ * Resolves a filter `field` against an entity: 'id' addresses the entity id;
2059
+ * any other field addresses `attrs[field]` (undefined when attrs are absent).
2060
+ */
2061
+ declare function resolveFilterField(item: GraphNode<unknown> | AcceptedEdge<unknown>, field: string): unknown;
2062
+ /**
2063
+ * Evaluates one expr for one item via a field resolver. NEVER throws:
2064
+ * malformed shapes and §8-hygiene failures fail the item (return false);
2065
+ * a malformed operand under 'not' also fails (junk never passes by double
2066
+ * negation). `range` requires a finite number value; missing bounds are
2067
+ * unbounded; includeMin/includeMax default true. `is-null` matches null OR
2068
+ * undefined (absent attr).
2069
+ */
2070
+ declare function evaluateFilterExpr(expr: FilterExpr, resolve: (field: string) => unknown): boolean;
2071
+ /**
2072
+ * Structural checker for serializable exprs: unknown ops and malformed
2073
+ * shapes are reported as `$`-rooted path strings ([] = valid). The instance
2074
+ * converts findings into validation errors at applyHostUpdate (§9.1) —
2075
+ * evaluation itself never throws on the same junk, it just fails the item.
2076
+ */
2077
+ declare function validateFilterExpr(expr: FilterExpr): string[];
2078
+ /** ONE aggregated throw tally per compiled filter → one 'filter-error'
2079
+ * diagnostic (count + capped samples), never O(bad rows) diagnostics. */
2080
+ interface FilterErrorAggregate {
2081
+ count: number;
2082
+ /** At most DIAGNOSTIC_SAMPLE_CAP offending entity ids. */
2083
+ samples: string[];
2084
+ }
2085
+ interface CompiledFilter<T> {
2086
+ /** True = item passes (stays fully visible); false = item fails the mask. */
2087
+ test(item: T): boolean;
2088
+ /** Live tally of predicate throws (function specs only; the expr path
2089
+ * never throws). The caller converts a nonzero count to a 'filter-error'
2090
+ * diagnostic after the evaluation pass. */
2091
+ readonly errors: FilterErrorAggregate;
2092
+ }
2093
+ /** Compiles the node lane of a FilterSpec (absent selector = pass-all). */
2094
+ declare function compileNodeFilter<N = Record<string, unknown>, E = Record<string, unknown>>(spec: FilterSpec<N, E>): CompiledFilter<GraphNode<N>>;
2095
+ /** Compiles the edge lane of a FilterSpec (absent selector = pass-all). */
2096
+ declare function compileEdgeFilter<N = Record<string, unknown>, E = Record<string, unknown>>(spec: FilterSpec<N, E>): CompiledFilter<AcceptedEdge<E>>;
2097
+ /**
2098
+ * Canonical structural key for a FilterSpec or FilterExpr: object keys are
2099
+ * sorted, arrays preserve order, undefined-valued keys are omitted, and
2100
+ * function predicates map to a unique reference-identity token. Two inputs
2101
+ * with equal keys are §9.1-equivalent — the instance skips re-evaluation
2102
+ * when the key of an incoming filter matches the active one (identity churn
2103
+ * with equal structure never re-evaluates; swapping a function reference
2104
+ * always does).
2105
+ */
2106
+ declare function canonicalFilterKey(specOrExpr: unknown): string;
2107
+
2108
+ /**
2109
+ * §9.1 soft-mask kernel (S9-T02/T03) — failure COUNTERS, never Sets and
2110
+ * never per-source bit positions (so there is no 32-dimension cap).
2111
+ *
2112
+ * The mask owns four Uint16 counter lanes: hideFailures and dimFailures for
2113
+ * nodes AND edges. Every acquired {@link MaskSource} contributes +1 to a
2114
+ * lane counter for each slot it currently fails and -1 when that slot
2115
+ * re-enters. A slot is visible iff hideFailures === 0 and dimmed iff visible
2116
+ * AND dimFailures > 0 (spec §9.1 mask mechanics).
2117
+ *
2118
+ * Delta discipline: each source keeps its previous failing membership as
2119
+ * per-source Uint8 flag columns plus a dense slot list, so replacing a
2120
+ * membership touches counters ONLY for slots whose membership actually
2121
+ * changed (O(1) detection per presented slot). Counter transitions across
2122
+ * zero append the slot to reusable dirty lists; {@link SoftMask.drainDirty}
2123
+ * compares against the state at the previous drain, so a fail-then-restore
2124
+ * inside one drain period nets to no emission.
2125
+ *
2126
+ * {@link SoftMask.applyNodeCascadeToEdges} implements §9's SINGLE edge-
2127
+ * survival rule over the mask lane: an edge hide-fails iff EITHER endpoint
2128
+ * hide-fails (equivalently: survives iff BOTH endpoints survive —
2129
+ * scope.ts `cascadeEdges` is the hard-scope variant of the same rule). It is
2130
+ * fed through one dedicated internal cascade source from the node hide lane:
2131
+ * O(E) recompute per call, but only edges whose state changed produce
2132
+ * counter deltas.
2133
+ *
2134
+ * Overflow guard: increments clamp at 0xFFFF and latch a one-time
2135
+ * {@link SoftMask.overflowed} flag the caller reports (an implementation may
2136
+ * widen to Uint32 without changing semantics, §9.1). Debug builds assert
2137
+ * balanced increments/decrements whenever a clear()/release() leaves zero
2138
+ * held memberships.
2139
+ */
2140
+ /** Default muted alpha for dimmed entities (§9.1 'dim' mode). */
2141
+ declare const DIM_ALPHA_DEFAULT = 0.15;
2142
+ /**
2143
+ * A handle contributing failure memberships to the mask. Setting a lane
2144
+ * REPLACES that lane's previous membership for this source (the kernel
2145
+ * applies only the delta). `null` = empty membership for that lane; an
2146
+ * omitted `dimIdx` leaves the dim lane untouched.
2147
+ */
2148
+ interface MaskSource {
2149
+ readonly name: string;
2150
+ setNodeFailures(hideIdx: Iterable<number> | null, dimIdx?: Iterable<number> | null): void;
2151
+ setEdgeFailures(hideIdx: Iterable<number> | null, dimIdx?: Iterable<number> | null): void;
2152
+ /** Empties all four lane memberships (the source stays usable). */
2153
+ clear(): void;
2154
+ /** clear() + permanently retires the handle (further set/clear throws;
2155
+ * release() itself is idempotent). */
2156
+ release(): void;
2157
+ }
2158
+ /**
2159
+ * One drain payload. The four index arrays are REUSED across drains — copy
2160
+ * before the next drainDirty() call if you need to keep them.
2161
+ */
2162
+ interface MaskDrain {
2163
+ /** Node slots whose hide-visibility (hideFailures 0 ↔ nonzero) flipped
2164
+ * since the previous drain. */
2165
+ nodes: readonly number[];
2166
+ /** Edge slots whose hide-visibility flipped since the previous drain. */
2167
+ edges: readonly number[];
2168
+ /** Node slots whose dim state (dimFailures 0 ↔ nonzero) flipped — the
2169
+ * alpha lane. Consumers recompute alpha over `nodes ∪ nodesAlpha` (a hide
2170
+ * flip also changes the effective alpha; a slot may appear in both). */
2171
+ nodesAlpha: readonly number[];
2172
+ /** Edge slots whose dim state flipped. */
2173
+ edgesAlpha: readonly number[];
2174
+ nodeVisibleCount: number;
2175
+ edgeVisibleCount: number;
2176
+ }
2177
+ declare class SoftMask {
2178
+ private nodeCap;
2179
+ private edgeCap;
2180
+ private readonly nodeHideLane;
2181
+ private readonly nodeDimLane;
2182
+ private readonly edgeHideLane;
2183
+ private readonly edgeDimLane;
2184
+ private readonly sources;
2185
+ /** Dedicated internal source implementing the §9 node→edge cascade. */
2186
+ private cascadeSource;
2187
+ private overflowedFlag;
2188
+ /** Total memberships currently held across all sources and lanes. */
2189
+ private totalHeld;
2190
+ constructor(nodeCapacity: number, edgeCapacity: number);
2191
+ get nodeCapacity(): number;
2192
+ get edgeCapacity(): number;
2193
+ /** One-time latch: some counter hit 0xFFFF and an increment was dropped.
2194
+ * Counts may drift afterwards; the caller reports it (§9.1 overflow guard). */
2195
+ get overflowed(): boolean;
2196
+ get nodeHideFailures(): Uint16Array;
2197
+ get nodeDimFailures(): Uint16Array;
2198
+ get edgeHideFailures(): Uint16Array;
2199
+ get edgeDimFailures(): Uint16Array;
2200
+ /**
2201
+ * Grows capacities for structure changes (existing slot state is
2202
+ * preserved; new slots start fully visible). Capacities never shrink —
2203
+ * a smaller value is a no-op for that dimension.
2204
+ */
2205
+ grow(nodeCapacity: number, edgeCapacity: number): void;
2206
+ /** Registers a new failure source. No cap on source count (§9.1). */
2207
+ acquire(name: string): MaskSource;
2208
+ /**
2209
+ * §9 edge cascade over the mask lane: recomputes, from the CURRENT node
2210
+ * hide lane, the set of edges with at least one hidden endpoint, and feeds
2211
+ * it to the dedicated internal cascade source (edge hide lane only).
2212
+ * `links` is the flat `[src0, tgt0, src1, tgt1, …]` node-slot pair buffer
2213
+ * (§7.1 CSR input shape); edge slot i has endpoints at links[2i]/[2i+1].
2214
+ * O(E) scan per call — typically once per drain — but only edges whose
2215
+ * cascade state changed produce counter deltas (and thus dirty entries).
2216
+ * Edges beyond `links.length / 2` are treated as having no hidden
2217
+ * endpoint (their previous cascade contribution, if any, is removed).
2218
+ */
2219
+ applyNodeCascadeToEdges(links: Uint32Array): void;
2220
+ /**
2221
+ * Drains the zero-crossing dirty lists accumulated since the previous
2222
+ * drain. Only NET flips are emitted (state compared against the previous
2223
+ * drain). The returned index arrays are reused by the next drain.
2224
+ */
2225
+ drainDirty(): MaskDrain;
2226
+ visibleNodeCount(): number;
2227
+ visibleEdgeCount(): number;
2228
+ /** Visible iff hideFailures === 0 (out-of-range slots read not-visible). */
2229
+ isNodeVisible(index: number): boolean;
2230
+ isEdgeVisible(index: number): boolean;
2231
+ /** Dimmed iff visible AND dimFailures > 0 (§9.1). */
2232
+ isNodeDimmed(index: number): boolean;
2233
+ isEdgeDimmed(index: number): boolean;
2234
+ /** 1 (fully visible) | dimAlpha (dimmed) | 0 (hidden). */
2235
+ nodeAlpha(index: number, dimAlpha?: number): number;
2236
+ edgeAlpha(index: number, dimAlpha?: number): number;
2237
+ /**
2238
+ * Replaces one source-lane membership with `incoming` (null = empty),
2239
+ * applying counter deltas ONLY for slots whose membership changed.
2240
+ * Incoming slots are validated up front so a RangeError never leaves the
2241
+ * membership partially applied; duplicates within `incoming` count once.
2242
+ */
2243
+ private applyMembership;
2244
+ private increment;
2245
+ private decrement;
2246
+ /** Records the first zero-crossing of a slot per drain period, remembering
2247
+ * whether the counter was zero at the previous drain. */
2248
+ private markDirty;
2249
+ private drainLane;
2250
+ private clearSource;
2251
+ /** Debug balanced-increment assert: whenever no source holds any
2252
+ * membership, every counter must read zero (skipped once overflowed —
2253
+ * clamped increments legitimately drift the books). */
2254
+ private assertBalancedIfIdle;
2255
+ }
2256
+
2257
+ /**
2258
+ * §16.6 crossfilter — typed-column engine (v0.7 node-dimension subset).
2259
+ *
2260
+ * `TypedColumnCrossfilter` is the columnar backend the instance wraps into the
2261
+ * public `CrossfilterSession`. Design (crossfilter.js lineage):
2262
+ *
2263
+ * - **Parse once.** `build()` extracts every dimension exactly once into typed
2264
+ * columns (numeric/temporal → `Float64Array` epoch-ms/values, categorical →
2265
+ * dictionary codes). §8 hygiene: non-finite numerics, unparseable temporals,
2266
+ * and non-string/non-finite categorical values are excluded from the
2267
+ * dimension (tracked per dimension in `excludedRows`; the slot is marked
2268
+ * invalid). Temporal parsing: numbers are epoch ms verbatim; strings go
2269
+ * through `Date.parse` (ES2022 parses `'YYYY-MM-DD'` as UTC midnight);
2270
+ * `Date` instances use `getTime()`.
2271
+ * - **O(Δ) brushes.** Each range dimension keeps a one-time argsorted
2272
+ * permutation. A brush move performs two binary searches and walks ONLY the
2273
+ * symmetric difference between the old and new in-range windows; categorical
2274
+ * brushes walk only the per-code slot lists whose excluded flag changed. The
2275
+ * brush path never re-sorts. A per-slot, per-dimension pass flag plus a
2276
+ * global per-slot failure counter make a row selection-visible iff it fails
2277
+ * zero dimensions. Hygiene-excluded rows fail any non-null brush on that
2278
+ * dimension (they cannot be in range / in a category) and pass a null brush.
2279
+ * - **Deltas out.** `setBrush` returns the slots whose overall visibility
2280
+ * flipped (`hidden`/`shown`) for the instance's mask source.
2281
+ * - **Lazy dual layer.** `summarize()` returns immutable summaries whose
2282
+ * `filtered` layer counts rows passing every OTHER dimension's brush plus an
2283
+ * external node-mask predicate (`setExternalMask`). v0.7 recomputes a dirty
2284
+ * dimension's filtered layer lazily per `summarize()` call — O(rows) per
2285
+ * dirty summarize, documented and acceptable at this tier (§17 fixtures own
2286
+ * the perf claims; the O(Δ) guarantee covers the brush/visibility path).
2287
+ * - **Revisions & notify.** `selectionRevision` starts at 0 and advances
2288
+ * exactly once per observable `setBrush` (a brush state change is observable
2289
+ * via `getBrush` even when no row flips). v0.7 is synchronous, so
2290
+ * latest-call-wins degenerates to "every call applies immediately in call
2291
+ * order". Subscribers fire once per observable change after state is
2292
+ * consistent; synchronous re-entrancy (a subscriber mutating the engine) is
2293
+ * coalesced into one trailing notification pass. Model updates
2294
+ * (`appendRows`/`replaceAll`) keep the current `selectionRevision` (§16.6)
2295
+ * but do notify. External-mask changes notify (summaries changed) without
2296
+ * advancing the selection revision.
2297
+ * - **Incremental append.** `appendRows` extends columns/codes in place and
2298
+ * merges the pre-sorted old permutation with the sorted new block
2299
+ * (permutation merge — never a full re-argsort); bins extend incrementally
2300
+ * unless the domain grew (then a re-bin, still sort-free). Brushes persist
2301
+ * by key and are re-applied to the NEW slots only; the returned delta covers
2302
+ * only new slots (`shown` = new passing, `hidden` = new failing).
2303
+ * `replaceAll` rebuilds columns but preserves brushes by dimension key,
2304
+ * re-applying them as one combined delta against an all-visible baseline of
2305
+ * the new roster (`shown` is always empty). `replaceAll` clears the external
2306
+ * mask (slot indices changed meaning); `appendRows` keeps it and treats new
2307
+ * slots as passing until the instance re-supplies it.
2308
+ */
2309
+
2310
+ /** Default histogram bin count for numeric/temporal dimensions (§16.6). */
2311
+ declare const DEFAULT_BIN_COUNT = 24;
2312
+ /** Slots whose overall selection-visibility flipped in one operation. */
2313
+ interface BrushDelta {
2314
+ /** Slots that flipped visible → hidden. */
2315
+ hidden: number[];
2316
+ /** Slots that flipped hidden → visible. */
2317
+ shown: number[];
2318
+ }
2319
+ /** Test instrumentation counters (see crossfilter.test.ts O(Δ) evidence). */
2320
+ interface CrossfilterStats {
2321
+ /** Slots touched by brush delta walks (the O(Δ) loop). */
2322
+ slotsWalked: number;
2323
+ /** Full-column argsorts (build/replaceAll only — never append or brush). */
2324
+ fullSorts: number;
2325
+ /** Permutation merges performed by appendRows. */
2326
+ permutationMerges: number;
2327
+ }
2328
+ declare class TypedColumnCrossfilter<N = Record<string, unknown>> {
2329
+ /** Test instrumentation; see CrossfilterStats. Reset with resetStats(). */
2330
+ readonly stats: CrossfilterStats;
2331
+ private dims;
2332
+ private byKey;
2333
+ private specs;
2334
+ private n;
2335
+ /** Per-slot count of dimensions the slot currently fails. */
2336
+ private failCount;
2337
+ private externalMask;
2338
+ private revision;
2339
+ private readonly subscribers;
2340
+ private built;
2341
+ private disposed;
2342
+ private notifying;
2343
+ private renotify;
2344
+ /** Monotonic from 0; advances exactly once per observable setBrush change. */
2345
+ get selectionRevision(): number;
2346
+ resetStats(): void;
2347
+ /**
2348
+ * (Re)initialize columns from scratch. Clears all brushes and the external
2349
+ * mask (use replaceAll to rebuild while preserving brushes by key). Does not
2350
+ * notify and does not touch selectionRevision.
2351
+ */
2352
+ build(nodes: readonly GraphNode<N>[], specs: readonly DimensionSpec<N>[]): void;
2353
+ rowCount(): number;
2354
+ getBrush(key: string): BrushState;
2355
+ isSlotVisible(slot: number): boolean;
2356
+ /** Fresh array of selection-visible slots, ascending. */
2357
+ visibleSlots(): number[];
2358
+ /**
2359
+ * Apply a brush (latest-call-wins: v0.7 is synchronous, so each call applies
2360
+ * immediately in call order). Returns the slots whose overall
2361
+ * selection-visibility flipped. A no-op (deep-equal brush) returns empty
2362
+ * deltas without advancing selectionRevision or notifying.
2363
+ */
2364
+ setBrush(key: string, brush: BrushState): BrushDelta;
2365
+ /**
2366
+ * External node-mask predicate for the joint "filtered" second layer (the
2367
+ * instance wires the §9.1 filter-prop node mask in). Affects summaries only,
2368
+ * never selection visibility. Length must equal rowCount(). Notifies on
2369
+ * observable change; does NOT advance selectionRevision.
2370
+ */
2371
+ setExternalMask(passSlots: Uint8Array | null): void;
2372
+ /**
2373
+ * Immutable summary. The filtered layer is recomputed lazily when dirty —
2374
+ * O(rows) per dirty summarize (v0.7 tier; see module doc). Returned objects
2375
+ * are frozen and never mutated by later operations.
2376
+ */
2377
+ summarize(key: string): DimensionSummary;
2378
+ /**
2379
+ * Incrementally extend columns with new rows (S9-T09 subset): no full
2380
+ * rebuild, no full re-argsort — the pre-sorted old permutation merges with
2381
+ * the sorted new block. Brushes stay by key and are applied to the NEW slots
2382
+ * only; the returned delta covers only new slots. Keeps selectionRevision;
2383
+ * notifies when rows were appended.
2384
+ */
2385
+ appendRows(newNodes: readonly GraphNode<N>[]): BrushDelta;
2386
+ /**
2387
+ * Full rebuild from a new roster, PRESERVING brushes by dimension key and
2388
+ * re-applying them as one combined delta against an all-visible baseline of
2389
+ * the new roster (shown is always empty). Clears the external mask. Keeps
2390
+ * selectionRevision; notifies once.
2391
+ */
2392
+ replaceAll(nodes: readonly GraphNode<N>[]): BrushDelta;
2393
+ /** Fires once per observable selection/summary change, state consistent. */
2394
+ subscribe(cb: () => void): () => void;
2395
+ /** Idempotent; every other method throws afterwards. */
2396
+ dispose(): void;
2397
+ private ensureLive;
2398
+ private ensureBuilt;
2399
+ private dim;
2400
+ private buildDims;
2401
+ private buildRangeDim;
2402
+ private buildCatDim;
2403
+ /** Recompute domain, bin edges, per-slot bin index, and totals. Sort-free. */
2404
+ private rebinRange;
2405
+ private binIndex;
2406
+ private flip;
2407
+ private walkSorted;
2408
+ private walkList;
2409
+ private applyRangeTransition;
2410
+ private applyCatTransition;
2411
+ private appendRange;
2412
+ private appendCat;
2413
+ private recomputeFiltered;
2414
+ /** One callback pass per observable change; synchronous re-entrancy coalesces. */
2415
+ private notify;
2416
+ }
2417
+
2418
+ /**
2419
+ * §11 scales & domains (S10-T01/T07) — canonical scale keying, default
2420
+ * palettes, sRGB color interpolation, domain-state machinery, and stable
2421
+ * categorical assignment. Pure and engine-free; the instance/projection
2422
+ * layers consume these primitives when a styling channel carries a `Scale`.
2423
+ *
2424
+ * Semantics (orbit-spec.md §8/§11):
2425
+ * - Scales are plain descriptors compared by CANONICAL STRUCTURAL VALUE —
2426
+ * equal inline literals produce equal keys and never reproject. A function
2427
+ * `by` keys by reference identity (WeakMap token), never by source text.
2428
+ * - Domains default to the whole dataset revision and stay FROZEN across
2429
+ * masking/brushing/isolation ('dataset' scope): the same metric value never
2430
+ * changes visual meaning because a user brushed. 'hard-scope'/'visible' are
2431
+ * explicit opt-ins that recompute when the caller's scope generation bumps;
2432
+ * streaming 'expand' permits monotonic domain growth on recompute.
2433
+ * - Explicit numeric domains always win verbatim (never computed, cached, or
2434
+ * unioned).
2435
+ * - Categorical values declared in `domain` take their declared position;
2436
+ * out-of-domain values take a stable fnv-1a hash slot — NEVER first-seen
2437
+ * order, so arrival order can never recolor a category.
2438
+ * - §8 numeric hygiene: null/non-finite metric values resolve to `null`
2439
+ * (caller falls back to the default style) and are excluded from domains.
2440
+ */
2441
+
2442
+ type SequentialScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
2443
+ kind: 'sequential';
2444
+ }>;
2445
+ type CategoricalScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
2446
+ kind: 'categorical';
2447
+ }>;
2448
+ type DivergingScale<T, N = Record<string, unknown>> = Extract<Scale<T, N>, {
2449
+ kind: 'diverging';
2450
+ }>;
2451
+ /**
2452
+ * Canonical structural key for a Scale descriptor: object keys sorted, array
2453
+ * order preserved, undefined-valued keys omitted, and a function `by` mapped
2454
+ * to a unique reference-identity token (same WeakMap approach — and canonical
2455
+ * grammar — as `canonicalFilterKey`). Two scales with equal keys are
2456
+ * §11-equivalent: equal inline literals MUST and DO produce equal keys, so
2457
+ * identity churn never reprojects; swapping a function reference always does.
2458
+ */
2459
+ declare function canonicalScaleKey<T, N>(scale: Scale<T, N>): string;
2460
+ /** 12 brand-neutral categorical hues distinguishable on light AND dark. */
2461
+ declare const CATEGORICAL_PALETTE: readonly string[];
2462
+ /** Default sequential ramp endpoints (low → high). */
2463
+ declare const SEQUENTIAL_RANGE_DEFAULT: readonly [string, string];
2464
+ /** Default diverging stops (low → mid → high). */
2465
+ declare const DIVERGING_RANGE_DEFAULT: readonly [string, string, string];
2466
+ /**
2467
+ * Interpolates between two CSS color strings in sRGB (component-wise,
2468
+ * including alpha) and returns an `rgba(r, g, b, a)` string. `t` is clamped
2469
+ * to [0,1] (non-finite t → 0). Unparseable endpoints fall back to the
2470
+ * projection lane's neutral gray — never a throw, never NaN output.
2471
+ */
2472
+ declare function interpolateColor(a: string, b: string, t: number): string;
2473
+ /**
2474
+ * Sequential color: maps `value` across `domain` onto the two-stop ramp.
2475
+ * Returns null (caller renders the default style, §8 hygiene) when the value
2476
+ * is null/non-finite or the domain is null.
2477
+ */
2478
+ declare function sequentialColor<N = Record<string, unknown>>(scale: SequentialScale<string, N>, value: number | null, domain: readonly [number, number] | null): string | null;
2479
+ /**
2480
+ * Diverging color: `mid` maps to the middle stop exactly; each half
2481
+ * interpolates independently ([domain[0]..mid] over range[0..1],
2482
+ * [mid..domain[1]] over range[1..2]). A degenerate half collapses to the
2483
+ * middle stop. Null/non-finite value or null domain → null.
2484
+ */
2485
+ declare function divergingColor<N = Record<string, unknown>>(scale: DivergingScale<string, N>, value: number | null, domain: readonly [number, number] | null): string | null;
2486
+ /**
2487
+ * Sequential size: linear map of `value` across `domain` onto [lo,hi],
2488
+ * clamped at the range endpoints. Degenerate domains yield the range
2489
+ * midpoint; null/non-finite values and null domains yield null (default
2490
+ * style — NaN is never handed to a size buffer, §8).
2491
+ */
2492
+ declare function sequentialSize(range: readonly [number, number], value: number | null, domain: readonly [number, number] | null): number | null;
2493
+ /**
2494
+ * [min,max] over the finite numbers in `values` (§8 hygiene: null and
2495
+ * non-finite entries are excluded), or null when nothing qualifies. A single
2496
+ * qualifying value yields a degenerate [v,v] domain.
2497
+ */
2498
+ declare function computeNumericDomain(values: Iterable<number | null>): [number, number] | null;
2499
+ interface ResolveDomainArgs {
2500
+ /** Cache key — `canonicalScaleKey(scale)` (the metric name is part of the
2501
+ * descriptor, so the key already discriminates by metric). Opaque here. */
2502
+ key: string;
2503
+ /** Explicit caller domain — returned VERBATIM; never computed, cached, or
2504
+ * expand-unioned (§11: explicit always wins). */
2505
+ explicit?: readonly [number, number] | undefined;
2506
+ /** Defaults: scope 'dataset', streaming 'freeze-per-revision'. */
2507
+ policy?: DomainPolicy | undefined;
2508
+ /** The dataset revision the caller is resolving against. */
2509
+ datasetRevision: number | string;
2510
+ /** Caller-owned generation counter for 'hard-scope'/'visible' scopes
2511
+ * (bump = recompute). IGNORED under 'dataset' scope — masking/brushing
2512
+ * must never change what a color means. */
2513
+ scopeGeneration?: number | undefined;
2514
+ /** Source lineage this resolve belongs to (F11-02: datasetKey + source
2515
+ * revision). `streaming: 'expand'` unions only WITHIN one lineage — a
2516
+ * source replacement starts fresh instead of unioning dead extrema.
2517
+ * Callers that omit it keep the legacy always-union behavior. */
2518
+ lineage?: string | undefined;
2519
+ /** Domain producer (typically wraps computeNumericDomain over the metric
2520
+ * column). Called at most once per freeze coordinate. */
2521
+ compute: () => readonly [number, number] | null;
2522
+ }
2523
+ /**
2524
+ * Per-instance domain freezer keyed by canonical scale key (§11).
2525
+ *
2526
+ * FREEZE-PER-REVISION: under scope 'dataset' the domain is computed ONCE per
2527
+ * {key, datasetRevision} — repeat resolves return the frozen value without
2528
+ * calling `compute`, no matter how the underlying data was masked or brushed
2529
+ * in between. 'hard-scope'/'visible' additionally recompute when the caller's
2530
+ * scopeGeneration bumps. Streaming 'expand' turns every recompute into a
2531
+ * monotonic union with the previous domain (the domain only ever grows);
2532
+ * 'freeze-per-revision' replaces it. Explicit domains bypass the store.
2533
+ */
2534
+ declare class DomainStore {
2535
+ private readonly entries;
2536
+ resolveDomain(args: ResolveDomainArgs): readonly [number, number] | null;
2537
+ /**
2538
+ * Dataset replace: `revision` is the revision now current — every entry
2539
+ * frozen for any OTHER revision is dropped (including expand-union lineage,
2540
+ * so a replaced dataset never unions against dead data). Entries already
2541
+ * at `revision` survive.
2542
+ */
2543
+ invalidateDataset(revision: number | string): void;
2544
+ /** Full reset (dataset identity change / instance teardown). */
2545
+ clear(): void;
2546
+ }
2547
+ /**
2548
+ * Palette slot for a categorical value: values declared in `domain` take
2549
+ * their declared position (mod palette length — fixed order → stable colors);
2550
+ * out-of-domain values take fnv-1a(value) mod palette length — stable across
2551
+ * sessions and arrival orders, NEVER first-seen order. Returns -1 for an
2552
+ * empty palette.
2553
+ */
2554
+ declare function categoricalIndex(domain: readonly string[] | undefined, palette: readonly unknown[], value: string): number;
2555
+ /**
2556
+ * Stable legend row order: declared `domain` values FIRST in declared order
2557
+ * (including currently-empty categories), then extra seen values sorted
2558
+ * lexicographically. Duplicates (within `seen` or already declared) collapse.
2559
+ */
2560
+ declare function categoricalRows(domain: readonly string[] | undefined, seen: Iterable<string>): string[];
2561
+
2562
+ /**
2563
+ * DEF2 capability-policy module (spec §13; plan S1-T08/S4-T05).
2564
+ *
2565
+ * §13 (normative): for capabilities where the core owns a fallback path, the
2566
+ * core selects native-vs-fallback ONCE at mount from the engine's declared
2567
+ * `capabilities` record — never by sniffing method presence — and unsupported
2568
+ * *requested* props degrade loudly (a §5.1 diagnostic), never as silent no-ops.
2569
+ *
2570
+ * This module is the single place those decisions are made:
2571
+ * - {@link resolveEnginePolicy} turns the capability record + the host's
2572
+ * requested features into one frozen {@link EnginePolicy} at mount.
2573
+ * - {@link assertCapabilityMethodParity} is the mount-time dev-mode record
2574
+ * vs. method-surface assertion, restricted to honestly checkable pairs.
2575
+ * - {@link normalizeCommitForCapabilities} strips commit payload an engine
2576
+ * declared it cannot honor, so incapable adapters never see it.
2577
+ */
2578
+
2579
+ /** One loud degradation: a feature the host requested that the mounted engine
2580
+ * does not declare. Feeds the §5.1 dev diagnostic at mount. */
2581
+ interface EnginePolicyDegradation {
2582
+ readonly feature: string;
2583
+ readonly reason: string;
2584
+ }
2585
+ /** Host-requested capability-gated features, gathered from mount-time props
2586
+ * (`edgeArrows` prop → edgeArrows; image-bearing nodeStyle/atlas → images). */
2587
+ interface RequestedEngineFeatures {
2588
+ edgeArrows?: boolean;
2589
+ images?: boolean;
2590
+ /** §16.3 stage-4: a `clusters` spec is active this session. */
2591
+ clusters?: boolean;
2592
+ }
2593
+ /**
2594
+ * The frozen mount-time native-vs-fallback record (§13). Evaluated exactly
2595
+ * once per mount from `EngineCapabilities` and never revisited — capability
2596
+ * records are static declarations fixed at engine construction, so the policy
2597
+ * must not drift even if a caller mutates the input record afterwards.
2598
+ */
2599
+ interface EnginePolicy {
2600
+ /** §16.12 arrowheads: engine-drawn, or the prop is inert (+ dev warning). */
2601
+ readonly edgeArrows: 'native' | 'inert';
2602
+ /** §8 image sprites: atlas-backed, or the placeholder glyph with refs
2603
+ * retained for a future compatible engine. */
2604
+ readonly images: 'native' | 'placeholder';
2605
+ /** §13 link hover/click: engine events, or the core's CPU grid fallback. */
2606
+ readonly linkPicking: 'native' | 'cpu-fallback';
2607
+ /** §16.3 stage-4 cluster force: engine-applied, or inert (membership,
2608
+ * labels, and centroids are core-owned and unaffected). */
2609
+ readonly clusterForce: 'native' | 'inert';
2610
+ /** Channels eligible for ranged (partial) uploads; empty = full replaces. */
2611
+ readonly rangedChannels: ReadonlySet<EngineBufferChannel>;
2612
+ /** Exactly one entry per REQUESTED-but-unsupported feature; requested and
2613
+ * supported — or simply never requested — contributes nothing. */
2614
+ readonly degradations: readonly EnginePolicyDegradation[];
2615
+ }
2616
+ /**
2617
+ * Resolve the mount-time engine policy from the declared capability record.
2618
+ *
2619
+ * Decisions come from `capabilities` ONLY — method sniffing is forbidden by
2620
+ * §13. The result is deep-frozen and holds a defensive copy of
2621
+ * `rangeUpdates`, so mutating the input record afterwards changes nothing.
2622
+ * A degradation entry exists only for features the host actually requested
2623
+ * that the engine does not declare; unrequested gaps stay silent.
2624
+ */
2625
+ declare function resolveEnginePolicy(capabilities: EngineCapabilities, requested: RequestedEngineFeatures): EnginePolicy;
2626
+ /**
2627
+ * Mount-time dev-mode assertion: does the declared capability record agree
2628
+ * with the engine's method surface? Returns a list of human-readable
2629
+ * mismatches (empty = consistent).
2630
+ *
2631
+ * Why so few checks? §13's illustrative parity assertion
2632
+ * (`capabilities.linkPicking === (typeof engine.linkAt === 'function')`)
2633
+ * presumes the spec's full method surface; the v0.1 adapter contract
2634
+ * deliberately narrows it, leaving most capabilities with NO honestly
2635
+ * checkable method pair. Those defer to the §19 conformance suite, which
2636
+ * validates declared records against observed behavior:
2637
+ *
2638
+ * - `linkPicking`: native picking arrives through mount-time host events
2639
+ * (`onLinkClick`/`onLinkHover`), not a probeable `linkAt` method — there
2640
+ * is nothing on the engine object to compare the record against.
2641
+ * - `edgeArrows` / `pointImages` / `rangeUpdates`: honored inside
2642
+ * `commit()` payload handling (`config.linkArrows`, `resources`, ranged
2643
+ * uploads) with no distinguishing method; only behavior can validate them.
2644
+ * - `simulation`: `start()`/`pause()` are mandatory on every engine (static
2645
+ * engines no-op them), so method presence carries no signal either way.
2646
+ * - `pointsInPolygon`-family (`pointsInRect`, `captureScreenshot`,
2647
+ * `neighborIndices`, `screenToSpace`/`spaceToScreen`, `setPinnedIndices`,
2648
+ * `zoomToIndex`): optional-by-contract with no declaring capability bit —
2649
+ * absence is legitimate, so no check is fabricated for them.
2650
+ *
2651
+ * The one honest pair on this surface: `trackedPositions` declares position
2652
+ * readback works, so `getPositions` must actually be present.
2653
+ */
2654
+ declare function assertCapabilityMethodParity(engine: GraphEngine): string[];
2655
+ /**
2656
+ * Strip commit payload the engine's capability record says it cannot honor:
2657
+ * - `resources` (image atlas + per-point image index) unless `pointImages`;
2658
+ * - `config.linkArrows` unless `edgeArrows`;
2659
+ * - `config.cluster` unless `clusterForce` (§16.3 stage 4 — the core keeps
2660
+ * membership, labels, and centroids; only the FORCE is engine-side).
2661
+ * A `config` left empty by the strip is dropped entirely.
2662
+ *
2663
+ * IDENTITY-PRESERVING: when nothing needs stripping the SAME commit object
2664
+ * reference is returned, so downstream dirty/equality checks stay cheap. The
2665
+ * input commit is never mutated. `dropped` names each stripped payload path.
2666
+ */
2667
+ declare function normalizeCommitForCapabilities(commit: EngineCommit, capabilities: EngineCapabilities): {
2668
+ commit: EngineCommit;
2669
+ dropped: readonly string[];
2670
+ };
2671
+
2672
+ /**
2673
+ * §8 shared numeric hygiene (R-19-10) — THE single coercion layer for every
2674
+ * numeric consumer in orbit-core.
2675
+ *
2676
+ * Wherever a caller-supplied value feeds a numeric sink — size/width
2677
+ * projection buffers (§8), scale domains (§11), metric columns (§12),
2678
+ * crossfilter bins (§16.6), table lanes — the value is REQUIRED to route
2679
+ * through `coerceNumeric` / `coerceNumericInto`. Non-numeric and non-finite
2680
+ * inputs (including the string sentinels `"NaN"` / `"Infinity"` /
2681
+ * `"-Infinity"` that JSON transports smuggle through, §5) coerce to `null`:
2682
+ * the row falls back to the default style and is excluded from domain
2683
+ * computation. NaN NEVER escapes this module — not as a return value and not
2684
+ * into a GPU buffer.
2685
+ *
2686
+ * Coercion rules:
2687
+ * - numbers pass iff `Number.isFinite` (NaN / ±Infinity → null);
2688
+ * - strings are trimmed; empty → null; the case-insensitive sentinels
2689
+ * 'NaN' / 'Infinity' / '-Infinity' / '+Infinity' → null; anything else
2690
+ * parses via `Number(...)` and passes iff finite (so '1e3' → 1000 but
2691
+ * '12px' → null — `Number`, not `parseFloat`, so no partial prefixes);
2692
+ * - booleans, objects, arrays, functions, symbols, bigints, null, and
2693
+ * undefined → null (no `valueOf`/`toString` coercion side channels).
2694
+ *
2695
+ * FOLLOW-UP (do not fix here): `crossfilter.ts` predates this module and
2696
+ * carries its own inline finite checks; migrating it to route through
2697
+ * `coerceNumeric` is a tracked S10 follow-up so the sentinel-string rules
2698
+ * stay defined in exactly one place.
2699
+ */
2700
+ declare function coerceNumeric(value: unknown): number | null;
2701
+ /**
2702
+ * Buffer-writer variant for hot projection loops: coerces `value` and writes
2703
+ * it at `target[index]`, falling back to `fallback` when coercion yields
2704
+ * null. Returns true iff the caller's value was admitted (false = fallback
2705
+ * was written).
2706
+ *
2707
+ * The fallback itself is hygiene-checked (a non-finite fallback writes 0) so
2708
+ * NaN cannot reach the buffer through EITHER argument. Allocation-free.
2709
+ */
2710
+ declare function coerceNumericInto(target: Float32Array, index: number, value: unknown, fallback: number): boolean;
2711
+
2712
+ /**
2713
+ * §12 metrics (v0.8 subset): lazy degree-family primitives over the
2714
+ * maintained topology plus revision-gated admission of async metric columns.
2715
+ *
2716
+ * - The degree family (degree / inDegree / outDegree) is computed LAZILY on
2717
+ * first request per model revision, in ONE combined O(n + L) pass, and
2718
+ * cached as Float64Arrays until the model revision changes.
2719
+ * - SELF-LOOP SEMANTICS: a self-loop (a, a) contributes exactly 1 to each of
2720
+ * degree, inDegree, and outDegree of `a`. The CSR adjacency lists a
2721
+ * self-loop twice under its point (once per endpoint slot, see
2722
+ * adjacency.ts), so degree = CSR row length MINUS the point's self-loop
2723
+ * count; in/out come from a directed pass over the flat link pairs.
2724
+ * - Async columns join once against the accepted model (§12): 'index' align
2725
+ * is positional over accepted-base order; 'ids' align joins by id with
2726
+ * unknown ids counted+sampled, duplicate ids counted (last occurrence
2727
+ * wins), and absent rows null. Every value routes through `coerceNumeric`
2728
+ * (§8 hygiene, R-19-10). A column computed for a stale model revision is
2729
+ * DISCARDED with a diagnostic — admission is the correctness gate, abort
2730
+ * is only an optimization (§9.2).
2731
+ * - Storage encodes null as NaN inside Float64Arrays; `getMetricValue`
2732
+ * converts back at the boundary so NaN never escapes to callers.
2733
+ * - Admitted columns SHADOW the built-in degree family under the same name
2734
+ * (a precomputed/server 'degree' column wins; §12 precomputed path).
2735
+ */
2736
+
2737
+ /** Topology snapshot the degree family is computed from (§12). */
2738
+ interface MetricModelInput<N = Record<string, unknown>> {
2739
+ /** Accepted nodes in accepted-base order (index i ↔ point i). */
2740
+ nodes: readonly GraphNode<N>[];
2741
+ /** CSR adjacency over `links` (self-loops listed twice; adjacency.ts). */
2742
+ adjacency: Adjacency;
2743
+ /** Flat `[src0, tgt0, src1, tgt1, …]` directed point-index pairs. */
2744
+ links: Uint32Array;
2745
+ datasetRevision: number | string;
2746
+ modelRevision: number;
2747
+ }
2748
+ interface AdmitColumnsOptions {
2749
+ /** Accepted id → accepted-base index (the core map — no public copy). */
2750
+ nodeIndex: ReadonlyMap<NodeId, number>;
2751
+ /** Accepted node count ('index' align must match exactly). */
2752
+ count: number;
2753
+ /** The model revision current when the update carrying the columns was
2754
+ * ISSUED (the admission gate compares each column's own
2755
+ * `forModelRevision` stamp against this — I1). */
2756
+ modelRevision: number;
2757
+ }
2758
+ interface AdmitColumnsResult {
2759
+ /** Metric names admitted by this call, in input order. */
2760
+ admitted: readonly string[];
2761
+ diagnostics: readonly GraphDiagnostic[];
2762
+ }
2763
+ declare class MetricStore<N = Record<string, unknown>> {
2764
+ private model;
2765
+ private degreeCache;
2766
+ /** Admitted async columns by metric name; NaN encodes null. */
2767
+ private readonly columns;
2768
+ private degreePasses;
2769
+ /** Number of combined degree-family compute passes (test observability). */
2770
+ get degreeComputePasses(): number;
2771
+ /**
2772
+ * Installs the topology the degree family derives from. A changed
2773
+ * {datasetRevision, modelRevision} coordinate invalidates the lazy degree
2774
+ * cache AND drops every admitted column (their accepted-base alignment is
2775
+ * meaningless against a different model); re-setting the identical
2776
+ * coordinate keeps both.
2777
+ */
2778
+ setModel(model: MetricModelInput<N>): void;
2779
+ /**
2780
+ * Joins async metric columns against the accepted model (§12).
2781
+ * Revision-gated PER COLUMN (I1): a column whose issue-time
2782
+ * `forModelRevision` stamp differs from `opts.modelRevision` is discarded
2783
+ * (info diagnostic — a normal async race outcome, §9.2). A missing or
2784
+ * mismatched stamp is never defaulted to the current revision — that
2785
+ * would make the gate self-satisfying.
2786
+ * Structural rejections ('index' length mismatch, missing/mismatched ids)
2787
+ * emit ONE warning diagnostic per column; a joined column emits at most
2788
+ * one unknown-ids and one duplicate-ids diagnostic, each carrying a total
2789
+ * count and at most DIAGNOSTIC_SAMPLE_CAP sample ids.
2790
+ */
2791
+ admitColumns(columns: readonly MetricColumn[], opts: AdmitColumnsOptions): AdmitColumnsResult;
2792
+ /**
2793
+ * Allocation-free hot path: one map lookup + one typed-array read.
2794
+ * NaN-encoded nulls convert back to `null` at this boundary; unknown
2795
+ * metrics and out-of-range indices are `null`, never NaN.
2796
+ */
2797
+ getMetricValue(metric: MetricName, index: number): number | null;
2798
+ /**
2799
+ * Raw column in accepted-base order, or null when unavailable. NaN encodes
2800
+ * null (§12) — consumers exclude NaN slots from domains. Do NOT mutate:
2801
+ * this is the live cache, not a copy.
2802
+ */
2803
+ metricValues(metric: MetricName): Float64Array | null;
2804
+ /** True when the metric would resolve; never triggers a compute pass. */
2805
+ hasMetric(name: MetricName): boolean;
2806
+ private resolve;
2807
+ /** One combined O(n + L) pass computes all three family members. */
2808
+ private ensureDegrees;
2809
+ }
2810
+
2811
+ /**
2812
+ * §16.8 minimap / overview — CPU fallback lane (S11-T09/T10).
2813
+ *
2814
+ * v0.9 trim: no engine exposes `capabilities.overviewPass` yet (cosmos has no
2815
+ * second draw pass — M0 matrix), so this controller IS the minimap thumbnail
2816
+ * path: an O(n) CPU rasterization of the §7.1 position mirror into a small
2817
+ * RGBA dot field. It is a pure controller — no DOM, no canvas — the React
2818
+ * component blits the returned `Uint8ClampedArray` into an `ImageData`/canvas
2819
+ * and draws the viewport rectangle on top (O(1) from `getViewport()`, fully
2820
+ * decoupled from thumbnail refresh cadence).
2821
+ *
2822
+ * Refresh cadence (spec §16.8): ≤ 2 Hz while the simulation is hot (positions
2823
+ * change continuously on the GPU, so the epoch is ignored), ≤ 1 Hz after a
2824
+ * change while idle, and ZERO work while idle with an unchanged positions
2825
+ * epoch — `shouldRefresh` is the single throttle gate and latches its clock /
2826
+ * epoch only when it answers true (a `true` must be followed by one
2827
+ * `rasterize()`).
2828
+ *
2829
+ * Orientation: minimap pixel y grows DOWNWARD while world (space) y grows
2830
+ * upward — the same flip cosmos applies in its space→screen scale (dist
2831
+ * `scalePointY.domain([S, 0])`), so the thumbnail visually matches the main
2832
+ * canvas. `worldToMinimap`/`minimapToWorld` are exact inverses over the last
2833
+ * rasterized bounds.
2834
+ */
2835
+ /** Position mirror handed to the controller: interleaved x,y space coords. */
2836
+ interface OverviewScene {
2837
+ /** Interleaved `[x0, y0, x1, y1, …]`; may be longer than `count` pairs. */
2838
+ positions: Float32Array;
2839
+ /** Number of points (pairs) to read from `positions`. */
2840
+ count: number;
2841
+ }
2842
+ interface OverviewControllerOptions {
2843
+ /** Latest scene snapshot, or null when nothing is loaded yet. */
2844
+ getScene: () => OverviewScene | null;
2845
+ /**
2846
+ * Soft-filter visibility mask. Hidden points still rasterize (and still
2847
+ * contribute to bounds — the thumbnail must not re-frame when a filter
2848
+ * toggles) but at a dimmed alpha.
2849
+ */
2850
+ getVisible?: (index: number) => boolean;
2851
+ /** Square thumbnail edge in pixels. Default {@link OVERVIEW_SIZE_DEFAULT}. */
2852
+ size?: number;
2853
+ }
2854
+ /** World-space extent of the last rasterization (finite positions only). */
2855
+ interface OverviewBounds {
2856
+ minX: number;
2857
+ minY: number;
2858
+ maxX: number;
2859
+ maxY: number;
2860
+ }
2861
+ interface OverviewRaster {
2862
+ /** RGBA, `size * size * 4` bytes — blit via `new ImageData(bitmap, size)`. */
2863
+ bitmap: Uint8ClampedArray;
2864
+ bounds: OverviewBounds;
2865
+ }
2866
+ /** Spec §16.8: the CPU fallback rasterizes into a 256² target. */
2867
+ declare const OVERVIEW_SIZE_DEFAULT = 256;
2868
+ /** Hot-simulation refresh floor: ≤ 2 Hz. */
2869
+ declare const OVERVIEW_HOT_INTERVAL_MS = 500;
2870
+ /** Idle-after-change refresh floor: ≤ 1 Hz. */
2871
+ declare const OVERVIEW_IDLE_INTERVAL_MS = 1000;
2872
+ declare class OverviewController {
2873
+ private readonly getScene;
2874
+ private readonly getVisible;
2875
+ readonly size: number;
2876
+ /** Transform of the LAST rasterization; null until rasterize() succeeds. */
2877
+ private frame;
2878
+ private lastRefreshMs;
2879
+ private lastEpoch;
2880
+ constructor(options: OverviewControllerOptions);
2881
+ /**
2882
+ * The single throttle gate (see module header). Latches `nowMs`/`epoch`
2883
+ * when it returns true, so each `true` accounts for exactly one refresh:
2884
+ *
2885
+ * - hot (`simulationRunning`): time-gated only (≤ 2 Hz) — the epoch is
2886
+ * ignored because positions change continuously without epoch advances;
2887
+ * - idle: refresh only when the epoch ADVANCED since the last refresh,
2888
+ * time-gated at ≤ 1 Hz;
2889
+ * - idle + unchanged epoch: always false — zero work, forever.
2890
+ */
2891
+ shouldRefresh(nowMs: number, simulationRunning: boolean, epoch: number): boolean;
2892
+ /**
2893
+ * Rasterizes the current scene into a fresh `size²` RGBA dot field: 1 px
2894
+ * white dots whose alpha ACCUMULATES on overlap (heatmap-ish density),
2895
+ * dimmed for mask-hidden points; NaN pairs (§7.3 tombstones) are skipped.
2896
+ * World bounds map into the thumbnail with a 5 % edge padding at a UNIFORM
2897
+ * scale (aspect preserved, centered on the short axis) and a downward pixel
2898
+ * y (see module header). Returns null when there is no scene or no point.
2899
+ */
2900
+ rasterize(): OverviewRaster | null;
2901
+ /**
2902
+ * World (space) → minimap pixel coords over the LAST rasterized frame
2903
+ * (fractional; callers round for pixel work). Null before any rasterize.
2904
+ */
2905
+ worldToMinimap(x: number, y: number): [number, number] | null;
2906
+ /** Exact inverse of {@link worldToMinimap}. Null before any rasterize. */
2907
+ minimapToWorld(px: number, py: number): [number, number] | null;
2908
+ }
2909
+
2910
+ /**
2911
+ * §16.14 SVG export (S15 export lane) — the M7 pure module.
2912
+ *
2913
+ * ENGINE-INDEPENDENT BY CONSTRUCTION, not by discipline: positions and
2914
+ * projected styles come IN as a plain descriptor, vector markup goes OUT as a
2915
+ * string. No engine, no instance, no DOM — the post-v1 server snapshot path
2916
+ * (spec §13, R-13-53) reuses this verbatim under plain Node, and the test
2917
+ * suite runs it exactly that way. Keeping it engine-free is an M7
2918
+ * requirement, which is why it is a standalone module rather than instance
2919
+ * code that might one day be extracted.
2920
+ *
2921
+ * Output discipline (§16.14):
2922
+ * - One element per node/edge/label, assembled OFF-DOM by chunked
2923
+ * array-join — O(elements), never a live DOM node per element.
2924
+ * - Every label and attribute string is XML-escaped (§14's untrusted-content
2925
+ * rule in its XML form): a hostile label renders as literal text in every
2926
+ * downstream vector tool, never as markup.
2927
+ * - Bounded: above `maxElements` (default 50 000 — the practical ceiling for
2928
+ * downstream vector editors) rendering THROWS before assembling anything;
2929
+ * the instance wraps that into the typed `export-too-large` rejection, and
2930
+ * the raster-hybrid form (a PNG base layer plus a small vector overlay) is
2931
+ * the sanctioned way past it.
2932
+ */
2933
+ interface SvgSceneNode {
2934
+ x: number;
2935
+ y: number;
2936
+ /** Radius in output px (the caller halves its size-buffer diameter). */
2937
+ r: number;
2938
+ /** Any CSS color string (the caller projects RGBA buffers to rgba()). */
2939
+ color: string;
2940
+ }
2941
+ interface SvgSceneEdge {
2942
+ x1: number;
2943
+ y1: number;
2944
+ x2: number;
2945
+ y2: number;
2946
+ color: string;
2947
+ width: number;
2948
+ }
2949
+ interface SvgSceneLabel {
2950
+ x: number;
2951
+ y: number;
2952
+ /** UNTRUSTED text — escaped here, never pre-escaped by callers (double
2953
+ * escaping is a rendering bug, missing escaping is an injection). */
2954
+ text: string;
2955
+ color: string;
2956
+ /** Font size in px. Default 11. */
2957
+ size?: number;
2958
+ }
2959
+ interface SvgScene {
2960
+ width: number;
2961
+ height: number;
2962
+ background: string;
2963
+ nodes: readonly SvgSceneNode[];
2964
+ edges: readonly SvgSceneEdge[];
2965
+ labels?: readonly SvgSceneLabel[];
2966
+ /**
2967
+ * Raster-hybrid base layer: a data URI (typically the engine screenshot).
2968
+ * When present, `nodes` and `edges` are expected to be EMPTY — the raster
2969
+ * carries them — and only labels/overlay chrome emit as vectors.
2970
+ */
2971
+ rasterBase?: {
2972
+ href: string;
2973
+ };
2974
+ }
2975
+ interface RenderSvgOptions {
2976
+ /** Element budget across nodes+edges+labels. Default 50 000. */
2977
+ maxElements?: number;
2978
+ }
2979
+ declare const SVG_MAX_ELEMENTS_DEFAULT = 50000;
2980
+ /** Thrown (not returned) on budget overflow — a pure module has no
2981
+ * diagnostics lane; the instance converts this into `export-too-large`. */
2982
+ declare class SvgBudgetError extends Error {
2983
+ readonly elementCount: number;
2984
+ readonly limit: number;
2985
+ constructor(elementCount: number, limit: number);
2986
+ }
2987
+ declare function escapeXml(text: string): string;
2988
+ /**
2989
+ * Render a scene to an SVG document string. Pure: same input, same output,
2990
+ * byte for byte. Throws {@link SvgBudgetError} when the element budget is
2991
+ * exceeded — BEFORE assembling any markup.
2992
+ */
2993
+ declare function renderSvg(scene: SvgScene, opts?: RenderSvgOptions): string;
2994
+
2995
+ export { ATLAS_MAX_CONCURRENT_DEFAULT, ATLAS_MAX_ENTRIES_DEFAULT, ATLAS_MAX_RETRIES_DEFAULT, AcceptanceQueue, AcceptedEdge, AcceptedGraph, AccessibilityConfig, Accessor, type Adjacency, type AdmitColumnsOptions, type AdmitColumnsResult, type AdmitServiceResultArgs, BASE_PENDING_KEY, BRUSH_HISTORY_COALESCE_MS, BeginIngestOptions, type BrushDelta, BrushState, CATEGORICAL_PALETTE, type CategoricalScale, type CompiledFilter, type CreateGraphInstanceOptions, type CreateRequestContextArgs, CrossfilterSession, type CrossfilterStats, DEFAULT_BIN_COUNT, DIM_ALPHA_DEFAULT, DIVERGING_RANGE_DEFAULT, DimensionSpec, DimensionSummary, type DivergingScale, DomainPolicy, DomainStore, EDGE_PICK_TOLERANCE_PX, EdgeId, type EdgePickRoute, EdgePickingFacade, type EdgePickingFacadeOptions, type EnginePolicy, type EnginePolicyDegradation, type ExpandNodeResult, type ExpansionOverlayRecord, ExpansionService, type FetchLike, type FilterErrorAggregate, FilterExpr, FilterSpec, GRAPH_THEME_DARK, GRAPH_THEME_LIGHT, GraphDiagnostic, GraphEventMap, GraphEventName, GraphHostUpdate, type GraphInstance, GraphListenerControl, GraphNode, type GraphServices, GraphSnapshot, GraphStoreState, GraphTheme, type GraphViewState, type GroupByDerivation, GroupBySpec, type GroupRewrite, GroupSpec, type GroupValidationResult, HISTORY_LIMIT_DEFAULT, type HistoryCommand, type HistoryDepths, HistoryKernel, type HistoryKernelOptions, INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT, INGEST_MAX_PENDING_BYTES_DEFAULT, INGEST_OVERFLOW_FACTOR, type ImageAtlasBatch, ImageAtlasPipeline, type ImageAtlasPipelineOptions, type ImageDecode, type ImageResolver, IngestBatch, IngestSession, JsonValue, LABEL_MAX_VISIBLE_CAP, LABEL_MAX_VISIBLE_DEFAULT, type LabelCandidate, type LabelCandidateResult, type LabelCandidateViewport, LabelConfig, LabelPlacement, type LabelSubscriptions, type LinkPickGridSnapshot, LinkPickIndex, type LinkVisibilityMask, type LocalExpansionBase, type LocalSearchBase, type LocalSearchService, META_EDGE_MAX_WIDTH, type MaskDrain, type MaskSource, type MergeBase, type MergeResult, MetaEdge, type MetaEdgeRecord, MetricColumn, type MetricModelInput, MetricName, MetricStore, NodeId, OVERVIEW_HOT_INTERVAL_MS, OVERVIEW_IDLE_INTERVAL_MS, OVERVIEW_SIZE_DEFAULT, type OverviewBounds, OverviewController, type OverviewControllerOptions, type OverviewRaster, type OverviewScene, PHYSICAL_DEFAULT_LINK_WIDTH, PHYSICAL_DEFAULT_POINT_SIZE, PathOptions, PathResult, PathService, PendingExpansions, type ReconcileResult, Reconciler, type RegisterExpansionResult, RenderScene, type RenderSvgOptions, RequestContext, type RequestContextHandle, type RequestedEngineFeatures, type ResolveDomainArgs, ResolvedCluster, ResolvedGroup, type ResolvedScope, RevisionAwareService, RevisionDimension, type RevisionSnapshot, Revisions, type RowTally, SEARCH_CACHE_LIMIT, SEARCH_LIMIT_DEFAULT, SEARCH_SCAN_CHUNK, SEARCH_SCORE_EXACT_ID, SEARCH_SCORE_ID_PREFIX, SEARCH_SCORE_SUBSTRING, SEARCH_SCORE_TOKEN_START_BONUS, SEQUENTIAL_RANGE_DEFAULT, SUPER_NODE_MAX_SIZE, SVG_MAX_ELEMENTS_DEFAULT, Scale, type ScaleChannelInfo, type ScaleInfoRow, SceneGroups, SceneLinkRef, ScenePointRef, SearchActivation, SearchResult, type SearchService, type SelectLabelCandidatesArgs, SelectionState, type SequentialScale, type SerializableScale, type ServiceCacheKeyArgs, type SessionContribution, type SetViewStateResult, SoftMask, type StageResult, type StagingTallies, type StampedEdge, type StampedNode, SubgraphSpec, type SuperNodeRecord, SvgBudgetError, type SvgScene, type SvgSceneEdge, type SvgSceneLabel, type SvgSceneNode, TIMELINE_STEP_DEFAULT, TIMELINE_TICK_MS_DEFAULT, ThemeInput, TimelinePlayback, TypedColumnCrossfilter, VIEW_STATE_VERSION, type ViewBrushState, type ViewLayoutSpec, type ViewStateVerdict, type ViewStyling, ViewportState, admitServiceResult, assertCapabilityMethodParity, baseFromAccepted, baseFromContribution, buildAcceptedAdjacency, buildAdjacency, canonicalFilterKey, canonicalJson, canonicalScaleKey, cascadeEdges, categoricalIndex, categoricalRows, coerceNumeric, coerceNumericInto, collapseParallelEdges, compileEdgeFilter, compileNodeFilter, computeNumericDomain, createGraphInstance, createLocalExpansionService, createLocalSearchService, createRequestContext, deriveGroupsByKey, divergingColor, escapeXml, estimateBatchBytes, evaluateFilterExpr, groupByDerivedId, groupSceneKey, interpolateColor, medianLinkWidthPx, mergeDiagnostics, mergeModel, metaEdgePublicId, metaEdgeSceneKey, metaEdgeWidthFor, neighborsOf, newContribution, newStagingTallies, nextRequestId, normalizeCommitForCapabilities, parseColor, pointSegmentDistanceSquared, projectColors, projectSizes, renderSvg, resolveEnginePolicy, resolveFilterField, resolveManualGroups, resolveScope, resolveTheme, rewriteGroups, sameDataRef, sameGroupBySpec, sameGroupSpecArrays, sceneGroupsOf, sceneLinkRefAt, scenePointRefAt, selectLabelCandidates, sequentialColor, sequentialSize, serviceCacheKey, sessionCommitDiagnostics, stageBatch, superNodeSizeFor, validateFilterExpr, validateGroupBySpec, validateGroupSpecs, validateSnapshot, validateViewState };