@modernrelay/orbit-core 0.13.6 → 0.15.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.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { DIAGNOSTIC_SAMPLE_CAP, encodeStringTable, collectTransfers, deriveClusters, resolveClusterCenters, clusterCentroids, EnvelopeSequencer, RequestLedger } from './chunk-FG6TQANI.js';
2
- export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED, DIAGNOSTIC_SAMPLE_CAP, acceptColumnar, clusterCentroids, collectTransfers, decodeStringTable, deriveClusters, encodeStringTable, generateClusterCenters, judgeEpoch, resolveClusterCenters } from './chunk-FG6TQANI.js';
1
+ import { DIAGNOSTIC_SAMPLE_CAP, encodeStringTable, collectTransfers, deriveClusters, resolveClusterCenters, clusterCentroids, EnvelopeSequencer, RequestLedger } from './chunk-TDBIVBJ3.js';
2
+ export { DEFAULT_CLUSTER_CENTER_RADIUS, DEFAULT_LAYOUT_SEED, DIAGNOSTIC_SAMPLE_CAP, acceptColumnar, clusterCentroids, collectTransfers, decodeStringTable, deriveClusters, encodeStringTable, generateClusterCenters, judgeEpoch, resolveClusterCenters } from './chunk-TDBIVBJ3.js';
3
3
  import { createStore } from 'zustand/vanilla';
4
4
 
5
5
  // src/errors.ts
@@ -123,6 +123,10 @@ function validateSnapshot(snapshot) {
123
123
  record(invalidEdge, typeof edge.id === "string" ? edge.id : `[${i}]`);
124
124
  continue;
125
125
  }
126
+ if (typeof edge.id === "string" && edge.id.includes("\0")) {
127
+ record(invalidEdge, `[${i}]`);
128
+ continue;
129
+ }
126
130
  if (!nodeIndex.has(source)) {
127
131
  record(danglingEdge, source);
128
132
  continue;
@@ -172,7 +176,7 @@ function validateSnapshot(snapshot) {
172
176
  "invalid-edge",
173
177
  "error",
174
178
  invalidEdge,
175
- `${invalidEdge.count} edge row(s) dropped: missing or non-string source/target`
179
+ `${invalidEdge.count} edge row(s) dropped: missing or non-string source/target, or NUL-containing explicit id`
176
180
  );
177
181
  pushDiagnostic(
178
182
  diagnostics,
@@ -349,7 +353,7 @@ function validateGroupSpecs(specs, nodeIndex) {
349
353
  severity: "error",
350
354
  count,
351
355
  sampleIds: samples,
352
- message: `groups rejected before scene rewrite (\xA716.3 acyclic/singly-parented): ${kinds} \u2014 the previous group configuration stays active`
356
+ message: `groups rejected before scene rewrite: ${kinds} \u2014 the previous group configuration stays active`
353
357
  }
354
358
  };
355
359
  }
@@ -419,7 +423,7 @@ function validateGroupBySpec(spec) {
419
423
  if (!expandOk) problems.push("semanticZoom.expandAbove must be a finite number");
420
424
  if (collapseOk && expandOk && !(sz.expandAbove > sz.collapseBelow)) {
421
425
  problems.push(
422
- "semanticZoom.expandAbove must be strictly greater than collapseBelow (hysteresis, R-16.3-22)"
426
+ "semanticZoom.expandAbove must be strictly greater than collapseBelow to provide a hysteresis gap"
423
427
  );
424
428
  }
425
429
  }
@@ -429,7 +433,7 @@ function validateGroupBySpec(spec) {
429
433
  severity: "error",
430
434
  count: problems.length,
431
435
  sampleIds: [],
432
- message: `groupBy rejected (\xA716.3/D4): ${problems.join("; ")} \u2014 the previous groupBy configuration stays active`
436
+ message: `groupBy rejected: ${problems.join("; ")} \u2014 the previous groupBy configuration stays active`
433
437
  };
434
438
  }
435
439
  function deriveGroupsByKey(nodes, by, isCollapsedKey) {
@@ -476,7 +480,7 @@ function deriveGroupsByKey(nodes, by, isCollapsedKey) {
476
480
  severity: "warning",
477
481
  count: errorCount,
478
482
  sampleIds: errorSamples,
479
- message: "groupBy.by threw; the affected nodes derived as ungrouped (\xA716.3/\xA78)"
483
+ message: "groupBy.by threw; the affected nodes derived as ungrouped"
480
484
  }
481
485
  };
482
486
  }
@@ -701,27 +705,27 @@ var EMPTY_INDEX = /* @__PURE__ */ new Map();
701
705
  var Reconciler = class {
702
706
  datasetKey = null;
703
707
  hasScene = false;
704
- // Previous scene, kept for the structural diff (§7.2).
708
+ // Previous scene, kept for the structural diff.
705
709
  prevIdByIndex = [];
706
710
  prevIndexById = EMPTY_INDEX;
707
711
  prevEdgeIdByIndex = [];
708
712
  prevLinks = new Uint32Array(0);
709
713
  /**
710
714
  * Declared node.x/y as of the previous reconcile, for nodes that declared
711
- * them. Lets Priority 1 distinguish a CHANGED declaration (caller intent
715
+ * them. Lets Priority 1 distinguish a CHANGED declaration (caller intent
712
716
  * wins over any cache) from an unchanged one (defers to live drift). Fresh
713
717
  * per pass, so departed ids drop and a re-add treats its declaration as new.
714
718
  */
715
719
  prevDeclared = /* @__PURE__ */ new Map();
716
720
  /**
717
- * CPU position mirror for the CURRENT scene, indexed by slot (§7.1 posBuf).
721
+ * CPU position mirror for the CURRENT scene, indexed by slot.
718
722
  * Owned copy — never aliases a published scene's positions array, so
719
723
  * noteEnginePositions never mutates an already-published RenderScene.
720
724
  */
721
725
  livePositions = new Float32Array(0);
722
726
  /**
723
- * Ids removed from the scene → last known finite position (§7.1/§7.3
724
- * leave-and-return guarantee). Map insertion order doubles as LRU order;
727
+ * Ids removed from the scene → last known finite position, preserving the
728
+ * leave-and-return guarantee. Map insertion order doubles as LRU order;
725
729
  * invariant: never overlaps the current scene's id set.
726
730
  */
727
731
  departed = /* @__PURE__ */ new Map();
@@ -835,7 +839,7 @@ var Reconciler = class {
835
839
  }
836
840
  /**
837
841
  * Copy engine-read positions into the live cache for the CURRENT scene's
838
- * slots (§7.1 per-event readback — simulation end, pre-structural-swap).
842
+ * slots.
839
843
  * Extra trailing floats are ignored; a short buffer updates a prefix.
840
844
  */
841
845
  noteEnginePositions(positions) {
@@ -1239,7 +1243,7 @@ function stageBatch(contribution, batch, tallies, nextOrder) {
1239
1243
  for (let i = 0; i < rawNodes.length; i++) {
1240
1244
  const row = rawNodes[i];
1241
1245
  const id = typeof row === "object" && row !== null ? row.id : void 0;
1242
- if (typeof id !== "string") {
1246
+ if (typeof id !== "string" || id.includes("\0")) {
1243
1247
  recordRow(tallies.invalidNodes, `[${batch.sequence}:${i}]`);
1244
1248
  continue;
1245
1249
  }
@@ -1263,6 +1267,10 @@ function stageBatch(contribution, batch, tallies, nextOrder) {
1263
1267
  recordRow(tallies.invalidEdges, typeof edge.id === "string" ? edge.id : `[${batch.sequence}:${i}]`);
1264
1268
  continue;
1265
1269
  }
1270
+ if (typeof edge.id === "string" && edge.id.includes("\0")) {
1271
+ recordRow(tallies.invalidEdges, `[${batch.sequence}:${i}]`);
1272
+ continue;
1273
+ }
1266
1274
  const hasExplicitId = typeof edge.id === "string";
1267
1275
  let id;
1268
1276
  if (hasExplicitId) {
@@ -1391,7 +1399,7 @@ function mergeDiagnostics(merge) {
1391
1399
  severity: "info",
1392
1400
  count: merge.shadowedCount,
1393
1401
  sampleIds: merge.shadowedSamples,
1394
- message: `${merge.shadowedCount} overlay node row(s) shadowed by a same-id row admitted earlier (\xA77.5); rows retained`
1402
+ message: `${merge.shadowedCount} overlay node row(s) shadowed by a same-id row admitted earlier; rows retained`
1395
1403
  });
1396
1404
  }
1397
1405
  if (merge.duplicateEdgeCount > 0) {
@@ -1415,7 +1423,7 @@ function sessionCommitDiagnostics(tallies, danglingCount, danglingSamples = [])
1415
1423
  "invalid-node",
1416
1424
  "error",
1417
1425
  tallies.invalidNodes,
1418
- `${tallies.invalidNodes.count} ingested node row(s) dropped: missing or non-string id`
1426
+ `${tallies.invalidNodes.count} ingested node row(s) dropped: missing, non-string, or NUL-containing id`
1419
1427
  );
1420
1428
  push(
1421
1429
  "duplicate-node-id",
@@ -1427,7 +1435,7 @@ function sessionCommitDiagnostics(tallies, danglingCount, danglingSamples = [])
1427
1435
  "invalid-edge",
1428
1436
  "error",
1429
1437
  tallies.invalidEdges,
1430
- `${tallies.invalidEdges.count} ingested edge row(s) dropped: missing or non-string source/target`
1438
+ `${tallies.invalidEdges.count} ingested edge row(s) dropped: missing or non-string source/target, or NUL-containing explicit id`
1431
1439
  );
1432
1440
  push(
1433
1441
  "duplicate-edge-id",
@@ -1441,7 +1449,7 @@ function sessionCommitDiagnostics(tallies, danglingCount, danglingSamples = [])
1441
1449
  severity: "warning",
1442
1450
  count: danglingCount,
1443
1451
  sampleIds: danglingSamples.slice(0, DIAGNOSTIC_SAMPLE_CAP),
1444
- message: `${danglingCount} ingested edge(s) still awaiting an endpoint at commit (pending-endpoint index; \xA77.5)`
1452
+ message: `${danglingCount} ingested edge(s) still awaiting an endpoint at commit (the endpoint was not ingested before the session committed)`
1445
1453
  });
1446
1454
  }
1447
1455
  return out;
@@ -1571,7 +1579,7 @@ var IncrementalAlphaComposer = class {
1571
1579
  bufB = null;
1572
1580
  staleA = [];
1573
1581
  staleB = [];
1574
- /** Which buffer the NEXT nextBuffer() call returns (0 = A, 1 = B). */
1582
+ /** Which buffer the NEXT nextBuffer call returns (0 = A, 1 = B). */
1575
1583
  next = 0;
1576
1584
  // --- seed key ---
1577
1585
  seededBase = null;
@@ -1674,7 +1682,7 @@ var PressureSampler = class {
1674
1682
  /**
1675
1683
  * Record one onFrame tick. `settled` marks a tick that arrived while the
1676
1684
  * scene was at rest (sim settled, no pending commit work) — the idle-
1677
- * wakeup counter, which reads 0 when the ADR-005 gated clock is honest.
1685
+ * wakeup counter, which reads 0 when the gated activity clock is honest.
1678
1686
  */
1679
1687
  noteFrame(timeMs, settled) {
1680
1688
  this.frames += 1;
@@ -1844,7 +1852,7 @@ var DegradeController = class {
1844
1852
  /**
1845
1853
  * Resource-admission trigger: engage the NEXT not-yet-engaged step in the
1846
1854
  * declared order. Null when the order is exhausted — the caller must then
1847
- * REJECT before allocating (§17: never silently erase semantic styling).
1855
+ * REJECT before allocating.
1848
1856
  */
1849
1857
  engageNextResourceStep(visible) {
1850
1858
  for (const step of this.limits.resourceDegradationOrder) {
@@ -2057,8 +2065,8 @@ var LinkPickIndex = class {
2057
2065
  while (!r.done) r = it.next();
2058
2066
  }
2059
2067
  /**
2060
- * Incremental build: yields whenever `now() − sliceStart ≥ budgetMs` so a
2061
- * scheduler can spread the work across idle frames (§17 long-task budget).
2068
+ * Incremental build: yields whenever `now − sliceStart ≥ budgetMs` so a
2069
+ * scheduler can spread the work across idle frames.
2062
2070
  * Pure generator — no rAF/timers here; the caller owns scheduling.
2063
2071
  *
2064
2072
  * The previous grid stays armed and queryable until the new one commits on
@@ -2222,7 +2230,7 @@ var LinkPickIndex = class {
2222
2230
  * Nearest link within `tolerance` (space units) of the space point
2223
2231
  * `(x, y)`, or null. Scans the candidate cells covering the tolerance
2224
2232
  * disc's bounding box (typically the 3×3 neighborhood; more when the
2225
- * tolerance exceeds the cell size), applies the optional §9.1 visibility
2233
+ * tolerance exceeds the cell size), applies the optional visibility
2226
2234
  * mask per candidate, then runs the exact point→segment distance test.
2227
2235
  * Nearest wins; exact-distance ties break toward the LOWER link index.
2228
2236
  * Returns null while unbuilt/invalidated (picking disarmed).
@@ -2374,7 +2382,7 @@ var EdgePickingFacade = class {
2374
2382
  this.generation++;
2375
2383
  this.index.invalidate();
2376
2384
  }
2377
- /** §9.1 mask pass-through stub: applied per candidate at query time. */
2385
+ /** mask pass-through stub: applied per candidate at query time. */
2378
2386
  setLinkVisibilityMask(mask) {
2379
2387
  this.mask = mask;
2380
2388
  }
@@ -2734,7 +2742,7 @@ function throwAborted2(signal, sourceId, targetId) {
2734
2742
  const reason = signal.reason;
2735
2743
  throw new OrbitOperationError(
2736
2744
  reason === void 0 ? { code: "aborted" } : { code: "aborted", cause: reason },
2737
- `local find('${sourceId}' \u2192 '${targetId}') aborted mid-scan (\xA716.2)`
2745
+ `local find('${sourceId}' \u2192 '${targetId}') aborted mid-scan`
2738
2746
  );
2739
2747
  }
2740
2748
  function createLocalPathService(getBase) {
@@ -2919,7 +2927,7 @@ function throwAborted3(signal, query) {
2919
2927
  const reason = signal.reason;
2920
2928
  throw new OrbitOperationError(
2921
2929
  reason === void 0 ? { code: "aborted" } : { code: "aborted", cause: reason },
2922
- `local search('${query}') aborted mid-scan (\xA716.5)`
2930
+ `local search('${query}') aborted mid-scan`
2923
2931
  );
2924
2932
  }
2925
2933
  function createLocalSearchService(getBase) {
@@ -2982,6 +2990,16 @@ function checkStringColumn(col, rows, where, isIds, out) {
2982
2990
  out.push({ where, problem: "not-a-string-column", detail: 'expected {kind:"string", dictionary, codes}' });
2983
2991
  return;
2984
2992
  }
2993
+ for (let d = 0; d < col.dictionary.length; d++) {
2994
+ if (typeof col.dictionary[d] !== "string") {
2995
+ out.push({
2996
+ where,
2997
+ problem: "not-a-string-column",
2998
+ detail: `dictionary entry ${d} is not a string`
2999
+ });
3000
+ return;
3001
+ }
3002
+ }
2985
3003
  if (col.codes.length !== rows) {
2986
3004
  const detached = col.codes.length === 0 && col.codes.buffer.byteLength === 0;
2987
3005
  out.push({
@@ -3230,10 +3248,12 @@ function detachColumnarBuffers(snapshot) {
3230
3248
  return detached;
3231
3249
  }
3232
3250
 
3233
- // src/worker/lane.ts
3234
- function defaultWorkerUrl() {
3235
- return new URL("./worker/entry.js", import.meta.url);
3251
+ // src/workerAsset.ts
3252
+ function createDefaultWorker() {
3253
+ return new Worker(new URL("./worker/entry.js", import.meta.url), { type: "module" });
3236
3254
  }
3255
+
3256
+ // src/worker/lane.ts
3237
3257
  function transportFromWorker(worker) {
3238
3258
  return {
3239
3259
  post: (envelope, transfers) => worker.postMessage(envelope, [...transfers]),
@@ -3275,10 +3295,7 @@ var WorkerLane = class {
3275
3295
  let transport = this.options.transport ?? null;
3276
3296
  if (transport === null) {
3277
3297
  const factory = this.options.factory;
3278
- const worker = factory !== void 0 && "create" in factory ? factory.create() : new Worker(
3279
- factory !== void 0 && "url" in factory ? factory.url : defaultWorkerUrl(),
3280
- { type: "module" }
3281
- );
3298
+ const worker = factory !== void 0 && "create" in factory ? factory.create() : factory !== void 0 && "url" in factory ? new Worker(factory.url, { type: "module" }) : createDefaultWorker();
3282
3299
  transport = transportFromWorker(worker);
3283
3300
  }
3284
3301
  transport.onReply((reply) => this.settle(reply));
@@ -3291,8 +3308,8 @@ var WorkerLane = class {
3291
3308
  }
3292
3309
  return this.availableState;
3293
3310
  }
3294
- /** Async worker death (P1: a constructed-but-dead worker must strand
3295
- * NOTHING): every pending request rejects as 'worker-failed' so callers
3311
+ /** A constructed-but-dead worker must strand NOTHING: every pending
3312
+ * request rejects as 'worker-failed' so callers
3296
3313
  * run their main-lane fallback, the lane goes permanently unavailable,
3297
3314
  * and the unavailability callback fires (the instance one-shots it). */
3298
3315
  fail(reason) {
@@ -3348,7 +3365,7 @@ var WorkerLane = class {
3348
3365
  }
3349
3366
  /** Abort everything in flight (epoch advance / detach / dataset swap).
3350
3367
  * The ledger's controllers fire each pending promise's abort listener;
3351
- * the sweep below catches anything tracked before a listener attached
3368
+ * the sweep below catches anything tracked before a listener attached
3352
3369
  * rejects stay idempotent through the delete guard. */
3353
3370
  abortAll() {
3354
3371
  this.ledger.abortAll();
@@ -3817,7 +3834,7 @@ var MetricStore = class {
3817
3834
  /** Admitted async columns by metric name; NaN encodes null. */
3818
3835
  columns = /* @__PURE__ */ new Map();
3819
3836
  degreePasses = 0;
3820
- /** §17 telemetry: estimated bytes of metric storage held (S13-T07). */
3837
+ /** telemetry: estimated bytes of metric storage held. */
3821
3838
  estimatedBytes() {
3822
3839
  let bytes = 0;
3823
3840
  for (const col of this.columns.values()) bytes += col.byteLength;
@@ -3848,10 +3865,10 @@ var MetricStore = class {
3848
3865
  }
3849
3866
  }
3850
3867
  /**
3851
- * Joins async metric columns against the accepted model (§12).
3868
+ * Joins async metric columns against the accepted model.
3852
3869
  * Revision-gated PER COLUMN (I1): a column whose issue-time
3853
3870
  * `forModelRevision` stamp differs from `opts.modelRevision` is discarded
3854
- * (info diagnostic — a normal async race outcome, §9.2). A missing or
3871
+ * (info diagnostic — a normal async race outcome). A missing or
3855
3872
  * mismatched stamp is never defaulted to the current revision — that
3856
3873
  * would make the gate self-satisfying.
3857
3874
  * Structural rejections ('index' length mismatch, missing/mismatched ids)
@@ -3869,7 +3886,7 @@ var MetricStore = class {
3869
3886
  "info",
3870
3887
  1,
3871
3888
  [column.metric],
3872
- `metric column '${column.metric}' discarded: computed for model revision ${String(column.forModelRevision)} but the update was issued at revision ${String(opts.modelRevision)} (\xA712/I1 revision-gated admission)`
3889
+ `metric column '${column.metric}' discarded: computed for model revision ${String(column.forModelRevision)} but the update was issued at revision ${String(opts.modelRevision)}`
3873
3890
  )
3874
3891
  );
3875
3892
  continue;
@@ -3979,7 +3996,7 @@ var MetricStore = class {
3979
3996
  }
3980
3997
  /**
3981
3998
  * Raw column in accepted-base order, or null when unavailable. NaN encodes
3982
- * null (§12) — consumers exclude NaN slots from domains. Do NOT mutate:
3999
+ * null — consumers exclude NaN slots from domains. Do NOT mutate:
3983
4000
  * this is the live cache, not a copy.
3984
4001
  */
3985
4002
  metricValues(metric) {
@@ -4025,7 +4042,7 @@ var MetricStore = class {
4025
4042
  };
4026
4043
 
4027
4044
  // src/capabilityPolicy.ts
4028
- var CLUSTER_FORCE_DEGRADATION_REASON = "clusters requested but the engine capability record does not declare clusterForce; the cluster force is inert while membership, cluster labels, and centroids still work (\xA716.3/R-13-39)";
4045
+ var CLUSTER_FORCE_DEGRADATION_REASON = "clusters requested but the engine capability record does not declare clusterForce; the cluster force is inert while membership, cluster labels, and centroids still work";
4029
4046
  function degradation(feature, reason) {
4030
4047
  return Object.freeze({ feature, reason });
4031
4048
  }
@@ -4038,7 +4055,7 @@ function resolveEnginePolicy(capabilities, requested) {
4038
4055
  degradations.push(
4039
4056
  degradation(
4040
4057
  "edgeArrows",
4041
- "edgeArrows requested but the engine capability record does not declare edgeArrows; the prop is inert (\xA716.12)"
4058
+ "edgeArrows requested but the engine capability record does not declare edgeArrows; the prop is inert"
4042
4059
  )
4043
4060
  );
4044
4061
  }
@@ -4046,7 +4063,7 @@ function resolveEnginePolicy(capabilities, requested) {
4046
4063
  degradations.push(
4047
4064
  degradation(
4048
4065
  "images",
4049
- "node images requested but the engine capability record does not declare pointImages; placeholder glyphs render and image refs are retained (\xA78/\xA713)"
4066
+ "node images requested but the engine capability record does not declare pointImages; placeholder glyphs render and image refs are retained"
4050
4067
  )
4051
4068
  );
4052
4069
  }
@@ -4069,14 +4086,14 @@ function assertCapabilityMethodParity(engine) {
4069
4086
  const mismatches = [];
4070
4087
  const capabilities = engine.capabilities;
4071
4088
  if (capabilities === null || typeof capabilities !== "object") {
4072
- mismatches.push("engine.capabilities is missing or not an object (\xA713 static declaration)");
4089
+ mismatches.push("engine.capabilities is missing or not an object");
4073
4090
  return mismatches;
4074
4091
  }
4075
4092
  const caps = capabilities;
4076
4093
  const getPositions = engine.getPositions;
4077
4094
  if (caps.trackedPositions === true && typeof getPositions !== "function") {
4078
4095
  mismatches.push(
4079
- "capabilities.trackedPositions is declared but engine.getPositions is absent (\xA713 tracked readback)"
4096
+ "capabilities.trackedPositions is declared but engine.getPositions is absent"
4080
4097
  );
4081
4098
  }
4082
4099
  return mismatches;
@@ -4163,7 +4180,7 @@ var ImageAtlasPipeline = class {
4163
4180
  generation = null;
4164
4181
  flushScheduled = false;
4165
4182
  disposed = false;
4166
- /** D5/F11-06: evicted-entry bitmaps awaiting close — closed AFTER the
4183
+ /** Evicted-entry bitmaps awaiting close — closed AFTER the
4167
4184
  * flush that carries their removeSlots, so the instance's recovery-replay
4168
4185
  * map (pruned synchronously in the batch callback) can never re-send a
4169
4186
  * closed bitmap. */
@@ -4397,7 +4414,7 @@ var ImageAtlasPipeline = class {
4397
4414
  return index;
4398
4415
  }
4399
4416
  /**
4400
- * I2 (roster-atomic resource mappings, F11-03): the SYNCHRONOUS point→slot
4417
+ * Roster-atomic resource mappings: the SYNCHRONOUS point→slot
4401
4418
  * mapping for the last requested roster — a slot appears only when its ref
4402
4419
  * is resolved AND the engine has already received the bitmap (delivered);
4403
4420
  * pending, failed, evicted, and reused-but-undelivered refs are the −1
@@ -4477,12 +4494,12 @@ var SoftMask = class {
4477
4494
  edgeHideLane;
4478
4495
  edgeDimLane;
4479
4496
  sources = /* @__PURE__ */ new Set();
4480
- /** Dedicated internal source implementing the §9 node→edge cascade. */
4497
+ /** Dedicated internal source implementing the node→edge cascade. */
4481
4498
  cascadeSource = null;
4482
4499
  overflowedFlag = false;
4483
4500
  /** Total memberships currently held across all sources and lanes. */
4484
4501
  totalHeld = 0;
4485
- /** §17 O(Δ) op counters (F10-02 gate instrumentation). */
4502
+ /** O(Δ) op counters. */
4486
4503
  statsBox = {
4487
4504
  slotsVisited: 0,
4488
4505
  zeroCrossings: 0,
@@ -4505,12 +4522,12 @@ var SoftMask = class {
4505
4522
  return this.edgeCap;
4506
4523
  }
4507
4524
  /** One-time latch: some counter hit 0xFFFF and an increment was dropped.
4508
- * Counts may drift afterwards; the caller reports it (§9.1 overflow guard). */
4525
+ * Counts may drift afterwards; the caller reports it. */
4509
4526
  get overflowed() {
4510
4527
  return this.overflowedFlag;
4511
4528
  }
4512
4529
  // Live counter columns (read-only views by convention — do not mutate;
4513
- // references are replaced on grow()).
4530
+ // references are replaced on grow).
4514
4531
  get nodeHideFailures() {
4515
4532
  return this.nodeHideLane.counters;
4516
4533
  }
@@ -4525,7 +4542,7 @@ var SoftMask = class {
4525
4542
  }
4526
4543
  /**
4527
4544
  * Grows capacities for structure changes (existing slot state is
4528
- * preserved; new slots start fully visible). Capacities never shrink
4545
+ * preserved; new slots start fully visible). Capacities never shrink
4529
4546
  * a smaller value is a no-op for that dimension.
4530
4547
  */
4531
4548
  grow(nodeCapacity, edgeCapacity) {
@@ -4550,7 +4567,7 @@ var SoftMask = class {
4550
4567
  this.edgeCap = edgeCapacity;
4551
4568
  }
4552
4569
  }
4553
- /** Registers a new failure source. No cap on source count (§9.1). */
4570
+ /** Registers a new failure source. No cap on source count. */
4554
4571
  acquire(name) {
4555
4572
  const state = {
4556
4573
  name,
@@ -4605,11 +4622,11 @@ var SoftMask = class {
4605
4622
  };
4606
4623
  }
4607
4624
  /**
4608
- * §9 edge cascade over the mask lane: recomputes, from the CURRENT node
4625
+ * edge cascade over the mask lane: recomputes, from the CURRENT node
4609
4626
  * hide lane, the set of edges with at least one hidden endpoint, and feeds
4610
4627
  * it to the dedicated internal cascade source (edge hide lane only).
4611
- * `links` is the flat `[src0, tgt0, src1, tgt1, …]` node-slot pair buffer
4612
- * (§7.1 CSR input shape); edge slot i has endpoints at links[2i]/[2i+1].
4628
+ * `links` is the flat `[src0, tgt0, src1, tgt1, …]` node-slot pair buffer;
4629
+ * edge slot i has endpoints at links[2i]/[2i+1].
4613
4630
  * O(E) scan per call — typically once per drain — but only edges whose
4614
4631
  * cascade state changed produce counter deltas (and thus dirty entries).
4615
4632
  * Edges beyond `links.length / 2` are treated as having no hidden
@@ -4640,11 +4657,11 @@ var SoftMask = class {
4640
4657
  }
4641
4658
  if (hide[source] !== 0 || hide[target] !== 0) failing.push(i);
4642
4659
  }
4643
- this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
4660
+ this.cascadeSource ??= this.acquire("node\u2192edge cascade");
4644
4661
  this.cascadeSource.setEdgeFailures(failing);
4645
4662
  }
4646
4663
  /**
4647
- * O(incident-edges) delta form of the §9.1 cascade (F10-02): for each node
4664
+ * O(incident-edges) delta form of the cascade: for each node
4648
4665
  * whose HIDE visibility crossed zero, recompute only its incident edges'
4649
4666
  * cascade state from the CURRENT node counters and apply the delta through
4650
4667
  * the same internal cascade source the full form uses — the two compose
@@ -4656,7 +4673,7 @@ var SoftMask = class {
4656
4673
  applyNodeCascadeToEdgesDelta(links, incidence, crossedNodes) {
4657
4674
  if (crossedNodes.length === 0) return;
4658
4675
  const hide = this.nodeHideLane.counters;
4659
- this.cascadeSource ??= this.acquire("\xA79.1 node\u2192edge cascade");
4676
+ this.cascadeSource ??= this.acquire("node\u2192edge cascade");
4660
4677
  const nowFailing = [];
4661
4678
  const nowClear = [];
4662
4679
  for (let k = 0; k < crossedNodes.length; k++) {
@@ -4686,7 +4703,7 @@ var SoftMask = class {
4686
4703
  edgeVisibleCount: this.edgeHideLane.zeroCount
4687
4704
  };
4688
4705
  }
4689
- /** §17 telemetry: estimated bytes of mask storage held (S13-T07):
4706
+ /** telemetry: estimated bytes of mask storage held:
4690
4707
  * four counter lanes (+pending trackers) and per-source flag columns. */
4691
4708
  estimatedBytes() {
4692
4709
  let bytes = 0;
@@ -4698,7 +4715,7 @@ var SoftMask = class {
4698
4715
  }
4699
4716
  return bytes;
4700
4717
  }
4701
- /** §17 O(Δ) op counters (live object — snapshot before comparing). */
4718
+ /** O(Δ) op counters (live object — snapshot before comparing). */
4702
4719
  get stats() {
4703
4720
  return this.statsBox;
4704
4721
  }
@@ -4720,7 +4737,7 @@ var SoftMask = class {
4720
4737
  isEdgeVisible(index) {
4721
4738
  return this.edgeHideLane.counters[index] === 0;
4722
4739
  }
4723
- /** Dimmed iff visible AND dimFailures > 0 (§9.1). */
4740
+ /** Dimmed iff visible AND dimFailures > 0. */
4724
4741
  isNodeDimmed(index) {
4725
4742
  return this.isNodeVisible(index) && (this.nodeDimLane.counters[index] ?? 0) > 0;
4726
4743
  }
@@ -4781,7 +4798,7 @@ var SoftMask = class {
4781
4798
  mem.holes = 0;
4782
4799
  }
4783
4800
  /**
4784
- * O(Δ) delta ops on one lane membership (F10-02). Adds and removes are
4801
+ * O(Δ) delta ops on one lane membership. Adds and removes are
4785
4802
  * idempotent per slot (adding a member / removing a non-member is a
4786
4803
  * no-op); removed slots leave HOLES in `list` (compacted past 50%), so
4787
4804
  * replace/clear passes must honor the bit0 guard above. `crossings`, when
@@ -4882,7 +4899,7 @@ var SoftMask = class {
4882
4899
  this.applyMembership(this.edgeDimLane, state.edgeDim, null);
4883
4900
  }
4884
4901
  /** Debug balanced-increment assert: whenever no source holds any
4885
- * membership, every counter must read zero (skipped once overflowed
4902
+ * membership, every counter must read zero (skipped once overflowed
4886
4903
  * clamped increments legitimately drift the books). */
4887
4904
  assertBalancedIfIdle() {
4888
4905
  if (!DEBUG || this.overflowedFlag || this.totalHeld !== 0) return;
@@ -4904,7 +4921,7 @@ var SVG_MAX_ELEMENTS_DEFAULT = 5e4;
4904
4921
  var SvgBudgetError = class extends Error {
4905
4922
  constructor(elementCount, limit) {
4906
4923
  super(
4907
- `SVG export of ${elementCount} elements exceeds the ${limit}-element budget \u2014 filter/isolate first, or use the raster-hybrid fallback (\xA716.14)`
4924
+ `SVG export of ${elementCount} elements exceeds the ${limit}-element budget \u2014 filter/isolate first, or use the raster-hybrid fallback`
4908
4925
  );
4909
4926
  this.elementCount = elementCount;
4910
4927
  this.limit = limit;
@@ -5026,6 +5043,7 @@ var isNum = (v) => typeof v === "number" && Number.isFinite(v);
5026
5043
  var isBool = (v) => typeof v === "boolean";
5027
5044
  var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
5028
5045
  var isStrArray = (v) => Array.isArray(v) && v.every(isStr);
5046
+ var isNumArray = (v) => Array.isArray(v) && v.every(isNum);
5029
5047
  function validateBrush(v, at, problems) {
5030
5048
  if (!isObj(v)) {
5031
5049
  problems.push(`${at}: not an object`);
@@ -5044,18 +5062,43 @@ function validateBrush(v, at, problems) {
5044
5062
  }
5045
5063
  problems.push(`${at}: unknown brush kind`);
5046
5064
  }
5047
- function validateScale(v, at, problems) {
5065
+ function validateScale(v, at, valueKind, problems) {
5048
5066
  if (!isObj(v)) {
5049
5067
  problems.push(`${at}: not an object`);
5050
5068
  return;
5051
5069
  }
5052
- if (v["kind"] === "sequential" || v["kind"] === "diverging") {
5070
+ const isValueArray = valueKind === "string" ? isStrArray : isNumArray;
5071
+ const valueLabel = valueKind === "string" ? "string" : "finite number";
5072
+ if (v["kind"] === "sequential") {
5073
+ if (!isStr(v["metric"])) problems.push(`${at}: metric must be a string`);
5074
+ if (!isValueArray(v["range"]) || v["range"].length !== 2) {
5075
+ problems.push(`${at}: sequential range must be a [${valueLabel}, ${valueLabel}] tuple`);
5076
+ }
5077
+ if (v["domain"] !== void 0 && (!isNumArray(v["domain"]) || v["domain"].length !== 2)) {
5078
+ problems.push(`${at}: sequential domain must be a [min, max] finite-number tuple`);
5079
+ }
5080
+ return;
5081
+ }
5082
+ if (v["kind"] === "diverging") {
5053
5083
  if (!isStr(v["metric"])) problems.push(`${at}: metric must be a string`);
5054
- if (!Array.isArray(v["range"])) problems.push(`${at}: range must be an array`);
5084
+ if (!isValueArray(v["range"]) || v["range"].length !== 3) {
5085
+ problems.push(
5086
+ `${at}: diverging range must be a [${valueLabel}, ${valueLabel}, ${valueLabel}] tuple`
5087
+ );
5088
+ }
5089
+ if (!isNum(v["mid"])) {
5090
+ problems.push(`${at}: diverging mid must be a finite number`);
5091
+ }
5055
5092
  return;
5056
5093
  }
5057
5094
  if (v["kind"] === "categorical") {
5058
5095
  if (!isStr(v["by"])) problems.push(`${at}: categorical 'by' must be a field string`);
5096
+ if (v["domain"] !== void 0 && !isStrArray(v["domain"])) {
5097
+ problems.push(`${at}: categorical domain must be a string[]`);
5098
+ }
5099
+ if (v["palette"] !== void 0 && !isValueArray(v["palette"])) {
5100
+ problems.push(`${at}: categorical palette must be a ${valueLabel}[]`);
5101
+ }
5059
5102
  return;
5060
5103
  }
5061
5104
  problems.push(`${at}: unknown scale kind`);
@@ -5180,10 +5223,10 @@ function validateViewState(raw) {
5180
5223
  problems.push("styling: must be an object");
5181
5224
  } else {
5182
5225
  if (styling["nodeColor"] !== void 0) {
5183
- validateScale(styling["nodeColor"], "styling.nodeColor", problems);
5226
+ validateScale(styling["nodeColor"], "styling.nodeColor", "string", problems);
5184
5227
  }
5185
5228
  if (styling["nodeSize"] !== void 0) {
5186
- validateScale(styling["nodeSize"], "styling.nodeSize", problems);
5229
+ validateScale(styling["nodeSize"], "styling.nodeSize", "number", problems);
5187
5230
  }
5188
5231
  for (const flag of ["showLinks", "edgeArrows"]) {
5189
5232
  if (styling[flag] !== void 0 && !isBool(styling[flag])) {
@@ -5264,7 +5307,7 @@ function upperBound(values, sorted, target) {
5264
5307
  return lo;
5265
5308
  }
5266
5309
  var TypedColumnCrossfilter = class {
5267
- /** Test instrumentation; see CrossfilterStats. Reset with resetStats(). */
5310
+ /** Test instrumentation; see CrossfilterStats. Reset with resetStats. */
5268
5311
  stats = {
5269
5312
  slotsWalked: 0,
5270
5313
  fullSorts: 0,
@@ -5272,7 +5315,7 @@ var TypedColumnCrossfilter = class {
5272
5315
  binUpdates: 0,
5273
5316
  filteredRecomputes: 0
5274
5317
  };
5275
- /** Count of dims whose filtered layer is live-maintained (F10-02). */
5318
+ /** Count of dims whose filtered layer is live-maintained. */
5276
5319
  liveDims = 0;
5277
5320
  dims = [];
5278
5321
  byKey = /* @__PURE__ */ new Map();
@@ -5298,7 +5341,7 @@ var TypedColumnCrossfilter = class {
5298
5341
  this.stats.binUpdates = 0;
5299
5342
  this.stats.filteredRecomputes = 0;
5300
5343
  }
5301
- /** §17 telemetry: estimated bytes of typed-column storage held (S13-T07).
5344
+ /** telemetry: estimated bytes of typed-column storage held.
5302
5345
  * Documented components: per-dim value/permutation/bin/code/pass arrays,
5303
5346
  * the global failure counter, and the external mask. */
5304
5347
  estimatedBytes() {
@@ -5313,7 +5356,7 @@ var TypedColumnCrossfilter = class {
5313
5356
  }
5314
5357
  return bytes;
5315
5358
  }
5316
- /** F10-02 live-layer bookkeeping — the ONLY writer of `filteredLive`. */
5359
+ /** live-layer bookkeeping — the ONLY writer of `filteredLive`. */
5317
5360
  setFilteredLive(dim, live) {
5318
5361
  if (dim.filteredLive === live) return;
5319
5362
  dim.filteredLive = live;
@@ -5376,8 +5419,8 @@ var TypedColumnCrossfilter = class {
5376
5419
  }
5377
5420
  /**
5378
5421
  * External node-mask predicate for the joint "filtered" second layer (the
5379
- * instance wires the §9.1 filter-prop node mask in). Affects summaries only,
5380
- * never selection visibility. Length must equal rowCount(). Notifies on
5422
+ * instance wires the filter-prop node mask in). Affects summaries only,
5423
+ * never selection visibility. Length must equal rowCount. Notifies on
5381
5424
  * observable change; does NOT advance selectionRevision.
5382
5425
  */
5383
5426
  setExternalMask(passSlots) {
@@ -5402,7 +5445,7 @@ var TypedColumnCrossfilter = class {
5402
5445
  this.notify();
5403
5446
  }
5404
5447
  /**
5405
- * Immutable summary. The filtered layer is recomputed lazily when dirty
5448
+ * Immutable summary. The filtered layer is recomputed lazily when dirty
5406
5449
  * O(rows) per dirty summarize (v0.7 tier; see module doc). Returned objects
5407
5450
  * are frozen and never mutated by later operations.
5408
5451
  */
@@ -5454,7 +5497,7 @@ var TypedColumnCrossfilter = class {
5454
5497
  return Object.freeze(domain !== void 0 ? { ...base, domain } : base);
5455
5498
  }
5456
5499
  /**
5457
- * Incrementally extend columns with new rows (S9-T09 subset): no full
5500
+ * Incrementally extend columns with new rows: no full
5458
5501
  * rebuild, no full re-argsort — the pre-sorted old permutation merges with
5459
5502
  * the sorted new block. Brushes stay by key and are applied to the NEW slots
5460
5503
  * only; the returned delta covers only new slots. Keeps selectionRevision;
@@ -5708,16 +5751,16 @@ var TypedColumnCrossfilter = class {
5708
5751
  }
5709
5752
  }
5710
5753
  /**
5711
- * F10-02 inline maintenance of LIVE filtered layers, dispatched from the
5754
+ * inline maintenance of LIVE filtered layers, dispatched from the
5712
5755
  * one place that knows the failCount transition. `boundary` is the
5713
5756
  * other-failures picture at the interesting side of the flip (after for
5714
5757
  * shown, before for hidden):
5715
- * - 0 → the slot crossed the FULLY-VISIBLE boundary: every other live
5716
- * layer counts it (own layers ignore the own-dim brush, so the brushed
5717
- * dim's layer is provably unchanged by its own flip);
5718
- * - 1 → exactly one OTHER dim still fails the slot: only that dim's
5719
- * what-if-I-cleared-mine layer flips;
5720
- * - ≥2 → no layer can change.
5758
+ * - 0 → the slot crossed the FULLY-VISIBLE boundary: every other live
5759
+ * layer counts it (own layers ignore the own-dim brush, so the brushed
5760
+ * dim's layer is provably unchanged by its own flip);
5761
+ * - 1 → exactly one OTHER dim still fails the slot: only that dim's
5762
+ * what-if-I-cleared-mine layer flips;
5763
+ * - ≥2 → no layer can change.
5721
5764
  * External-mask-excluded and hygiene-invalid slots contribute nothing
5722
5765
  * either way and are skipped.
5723
5766
  */
@@ -6022,7 +6065,7 @@ function assertSerializable(value, slice) {
6022
6065
  switch (typeof v) {
6023
6066
  case "function":
6024
6067
  throw new TypeError(
6025
- `history command for slice "${slice}" carries a function at ${path} \u2014 commands are value diffs, not closures (\xA716.14)`
6068
+ `history command for slice "${slice}" carries a function at ${path} \u2014 commands are value diffs, not closures`
6026
6069
  );
6027
6070
  case "symbol":
6028
6071
  case "bigint":
@@ -6037,7 +6080,7 @@ function assertSerializable(value, slice) {
6037
6080
  if (v === null) return;
6038
6081
  if (v instanceof Map || v instanceof Set) {
6039
6082
  throw new TypeError(
6040
- `history command for slice "${slice}" carries a ${v instanceof Map ? "Map" : "Set"} at ${path} \u2014 convert to arrays/objects before recording (\xA716.14)`
6083
+ `history command for slice "${slice}" carries a ${v instanceof Map ? "Map" : "Set"} at ${path} \u2014 convert to arrays/objects before recording`
6041
6084
  );
6042
6085
  }
6043
6086
  if (seen.has(v)) {
@@ -6059,7 +6102,7 @@ var HistoryKernel = class {
6059
6102
  debug;
6060
6103
  undoStack = [];
6061
6104
  redoStack = [];
6062
- /** Nested begin() joins the outer transaction (depth-counted). */
6105
+ /** Nested begin joins the outer transaction (depth-counted). */
6063
6106
  txDepth = 0;
6064
6107
  txCommands = [];
6065
6108
  txLabel;
@@ -6125,7 +6168,7 @@ var HistoryKernel = class {
6125
6168
  }
6126
6169
  this.pushEntry({ label: void 0, commands: [command] }, null);
6127
6170
  }
6128
- /** Close a transaction; the outermost end() pushes one stack entry. */
6171
+ /** Close a transaction; the outermost end pushes one stack entry. */
6129
6172
  end() {
6130
6173
  if (!this.enabled) return;
6131
6174
  if (this.txDepth === 0) throw new Error("HistoryKernel.end() without a matching begin()");
@@ -6395,7 +6438,7 @@ function createGraphInstance(opts) {
6395
6438
  if (accepted === null) {
6396
6439
  throw new OrbitOperationError(
6397
6440
  { code: "aborted", cause: "no accepted base" },
6398
- "expansion requires an accepted base dataset (\xA79.2)"
6441
+ "expansion requires an accepted base dataset"
6399
6442
  );
6400
6443
  }
6401
6444
  return { accepted, adjacency: acceptedAdjacencyOf() };
@@ -6628,7 +6671,7 @@ function createGraphInstance(opts) {
6628
6671
  severity: "error",
6629
6672
  count: 1,
6630
6673
  sampleIds: [],
6631
- message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (\xA75: source coordinates are immutable; publish a new sourceRevision)"
6674
+ message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (source coordinates are immutable; publish a new sourceRevision)"
6632
6675
  }
6633
6676
  ];
6634
6677
  publish({ diagnostics: composeDiagnostics() });
@@ -6662,7 +6705,7 @@ function createGraphInstance(opts) {
6662
6705
  severity: "error",
6663
6706
  count: 1,
6664
6707
  sampleIds: [],
6665
- message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (\xA75: source coordinates are immutable; publish a new sourceRevision)"
6708
+ message: "columnar snapshot mutated while worker acceptance was pending \u2014 rejected whole (source coordinates are immutable; publish a new sourceRevision)"
6666
6709
  }
6667
6710
  ];
6668
6711
  publish({ diagnostics: composeDiagnostics() });
@@ -6754,7 +6797,7 @@ function createGraphInstance(opts) {
6754
6797
  if (event.engaged && !capLabelsNudged) {
6755
6798
  capLabelsNudged = true;
6756
6799
  console.warn(
6757
- "orbit: cap-dom-labels engaged \u2014 DOM labels are hard-capped at the label budget above limits.domLabelNodes visible nodes (\xA717). A GPU label lane (labelStrategy 'sdf') is the planned scale path (S13-T05, deferred)."
6800
+ "orbit: cap-dom-labels engaged \u2014 DOM labels are hard-capped at the label budget above limits.domLabelNodes visible nodes. A GPU label lane (labelStrategy 'sdf') is the planned scale path."
6758
6801
  );
6759
6802
  }
6760
6803
  scheduleViewportRerank();
@@ -7407,7 +7450,7 @@ function createGraphInstance(opts) {
7407
7450
  severity: "warning",
7408
7451
  count: errorCount,
7409
7452
  sampleIds: samples,
7410
- message: `filter predicate threw for ${errorCount} item(s); failing open \u2014 throwing predicates never hide data (\xA79.1)`
7453
+ message: `filter predicate threw for ${errorCount} item(s); failing open \u2014 throwing predicates never hide data`
7411
7454
  }
7412
7455
  ];
7413
7456
  } else {
@@ -7918,7 +7961,7 @@ function createGraphInstance(opts) {
7918
7961
  }
7919
7962
  function requireCrossfilterEngine() {
7920
7963
  if (crossfilterEngine === null) {
7921
- throw new Error("crossfilter session is not available (set the crossfilter prop first, \xA716.6)");
7964
+ throw new Error("crossfilter session is not available; set the crossfilter prop first");
7922
7965
  }
7923
7966
  return crossfilterEngine;
7924
7967
  }
@@ -7969,13 +8012,13 @@ function createGraphInstance(opts) {
7969
8012
  const eng = crossfilterEngine;
7970
8013
  if (eng === null) {
7971
8014
  throw new TypeError(
7972
- "playTimeline: the crossfilter prop must configure dimensions first (\xA716.6)"
8015
+ "playTimeline: the crossfilter prop must configure dimensions first"
7973
8016
  );
7974
8017
  }
7975
8018
  const summary = eng.summarize(key);
7976
8019
  if (summary.kind === "categorical" || summary.domain === void 0) {
7977
8020
  throw new TypeError(
7978
- `playTimeline: dimension "${key}" must be numeric/temporal with a non-empty domain (\xA716.6)`
8021
+ `playTimeline: dimension "${key}" must be numeric/temporal with a non-empty domain`
7979
8022
  );
7980
8023
  }
7981
8024
  const { min, max } = summary.domain;
@@ -7997,7 +8040,7 @@ function createGraphInstance(opts) {
7997
8040
  );
7998
8041
  }
7999
8042
  if (mode !== "sliding" && mode !== "cumulative") {
8000
- throw new TypeError(`playTimeline: unsupported mode "${String(mode)}" (\xA716.6)`);
8043
+ throw new TypeError(`playTimeline: unsupported mode "${String(mode)}"`);
8001
8044
  }
8002
8045
  stopTimelineTimer();
8003
8046
  timelineSessionSeq += 1;
@@ -8193,7 +8236,7 @@ function createGraphInstance(opts) {
8193
8236
  simRestarted = true;
8194
8237
  }
8195
8238
  }
8196
- eng.commit(commit);
8239
+ commitToEngine(eng, commit);
8197
8240
  revisions.appliedRender = eng.appliedRevision();
8198
8241
  const facade = session !== null ? session.edgePicking : null;
8199
8242
  if (facade !== null) {
@@ -8470,7 +8513,7 @@ function createGraphInstance(opts) {
8470
8513
  severity: "warning",
8471
8514
  count: overload,
8472
8515
  sampleIds: [],
8473
- message: `showLabelsFor exceeds tracked-label capacity; ${overload} forced label(s) omitted (\xA714)`
8516
+ message: `showLabelsFor exceeds tracked-label capacity; ${overload} forced label(s) omitted`
8474
8517
  }
8475
8518
  ];
8476
8519
  diagsChanged = true;
@@ -8746,7 +8789,7 @@ function createGraphInstance(opts) {
8746
8789
  severity: "warning",
8747
8790
  count: state.streak,
8748
8791
  sampleIds: [],
8749
- message: `${channel} reprojected on ${state.streak} consecutive commits with identical sampled outputs \u2014 likely an inline lambda prop; hoist or memoize it (\xA78)`
8792
+ message: `${channel} reprojected on ${state.streak} consecutive commits with identical sampled outputs \u2014 likely an inline lambda prop; hoist or memoize it`
8750
8793
  }
8751
8794
  ];
8752
8795
  }
@@ -9148,9 +9191,9 @@ function createGraphInstance(opts) {
9148
9191
  nodeIds: orderByAcceptedBase(full.nodeIds, nodeIndexBase()),
9149
9192
  edgeIds: orderByAcceptedBase(full.edgeIds, edgeIndexById),
9150
9193
  // Group namespace: the raw full-state form stays TOLERANT (deduped,
9151
- // not validated — pinned pre-S12 behavior); ids that stop naming a
9194
+ // not validated — pinned legacy behavior); ids that stop naming a
9152
9195
  // resolved group prune through the ownership path whenever the groups
9153
- // RESOLUTION changes (§16.2/§16.3). selectGroups() is the validating
9196
+ // RESOLUTION changes. selectGroups is the validating
9154
9197
  // mutator.
9155
9198
  groupIds: dedupeFirstOccurrence(full.groupIds)
9156
9199
  });
@@ -9429,7 +9472,7 @@ function createGraphInstance(opts) {
9429
9472
  if (DEV) {
9430
9473
  rejectGroupOp(
9431
9474
  "warning",
9432
- "groupNodes ignored: membership is derived and read-only under groupBy (\xA716.3) \u2014 change the groupBy accessor or remove it",
9475
+ "groupNodes ignored: membership is derived and read-only under groupBy \u2014 change the groupBy accessor or remove it",
9433
9476
  spec.id
9434
9477
  );
9435
9478
  }
@@ -9452,7 +9495,7 @@ function createGraphInstance(opts) {
9452
9495
  if (DEV) {
9453
9496
  rejectGroupOp(
9454
9497
  "warning",
9455
- "ungroup ignored: membership is derived and read-only under groupBy (\xA716.3) \u2014 change the groupBy accessor or remove it",
9498
+ "ungroup ignored: membership is derived and read-only under groupBy \u2014 change the groupBy accessor or remove it",
9456
9499
  groupId
9457
9500
  );
9458
9501
  }
@@ -9477,7 +9520,7 @@ function createGraphInstance(opts) {
9477
9520
  if (DEV) {
9478
9521
  rejectGroupOp(
9479
9522
  "warning",
9480
- "setGroupCollapsed ignored: groups and groupBy are both configured (\xA716.3 config error) \u2014 remove one",
9523
+ "setGroupCollapsed ignored: groups and groupBy are both configured \u2014 remove one",
9481
9524
  groupId
9482
9525
  );
9483
9526
  }
@@ -9488,7 +9531,7 @@ function createGraphInstance(opts) {
9488
9531
  if (DEV) {
9489
9532
  rejectGroupOp(
9490
9533
  "warning",
9491
- `setGroupCollapsed ignored: no derived group '${groupId}' in the current derivation (\xA716.3)`,
9534
+ `setGroupCollapsed ignored: no derived group '${groupId}' in the current derivation`,
9492
9535
  groupId
9493
9536
  );
9494
9537
  }
@@ -9702,7 +9745,7 @@ function createGraphInstance(opts) {
9702
9745
  function invalidateReplaceSessions(owner) {
9703
9746
  for (const s of [...openSessions]) {
9704
9747
  if (s === owner || s.purpose !== "replace") continue;
9705
- abortSessionInternal(s, "stale-base: the model changed outside the session (\xA77.5)", false);
9748
+ abortSessionInternal(s, "stale-base: the model changed outside the session", false);
9706
9749
  }
9707
9750
  }
9708
9751
  function abortAllSessions(except, cause) {
@@ -9771,7 +9814,7 @@ function createGraphInstance(opts) {
9771
9814
  if (expansionHandles.size === 0 && expansionLedger.size === 0) return;
9772
9815
  const err = new OrbitOperationError(
9773
9816
  { code: "aborted", cause: "dataset-changed" },
9774
- "expansion aborted: the datasetKey changed (\xA75/\xA79.2)"
9817
+ "expansion aborted: the datasetKey changed"
9775
9818
  );
9776
9819
  for (const [requestId, handle] of expansionHandles) {
9777
9820
  handle.abort(err);
@@ -9792,7 +9835,7 @@ function createGraphInstance(opts) {
9792
9835
  scopeExtraIds.clear();
9793
9836
  expansionOverlays.clear();
9794
9837
  abortExpansionsForDatasetSwap();
9795
- abortSearchFlight("dataset-changed", "search aborted: the datasetKey changed (\xA75/\xA716.5)");
9838
+ abortSearchFlight("dataset-changed", "search aborted: the datasetKey changed");
9796
9839
  resetMaskState();
9797
9840
  historyKernel.clear();
9798
9841
  stopTimelineTimer();
@@ -9954,7 +9997,7 @@ function createGraphInstance(opts) {
9954
9997
  const labelRerank = recomputeCandidates();
9955
9998
  const patch = {
9956
9999
  revisions,
9957
- // Counts are ACCEPTED-MODEL counts; the scene may be scoped (§9.2).
10000
+ // Counts are ACCEPTED-MODEL counts; the scene may be scoped.
9958
10001
  nodeCount: p.merged.nodes.length,
9959
10002
  edgeCount: p.merged.edges.length,
9960
10003
  diagnostics: composeDiagnostics()
@@ -10014,7 +10057,7 @@ function createGraphInstance(opts) {
10014
10057
  cancelFlushTimer(rec);
10015
10058
  return rejectOperation(
10016
10059
  { code: "overlay-id-conflict", overlayId: id },
10017
- `overlayId '${id}' is already reserved by an open or committed overlay for this dataset (\xA77.5)`
10060
+ `overlayId '${id}' is already reserved by an open or committed overlay for this dataset`
10018
10061
  );
10019
10062
  }
10020
10063
  reservedOverlayIds.set(id, rec);
@@ -10086,14 +10129,14 @@ function createGraphInstance(opts) {
10086
10129
  if (rec.atomic && queuedBytes > rec.maxPendingBytes) {
10087
10130
  return rejectOperation(
10088
10131
  { code: "queue-overflow", queuedBytes, limit: rec.maxPendingBytes },
10089
- `append(): atomic session would queue ${queuedBytes} bytes, exceeding its non-drainable budget of ${rec.maxPendingBytes}; the append was rejected and the session remains open (\xA77.5)`
10132
+ `append(): atomic session would queue ${queuedBytes} bytes, exceeding its non-drainable budget of ${rec.maxPendingBytes}; the append was rejected and the session remains open`
10090
10133
  );
10091
10134
  }
10092
10135
  const overflowLimit = rec.maxPendingBytes * INGEST_OVERFLOW_FACTOR;
10093
10136
  if (bytes > overflowLimit) {
10094
10137
  return rejectOperation(
10095
10138
  { code: "queue-overflow", queuedBytes: bytes, limit: overflowLimit },
10096
- `append(): batch of ${bytes} bytes exceeds the absolute cap of ${overflowLimit} (\xA77.5)`
10139
+ `append(): batch of ${bytes} bytes exceeds the absolute cap of ${overflowLimit}`
10097
10140
  );
10098
10141
  }
10099
10142
  const counts = stageBatch(
@@ -10151,7 +10194,7 @@ function createGraphInstance(opts) {
10151
10194
  };
10152
10195
  }
10153
10196
  const datasetChanged = baseAccepted !== null && baseAccepted.datasetKey !== rec.datasetKey;
10154
- abortAllSessions(rec, "replaced: a replace session committed (\xA77.5)");
10197
+ abortAllSessions(rec, "replaced: a replace session committed");
10155
10198
  const overlaysCleared = clearOverlayState();
10156
10199
  const newBase = baseFromContribution(rec.datasetKey, sourceRevision, c, []);
10157
10200
  const commitDiags = sessionCommitDiagnostics(rec.tallies, newBase.pendingEdges.length);
@@ -10249,7 +10292,7 @@ function createGraphInstance(opts) {
10249
10292
  code: "resource-limit",
10250
10293
  detail: { reason: err instanceof Error ? err.message : String(err) }
10251
10294
  },
10252
- `commit() failed; session rolled back (\xA77.5)`
10295
+ `commit() failed; session rolled back`
10253
10296
  );
10254
10297
  }
10255
10298
  rec.state = "committed";
@@ -10273,16 +10316,16 @@ function createGraphInstance(opts) {
10273
10316
  }
10274
10317
  if (purpose === "replace") {
10275
10318
  if (opts2.sourceRevision === void 0) {
10276
- throw new TypeError("beginIngest: purpose:'replace' requires sourceRevision (\xA77.5)");
10319
+ throw new TypeError("beginIngest: purpose:'replace' requires sourceRevision");
10277
10320
  }
10278
10321
  if (opts2.atomic === false) {
10279
10322
  throw new TypeError(
10280
- "beginIngest: replace sessions are always atomic \u2014 progressive replace frames would expose rows before their sourceRevision existed (\xA77.5)"
10323
+ "beginIngest: replace sessions are always atomic \u2014 progressive replace frames would expose rows before their sourceRevision existed"
10281
10324
  );
10282
10325
  }
10283
10326
  if (baseSource === "declarative") {
10284
10327
  throw new TypeError(
10285
- "beginIngest: purpose:'replace' is unavailable while a declarative data source is active; keep applying snapshots via applyHostUpdate, or use an overlay session (\xA77.5/T16)"
10328
+ "beginIngest: purpose:'replace' is unavailable while a declarative data source is active; keep applying snapshots via applyHostUpdate, or use an overlay session"
10286
10329
  );
10287
10330
  }
10288
10331
  }
@@ -10297,7 +10340,7 @@ function createGraphInstance(opts) {
10297
10340
  if (baseAccepted === null || baseAccepted.datasetKey !== opts2.datasetKey) {
10298
10341
  throw new OrbitOperationError(
10299
10342
  { code: "stale-revision", expected: currentModel, actual: opts2.baseModelRevision },
10300
- baseAccepted === null ? "beginIngest: overlay sessions require an accepted base dataset (\xA77.5 lineage)" : `beginIngest: overlay sessions must name the current datasetKey '${baseAccepted.datasetKey}', got '${opts2.datasetKey}' (\xA77.5 lineage)`
10343
+ baseAccepted === null ? "beginIngest: overlay sessions require an accepted base dataset" : `beginIngest: overlay sessions must name the current datasetKey '${baseAccepted.datasetKey}', got '${opts2.datasetKey}'`
10301
10344
  );
10302
10345
  }
10303
10346
  }
@@ -10306,7 +10349,7 @@ function createGraphInstance(opts) {
10306
10349
  purpose,
10307
10350
  datasetKey: opts2.datasetKey,
10308
10351
  sourceRevision: opts2.sourceRevision ?? null,
10309
- // replace is always atomic; overlays default atomic:true (§7.5).
10352
+ // replace is always atomic; overlays default atomic:true.
10310
10353
  atomic: purpose === "replace" ? true : opts2.atomic ?? true,
10311
10354
  overlayId,
10312
10355
  maxFlushLatencyMs: opts2.maxFlushLatencyMs ?? INGEST_MAX_FLUSH_LATENCY_MS_DEFAULT,
@@ -10419,11 +10462,11 @@ function createGraphInstance(opts) {
10419
10462
  pushServiceDiagnostic(
10420
10463
  "service-aborted",
10421
10464
  "info",
10422
- `expansion result for '${id}' discarded before admission: ${why} (\xA79.2)`
10465
+ `expansion result for '${id}' discarded before admission: ${why}`
10423
10466
  );
10424
10467
  throw new OrbitOperationError(
10425
10468
  { code: "aborted", cause: why },
10426
- `expandNode('${id}') result discarded: ${why} (\xA79.2)`
10469
+ `expandNode('${id}') result discarded: ${why}`
10427
10470
  );
10428
10471
  }
10429
10472
  async function mergeExpansionSession(id, ctx, at, batches, provenance) {
@@ -10644,14 +10687,14 @@ function createGraphInstance(opts) {
10644
10687
  expansionPromises.delete(id);
10645
10688
  const err = new OrbitOperationError(
10646
10689
  { code: "aborted", cause: "collapsed" },
10647
- `expandNode('${id}') aborted by retractExpansion() (\xA79.2/\xA716.3)`
10690
+ `expandNode('${id}') aborted by retractExpansion()`
10648
10691
  );
10649
10692
  expansionHandles.get(requestId)?.abort(err);
10650
10693
  expansionRejectors.get(requestId)?.(err);
10651
10694
  pushServiceDiagnostic(
10652
10695
  "service-aborted",
10653
10696
  "info",
10654
- `pending expansion of '${id}' aborted by retractExpansion() (\xA79.2)`
10697
+ `pending expansion of '${id}' aborted by retractExpansion()`
10655
10698
  );
10656
10699
  publish({ pendingExpansions: expansionLedger.ids() });
10657
10700
  }
@@ -10858,14 +10901,14 @@ function createGraphInstance(opts) {
10858
10901
  if (ctx.signal.aborted) {
10859
10902
  throw new OrbitOperationError(
10860
10903
  { code: "aborted", cause: "superseded" },
10861
- `search('${query}') was superseded before admission (\xA716.5)`
10904
+ `search('${query}') was superseded before admission`
10862
10905
  );
10863
10906
  }
10864
10907
  const denial = acceptanceQueue.admit(() => searchAdmissible(ctx, at));
10865
10908
  if (denial !== null) {
10866
10909
  throw new OrbitOperationError(
10867
10910
  { code: "aborted", cause: denial },
10868
- `search('${query}') result discarded: ${denial} (\xA79.2/\xA716.5)`
10911
+ `search('${query}') result discarded: ${denial}`
10869
10912
  );
10870
10913
  }
10871
10914
  if (accepted === null) return raw;
@@ -10896,7 +10939,7 @@ function createGraphInstance(opts) {
10896
10939
  const at = revisionSnapshot();
10897
10940
  const key = serviceCacheKey({
10898
10941
  serviceId: "search",
10899
- // `fields` is defensive insurance (F12-03): searchIndex is
10942
+ // `fields` is defensive insurance: searchIndex is
10900
10943
  // construction-only (D7) so it cannot legitimately change, but keying
10901
10944
  // it here guarantees a cached result can never outlive the field
10902
10945
  // configuration it was computed under.
@@ -10910,7 +10953,7 @@ function createGraphInstance(opts) {
10910
10953
  searchFlight = null;
10911
10954
  const err = new OrbitOperationError(
10912
10955
  { code: "aborted", cause: "superseded" },
10913
- `search('${query}') superseded the older in-flight query (\xA716.5)`
10956
+ `search('${query}') superseded the older in-flight query`
10914
10957
  );
10915
10958
  flight.handle.abort(err);
10916
10959
  flight.reject(err);
@@ -10976,7 +11019,7 @@ function createGraphInstance(opts) {
10976
11019
  searchFlight = null;
10977
11020
  const err = new OrbitOperationError(
10978
11021
  { code: "aborted", cause: "cleared" },
10979
- "search cleared while in flight (\xA716.5)"
11022
+ "search cleared while in flight"
10980
11023
  );
10981
11024
  flight.handle.abort(err);
10982
11025
  flight.reject(err);
@@ -11043,7 +11086,7 @@ function createGraphInstance(opts) {
11043
11086
  if (denial !== null) {
11044
11087
  throw new OrbitOperationError(
11045
11088
  { code: "aborted", cause: "stale" },
11046
- `findPath('${sourceId}' \u2192 '${targetId}') discarded: ${denial} (\xA79.2)`
11089
+ `findPath('${sourceId}' \u2192 '${targetId}') discarded: ${denial}`
11047
11090
  );
11048
11091
  }
11049
11092
  if (token !== pathSeq || result === null) return result;
@@ -11135,7 +11178,7 @@ function createGraphInstance(opts) {
11135
11178
  findings.push(`mode: unknown filter mode '${String(spec.mode)}'`);
11136
11179
  }
11137
11180
  if (findings.length > 0) {
11138
- throw new TypeError(`applyHostUpdate: invalid filter (\xA79.1): ${findings.join("; ")}`);
11181
+ throw new TypeError(`applyHostUpdate: invalid filter: ${findings.join("; ")}`);
11139
11182
  }
11140
11183
  }
11141
11184
  const prev = store.getState();
@@ -11175,7 +11218,7 @@ function createGraphInstance(opts) {
11175
11218
  severity: "error",
11176
11219
  count: issues.length,
11177
11220
  sampleIds: issues.slice(0, DIAGNOSTIC_SAMPLE_CAP).map((i) => i.where),
11178
- message: `columnar snapshot rejected whole (\xA75.1): ${issues[0].where} \u2014 ${issues[0].detail}`
11221
+ message: `columnar snapshot rejected whole: ${issues[0].where} \u2014 ${issues[0].detail}`
11179
11222
  }
11180
11223
  ];
11181
11224
  columnarRejected = true;
@@ -11196,14 +11239,14 @@ function createGraphInstance(opts) {
11196
11239
  const isReplay = baseAccepted !== null && baseAccepted.datasetKey === data.datasetKey && baseAccepted.sourceRevision === data.sourceRevision;
11197
11240
  if (!isReplay) {
11198
11241
  const nextAccepted = preAccepted ?? validateSnapshot(data);
11199
- abortAllSessions(null, "replaced: a declarative snapshot was applied (\xA77.5)");
11242
+ abortAllSessions(null, "replaced: a declarative snapshot was applied");
11200
11243
  overlayIdsCleared = clearOverlayState();
11201
11244
  datasetKeyChanged = baseAccepted !== null && baseAccepted.datasetKey !== data.datasetKey;
11202
11245
  if (datasetKeyChanged) {
11203
11246
  reconciler = new Reconciler();
11204
11247
  engineDiags = [];
11205
11248
  abortExpansionsForDatasetSwap();
11206
- abortSearchFlight("dataset-changed", "search aborted: the datasetKey changed (\xA75/\xA716.5)");
11249
+ abortSearchFlight("dataset-changed", "search aborted: the datasetKey changed");
11207
11250
  resetMaskState();
11208
11251
  historyKernel.clear();
11209
11252
  stopTimelineTimer();
@@ -11318,7 +11361,7 @@ function createGraphInstance(opts) {
11318
11361
  severity: "error",
11319
11362
  count: 1,
11320
11363
  sampleIds: [],
11321
- message: "groups and groupBy are mutually exclusive (\xA716.3 R-16.3-15): NEITHER applies until the host removes one"
11364
+ message: "groups and groupBy are mutually exclusive: NEITHER applies until the host removes one"
11322
11365
  }
11323
11366
  ];
11324
11367
  groupsDiagsChanged = true;
@@ -11356,7 +11399,7 @@ function createGraphInstance(opts) {
11356
11399
  severity: "warning",
11357
11400
  count: 1,
11358
11401
  sampleIds: [],
11359
- message: "parallelEdgeGrouping is inoperative: the accepted edge list has no same-endpoint-pair parallels (ids synthesized from (type,source,target)-style dedupe already collapse them) \u2014 the toggle changed nothing (\xA716.3 R-16.3-24)"
11402
+ message: "parallelEdgeGrouping is inoperative: the accepted edge list has no same-endpoint-pair parallels (ids synthesized from (type,source,target)-style dedupe already collapse them) \u2014 the toggle changed nothing"
11360
11403
  }
11361
11404
  ];
11362
11405
  parallelRejected = true;
@@ -11436,7 +11479,7 @@ function createGraphInstance(opts) {
11436
11479
  severity: "warning",
11437
11480
  count: update.metrics.length,
11438
11481
  sampleIds: update.metrics.slice(0, DIAGNOSTIC_SAMPLE_CAP).map((c) => c.metric),
11439
- message: "metric columns discarded: no accepted model to join against (\xA712)"
11482
+ message: "metric columns discarded: no accepted model to join against"
11440
11483
  }
11441
11484
  ];
11442
11485
  } else {
@@ -11445,7 +11488,7 @@ function createGraphInstance(opts) {
11445
11488
  count: accepted.nodes.length,
11446
11489
  // I1: columns must be stamped with the revision current at ISSUE
11447
11490
  // time — never the post-update revision (that made the gate
11448
- // self-satisfying, F11-01).
11491
+ // self-satisfying).
11449
11492
  modelRevision: issuedModelSeq
11450
11493
  });
11451
11494
  metricDiags = res.diagnostics;
@@ -11582,7 +11625,7 @@ function createGraphInstance(opts) {
11582
11625
  severity: "warning",
11583
11626
  count: 1,
11584
11627
  sampleIds: [],
11585
- message: "searchIndex is construction-only (\xA716.5/D7): pass it to createGraphInstance options (or key-remount <Graph>) \u2014 this update's searchIndex was ignored"
11628
+ message: "searchIndex is construction-only: pass it to createGraphInstance options (or key-remount <Graph>) \u2014 this update's searchIndex was ignored"
11586
11629
  }
11587
11630
  ];
11588
11631
  searchIndexRejected = true;
@@ -12019,9 +12062,9 @@ function createGraphInstance(opts) {
12019
12062
  applyEmphasis(s.engine, node === null ? null : index);
12020
12063
  emit("nodeHover", { node });
12021
12064
  },
12022
- // §13 native-route edge events (§7.4 mapping). Host onLink* events are
12065
+ // native-route edge events. Host onLink* events are
12023
12066
  // honored regardless of the committed route — an adapter that surfaces
12024
- // them despite declaring linkPicking:false is a harmless pass-through
12067
+ // them despite declaring linkPicking:false is a harmless pass-through
12025
12068
  // but only the 'native' route RELIES on them; the fallback route feeds
12026
12069
  // the same typed events through the pointer samplers.
12027
12070
  onLinkClick(linkIndex) {
@@ -12058,7 +12101,7 @@ function createGraphInstance(opts) {
12058
12101
  const prevented = emit("nodeDragEnd", { node, x, y });
12059
12102
  if (!prevented) writePin(node.id, [x, y]);
12060
12103
  },
12061
- // §14/§15: right-click / long-press → typed 'contextMenu' event. The
12104
+ // Right-click or long-press → typed 'contextMenu' event. The
12062
12105
  // core adds NO built-in follow-up — components own the menu.
12063
12106
  onContextMenu(index, screen) {
12064
12107
  if (!active()) return;
@@ -12072,7 +12115,7 @@ function createGraphInstance(opts) {
12072
12115
  emit("contextMenu", { target: { kind: "node", node }, screen: screenPt });
12073
12116
  },
12074
12117
  /**
12075
- * §13/§17 overlay scheduler tick (adapter activity clock). Per tick:
12118
+ * Overlay scheduler tick (adapter activity clock). Per tick:
12076
12119
  * (1) sim-hot degradation — while the simulation runs, ONE getPositions
12077
12120
  * readback refreshes the CPU cache at a capped >=500ms cadence (M0:
12078
12121
  * tracked readback stalls ~18ms, so the lane NEVER reads back per
@@ -12299,8 +12342,8 @@ function createGraphInstance(opts) {
12299
12342
  route,
12300
12343
  screenToSpace: (p) => eng.screenToSpace?.(p) ?? null,
12301
12344
  medianLinkWidthPx: () => medianLinkWidthPx(lastLinkWidths),
12302
- // S9-T18: the facade's per-candidate visibility mask reads the LIVE
12303
- // §9.1 mask — no re-arm needed on mask changes (§13 grid invariance).
12345
+ // the facade's per-candidate visibility mask reads the LIVE
12346
+ // mask — no re-arm needed on mask changes.
12304
12347
  linkVisible: (linkIndex) => maskEdgeVisibleAt(linkIndex)
12305
12348
  });
12306
12349
  const restartAlpha = layout === "force" ? 1 : null;
@@ -12405,7 +12448,7 @@ function createGraphInstance(opts) {
12405
12448
  if (g.requestAnimationFrame === wrapper) g.requestAnimationFrame = native;
12406
12449
  if (registrations > 0 && !destroyed) {
12407
12450
  console.warn(
12408
- `orbit: ${registrations} requestAnimationFrame registration(s) observed over 500ms while this instance is quiescent (\xA717/S13-T06 one-frame-loop). If no app-owned animation is running, a second frame loop is leaking.`
12451
+ `orbit: ${registrations} requestAnimationFrame registration(s) observed over 500ms while this instance is quiescent. If no app-owned animation is running, a second frame loop is leaking.`
12409
12452
  );
12410
12453
  }
12411
12454
  }, 500);
@@ -12480,7 +12523,7 @@ function createGraphInstance(opts) {
12480
12523
  function warnViewStateDrop(channel, why) {
12481
12524
  if (!DEV || viewStateDropWarned.has(channel)) return;
12482
12525
  viewStateDropWarned.add(channel);
12483
- console.warn(`orbit: getViewState omitted ${channel} \u2014 ${why} (\xA716.14)`);
12526
+ console.warn(`orbit: getViewState omitted ${channel} \u2014 ${why}`);
12484
12527
  }
12485
12528
  function serializableScaleOf(channel, value) {
12486
12529
  if (value === void 0) return void 0;
@@ -12489,33 +12532,34 @@ function createGraphInstance(opts) {
12489
12532
  return void 0;
12490
12533
  }
12491
12534
  if (!isScaleValue(value)) return void 0;
12492
- if (value.kind === "sequential") {
12535
+ const scale = value;
12536
+ if (scale.kind === "sequential") {
12493
12537
  return {
12494
12538
  kind: "sequential",
12495
- metric: value.metric,
12496
- range: value.range,
12539
+ metric: scale.metric,
12540
+ range: scale.range,
12497
12541
  // The numeric-array domain form is data; a DomainPolicy is app
12498
12542
  // behavior config and stays with the app.
12499
- ...Array.isArray(value.domain) ? { domain: value.domain } : {}
12543
+ ...Array.isArray(scale.domain) ? { domain: scale.domain } : {}
12500
12544
  };
12501
12545
  }
12502
- if (value.kind === "diverging") {
12546
+ if (scale.kind === "diverging") {
12503
12547
  return {
12504
12548
  kind: "diverging",
12505
- metric: value.metric,
12506
- range: value.range,
12507
- mid: value.mid
12549
+ metric: scale.metric,
12550
+ range: scale.range,
12551
+ mid: scale.mid
12508
12552
  };
12509
12553
  }
12510
- if (typeof value.by !== "string") {
12554
+ if (typeof scale.by !== "string") {
12511
12555
  warnViewStateDrop(channel, "a function-`by` categorical scale does not serialize");
12512
12556
  return void 0;
12513
12557
  }
12514
12558
  return {
12515
12559
  kind: "categorical",
12516
- by: value.by,
12517
- ...value.palette !== void 0 ? { palette: value.palette } : {},
12518
- ...value.domain !== void 0 ? { domain: value.domain } : {}
12560
+ by: scale.by,
12561
+ ...scale.palette !== void 0 ? { palette: scale.palette } : {},
12562
+ ...scale.domain !== void 0 ? { domain: scale.domain } : {}
12519
12563
  };
12520
12564
  }
12521
12565
  function serializableThemeOf() {
@@ -12597,7 +12641,7 @@ function createGraphInstance(opts) {
12597
12641
  if (visible.length > limit) {
12598
12642
  throw new OrbitOperationError(
12599
12643
  { code: "export-materialization-too-large", rowCount: visible.length, limit },
12600
- `includePositions over ${limit} nodes \u2014 persist the layout through the export lane and reference it from dataRef (\xA716.14)`
12644
+ `includePositions over ${limit} nodes \u2014 persist the layout through the export lane and reference it from dataRef`
12601
12645
  );
12602
12646
  }
12603
12647
  const pos = eng.getPositions();
@@ -12697,7 +12741,7 @@ function createGraphInstance(opts) {
12697
12741
  severity: "error",
12698
12742
  count: verdict.problems.length,
12699
12743
  sampleIds: [],
12700
- message: `setViewState rejected (${verdict.code}): ${verdict.problems.slice(0, 3).join("; ")} \u2014 nothing was applied (\xA716.14)`
12744
+ message: `setViewState rejected (${verdict.code}): ${verdict.problems.slice(0, 3).join("; ")} \u2014 nothing was applied`
12701
12745
  }
12702
12746
  ];
12703
12747
  pendingDiagnosticsRefresh = true;
@@ -12835,7 +12879,7 @@ function createGraphInstance(opts) {
12835
12879
  ...prevState.revisions,
12836
12880
  render: prevState.revisions.render + 1
12837
12881
  };
12838
- eng.commit({
12882
+ commitToEngine(eng, {
12839
12883
  revision: nextRevisions.render,
12840
12884
  structure: {
12841
12885
  pointCount: scene.count,
@@ -12843,6 +12887,7 @@ function createGraphInstance(opts) {
12843
12887
  links: scene.links
12844
12888
  }
12845
12889
  });
12890
+ nextRevisions.appliedRender = eng.appliedRevision();
12846
12891
  eng.pause();
12847
12892
  const patch = { revisions: nextRevisions };
12848
12893
  if (prevState.simulationRunning) patch.simulationRunning = false;
@@ -12920,7 +12965,7 @@ function createGraphInstance(opts) {
12920
12965
  if (eng2 === null || eng2.captureScreenshot === void 0) {
12921
12966
  throw new OrbitOperationError(
12922
12967
  { code: "aborted", cause: "no screenshot capability" },
12923
- 'exportImage("png") requires an engine with screenshot capture (\xA716.14)'
12968
+ 'exportImage("png") requires an engine with screenshot capture'
12924
12969
  );
12925
12970
  }
12926
12971
  const blob = await eng2.captureScreenshot();
@@ -12961,7 +13006,7 @@ function createGraphInstance(opts) {
12961
13006
  if (elementCount > limit && opts2?.fallback !== "raster-hybrid") {
12962
13007
  throw new OrbitOperationError(
12963
13008
  { code: "export-too-large", elementCount, limit },
12964
- `SVG export of ${elementCount} elements exceeds ${limit} \u2014 filter/isolate first, or pass { fallback: 'raster-hybrid' } (\xA716.14)`
13009
+ `SVG export of ${elementCount} elements exceeds ${limit} \u2014 filter/isolate first, or pass { fallback: 'raster-hybrid' }`
12965
13010
  );
12966
13011
  }
12967
13012
  let minX = Infinity;
@@ -13092,7 +13137,7 @@ function createGraphInstance(opts) {
13092
13137
  if (rowCount > limit) {
13093
13138
  throw new OrbitOperationError(
13094
13139
  { code: "export-materialization-too-large", rowCount, limit },
13095
- `exportData would materialize ${rowCount} rows (limit ${limit}) \u2014 use exportDataStream (\xA716.14)`
13140
+ `exportData would materialize ${rowCount} rows (limit ${limit}) \u2014 use exportDataStream`
13096
13141
  );
13097
13142
  }
13098
13143
  if (scope === "accepted") return { nodes: pin.accepted.nodes, edges: pin.accepted.edges };
@@ -13135,7 +13180,7 @@ function createGraphInstance(opts) {
13135
13180
  if (bound > limit) {
13136
13181
  throw new OrbitOperationError(
13137
13182
  { code: "export-materialization-too-large", rowCount: bound, limit },
13138
- `exportLayout would materialize ${bound} rows (limit ${limit}) \u2014 use exportLayoutStream (\xA716.14)`
13183
+ `exportLayout would materialize ${bound} rows (limit ${limit}) \u2014 use exportLayoutStream`
13139
13184
  );
13140
13185
  }
13141
13186
  const out = /* @__PURE__ */ new Map();
@@ -13214,9 +13259,8 @@ function createGraphInstance(opts) {
13214
13259
  visibleNodeCount: st.visible.nodes,
13215
13260
  visibleEdgeCount: st.visible.edges,
13216
13261
  estimatedCpuBytes: estimateCpuBytes(),
13217
- // The acceptance queue is synchronous (depth is 0 outside a job, and a
13218
- // reader inside a job observes 1); async ingestion depth arrives with
13219
- // the worker lane (PR-E).
13262
+ // The acceptance queue is synchronous: depth is 0 outside a job, and a
13263
+ // reader inside a job observes 1.
13220
13264
  queueDepth: acceptanceQueue.active ? 1 : 0,
13221
13265
  modelRevision: st.revisions.model,
13222
13266
  scopeRevision: st.revisions.scope,
@@ -13440,7 +13484,7 @@ var OverviewController = class {
13440
13484
  getScene;
13441
13485
  getVisible;
13442
13486
  size;
13443
- /** Transform of the LAST rasterization; null until rasterize() succeeds. */
13487
+ /** Transform of the LAST rasterization; null until rasterize succeeds. */
13444
13488
  frame = null;
13445
13489
  // --- throttle state (latched by shouldRefresh when it answers true) ---
13446
13490
  lastRefreshMs = null;
@@ -13455,9 +13499,9 @@ var OverviewController = class {
13455
13499
  * when it returns true, so each `true` accounts for exactly one refresh:
13456
13500
  *
13457
13501
  * - hot (`simulationRunning`): time-gated only (≤ 2 Hz) — the epoch is
13458
- * ignored because positions change continuously without epoch advances;
13502
+ * ignored because positions change continuously without epoch advances;
13459
13503
  * - idle: refresh only when the epoch ADVANCED since the last refresh,
13460
- * time-gated at ≤ 1 Hz;
13504
+ * time-gated at ≤ 1 Hz;
13461
13505
  * - idle + unchanged epoch: always false — zero work, forever.
13462
13506
  */
13463
13507
  shouldRefresh(nowMs2, simulationRunning, epoch) {
@@ -13475,7 +13519,7 @@ var OverviewController = class {
13475
13519
  /**
13476
13520
  * Rasterizes the current scene into a fresh `size²` RGBA dot field: 1 px
13477
13521
  * white dots whose alpha ACCUMULATES on overlap (heatmap-ish density),
13478
- * dimmed for mask-hidden points; NaN pairs (§7.3 tombstones) are skipped.
13522
+ * dimmed for mask-hidden points; NaN pairs are skipped.
13479
13523
  * World bounds map into the thumbnail with a 5 % edge padding at a UNIFORM
13480
13524
  * scale (aspect preserved, centered on the short axis) and a downward pixel
13481
13525
  * y (see module header). Returns null when there is no scene or no point.