@elaraai/e3-ui-components 1.0.79 → 1.0.81

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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/components/DatasetPreview.d.ts +10 -2
  3. package/dist/components/DatasetPreview.d.ts.map +1 -1
  4. package/dist/components/EastValueViewer.d.ts.map +1 -1
  5. package/dist/components/PagedDatasetPreview.d.ts +15 -1
  6. package/dist/components/PagedDatasetPreview.d.ts.map +1 -1
  7. package/dist/components/TaskPreview.d.ts.map +1 -1
  8. package/dist/components/UITaskPreview.d.ts +7 -3
  9. package/dist/components/UITaskPreview.d.ts.map +1 -1
  10. package/dist/decision/constraint-format.d.ts +15 -7
  11. package/dist/decision/constraint-format.d.ts.map +1 -1
  12. package/dist/decision/contract.d.ts +28 -0
  13. package/dist/decision/contract.d.ts.map +1 -0
  14. package/dist/decision/facets.d.ts +21 -3
  15. package/dist/decision/facets.d.ts.map +1 -1
  16. package/dist/decision/grouping.d.ts +11 -0
  17. package/dist/decision/grouping.d.ts.map +1 -1
  18. package/dist/decision/handle-runtime.d.ts.map +1 -1
  19. package/dist/decision/journal.d.ts.map +1 -1
  20. package/dist/decision/lever-editor.d.ts +11 -18
  21. package/dist/decision/lever-editor.d.ts.map +1 -1
  22. package/dist/decision/queue.d.ts.map +1 -1
  23. package/dist/diff/format.d.ts +20 -12
  24. package/dist/diff/format.d.ts.map +1 -1
  25. package/dist/diff/index.d.ts.map +1 -1
  26. package/dist/diff/manual.d.ts +37 -18
  27. package/dist/diff/manual.d.ts.map +1 -1
  28. package/dist/diff/walker.d.ts +14 -6
  29. package/dist/diff/walker.d.ts.map +1 -1
  30. package/dist/experiment/charts.d.ts +4 -1
  31. package/dist/experiment/charts.d.ts.map +1 -1
  32. package/dist/experiment/derive.d.ts +25 -5
  33. package/dist/experiment/derive.d.ts.map +1 -1
  34. package/dist/experiment/index.d.ts.map +1 -1
  35. package/dist/experiment/run-runtime.d.ts +4 -8
  36. package/dist/experiment/run-runtime.d.ts.map +1 -1
  37. package/dist/hooks/index.d.ts +1 -1
  38. package/dist/hooks/index.d.ts.map +1 -1
  39. package/dist/hooks/useDatasetValue.d.ts +13 -2
  40. package/dist/hooks/useDatasetValue.d.ts.map +1 -1
  41. package/dist/hooks/useTaskDetails.d.ts +1 -5
  42. package/dist/hooks/useTaskDetails.d.ts.map +1 -1
  43. package/dist/hooks/workspaces.d.ts +16 -3
  44. package/dist/hooks/workspaces.d.ts.map +1 -1
  45. package/dist/index.cjs +1361 -1028
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +1385 -1052
  48. package/dist/index.js.map +1 -1
  49. package/dist/platform/bind-runtime.d.ts +14 -5
  50. package/dist/platform/bind-runtime.d.ts.map +1 -1
  51. package/dist/platform/dataset-hooks.d.ts +12 -4
  52. package/dist/platform/dataset-hooks.d.ts.map +1 -1
  53. package/dist/platform/dataset-store.d.ts +47 -11
  54. package/dist/platform/dataset-store.d.ts.map +1 -1
  55. package/dist/platform/func-runtime.d.ts +16 -6
  56. package/dist/platform/func-runtime.d.ts.map +1 -1
  57. package/dist/platform/index.d.ts +1 -1
  58. package/dist/platform/index.d.ts.map +1 -1
  59. package/dist/platform/paged-runtime.d.ts +183 -49
  60. package/dist/platform/paged-runtime.d.ts.map +1 -1
  61. package/dist/platform/record-runtime.d.ts +14 -4
  62. package/dist/platform/record-runtime.d.ts.map +1 -1
  63. package/dist/platform/staged-store.d.ts +19 -2
  64. package/dist/platform/staged-store.d.ts.map +1 -1
  65. package/package.json +16 -15
package/dist/index.cjs CHANGED
@@ -2077,6 +2077,7 @@ const realClock = {
2077
2077
  return { clear: () => clearInterval(id3) };
2078
2078
  }
2079
2079
  };
2080
+ const HASH_POLL_MS = 1e3;
2080
2081
  function createDefaultDatasetApi(apiUrl, repo, getToken) {
2081
2082
  const opts = () => ({ token: getToken() });
2082
2083
  return {
@@ -2142,12 +2143,17 @@ class ReactiveDatasetCache {
2142
2143
  __publicField(this, "keyVersions", /* @__PURE__ */ new Map());
2143
2144
  // Workspace status polling — groups subscriptions by workspace for
2144
2145
  // efficiency. Each entry holds the active interval handle from the
2145
- // injected {@link Clock} so tests can drive ticking deterministically.
2146
+ // injected {@link Clock} so tests can drive ticking deterministically,
2147
+ // and each polled path with how many watchers poll it.
2146
2148
  __publicField(this, "workspacePollers", /* @__PURE__ */ new Map());
2147
2149
  __publicField(this, "clock");
2148
2150
  // In-flight poll dedup — concurrent callers (interval tick + a
2149
2151
  // post-write trigger) share a single round-trip rather than racing.
2150
2152
  __publicField(this, "inFlightPolls", /* @__PURE__ */ new Map());
2153
+ // Hash-only watches, by workspace and path: each callback with the hash it
2154
+ // was last told. A watched path rides its workspace's poll, but its
2155
+ // content is never fetched — a paged source reads windows, never the value.
2156
+ __publicField(this, "hashWatches", /* @__PURE__ */ new Map());
2151
2157
  // Batching
2152
2158
  __publicField(this, "batchDepth", 0);
2153
2159
  __publicField(this, "changedKeys", /* @__PURE__ */ new Set());
@@ -2236,9 +2242,7 @@ class ReactiveDatasetCache {
2236
2242
  hash: void 0,
2237
2243
  status: east.variant("stale", null)
2238
2244
  });
2239
- if (this.workspacePollers.has(workspace)) {
2240
- void this.pollWorkspaceStatus(workspace);
2241
- }
2245
+ void this.refresh(workspace);
2242
2246
  } catch (error3) {
2243
2247
  if (!this.destroyed && this.writeEpochs.get(key) === myEpoch) {
2244
2248
  const baseline = this.writeBaselines.get(key);
@@ -2344,15 +2348,15 @@ class ReactiveDatasetCache {
2344
2348
  * 3. Only fetches full content when hash changes
2345
2349
  *
2346
2350
  * Multiple subscriptions to the same workspace share a single poller.
2347
- * Pair with {@link clearRefetchInterval} on unmount so the poller can
2348
- * stop when nothing is watching.
2351
+ * Pair each call with one {@link clearRefetchInterval} on unmount so the
2352
+ * poller can stop when nothing is watching.
2349
2353
  */
2350
2354
  setRefetchInterval(workspace, path2, intervalMs) {
2351
2355
  var _a2;
2352
2356
  const pathStr = datasetPathToString(path2);
2353
2357
  let poller = this.workspacePollers.get(workspace);
2354
2358
  if (poller) {
2355
- poller.paths.add(pathStr);
2359
+ poller.paths.set(pathStr, (poller.paths.get(pathStr) ?? 0) + 1);
2356
2360
  if (intervalMs < poller.intervalMs) {
2357
2361
  (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2358
2362
  poller.intervalMs = intervalMs;
@@ -2364,7 +2368,7 @@ class ReactiveDatasetCache {
2364
2368
  } else {
2365
2369
  poller = {
2366
2370
  intervalMs,
2367
- paths: /* @__PURE__ */ new Set([pathStr]),
2371
+ paths: /* @__PURE__ */ new Map([[pathStr, 1]]),
2368
2372
  handle: this.clock.setInterval(
2369
2373
  () => this.pollWorkspaceStatus(workspace),
2370
2374
  intervalMs
@@ -2375,23 +2379,79 @@ class ReactiveDatasetCache {
2375
2379
  }
2376
2380
  }
2377
2381
  /**
2378
- * Stop polling a dataset; the workspace poller is torn down when its
2379
- * last watched path is cleared.
2382
+ * Stop polling a dataset for one watcher; the path goes when its last
2383
+ * watcher clears it, and the workspace poller is torn down when its last
2384
+ * path goes.
2380
2385
  */
2381
2386
  clearRefetchInterval(workspace, path2) {
2382
- var _a2;
2383
2387
  const poller = this.workspacePollers.get(workspace);
2384
2388
  if (!poller) return;
2385
- poller.paths.delete(datasetPathToString(path2));
2386
- if (poller.paths.size === 0) {
2389
+ const pathStr = datasetPathToString(path2);
2390
+ const watchers = (poller.paths.get(pathStr) ?? 0) - 1;
2391
+ if (watchers > 0) {
2392
+ poller.paths.set(pathStr, watchers);
2393
+ return;
2394
+ }
2395
+ poller.paths.delete(pathStr);
2396
+ this.stopIdlePoller(workspace);
2397
+ }
2398
+ /**
2399
+ * Follow a dataset's content hash without fetching its content; see the
2400
+ * interface doc.
2401
+ */
2402
+ watchHash(workspace, path2, onHash, intervalMs = HASH_POLL_MS) {
2403
+ var _a2;
2404
+ const pathStr = datasetPathToString(path2);
2405
+ let byPath = this.hashWatches.get(workspace);
2406
+ if (!byPath) {
2407
+ byPath = /* @__PURE__ */ new Map();
2408
+ this.hashWatches.set(workspace, byPath);
2409
+ }
2410
+ let callbacks = byPath.get(pathStr);
2411
+ if (!callbacks) {
2412
+ callbacks = /* @__PURE__ */ new Map();
2413
+ byPath.set(pathStr, callbacks);
2414
+ }
2415
+ callbacks.set(onHash, void 0);
2416
+ const poller = this.workspacePollers.get(workspace);
2417
+ if (!poller) {
2418
+ this.workspacePollers.set(workspace, {
2419
+ intervalMs,
2420
+ paths: /* @__PURE__ */ new Map(),
2421
+ handle: this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs)
2422
+ });
2423
+ void this.pollWorkspaceStatus(workspace);
2424
+ } else if (intervalMs < poller.intervalMs) {
2387
2425
  (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2388
- this.workspacePollers.delete(workspace);
2426
+ poller.intervalMs = intervalMs;
2427
+ poller.handle = this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs);
2389
2428
  }
2429
+ return () => {
2430
+ const watched = this.hashWatches.get(workspace);
2431
+ const set3 = watched == null ? void 0 : watched.get(pathStr);
2432
+ if (!watched || !set3) return;
2433
+ set3.delete(onHash);
2434
+ if (set3.size === 0) watched.delete(pathStr);
2435
+ if (watched.size === 0) this.hashWatches.delete(workspace);
2436
+ this.stopIdlePoller(workspace);
2437
+ };
2438
+ }
2439
+ /** Stops a workspace's poller once it has no path to reconcile and no hash
2440
+ * to follow — without this, a long-lived session polls forever. */
2441
+ stopIdlePoller(workspace) {
2442
+ var _a2;
2443
+ const poller = this.workspacePollers.get(workspace);
2444
+ if (!poller || poller.paths.size > 0 || this.hashWatches.has(workspace)) return;
2445
+ (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2446
+ this.workspacePollers.delete(workspace);
2390
2447
  }
2391
2448
  refresh(workspace) {
2392
2449
  if (this.destroyed) return Promise.resolve();
2393
2450
  if (!this.workspacePollers.has(workspace)) return Promise.resolve();
2394
- return this.pollWorkspaceStatus(workspace);
2451
+ const inFlight = this.inFlightPolls.get(workspace);
2452
+ if (inFlight === void 0) return this.pollWorkspaceStatus(workspace);
2453
+ const again = () => this.destroyed || !this.workspacePollers.has(workspace) ? void 0 : this.pollWorkspaceStatus(workspace);
2454
+ return inFlight.then(again, again);
2395
2455
  }
2396
2456
  /**
2397
2457
  * Poll workspace status and reconcile the cache with the server.
@@ -2407,11 +2467,12 @@ class ReactiveDatasetCache {
2407
2467
  return promise4;
2408
2468
  }
2409
2469
  async doPollWorkspaceStatus(workspace) {
2470
+ var _a2;
2410
2471
  if (this.destroyed) return;
2411
2472
  const poller = this.workspacePollers.get(workspace);
2412
- if (!poller || poller.paths.size === 0) return;
2473
+ if (!poller || poller.paths.size === 0 && !this.hashWatches.has(workspace)) return;
2413
2474
  const epochsAtStart = /* @__PURE__ */ new Map();
2414
- for (const pathStr of poller.paths) {
2475
+ for (const pathStr of poller.paths.keys()) {
2415
2476
  const key = pathStr ? `${workspace}.${pathStr}` : workspace;
2416
2477
  epochsAtStart.set(key, this.writeEpochs.get(key) ?? 0);
2417
2478
  }
@@ -2425,15 +2486,28 @@ class ReactiveDatasetCache {
2425
2486
  if (this.destroyed) return;
2426
2487
  const infoByPath = /* @__PURE__ */ new Map();
2427
2488
  for (const info of status.datasets) infoByPath.set(info.path, info);
2489
+ for (const [pathStr, callbacks] of this.hashWatches.get(workspace) ?? []) {
2490
+ const info = infoByPath.get(pathStr ? `.${pathStr}` : "");
2491
+ const hash2 = ((_a2 = info == null ? void 0 : info.hash) == null ? void 0 : _a2.type) === "some" ? info.hash.value : null;
2492
+ for (const [onHash, told] of [...callbacks]) {
2493
+ if (told === hash2 || !callbacks.has(onHash)) continue;
2494
+ callbacks.set(onHash, hash2);
2495
+ try {
2496
+ onHash(hash2);
2497
+ } catch (error3) {
2498
+ console.error(`A hash watch on ${workspace}.${pathStr} failed:`, error3);
2499
+ }
2500
+ }
2501
+ }
2428
2502
  const pending = [];
2429
2503
  this.batch(() => {
2430
- var _a2;
2431
- for (const pathStr of poller.paths) {
2504
+ var _a3;
2505
+ for (const pathStr of poller.paths.keys()) {
2432
2506
  const e3Path = pathStr ? `.${pathStr}` : "";
2433
2507
  const info = infoByPath.get(e3Path);
2434
2508
  const key = pathStr ? `${workspace}.${pathStr}` : workspace;
2435
2509
  const path2 = this.stringToPath(pathStr);
2436
- const currentHash = ((_a2 = info == null ? void 0 : info.hash) == null ? void 0 : _a2.type) === "some" ? info.hash.value : null;
2510
+ const currentHash = ((_a3 = info == null ? void 0 : info.hash) == null ? void 0 : _a3.type) === "some" ? info.hash.value : null;
2437
2511
  const knownHash = this.knownHashes.get(key);
2438
2512
  if (info == null ? void 0 : info.status) {
2439
2513
  const previous = this.statuses.get(key);
@@ -2599,6 +2673,7 @@ class ReactiveDatasetCache {
2599
2673
  }
2600
2674
  this.workspacePollers.clear();
2601
2675
  this.inFlightPolls.clear();
2676
+ this.hashWatches.clear();
2602
2677
  this.keySubscribers.clear();
2603
2678
  this.globalSubscribers.clear();
2604
2679
  this.knownHashes.clear();
@@ -2713,9 +2788,16 @@ class MemoryStagedAdapter {
2713
2788
  this.map.clear();
2714
2789
  }
2715
2790
  }
2791
+ const SCOPE_SEPARATOR = "\0";
2792
+ function cacheKeyOf(key) {
2793
+ return key.slice(key.indexOf(SCOPE_SEPARATOR) + 1);
2794
+ }
2716
2795
  class StagedStore {
2717
2796
  constructor(adapter) {
2797
+ // By scope and `datasetCacheKey`; subscribers and versions go by the
2798
+ // `datasetCacheKey` alone, which is what a reactive view tracks.
2718
2799
  __publicField(this, "entries", /* @__PURE__ */ new Map());
2800
+ __publicField(this, "scope", null);
2719
2801
  __publicField(this, "subscribers", /* @__PURE__ */ new Map());
2720
2802
  __publicField(this, "versions", /* @__PURE__ */ new Map());
2721
2803
  __publicField(this, "adapter");
@@ -2739,25 +2821,25 @@ class StagedStore {
2739
2821
  }
2740
2822
  }
2741
2823
  hasPending(workspace, path2) {
2742
- return this.entries.has(datasetCacheKey(workspace, path2));
2824
+ return this.entries.has(this.keyOf(workspace, path2));
2743
2825
  }
2744
2826
  getBuffered(workspace, path2) {
2745
2827
  var _a2;
2746
- return (_a2 = this.entries.get(datasetCacheKey(workspace, path2))) == null ? void 0 : _a2.buffered;
2828
+ return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.buffered;
2747
2829
  }
2748
2830
  getSnapshot(workspace, path2) {
2749
2831
  var _a2;
2750
- return (_a2 = this.entries.get(datasetCacheKey(workspace, path2))) == null ? void 0 : _a2.snapshot;
2832
+ return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.snapshot;
2751
2833
  }
2752
2834
  getEntry(workspace, path2) {
2753
- return this.entries.get(datasetCacheKey(workspace, path2));
2835
+ return this.entries.get(this.keyOf(workspace, path2));
2754
2836
  }
2755
2837
  write(workspace, path2, snapshotIfNew, buffered) {
2756
- const key = datasetCacheKey(workspace, path2);
2838
+ const key = this.keyOf(workspace, path2);
2757
2839
  const existing = this.entries.get(key);
2758
2840
  const next2 = existing ? { snapshot: existing.snapshot, buffered } : { snapshot: snapshotIfNew, buffered };
2759
2841
  this.entries.set(key, next2);
2760
- this.notify(key);
2842
+ this.notify(datasetCacheKey(workspace, path2));
2761
2843
  const persisted = {
2762
2844
  snapshot: next2.snapshot,
2763
2845
  buffered: next2.buffered
@@ -2765,15 +2847,26 @@ class StagedStore {
2765
2847
  this.persist(key, () => this.adapter.save(key, persisted));
2766
2848
  }
2767
2849
  discard(workspace, path2) {
2768
- const key = datasetCacheKey(workspace, path2);
2850
+ const key = this.keyOf(workspace, path2);
2769
2851
  if (!this.entries.has(key)) return false;
2770
2852
  this.entries.delete(key);
2771
- this.notify(key);
2853
+ this.notify(datasetCacheKey(workspace, path2));
2772
2854
  this.persist(key, () => this.adapter.remove(key));
2773
2855
  return true;
2774
2856
  }
2775
2857
  listKeys() {
2776
- return [...this.entries.keys()];
2858
+ const prefix2 = `${this.scope ?? ""}${SCOPE_SEPARATOR}`;
2859
+ return [...this.entries.keys()].filter((key) => key.startsWith(prefix2)).map(cacheKeyOf);
2860
+ }
2861
+ setScope(scope) {
2862
+ const before = this.scope;
2863
+ this.scope = scope;
2864
+ this.notifyScopes(before, scope);
2865
+ return () => {
2866
+ if (this.scope !== scope) return;
2867
+ this.scope = null;
2868
+ this.notifyScopes(scope, null);
2869
+ };
2777
2870
  }
2778
2871
  subscribe(key, callback) {
2779
2872
  let set3 = this.subscribers.get(key);
@@ -2796,15 +2889,29 @@ class StagedStore {
2796
2889
  this.errorListeners.delete(cb);
2797
2890
  };
2798
2891
  }
2799
- /** Test-only: clear in-memory + persisted state. */
2892
+ /** Test-only: clear in-memory + persisted state, in every scope. */
2800
2893
  async clear() {
2801
2894
  const keys = [...this.entries.keys()];
2802
2895
  this.entries.clear();
2803
- for (const key of keys) this.notify(key);
2896
+ for (const key of new Set(keys.map(cacheKeyOf))) this.notify(key);
2804
2897
  await this.flushPending();
2805
2898
  await this.adapter.clear();
2806
2899
  }
2807
2900
  // ----- internals -------------------------------------------------------
2901
+ /** The key a path's entry is held under in the current scope. */
2902
+ keyOf(workspace, path2) {
2903
+ return `${this.scope ?? ""}${SCOPE_SEPARATOR}${datasetCacheKey(workspace, path2)}`;
2904
+ }
2905
+ /** Tells every path with an entry in either scope that what it reads has
2906
+ * changed: the scope's move shows one's entries and hides the other's. */
2907
+ notifyScopes(from2, to) {
2908
+ const keys = /* @__PURE__ */ new Set();
2909
+ for (const key of this.entries.keys()) {
2910
+ const scope = key.slice(0, key.indexOf(SCOPE_SEPARATOR));
2911
+ if (scope === (from2 ?? "") || scope === (to ?? "")) keys.add(cacheKeyOf(key));
2912
+ }
2913
+ for (const key of keys) this.notify(key);
2914
+ }
2808
2915
  notify(key) {
2809
2916
  this.versions.set(key, (this.versions.get(key) ?? 0) + 1);
2810
2917
  const subs = this.subscribers.get(key);
@@ -2821,7 +2928,7 @@ class StagedStore {
2821
2928
  const settled = prev2.then(op, op).then(
2822
2929
  () => void 0,
2823
2930
  (err) => {
2824
- this.emitPersistError(key, err);
2931
+ this.emitPersistError(cacheKeyOf(key), err);
2825
2932
  }
2826
2933
  ).then(() => {
2827
2934
  if (this.saveChains.get(key) === settled) this.saveChains.delete(key);
@@ -2852,11 +2959,18 @@ class StagedStore {
2852
2959
  return;
2853
2960
  }
2854
2961
  for (const [key, p2] of persisted) {
2962
+ if (!key.includes(SCOPE_SEPARATOR)) {
2963
+ this.persist(key, () => this.adapter.remove(key));
2964
+ this.emitPersistError(key, new Error(
2965
+ `a staged edit of ${key} that an older build left names no server or repository, and was dropped`
2966
+ ));
2967
+ continue;
2968
+ }
2855
2969
  this.entries.set(key, {
2856
2970
  snapshot: p2.snapshot,
2857
2971
  buffered: p2.buffered
2858
2972
  });
2859
- this.notify(key);
2973
+ this.notify(cacheKeyOf(key));
2860
2974
  }
2861
2975
  }
2862
2976
  }
@@ -2884,7 +2998,7 @@ function clearStagedStoreSingleton() {
2884
2998
  }
2885
2999
  const UNCHANGED_VARIANT = east.variant("unchanged", null);
2886
3000
  const blobEqual = east.equalFor(east.BlobType);
2887
- const eastTypeEqual$2 = east.equalFor(internal.EastTypeType);
3001
+ const eastTypeEqual$1 = east.equalFor(internal.EastTypeType);
2888
3002
  const helpersCache = new east.SortedMap(
2889
3003
  void 0,
2890
3004
  east.compareFor(internal.EastTypeType)
@@ -2973,8 +3087,14 @@ class BindRuntime {
2973
3087
  }
2974
3088
  this.cache = cache3;
2975
3089
  }
2976
- /** Clear the active cache and drop any queued writes. */
2977
- clearCache() {
3090
+ /**
3091
+ * Clear the active cache and drop any queued writes.
3092
+ *
3093
+ * @param cache - Clear only while this is the active cache, so a provider
3094
+ * tearing down after another installed its own leaves that one in place
3095
+ */
3096
+ clearCache(cache3) {
3097
+ if (cache3 !== void 0 && this.cache !== cache3) return;
2978
3098
  this.cache = null;
2979
3099
  this.clearPendingWrites();
2980
3100
  this.handleCache.clear();
@@ -3087,11 +3207,11 @@ class BindRuntime {
3087
3207
  const key = datasetCacheKey(workspace, sourcePath);
3088
3208
  const existing = this.bindingRegistry.get(key);
3089
3209
  if (existing) {
3090
- if (existing.mode === info.mode && existing.hasPatchDataset === info.hasPatchDataset && existing.sourceType === info.sourceType) {
3210
+ if (existing.mode === info.mode && existing.hasPatchDataset === info.hasPatchDataset && Object.is(existing.sourceType, info.sourceType)) {
3091
3211
  return;
3092
3212
  }
3093
3213
  const shapeChanged = existing.mode !== info.mode || existing.hasPatchDataset !== info.hasPatchDataset;
3094
- const typeChanged = !eastTypeEqual$2(existing.sourceType, info.sourceType);
3214
+ const typeChanged = !eastTypeEqual$1(existing.sourceType, info.sourceType);
3095
3215
  if (!shapeChanged && !typeChanged) {
3096
3216
  this.bindingRegistry.set(key, info);
3097
3217
  return;
@@ -3281,7 +3401,7 @@ class BindRuntime {
3281
3401
  };
3282
3402
  const bindingDescriptor = {
3283
3403
  source: sourcePath,
3284
- patch: patchPath ? east.variant("some", patchPath) : east.variant("none", null),
3404
+ patch: patchPath ? east.some(patchPath) : east.none,
3285
3405
  mode: east.variant(mode, null)
3286
3406
  };
3287
3407
  const writeStaged = (value) => {
@@ -3299,6 +3419,10 @@ class BindRuntime {
3299
3419
  this.queueWrite(() => cache3.launchDataflow(ws));
3300
3420
  return null;
3301
3421
  };
3422
+ const hasSource = () => {
3423
+ this.trackPath(ws, sourcePath);
3424
+ return cache3.has(ws, sourcePath);
3425
+ };
3302
3426
  if (mode === "direct" && !patchPath) {
3303
3427
  return {
3304
3428
  read: () => {
@@ -3321,7 +3445,7 @@ class BindRuntime {
3321
3445
  pending: () => false,
3322
3446
  commit: () => null,
3323
3447
  discard: () => null,
3324
- has: () => cache3.has(ws, sourcePath),
3448
+ has: hasSource,
3325
3449
  status: () => {
3326
3450
  this.trackPath(ws, sourcePath);
3327
3451
  return cache3.getStatus(ws, sourcePath);
@@ -3370,7 +3494,7 @@ class BindRuntime {
3370
3494
  this.queueWrite(() => cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT)));
3371
3495
  return null;
3372
3496
  },
3373
- has: () => cache3.has(ws, sourcePath),
3497
+ has: hasSource,
3374
3498
  status: () => {
3375
3499
  this.trackPath(ws, sourcePath);
3376
3500
  return cache3.getStatus(ws, sourcePath);
@@ -3414,7 +3538,7 @@ class BindRuntime {
3414
3538
  staged.discard(ws, sourcePath);
3415
3539
  return null;
3416
3540
  },
3417
- has: () => cache3.has(ws, sourcePath),
3541
+ has: hasSource,
3418
3542
  status: () => {
3419
3543
  this.trackPath(ws, sourcePath);
3420
3544
  return cache3.getStatus(ws, sourcePath);
@@ -3461,7 +3585,7 @@ class BindRuntime {
3461
3585
  staged.discard(ws, sourcePath);
3462
3586
  return null;
3463
3587
  },
3464
- has: () => cache3.has(ws, sourcePath),
3588
+ has: hasSource,
3465
3589
  status: () => {
3466
3590
  this.trackPath(ws, sourcePath);
3467
3591
  return cache3.getStatus(ws, sourcePath);
@@ -3519,8 +3643,8 @@ function getReactiveDatasetCache() {
3519
3643
  function initializeReactiveDatasetCache(cache3) {
3520
3644
  defaultBindRuntime.initializeCache(cache3);
3521
3645
  }
3522
- function clearReactiveDatasetCache() {
3523
- defaultBindRuntime.clearCache();
3646
+ function clearReactiveDatasetCache(cache3) {
3647
+ defaultBindRuntime.clearCache(cache3);
3524
3648
  }
3525
3649
  function enableBindingTracking() {
3526
3650
  return defaultBindRuntime.enableTracking();
@@ -3700,7 +3824,11 @@ class TrackedChannelStore {
3700
3824
  this.keyVersions.clear();
3701
3825
  }
3702
3826
  }
3703
- function createDefaultPagedApi(apiUrl, repo, getToken) {
3827
+ const NO_INDEX = { index: null, join: false };
3828
+ function selectorKey(selector2) {
3829
+ return selector2.index === null ? "" : `@${selector2.index}${selector2.join ? "+join" : ""}`;
3830
+ }
3831
+ function createDefaultPagedApi(apiUrl, repo, getToken, datasets) {
3704
3832
  const opts = () => ({ token: getToken() });
3705
3833
  return {
3706
3834
  async getPage(workspace, path2, window3) {
@@ -3708,58 +3836,115 @@ function createDefaultPagedApi(apiUrl, repo, getToken) {
3708
3836
  },
3709
3837
  async findKey(workspace, path2, query) {
3710
3838
  return e3ApiClient.datasetFindKey(apiUrl, repo, workspace, path2, query, opts());
3839
+ },
3840
+ async getRevision(workspace, path2) {
3841
+ const status = await e3ApiClient.datasetGetStatus(apiUrl, repo, workspace, path2, opts());
3842
+ return status.hash.type === "some" ? status.hash.value : null;
3843
+ },
3844
+ watchRevision(workspace, path2, onChange2) {
3845
+ return datasets.watchHash(workspace, path2, onChange2);
3711
3846
  }
3712
3847
  };
3713
3848
  }
3714
3849
  const RETRY_AFTER_MS = 2e3;
3715
3850
  const MAX_RETAINED_WINDOWS = 24;
3716
- const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set(["dataset_not_pageable"]);
3717
- function isPermanentPageError(err) {
3851
+ const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set([
3852
+ "bad_request",
3853
+ "dataset_not_canonical",
3854
+ "dataset_not_indexed",
3855
+ "dataset_not_pageable",
3856
+ "dataset_not_searchable",
3857
+ "dataset_not_segmented",
3858
+ "index_not_found",
3859
+ "key_parse_error"
3860
+ ]);
3861
+ const NO_VALUE_ERRORS = /* @__PURE__ */ new Set(["dataset_unassigned", "dataset_null"]);
3862
+ function toSelector(indexArg, joinArg) {
3863
+ const index2 = indexArg;
3864
+ return {
3865
+ index: index2 !== void 0 && index2.type === "some" ? index2.value : null,
3866
+ join: joinArg === true
3867
+ };
3868
+ }
3869
+ function checkDeclared(allowed, path2) {
3870
+ if (allowed === null) return;
3871
+ const pathStr = datasetPathToString(path2);
3872
+ if (!allowed.has(pathStr)) {
3873
+ throw new Error(
3874
+ `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
3875
+ );
3876
+ }
3877
+ }
3878
+ function errorCode(err) {
3718
3879
  const code = err == null ? void 0 : err.code;
3719
- return typeof code === "string" && PERMANENT_PAGE_ERRORS.has(code);
3880
+ return typeof code === "string" ? code : void 0;
3881
+ }
3882
+ function isPermanentPageError(err) {
3883
+ const code = errorCode(err);
3884
+ return code !== void 0 && PERMANENT_PAGE_ERRORS.has(code);
3885
+ }
3886
+ function failureOf(path2, err) {
3887
+ const code = errorCode(err);
3888
+ const details = err == null ? void 0 : err.details;
3889
+ const reason = code === void 0 ? err instanceof Error ? err.message : String(err) : typeof details === "string" && details !== "" ? `${code}: ${details}` : code;
3890
+ return `Data.bindPaged: ${datasetPathToString(path2)}: ${reason}`;
3720
3891
  }
3721
3892
  function keyTypeOf(sourceType) {
3722
3893
  if (sourceType.type === "Dict") return sourceType.value.key;
3723
3894
  if (sourceType.type === "Set") return sourceType.value;
3724
3895
  return null;
3725
3896
  }
3726
- function buildSeek(sourceType, T2, pathExpr, platform2) {
3727
- if (keyTypeOf(sourceType) === null) return east.none;
3897
+ function buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform2) {
3898
+ if (selector2.index === null && keyTypeOf(sourceType) === null) return east.none;
3728
3899
  const { seek } = internal$1.DataPagedPrimitives;
3729
3900
  return east.some(east.East.compile(
3730
3901
  east.East.function([eastUi.SeekQueryType], east.OptionType(eastUi.SeekRangeType), ($3, query) => {
3731
- $3.return(seek([T2], pathExpr, query));
3902
+ $3.return(seek([T2], pathExpr, indexExpr, joinExpr, query));
3732
3903
  }),
3733
3904
  platform2
3734
3905
  ));
3735
3906
  }
3736
- function pagedWindowKey(workspace, path2, offset, limit2) {
3737
- return `paged:${workspace}:${datasetPathToString(path2)}#${offset}+${limit2}`;
3907
+ function pagedRevisionKey(workspace, path2) {
3908
+ return `paged:${workspace}:${datasetPathToString(path2)}#revision`;
3909
+ }
3910
+ function pagedWindowKey(workspace, path2, revision, offset, limit2, selector2 = NO_INDEX) {
3911
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#${offset}+${limit2}`;
3738
3912
  }
3739
- function pagedTotalKey(workspace, path2) {
3740
- return `paged:${workspace}:${datasetPathToString(path2)}#total`;
3913
+ function pagedTotalKey(workspace, path2, revision, selector2 = NO_INDEX) {
3914
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#total`;
3741
3915
  }
3742
- function pagedSeekKey(workspace, path2, query) {
3743
- const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : `p=${query.prefix}`;
3744
- return `paged:${workspace}:${datasetPathToString(path2)}#seek:${q}`;
3916
+ function pagedSeekKey(workspace, path2, revision, query, selector2 = NO_INDEX) {
3917
+ const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : "prefix" in query ? `p=${query.prefix}` : `r=${(query.from ?? []).join("\0")}|${(query.to ?? []).join("\0")}`;
3918
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#seek:${q}`;
3745
3919
  }
3746
- function toFindQuery(query) {
3920
+ function toFindQuery(query, selector2 = NO_INDEX) {
3747
3921
  const q = query;
3748
- if (q.type === "key") return { key: q.value };
3749
- if (q.type === "prefix") return { prefix: q.value };
3922
+ const scope = selector2.index === null ? {} : { index: selector2.index };
3923
+ if (q.type === "key") return { key: q.value, ...scope };
3924
+ if (q.type === "prefix") return { prefix: q.value, ...scope };
3925
+ if (q.type === "range") {
3926
+ const r2 = q.value;
3927
+ const from2 = [...r2.from];
3928
+ const to = [...r2.to];
3929
+ if (from2.length === 0 && to.length === 0) {
3930
+ throw new Error("Data.bindPaged: a range seek names no end — give it a `from`, a `to`, or both");
3931
+ }
3932
+ return from2.length === 0 ? { to, ...scope } : to.length === 0 ? { from: from2, ...scope } : { from: from2, to, ...scope };
3933
+ }
3750
3934
  const f2 = q.value;
3751
3935
  const fields = [...f2.values];
3752
- return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value } : { fields };
3936
+ return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value, ...scope } : { fields, ...scope };
3753
3937
  }
3754
3938
  class PagedRuntime extends TrackedChannelStore {
3755
3939
  constructor() {
3756
3940
  super(...arguments);
3757
3941
  __publicField(this, "api", null);
3758
3942
  __publicField(this, "workspace", null);
3759
- // Compiled-handle cache (issue #106 perf): buildHandle compiles 2
3943
+ // Compiled-handle cache (issue #106 perf): buildHandle compiles several
3760
3944
  // East.functions per bind, and binds re-run every reactive frame. The
3761
- // method IR is a pure function of (sourceType, sourcePath), and the methods
3762
- // resolve api/workspace LIVE, so a cached handle still re-binds.
3945
+ // method IR is a pure function of (sourceType, path, selector, shape), and
3946
+ // the methods resolve api/workspace LIVE, so a cached handle still
3947
+ // re-binds.
3763
3948
  //
3764
3949
  // Keyed by TYPE first, then path — never by path alone. The window decoder
3765
3950
  // is baked from `sourceType`, so a path re-bound at a different type (a
@@ -3774,6 +3959,10 @@ class PagedRuntime extends TrackedChannelStore {
3774
3959
  /** Loaded window keys in least-recently-READ order (a Map preserves
3775
3960
  * insertion order; a read re-inserts). Bounds the decoded-window cache. */
3776
3961
  __publicField(this, "loadedWindows", /* @__PURE__ */ new Map());
3962
+ /** Each dataset's snapshot, by workspace and path. */
3963
+ __publicField(this, "snapshots", /* @__PURE__ */ new Map());
3964
+ /** Fetches launched so far — the source of every channel's `launchSeq`. */
3965
+ __publicField(this, "launches", 0);
3777
3966
  }
3778
3967
  /** Monotonic clock seam so tests can drive the retry gate. */
3779
3968
  now() {
@@ -3784,17 +3973,33 @@ class PagedRuntime extends TrackedChannelStore {
3784
3973
  }
3785
3974
  // ----- wiring ----------------------------------------------------------
3786
3975
  /** Install the API adapter + workspace — called by the React provider
3787
- * (or a test/showcase harness) before any handle is used. */
3976
+ * (or a test/showcase harness) before any handle is used. Another adapter
3977
+ * or workspace starts from nothing: no snapshot of the old one's carries
3978
+ * over. */
3788
3979
  initialize(api3, workspace) {
3980
+ if (this.api !== api3 || this.workspace !== workspace) this.reset();
3789
3981
  this.api = api3;
3790
3982
  this.workspace = workspace;
3791
3983
  }
3792
- /** Tear down the adapter and all window state. */
3793
- clear() {
3984
+ /**
3985
+ * Tear down the adapter and all window state.
3986
+ *
3987
+ * @param api - Clear only while this is the installed adapter, so a
3988
+ * provider tearing down after another installed its own leaves that one
3989
+ * in place
3990
+ */
3991
+ clear(api3) {
3992
+ if (api3 !== void 0 && this.api !== api3) return;
3794
3993
  this.api = null;
3795
3994
  this.workspace = null;
3796
- this.clearChannels();
3995
+ this.reset();
3797
3996
  this.handleCache.clear();
3997
+ }
3998
+ /** Stop following every dataset, and drop what every snapshot delivered. */
3999
+ reset() {
4000
+ for (const snapshot of this.snapshots.values()) snapshot.unwatch();
4001
+ this.snapshots.clear();
4002
+ this.clearChannels();
3798
4003
  this.loadedWindows.clear();
3799
4004
  }
3800
4005
  resolveWorkspace() {
@@ -3805,7 +4010,178 @@ class PagedRuntime extends TrackedChannelStore {
3805
4010
  }
3806
4011
  return this.workspace;
3807
4012
  }
3808
- // ----- window loading --------------------------------------------------
4013
+ // ----- snapshots -------------------------------------------------------
4014
+ /** A dataset's snapshot, followed from its first read until the runtime
4015
+ * is cleared. */
4016
+ snapshotOf(workspace, path2) {
4017
+ const key = `${workspace}:${datasetPathToString(path2)}`;
4018
+ const existing = this.snapshots.get(key);
4019
+ if (existing !== void 0) return existing;
4020
+ const snapshot = {
4021
+ key,
4022
+ revision: void 0,
4023
+ moves: 0,
4024
+ findSeq: 0,
4025
+ finding: false,
4026
+ channels: /* @__PURE__ */ new Set(),
4027
+ unwatch: () => {
4028
+ }
4029
+ };
4030
+ this.snapshots.set(key, snapshot);
4031
+ if (this.api !== null) {
4032
+ snapshot.unwatch = this.api.watchRevision(workspace, path2, (hash2) => this.moveTo(workspace, path2, snapshot, hash2));
4033
+ }
4034
+ return snapshot;
4035
+ }
4036
+ /** Whether a snapshot is still the runtime's, rather than one a clear or a
4037
+ * re-initialization dropped. */
4038
+ isLive(snapshot) {
4039
+ return this.snapshots.get(snapshot.key) === snapshot;
4040
+ }
4041
+ /** Whether a live snapshot is still pinned to `revision`. */
4042
+ pinnedTo(snapshot, revision) {
4043
+ return this.isLive(snapshot) && snapshot.revision === revision;
4044
+ }
4045
+ /**
4046
+ * The revision a read of `path` is pinned to — a hash, `null` while the
4047
+ * dataset has no value, or `undefined` while it is being found — tracked so
4048
+ * the read re-fires when the source moves.
4049
+ *
4050
+ * @throws The failed lookup's reason, until a read after the retry gap
4051
+ * looks again
4052
+ */
4053
+ revisionFor(workspace, path2) {
4054
+ this.track(pagedRevisionKey(workspace, path2));
4055
+ const snapshot = this.snapshotOf(workspace, path2);
4056
+ if (snapshot.revision !== void 0 || snapshot.finding) return snapshot.revision;
4057
+ if (snapshot.findError !== void 0 && this.now() - (snapshot.findFailedAtMs ?? 0) < RETRY_AFTER_MS) {
4058
+ throw new Error(snapshot.findError);
4059
+ }
4060
+ this.find(workspace, path2, snapshot);
4061
+ return void 0;
4062
+ }
4063
+ /**
4064
+ * Ask the server which content the dataset holds, and move there — unless
4065
+ * a move lands first, which came from something no older than the answer.
4066
+ */
4067
+ find(workspace, path2, snapshot) {
4068
+ const seq = ++snapshot.findSeq;
4069
+ const moves = snapshot.moves;
4070
+ snapshot.finding = true;
4071
+ const api3 = this.api;
4072
+ void (async () => {
4073
+ let revision;
4074
+ try {
4075
+ if (api3 === null) throw new Error("no PagedApi installed");
4076
+ revision = await api3.getRevision(workspace, path2);
4077
+ } catch (err) {
4078
+ if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
4079
+ snapshot.finding = false;
4080
+ if (snapshot.revision !== void 0) return;
4081
+ snapshot.findError = failureOf(path2, err);
4082
+ snapshot.findFailedAtMs = this.now();
4083
+ console.error(`Data.bindPaged: could not find which content ${datasetPathToString(path2)} holds:`, err);
4084
+ this.notify(pagedRevisionKey(workspace, path2));
4085
+ return;
4086
+ }
4087
+ if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
4088
+ snapshot.finding = false;
4089
+ if (snapshot.moves !== moves) return;
4090
+ this.moveTo(workspace, path2, snapshot, revision);
4091
+ })();
4092
+ }
4093
+ /**
4094
+ * Move a dataset's source to `revision`: drop what the old snapshot
4095
+ * delivered and re-fire every read of the dataset, so the windows and
4096
+ * searches still in use are fetched again, pinned to the new one. A move to
4097
+ * the revision the source already has changes nothing.
4098
+ */
4099
+ moveTo(workspace, path2, snapshot, revision) {
4100
+ if (!this.isLive(snapshot) || snapshot.revision === revision) return;
4101
+ snapshot.revision = revision;
4102
+ snapshot.moves += 1;
4103
+ delete snapshot.findError;
4104
+ delete snapshot.findFailedAtMs;
4105
+ for (const key of snapshot.channels) {
4106
+ this.entries.delete(key);
4107
+ this.loadedWindows.delete(key);
4108
+ }
4109
+ snapshot.channels.clear();
4110
+ this.notify(pagedRevisionKey(workspace, path2));
4111
+ }
4112
+ /**
4113
+ * Handle what a pinned fetch threw when it is not the read's failure: a
4114
+ * refusal naming other content moves the source there, and "no value"
4115
+ * moves it to no snapshot. Returns whether the error was one of those, or
4116
+ * arrived after the source had already moved on.
4117
+ */
4118
+ followed(workspace, path2, snapshot, revision, err) {
4119
+ if (!this.pinnedTo(snapshot, revision)) return true;
4120
+ if (err instanceof e3ApiClient.DatasetHashMismatchError) {
4121
+ if (err.currentHash !== null && err.currentHash !== revision) {
4122
+ this.moveTo(workspace, path2, snapshot, err.currentHash);
4123
+ return true;
4124
+ }
4125
+ this.find(workspace, path2, snapshot);
4126
+ return false;
4127
+ }
4128
+ const code = errorCode(err);
4129
+ if (code !== void 0 && NO_VALUE_ERRORS.has(code)) {
4130
+ this.moveTo(workspace, path2, snapshot, null);
4131
+ return true;
4132
+ }
4133
+ return false;
4134
+ }
4135
+ // ----- channels ----------------------------------------------------------
4136
+ /** A channel of the snapshot's current revision, dropped when it moves. */
4137
+ channel(snapshot, key) {
4138
+ snapshot.channels.add(key);
4139
+ return this.entry(key);
4140
+ }
4141
+ /** Whether a channel is due a fetch: never fetched, evicted, or failed
4142
+ * for a reason a retry can fix, longer ago than the retry gap. */
4143
+ due(entry) {
4144
+ if (entry.status === "idle") return true;
4145
+ if (entry.status !== "failed" || entry.permanent === true) return false;
4146
+ return this.now() - (entry.failedAtMs ?? 0) >= RETRY_AFTER_MS;
4147
+ }
4148
+ /**
4149
+ * Mark a channel's fetch as started, returning the fetch's sequence number.
4150
+ *
4151
+ * Deliberately does NOT notify — the read that triggers it runs inside a
4152
+ * render pass, and notifying there would re-enter the renderer. Only the
4153
+ * settle notifies.
4154
+ */
4155
+ launch(entry) {
4156
+ entry.status = "running";
4157
+ entry.launchSeq = ++this.launches;
4158
+ return entry.launchSeq;
4159
+ }
4160
+ /** Apply a fetch's outcome if the channel still waits on it, re-firing the
4161
+ * channel's reads. Returns whether it applied. */
4162
+ settle(key, seq, mutate) {
4163
+ const entry = this.entries.get(key);
4164
+ if (entry === void 0 || entry.launchSeq !== seq) return false;
4165
+ mutate(entry);
4166
+ this.notify(key);
4167
+ return true;
4168
+ }
4169
+ /** Settle a fetch as failed, with the reason a read will throw. */
4170
+ fail(key, seq, reason, permanent) {
4171
+ this.settle(key, seq, (e3) => {
4172
+ e3.status = "failed";
4173
+ e3.error = reason;
4174
+ e3.failedAtMs = this.now();
4175
+ e3.permanent = permanent;
4176
+ });
4177
+ }
4178
+ /** What a read of a channel answers: its value once loaded, `none` while
4179
+ * in flight, or its failure, thrown. */
4180
+ answer(entry, value) {
4181
+ if (entry.status === "loaded" && value !== void 0) return east.some(value);
4182
+ if (entry.status === "failed") throw new Error(entry.error ?? "Data.bindPaged: the read failed");
4183
+ return east.none;
4184
+ }
3809
4185
  /** Note a window as most-recently-read, and drop the coldest decoded
3810
4186
  * windows once the cache exceeds its cap. An evicted window returns to
3811
4187
  * `idle`, so the next read refetches it rather than reading a hole. */
@@ -3823,131 +4199,83 @@ class PagedRuntime extends TrackedChannelStore {
3823
4199
  delete entry.window;
3824
4200
  }
3825
4201
  }
3826
- /**
3827
- * Start the fetch for a window if it isn't loaded or already in flight.
3828
- *
3829
- * Deliberately does NOT notify on launch — the read that triggers it runs
3830
- * inside a render pass, and notifying there would re-enter the renderer.
3831
- * Only the settle notifies.
3832
- */
3833
- ensureWindow(sourceType, workspace, path2, offset, limit2) {
3834
- const key = pagedWindowKey(workspace, path2, offset, limit2);
3835
- const entry = this.entry(key);
3836
- if (entry.status === "running" || entry.status === "loaded") return;
3837
- if (entry.status === "failed") {
3838
- if (entry.permanent) return;
3839
- const since = this.now() - (entry.failedAtMs ?? 0);
3840
- if (since < RETRY_AFTER_MS) return;
3841
- }
3842
- entry.status = "running";
3843
- entry.launchSeq += 1;
3844
- const mySeq = entry.launchSeq;
4202
+ // ----- window loading --------------------------------------------------
4203
+ /** Start the pinned fetch for a window if it is due one, returning its
4204
+ * channel. */
4205
+ ensureWindow(sourceType, workspace, path2, snapshot, revision, offset, limit2, selector2, key) {
4206
+ const entry = this.channel(snapshot, key);
4207
+ if (!this.due(entry)) return entry;
4208
+ const seq = this.launch(entry);
3845
4209
  const api3 = this.api;
3846
4210
  void (async () => {
3847
- const settle = (mutate) => {
3848
- const current = this.entries.get(key);
3849
- if (!current || current.launchSeq !== mySeq) return;
3850
- mutate(current);
3851
- this.notify(key);
3852
- };
3853
- if (!api3) {
3854
- settle((e3) => {
3855
- e3.status = "failed";
3856
- e3.failedAtMs = this.now();
3857
- });
3858
- console.error("Data.bindPaged: no PagedApi installed");
3859
- return;
3860
- }
3861
4211
  let page;
3862
4212
  try {
3863
- page = await api3.getPage(workspace, path2, { offset, limit: limit2 });
3864
- } catch (err) {
3865
- const permanent = isPermanentPageError(err);
3866
- settle((e3) => {
3867
- e3.status = "failed";
3868
- e3.failedAtMs = this.now();
3869
- e3.permanent = permanent;
4213
+ if (api3 === null) throw new Error("no PagedApi installed");
4214
+ page = await api3.getPage(workspace, path2, {
4215
+ offset,
4216
+ limit: limit2,
4217
+ hash: revision,
4218
+ ...selector2.index !== null && { index: selector2.index, join: selector2.join }
3870
4219
  });
3871
- console.error(
3872
- permanent ? `Data.bindPaged: ${datasetPathToString(path2)} is not a pageable dataset — bind a collection (Array / Set / Dict), or use Data.bind for a whole value:` : `Data.bindPaged: fetch failed for ${key}:`,
3873
- err
3874
- );
4220
+ } catch (err) {
4221
+ if (this.followed(workspace, path2, snapshot, revision, err)) return;
4222
+ this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
4223
+ console.error(`Data.bindPaged: fetch failed for ${key}:`, err);
3875
4224
  return;
3876
4225
  }
4226
+ if (!this.pinnedTo(snapshot, revision)) return;
3877
4227
  let decoded;
3878
4228
  try {
3879
4229
  decoded = east.decodeBeast2For(sourceType)(page.data);
3880
4230
  } catch (err) {
3881
- settle((e3) => {
3882
- e3.status = "failed";
3883
- e3.failedAtMs = this.now();
3884
- });
4231
+ this.fail(key, seq, `${failureOf(path2, err)} (the window does not decode as the source's type)`, true);
3885
4232
  console.error(`Data.bindPaged: decode failed for ${key}:`, err);
3886
4233
  return;
3887
4234
  }
3888
- settle((e3) => {
4235
+ const landed = this.settle(key, seq, (e3) => {
3889
4236
  e3.status = "loaded";
3890
4237
  e3.window = decoded;
3891
4238
  e3.total = page.totalElements;
4239
+ delete e3.error;
3892
4240
  });
4241
+ if (!landed) return;
3893
4242
  this.touchWindow(key);
3894
- const totalKey = pagedTotalKey(workspace, path2);
3895
- const totalEntry = this.entry(totalKey);
4243
+ const totalKey = pagedTotalKey(workspace, path2, revision, selector2);
4244
+ const totalEntry = this.channel(snapshot, totalKey);
3896
4245
  if (totalEntry.total !== page.totalElements) {
3897
4246
  totalEntry.total = page.totalElements;
3898
4247
  totalEntry.status = "loaded";
3899
4248
  this.notify(totalKey);
3900
4249
  }
3901
4250
  })();
4251
+ return entry;
3902
4252
  }
3903
- /**
3904
- * Start the fence search for one key query if it isn't answered or already
3905
- * in flight — the seek sibling of {@link ensureWindow}, with the same
3906
- * launch-does-not-notify rule (the read runs inside a render pass).
3907
- */
3908
- ensureSeek(workspace, path2, query, key) {
3909
- const entry = this.entry(key);
3910
- if (entry.status === "running" || entry.status === "loaded") return;
3911
- if (entry.status === "failed") {
3912
- if (entry.permanent) return;
3913
- const since = this.now() - (entry.failedAtMs ?? 0);
3914
- if (since < RETRY_AFTER_MS) return;
3915
- }
3916
- entry.status = "running";
3917
- entry.launchSeq += 1;
3918
- const mySeq = entry.launchSeq;
4253
+ /** Start the pinned fence search for one key query if it is due one,
4254
+ * returning its channel — the seek sibling of {@link ensureWindow}. */
4255
+ ensureSeek(workspace, path2, snapshot, revision, query, key) {
4256
+ const entry = this.channel(snapshot, key);
4257
+ if (!this.due(entry)) return entry;
4258
+ const seq = this.launch(entry);
3919
4259
  const api3 = this.api;
3920
4260
  void (async () => {
3921
- const settle = (mutate) => {
3922
- const current = this.entries.get(key);
3923
- if (!current || current.launchSeq !== mySeq) return;
3924
- mutate(current);
3925
- this.notify(key);
3926
- };
3927
- if (!api3) {
3928
- settle((e3) => {
3929
- e3.status = "failed";
3930
- e3.failedAtMs = this.now();
3931
- });
3932
- console.error("Data.bindPaged: no PagedApi installed");
3933
- return;
3934
- }
4261
+ let range;
3935
4262
  try {
3936
- const range = await api3.findKey(workspace, path2, query);
3937
- settle((e3) => {
3938
- e3.status = "loaded";
3939
- e3.range = range;
3940
- });
4263
+ if (api3 === null) throw new Error("no PagedApi installed");
4264
+ range = await api3.findKey(workspace, path2, { ...query, hash: revision });
3941
4265
  } catch (err) {
3942
- const permanent = isPermanentPageError(err);
3943
- settle((e3) => {
3944
- e3.status = "failed";
3945
- e3.failedAtMs = this.now();
3946
- e3.permanent = permanent;
3947
- });
4266
+ if (this.followed(workspace, path2, snapshot, revision, err)) return;
4267
+ this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
3948
4268
  console.error(`Data.bindPaged: key search failed for ${key}:`, err);
4269
+ return;
3949
4270
  }
4271
+ if (!this.pinnedTo(snapshot, revision)) return;
4272
+ this.settle(key, seq, (e3) => {
4273
+ e3.status = "loaded";
4274
+ e3.range = range;
4275
+ delete e3.error;
4276
+ });
3950
4277
  })();
4278
+ return entry;
3951
4279
  }
3952
4280
  /**
3953
4281
  * The low-level primitives backing handle methods, bound to THIS runtime.
@@ -3957,53 +4285,92 @@ class PagedRuntime extends TrackedChannelStore {
3957
4285
  */
3958
4286
  buildPrimitives() {
3959
4287
  return [
3960
- internal$1.DataPagedPrimitives.page.implement((sourceType) => (pathArg, offsetArg, limitArg) => {
4288
+ internal$1.DataPagedPrimitives.page.implement((sourceType) => (pathArg, indexArg, joinArg, offsetArg, limitArg) => {
3961
4289
  const workspace = this.resolveWorkspace();
3962
4290
  const path2 = pathArg;
4291
+ const revision = this.revisionFor(workspace, path2);
4292
+ if (typeof revision !== "string") return east.none;
4293
+ const selector2 = toSelector(indexArg, joinArg);
3963
4294
  const offset = Number(offsetArg);
3964
4295
  const limit2 = Number(limitArg);
3965
- const key = pagedWindowKey(workspace, path2, offset, limit2);
4296
+ const key = pagedWindowKey(workspace, path2, revision, offset, limit2, selector2);
3966
4297
  this.track(key);
3967
- this.ensureWindow(sourceType, workspace, path2, offset, limit2);
3968
- const entry = this.entry(key);
3969
- if (entry.status === "loaded" && entry.window !== void 0) {
3970
- this.touchWindow(key);
3971
- return east.variant("some", entry.window);
3972
- }
3973
- return east.variant("none", null);
4298
+ const entry = this.ensureWindow(
4299
+ sourceType,
4300
+ workspace,
4301
+ path2,
4302
+ this.snapshotOf(workspace, path2),
4303
+ revision,
4304
+ offset,
4305
+ limit2,
4306
+ selector2,
4307
+ key
4308
+ );
4309
+ if (entry.status === "loaded" && entry.window !== void 0) this.touchWindow(key);
4310
+ return this.answer(entry, entry.window);
3974
4311
  }),
3975
- internal$1.DataPagedPrimitives.total.implement((_sourceType) => (pathArg) => {
4312
+ internal$1.DataPagedPrimitives.total.implement((_sourceType) => (pathArg, indexArg, joinArg) => {
3976
4313
  const workspace = this.resolveWorkspace();
3977
- const key = pagedTotalKey(workspace, pathArg);
4314
+ const path2 = pathArg;
4315
+ const revision = this.revisionFor(workspace, path2);
4316
+ if (typeof revision !== "string") return east.none;
4317
+ const key = pagedTotalKey(workspace, path2, revision, toSelector(indexArg, joinArg));
3978
4318
  this.track(key);
3979
- const entry = this.entry(key);
3980
- return entry.total !== void 0 ? east.variant("some", BigInt(entry.total)) : east.variant("none", null);
4319
+ const entry = this.channel(this.snapshotOf(workspace, path2), key);
4320
+ return entry.total !== void 0 ? east.some(BigInt(entry.total)) : east.none;
3981
4321
  }),
3982
- internal$1.DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, queryArg) => {
4322
+ internal$1.DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, indexArg, joinArg, queryArg) => {
3983
4323
  const workspace = this.resolveWorkspace();
3984
4324
  const path2 = pathArg;
3985
- const query = toFindQuery(queryArg);
3986
- const key = pagedSeekKey(workspace, path2, query);
4325
+ const revision = this.revisionFor(workspace, path2);
4326
+ if (typeof revision !== "string") return east.none;
4327
+ const selector2 = toSelector(indexArg, joinArg);
4328
+ const query = toFindQuery(queryArg, selector2);
4329
+ const key = pagedSeekKey(workspace, path2, revision, query, selector2);
3987
4330
  this.track(key);
3988
- this.ensureSeek(workspace, path2, query, key);
3989
- const entry = this.entry(key);
3990
- if (entry.status !== "loaded" || entry.range === void 0) return east.none;
3991
- const r2 = entry.range;
3992
- return east.some({ found: r2.found, row: BigInt(r2.row), count: BigInt(r2.count) });
4331
+ const entry = this.ensureSeek(workspace, path2, this.snapshotOf(workspace, path2), revision, query, key);
4332
+ const range = entry.range;
4333
+ return this.answer(entry, range === void 0 ? void 0 : { found: range.found, row: BigInt(range.row), count: BigInt(range.count) });
4334
+ }),
4335
+ internal$1.DataPagedPrimitives.revision.implement((_sourceType) => (pathArg, _indexArg, _joinArg) => {
4336
+ const revision = this.revisionFor(this.resolveWorkspace(), pathArg);
4337
+ return typeof revision === "string" ? east.some(revision) : east.none;
4338
+ }),
4339
+ internal$1.DataPagedPrimitives.refresh.implement((_sourceType) => (pathArg, _indexArg, _joinArg, targetArg) => {
4340
+ const workspace = this.resolveWorkspace();
4341
+ const path2 = pathArg;
4342
+ const target = targetArg;
4343
+ queueMicrotask(() => {
4344
+ if (this.workspace !== workspace) return;
4345
+ const snapshot = this.snapshotOf(workspace, path2);
4346
+ if (target.type === "some") this.moveTo(workspace, path2, snapshot, target.value);
4347
+ else this.find(workspace, path2, snapshot);
4348
+ });
4349
+ return null;
3993
4350
  })
3994
4351
  ];
3995
4352
  }
3996
4353
  /**
3997
- * Build the handle value for one `Data.bindPaged` evaluation. Both methods
3998
- * are thin IR-bearing `East.function`s over {@link buildPrimitives},
3999
- * capturing only the plain-data source path (the value type rides as a
4000
- * type-arg) — so the handle is ordinary serializable East data (issue #106).
4354
+ * Build the handle value for one paged bind. Every method is a thin
4355
+ * IR-bearing `East.function` over {@link buildPrimitives}, capturing only
4356
+ * the plain-data source path and selector (the value type rides as a
4357
+ * type-arg) — so the handle is ordinary serializable East data (issue
4358
+ * #106).
4359
+ *
4360
+ * @param sourceType - The dataset's type, or an index's window type
4361
+ * @param path - The dataset's path
4362
+ * @param selector - The rows the bind serves
4363
+ * @param shape - `released`: the handle `data_bind_paged` returns, without
4364
+ * `revision` or `refresh`; `pinned`: the handle `data_bind_paged_pinned`
4365
+ * returns. Both read through the same snapshot.
4366
+ * @returns The handle
4001
4367
  */
4002
- buildHandle(sourceType, path2) {
4003
- const pathKey = datasetPathToString(path2);
4368
+ buildHandle(sourceType, path2, selector2, shape) {
4369
+ const id3 = `${datasetPathToString(path2)}${selectorKey(selector2)}`;
4370
+ const cacheKey = `${id3}#${shape}`;
4004
4371
  let byPath = this.handleCache.get(sourceType);
4005
4372
  if (byPath) {
4006
- const hit = byPath.get(pathKey);
4373
+ const hit = byPath.get(cacheKey);
4007
4374
  if (hit) return hit;
4008
4375
  } else {
4009
4376
  byPath = /* @__PURE__ */ new Map();
@@ -4011,24 +4378,27 @@ class PagedRuntime extends TrackedChannelStore {
4011
4378
  }
4012
4379
  const T2 = east.fromEastTypeValue(sourceType);
4013
4380
  const pathExpr = east.East.value(path2, e3Types.TreePathType);
4381
+ const indexExpr = east.East.value(selector2.index === null ? east.none : east.some(selector2.index), east.OptionType(east.StringType));
4382
+ const joinExpr = east.East.value(selector2.join, east.BooleanType);
4014
4383
  const platform2 = this.buildPrimitives();
4015
- const { page, total } = internal$1.DataPagedPrimitives;
4384
+ const { page, total, revision, refresh } = internal$1.DataPagedPrimitives;
4016
4385
  const handle = {
4017
4386
  // The comparable identity east-ui's `PagedSourceType` requires:
4018
4387
  // East compares every function as EQUAL, so a struct of nothing but
4019
4388
  // closures is indistinguishable from any other and a memoized
4020
4389
  // component would never re-render on a source swap (#567 D4). The
4021
- // dataset path is the natural identity — same path, same rows.
4022
- id: pathKey,
4390
+ // dataset path and selector are the natural identity — same path,
4391
+ // same rows. The snapshot they serve is `revision`'s, not the id's.
4392
+ id: id3,
4023
4393
  page: east.East.compile(
4024
4394
  east.East.function([east.IntegerType, east.IntegerType], east.OptionType(T2), ($3, offset, limit2) => {
4025
- $3.return(page([T2], pathExpr, offset, limit2));
4395
+ $3.return(page([T2], pathExpr, indexExpr, joinExpr, offset, limit2));
4026
4396
  }),
4027
4397
  platform2
4028
4398
  ),
4029
4399
  total: east.East.compile(
4030
4400
  east.East.function([], east.OptionType(east.IntegerType), ($3) => {
4031
- $3.return(total([T2], pathExpr));
4401
+ $3.return(total([T2], pathExpr, indexExpr, joinExpr));
4032
4402
  }),
4033
4403
  platform2
4034
4404
  ),
@@ -4037,28 +4407,50 @@ class PagedRuntime extends TrackedChannelStore {
4037
4407
  // segments) against the stored fences; an Array's stream order has
4038
4408
  // nothing to search. Resolved at bind time from the dataset's own
4039
4409
  // type, so a component renders the affordance only when it works.
4040
- seek: buildSeek(sourceType, T2, pathExpr, platform2)
4410
+ seek: buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform2)
4041
4411
  };
4042
- byPath.set(pathKey, handle);
4412
+ if (shape === "pinned") {
4413
+ handle.revision = east.East.compile(
4414
+ east.East.function([], east.OptionType(east.StringType), ($3) => {
4415
+ $3.return(revision([T2], pathExpr, indexExpr, joinExpr));
4416
+ }),
4417
+ platform2
4418
+ );
4419
+ handle.refresh = east.East.compile(
4420
+ east.East.function([east.OptionType(east.StringType)], east.NullType, ($3, target) => {
4421
+ $3.return(refresh([T2], pathExpr, indexExpr, joinExpr, target));
4422
+ }),
4423
+ platform2
4424
+ );
4425
+ }
4426
+ byPath.set(cacheKey, handle);
4043
4427
  return handle;
4044
4428
  }
4045
4429
  // ----- platform building -------------------------------------------------
4046
- /** Build a `Data.bindPaged` PlatformFunction bound to this runtime. Pass
4047
- * `allowed=null` for an unscoped impl; pass a Set of path strings for
4048
- * manifest scoping. */
4430
+ /** Build the PlatformFunction behind `data_bind_paged` — the bind a UI
4431
+ * exported before pinned reads calls — bound to this runtime. Its handle
4432
+ * has no `revision` or `refresh`, but its reads are pinned, follow the
4433
+ * dataset and fail visibly all the same. Pass `allowed=null` for an
4434
+ * unscoped impl, or a Set of path strings for manifest scoping. */
4049
4435
  buildPlatform(allowed) {
4050
4436
  return internal$1.bindPagedPlatformFn.implement(
4051
4437
  (sourceType) => (pathArg) => {
4052
4438
  const path2 = pathArg;
4053
- if (allowed) {
4054
- const pathStr = datasetPathToString(path2);
4055
- if (!allowed.has(pathStr)) {
4056
- throw new Error(
4057
- `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
4058
- );
4059
- }
4060
- }
4061
- return this.buildHandle(sourceType, path2);
4439
+ checkDeclared(allowed, path2);
4440
+ return this.buildHandle(sourceType, path2, NO_INDEX, "released");
4441
+ }
4442
+ );
4443
+ }
4444
+ /** Build the PlatformFunction behind `data_bind_paged_pinned` — what
4445
+ * `Data.bindPaged` emits, for a dataset's own rows or a record's index —
4446
+ * bound to this runtime and scoped by `allowed` exactly as
4447
+ * {@link buildPlatform} is: an index read is a read of the record's path. */
4448
+ buildPinnedPlatform(allowed) {
4449
+ return internal$1.bindPagedPinnedPlatformFn.implement(
4450
+ (sourceType) => (pathArg, indexArg, joinArg) => {
4451
+ const path2 = pathArg;
4452
+ checkDeclared(allowed, path2);
4453
+ return this.buildHandle(sourceType, path2, toSelector(indexArg, joinArg), "pinned");
4062
4454
  }
4063
4455
  );
4064
4456
  }
@@ -4067,17 +4459,19 @@ const defaultPagedRuntime = new PagedRuntime();
4067
4459
  function initializePagedApi(api3, workspace) {
4068
4460
  defaultPagedRuntime.initialize(api3, workspace);
4069
4461
  }
4070
- function clearPagedApi() {
4071
- defaultPagedRuntime.clear();
4462
+ function clearPagedApi(api3) {
4463
+ defaultPagedRuntime.clear(api3);
4072
4464
  }
4073
4465
  const PagedPlatform = [
4074
4466
  defaultPagedRuntime.buildPlatform(null),
4467
+ defaultPagedRuntime.buildPinnedPlatform(null),
4075
4468
  ...defaultPagedRuntime.buildPrimitives()
4076
4469
  ];
4077
4470
  function createScopedPagedPlatform(pages) {
4078
4471
  const allowed = new Set(pages.map((p2) => datasetPathToString(p2)));
4079
4472
  return [
4080
4473
  defaultPagedRuntime.buildPlatform(allowed),
4474
+ defaultPagedRuntime.buildPinnedPlatform(allowed),
4081
4475
  ...defaultPagedRuntime.buildPrimitives()
4082
4476
  ];
4083
4477
  }
@@ -4100,13 +4494,12 @@ function createDefaultFunctionApi(apiUrl, repo, getToken) {
4100
4494
  async call(workspace, fn3, req) {
4101
4495
  return e3ApiClient.workspaceFunctionCall(apiUrl, repo, workspace, fn3, {
4102
4496
  args: req.args,
4103
- runner: east.variant("none", null),
4104
- limits: east.variant("none", null)
4497
+ runner: east.none,
4498
+ limits: east.none
4105
4499
  }, opts());
4106
4500
  }
4107
4501
  };
4108
4502
  }
4109
- const eastTypeEqual$1 = east.equalFor(internal.EastTypeType);
4110
4503
  function signatureOfFuncHandleType(handleType) {
4111
4504
  var _a2, _b2;
4112
4505
  if (handleType.type !== "Struct") {
@@ -4203,8 +4596,15 @@ class FuncRuntime extends TrackedChannelStore {
4203
4596
  this.api = api3;
4204
4597
  this.workspace = workspace;
4205
4598
  }
4206
- /** Tear down the adapter and all call state. */
4207
- clear() {
4599
+ /**
4600
+ * Tear down the adapter and all call state.
4601
+ *
4602
+ * @param api - Clear only while this is the installed adapter, so a
4603
+ * provider tearing down after another installed its own leaves that one
4604
+ * in place
4605
+ */
4606
+ clear(api3) {
4607
+ if (api3 !== void 0 && this.api !== api3) return;
4208
4608
  this.api = null;
4209
4609
  this.workspace = null;
4210
4610
  this.clearChannels();
@@ -4243,8 +4643,8 @@ class FuncRuntime extends TrackedChannelStore {
4243
4643
  stderr: ""
4244
4644
  };
4245
4645
  }
4246
- const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual$1(t4, sig.inputs[i]));
4247
- const outputMatches = eastTypeEqual$1(deployed.outputType, sig.output);
4646
+ const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => east.isTypeValueEqual(t4, sig.inputs[i]));
4647
+ const outputMatches = east.isTypeValueEqual(deployed.outputType, sig.output);
4248
4648
  if (!inputsMatch || !outputMatches) {
4249
4649
  const message = `signature mismatch for "${name}": bound (${sig.inputs.length} inputs) disagrees with the deployed package`;
4250
4650
  return {
@@ -4347,7 +4747,7 @@ class FuncRuntime extends TrackedChannelStore {
4347
4747
  internal$1.FuncBindPrimitives.read.implement((_outputType) => (nameArg) => {
4348
4748
  const { key, entry } = channel(nameArg);
4349
4749
  runtime.track(key);
4350
- return entry.result !== void 0 ? east.variant("some", entry.result) : east.variant("none", null);
4750
+ return entry.result !== void 0 ? east.some(entry.result) : east.none;
4351
4751
  }),
4352
4752
  internal$1.FuncBindPrimitives.status.implement((nameArg) => {
4353
4753
  const { key, entry } = channel(nameArg);
@@ -4357,7 +4757,7 @@ class FuncRuntime extends TrackedChannelStore {
4357
4757
  internal$1.FuncBindPrimitives.error.implement((nameArg) => {
4358
4758
  const { key, entry } = channel(nameArg);
4359
4759
  runtime.track(key);
4360
- return entry.status === "failed" && entry.error !== void 0 ? east.variant("some", entry.error) : east.variant("none", null);
4760
+ return entry.status === "failed" && entry.error !== void 0 ? east.some(entry.error) : east.none;
4361
4761
  }),
4362
4762
  internal$1.FuncBindPrimitives.pending.implement((nameArg) => {
4363
4763
  const { key, entry } = channel(nameArg);
@@ -4437,8 +4837,8 @@ const defaultFuncRuntime = new FuncRuntime();
4437
4837
  function initializeFunctionApi(api3, workspace) {
4438
4838
  defaultFuncRuntime.initialize(api3, workspace);
4439
4839
  }
4440
- function clearFunctionApi() {
4441
- defaultFuncRuntime.clear();
4840
+ function clearFunctionApi(api3) {
4841
+ defaultFuncRuntime.clear(api3);
4442
4842
  }
4443
4843
  const FuncPlatform = [
4444
4844
  defaultFuncRuntime.buildPlatform(null),
@@ -8211,12 +8611,6 @@ function errorOfMutationResult(result) {
8211
8611
  message: `reducer exited with code ${outcome.value.exitCode}`,
8212
8612
  stderr: outcome.value.stderr
8213
8613
  };
8214
- case "too_large":
8215
- return {
8216
- kind: east.variant("too_large", { bytes: outcome.value.bytes, limit: outcome.value.limit }),
8217
- message: `new state too large (${outcome.value.bytes} bytes, limit ${outcome.value.limit})`,
8218
- stderr: outcome.value.stderr
8219
- };
8220
8614
  case "timed_out":
8221
8615
  return {
8222
8616
  kind: east.variant("timed_out", { ms: outcome.value.ms }),
@@ -8276,8 +8670,15 @@ class RecordRuntime extends TrackedChannelStore {
8276
8670
  this.cache = cache3;
8277
8671
  this.workspace = workspace;
8278
8672
  }
8279
- /** Tear down the adapter and all record state. */
8280
- clear() {
8673
+ /**
8674
+ * Tear down the adapter and all record state.
8675
+ *
8676
+ * @param api - Clear only while this is the installed adapter, so a
8677
+ * provider tearing down after another installed its own leaves that one
8678
+ * in place
8679
+ */
8680
+ clear(api3) {
8681
+ if (api3 !== void 0 && this.api !== api3) return;
8281
8682
  this.api = null;
8282
8683
  this.cache = null;
8283
8684
  this.workspace = null;
@@ -8585,8 +8986,8 @@ const defaultRecordRuntime = new RecordRuntime();
8585
8986
  function initializeRecordApi(api3, cache3, workspace) {
8586
8987
  defaultRecordRuntime.initialize(api3, cache3, workspace);
8587
8988
  }
8588
- function clearRecordApi() {
8589
- defaultRecordRuntime.clear();
8989
+ function clearRecordApi(api3) {
8990
+ defaultRecordRuntime.clear(api3);
8590
8991
  }
8591
8992
  const RecordPlatform = [
8592
8993
  defaultRecordRuntime.buildPlatform(null),
@@ -8612,7 +9013,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
8612
9013
  state: `${def.name}-state-0`.padEnd(64, "0"),
8613
9014
  mutation: "$init",
8614
9015
  actor: "memory",
8615
- at: /* @__PURE__ */ new Date(0)
9016
+ at: /* @__PURE__ */ new Date(0),
9017
+ delta: east.none
8616
9018
  };
8617
9019
  compiled.set(def.name, { stateType, mutations, commits: [genesis], seq: 0 });
8618
9020
  void cache3.write(workspace, recordPath(def.name), east.encodeBeast2For(stateType)(def.initial));
@@ -8644,7 +9046,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
8644
9046
  state: `${record}-state-${c2.seq}`.padEnd(64, "0"),
8645
9047
  mutation,
8646
9048
  actor: "memory",
8647
- at: /* @__PURE__ */ new Date(0)
9049
+ at: /* @__PURE__ */ new Date(0),
9050
+ delta: east.none
8648
9051
  });
8649
9052
  return { outcome: east.variant("committed", { commitHash: hash2, stateHash: `${record}-state-${c2.seq}`.padEnd(64, "0") }) };
8650
9053
  },
@@ -8693,68 +9096,49 @@ function ReactiveDatasetProvider({
8693
9096
  children: children2
8694
9097
  }) {
8695
9098
  const e3 = useE3Config();
9099
+ const { apiUrl, workspace } = e3;
9100
+ const repo = e3.repo ?? "default";
8696
9101
  const tokenRef = React.useRef(e3.token ?? null);
8697
9102
  tokenRef.current = e3.token ?? null;
8698
- const api3 = React.useMemo(
8699
- () => createDefaultDatasetApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8700
- [e3.apiUrl, e3.repo]
8701
- );
8702
- const cache3 = React.useMemo(() => {
8703
- const cfg = {};
8704
- if (e3.workspace !== void 0) cfg.workspace = e3.workspace;
8705
- return new ReactiveDatasetCache(cfg, api3);
8706
- }, [api3, e3.workspace]);
8707
- React.useMemo(() => {
8708
- initializeReactiveDatasetCache(cache3);
8709
- cache3.setScheduler((notify3) => queueMicrotask(notify3));
8710
- }, [cache3]);
8711
- React.useMemo(() => {
8712
- if (e3.workspace !== void 0) {
8713
- initializeFunctionApi(
8714
- createDefaultFunctionApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8715
- e3.workspace
8716
- );
8717
- }
8718
- }, [e3.apiUrl, e3.repo, e3.workspace]);
8719
- React.useMemo(() => {
8720
- if (e3.workspace !== void 0) {
8721
- initializePagedApi(
8722
- createDefaultPagedApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8723
- e3.workspace
8724
- );
8725
- }
8726
- }, [e3.apiUrl, e3.repo, e3.workspace]);
8727
- React.useMemo(() => {
8728
- if (e3.workspace !== void 0) {
8729
- initializeRecordApi(
8730
- createDefaultRecordApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8731
- cache3,
8732
- e3.workspace
8733
- );
8734
- }
8735
- }, [e3.apiUrl, e3.repo, e3.workspace, cache3]);
9103
+ const [installed, setInstalled] = React.useState(null);
8736
9104
  React.useEffect(() => {
9105
+ const getToken = () => tokenRef.current;
9106
+ const cache22 = new ReactiveDatasetCache(
9107
+ workspace !== void 0 ? { workspace } : {},
9108
+ createDefaultDatasetApi(apiUrl, repo, getToken)
9109
+ );
9110
+ cache22.setScheduler((notify3) => queueMicrotask(notify3));
9111
+ initializeReactiveDatasetCache(cache22);
9112
+ const functionApi = createDefaultFunctionApi(apiUrl, repo, getToken);
9113
+ const pagedApi = createDefaultPagedApi(apiUrl, repo, getToken, cache22);
9114
+ const recordApi = createDefaultRecordApi(apiUrl, repo, getToken);
9115
+ if (workspace !== void 0) {
9116
+ initializeFunctionApi(functionApi, workspace);
9117
+ initializePagedApi(pagedApi, workspace);
9118
+ initializeRecordApi(recordApi, cache22, workspace);
9119
+ }
9120
+ const unscopeStaged = getStagedStore().setScope(JSON.stringify([apiUrl, repo]));
9121
+ setInstalled({ apiUrl, repo, workspace, cache: cache22 });
8737
9122
  return () => {
8738
- const ws = cache3.getConfig().workspace;
8739
- clearReactiveDatasetCache();
8740
- clearFunctionApi();
8741
- clearPagedApi();
8742
- clearRecordApi();
8743
- cache3.destroy();
8744
- if (ws) clearBindingRegistry(ws);
9123
+ clearReactiveDatasetCache(cache22);
9124
+ clearFunctionApi(functionApi);
9125
+ clearPagedApi(pagedApi);
9126
+ clearRecordApi(recordApi);
9127
+ unscopeStaged();
9128
+ cache22.destroy();
9129
+ if (workspace) clearBindingRegistry(workspace);
8745
9130
  else clearBindingRegistry();
8746
9131
  };
8747
- }, [cache3]);
9132
+ }, [apiUrl, repo, workspace]);
9133
+ const cache3 = installed !== null && installed.apiUrl === apiUrl && installed.repo === repo && installed.workspace === workspace ? installed.cache : null;
8748
9134
  React.useEffect(() => {
8749
- if (typeof window !== "undefined") {
8750
- window.__EAST_REACTIVE_DATASET_CACHE__ = cache3;
8751
- }
9135
+ if (cache3 === null || typeof window === "undefined") return;
9136
+ window.__EAST_REACTIVE_DATASET_CACHE__ = cache3;
8752
9137
  return () => {
8753
- if (typeof window !== "undefined") {
8754
- delete window.__EAST_REACTIVE_DATASET_CACHE__;
8755
- }
9138
+ delete window.__EAST_REACTIVE_DATASET_CACHE__;
8756
9139
  };
8757
9140
  }, [cache3]);
9141
+ if (cache3 === null) return null;
8758
9142
  return /* @__PURE__ */ jsxRuntime.jsx(ReactiveDatasetCacheContext.Provider, { value: cache3, children: children2 });
8759
9143
  }
8760
9144
  function useReactiveDatasetCache() {
@@ -8873,12 +9257,11 @@ function collectLeaves(node2, into = []) {
8873
9257
  else for (const c2 of node2.children) collectLeaves(c2, into);
8874
9258
  return into;
8875
9259
  }
9260
+ const readString = east.parseFor(east.StringType);
8876
9261
  function leafDisplayLabel(seg) {
8877
- if (seg.kind === "key" && typeof seg.key === "string" && seg.key.length >= 2 && seg.key.startsWith('"') && seg.key.endsWith('"')) {
8878
- try {
8879
- return JSON.parse(seg.key);
8880
- } catch {
8881
- }
9262
+ if (seg.kind === "key") {
9263
+ const read = readString(seg.key);
9264
+ if (read.success) return read.value;
8882
9265
  }
8883
9266
  return east.pathDisplay(seg);
8884
9267
  }
@@ -8930,57 +9313,28 @@ function walkPatchToTree(typeValue, patch, rootLabel) {
8930
9313
  return root2;
8931
9314
  }
8932
9315
  const MAX_INLINE_CHARS = 48;
8933
- function formatLeafValue(typeValue, value) {
8934
- if (value === null || value === void 0) return "null";
8935
- if (!typeValue) return "<value>";
8936
- switch (typeValue.type) {
8937
- case "Null":
8938
- return "null";
8939
- case "Boolean":
8940
- return value ? "true" : "false";
8941
- case "Integer":
8942
- return typeof value === "bigint" ? value.toString() : String(value);
8943
- case "Float":
8944
- return formatFloat(value);
8945
- case "String":
8946
- return value === "" ? '""' : String(value);
8947
- case "DateTime":
8948
- return value instanceof Date ? value.toISOString() : String(value);
8949
- case "Blob":
8950
- return value instanceof Uint8Array ? `${value.length} bytes` : truncate(east.printFor(typeValue)(value));
8951
- case "Variant":
8952
- if (east.isVariant(value)) {
8953
- const cases = typeValue.value;
8954
- const sub = cases.find((c2) => c2.name === value.type);
8955
- if (value.value === null || value.value === void 0 || !sub) return value.type;
8956
- return `${value.type}(${truncate(east.printFor(sub.type)(value.value))})`;
8957
- }
8958
- return "<value>";
8959
- default:
8960
- return truncate(east.printFor(typeValue)(value));
8961
- }
8962
- }
8963
- function formatFloat(v3) {
8964
- if (typeof v3 !== "number") return String(v3);
8965
- if (Number.isInteger(v3)) return v3.toFixed(1);
8966
- return Math.abs(v3) >= 1e3 ? v3.toLocaleString("en-US", { maximumFractionDigits: 2 }) : String(Number(v3.toFixed(4)));
9316
+ function formatLeafValue(leafType, value, words = eastUiComponents.formatters()) {
9317
+ if (leafType.type === "Float") return words.float(value);
9318
+ const text = east.printFor(leafType)(value);
9319
+ return leafType.type === "String" ? text : truncate(text);
8967
9320
  }
8968
9321
  function truncate(s2) {
8969
9322
  return s2.length > MAX_INLINE_CHARS ? s2.slice(0, MAX_INLINE_CHARS - 1) + "…" : s2;
8970
9323
  }
8971
- function formatBindingLabel(segments) {
8972
- if (segments.length === 0) return "(root)";
8973
- const last2 = segments[segments.length - 1];
8974
- if (last2 && typeof last2 === "object" && "type" in last2) {
8975
- if (last2.type === "field") return String(last2.value);
8976
- if (last2.type === "index") return `[${String(last2.value)}]`;
8977
- if (last2.type === "key") return String(last2.value);
9324
+ function formatBindingLabel(path2) {
9325
+ const last2 = path2[path2.length - 1];
9326
+ if (last2 === void 0) return "(root)";
9327
+ switch (last2.type) {
9328
+ case "field":
9329
+ return last2.value;
8978
9330
  }
8979
- return String(last2);
8980
9331
  }
8981
- function isPrimitiveLeafType(t4) {
8982
- if (!t4) return false;
8983
- switch (t4.type) {
9332
+ const DATETIME_LOCAL = internal.tokenizeDateTimeFormat("YYYY-MM-DDTHH:mm");
9333
+ const readBoolean = east.parseFor(east.BooleanType);
9334
+ const readInteger = east.parseFor(east.IntegerType);
9335
+ const readFloat = east.parseFor(east.FloatType);
9336
+ function isPrimitiveLeafType(leafType) {
9337
+ switch (leafType.type) {
8984
9338
  case "Boolean":
8985
9339
  case "Integer":
8986
9340
  case "Float":
@@ -8992,49 +9346,46 @@ function isPrimitiveLeafType(t4) {
8992
9346
  }
8993
9347
  }
8994
9348
  function formatManualDraft(leafType, value) {
8995
- if (!leafType || value === void 0 || value === null) return "";
9349
+ if (value === void 0) return "";
8996
9350
  switch (leafType.type) {
8997
9351
  case "Boolean":
8998
- return value ? "true" : "false";
8999
9352
  case "Integer":
9000
- return typeof value === "bigint" ? value.toString() : String(value);
9001
9353
  case "Float":
9002
- return typeof value === "number" ? String(value) : String(value);
9354
+ return east.printFor(leafType)(value);
9003
9355
  case "String":
9004
- return String(value);
9005
- case "DateTime": {
9006
- if (!(value instanceof Date)) return String(value);
9007
- return value.toISOString().slice(0, 16);
9008
- }
9356
+ return value;
9357
+ case "DateTime":
9358
+ return internal.formatDateTime(value, DATETIME_LOCAL);
9009
9359
  default:
9010
9360
  return "";
9011
9361
  }
9012
9362
  }
9013
9363
  function parseManualDraft(leafType, draft) {
9014
- try {
9015
- switch (leafType.type) {
9016
- case "Integer":
9017
- return { ok: true, value: BigInt(draft) };
9018
- case "Float": {
9019
- const n2 = Number(draft);
9020
- if (Number.isNaN(n2)) return { ok: false };
9021
- return { ok: true, value: n2 };
9022
- }
9023
- case "String":
9024
- return { ok: true, value: draft };
9025
- case "DateTime": {
9026
- const d2 = new Date(draft);
9027
- if (Number.isNaN(d2.getTime())) return { ok: false };
9028
- return { ok: true, value: d2 };
9029
- }
9030
- default:
9031
- return { ok: false };
9364
+ switch (leafType.type) {
9365
+ case "Boolean": {
9366
+ const read = readBoolean(draft);
9367
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9032
9368
  }
9033
- } catch {
9034
- return { ok: false };
9369
+ case "Integer": {
9370
+ const read = readInteger(draft);
9371
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9372
+ }
9373
+ case "Float": {
9374
+ const read = readFloat(draft);
9375
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9376
+ }
9377
+ case "String":
9378
+ return { ok: true, value: draft };
9379
+ case "DateTime": {
9380
+ const read = internal.parseDateTimeFormatted(draft, DATETIME_LOCAL);
9381
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9382
+ }
9383
+ default:
9384
+ return { ok: false };
9035
9385
  }
9036
9386
  }
9037
- const diffValueEqual = east.equalFor(internal$1.Diff.Component.schema);
9387
+ const diffValueEqual = east.equivalentFor(internal$1.Diff.Component.schema);
9388
+ const diffValueDataEqual = east.equalFor(internal$1.Diff.Component.schema);
9038
9389
  function getOpt$1(opt) {
9039
9390
  return opt.type === "some" ? opt.value : void 0;
9040
9391
  }
@@ -9260,6 +9611,7 @@ function ActionBtn({
9260
9611
  );
9261
9612
  }
9262
9613
  const DiffRow = React.memo(function DiffRow2({ row, depth, bindingPathStr, showActions, onDiscard, annotation, metrics }) {
9614
+ const words = eastUiComponents.useFormatters();
9263
9615
  const handleDiscard = React.useCallback(() => onDiscard(bindingPathStr, row.path), [onDiscard, bindingPathStr, row.path]);
9264
9616
  return /* @__PURE__ */ jsxRuntime.jsxs(
9265
9617
  react.Box,
@@ -9292,7 +9644,7 @@ const DiffRow = React.memo(function DiffRow2({ row, depth, bindingPathStr, showA
9292
9644
  row.stale && /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "11px", color: "fg.warning", fontWeight: 500, children: [
9293
9645
  /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }),
9294
9646
  " ",
9295
- row.stale.actual === void 0 ? "stale — source no longer has this entry" : `stale — source actually has ${formatLeafValue(row.leafType, row.stale.actual)}`
9647
+ row.stale.actual === void 0 ? "stale — source no longer has this entry" : `stale — source actually has ${formatLeafValue(row.leafType, row.stale.actual, words)}`
9296
9648
  ] }),
9297
9649
  annotation && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "11px", color: `${annotation.tone}.700`, fontWeight: 500, children: annotation.text })
9298
9650
  ] }),
@@ -9301,13 +9653,13 @@ const DiffRow = React.memo(function DiffRow2({ row, depth, bindingPathStr, showA
9301
9653
  row.before !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(Chip$1, { fontSize: metrics.chipFontSize, children: [
9302
9654
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.danger", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faMinus }) }),
9303
9655
  " ",
9304
- formatLeafValue(row.leafType, row.before)
9656
+ formatLeafValue(row.leafType, row.before, words)
9305
9657
  ] }),
9306
9658
  row.before !== void 0 && row.after !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.subtle", fontSize: "11px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faArrowRight }) }),
9307
9659
  row.after !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(Chip$1, { fontSize: metrics.chipFontSize, children: [
9308
9660
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.success", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faPlus }) }),
9309
9661
  " ",
9310
- formatLeafValue(row.leafType, row.after)
9662
+ formatLeafValue(row.leafType, row.after, words)
9311
9663
  ] })
9312
9664
  ] }),
9313
9665
  showActions && /* @__PURE__ */ jsxRuntime.jsx(ActionBtn, { icon: faXmark, title: "Discard this change", onClick: handleDiscard, size: metrics.actionBtnSize })
@@ -9316,9 +9668,11 @@ const DiffRow = React.memo(function DiffRow2({ row, depth, bindingPathStr, showA
9316
9668
  }
9317
9669
  );
9318
9670
  });
9671
+ const REMOVED = "(removed)";
9319
9672
  const ConflictRow = React.memo(function ConflictRow2({ row, depth, bindingPathStr, serverValue, resolution, onResolve, metrics }) {
9320
- const yoursStr = formatLeafValue(row.leafType, row.after);
9321
- const theirsStr = formatLeafValue(row.leafType, serverValue);
9673
+ const words = eastUiComponents.useFormatters();
9674
+ const yoursStr = row.after === void 0 ? REMOVED : formatLeafValue(row.leafType, row.after, words);
9675
+ const theirsStr = serverValue === void 0 ? REMOVED : formatLeafValue(row.leafType, serverValue, words);
9322
9676
  const isYours = (resolution == null ? void 0 : resolution.type) === "keepA";
9323
9677
  const isTheirs = (resolution == null ? void 0 : resolution.type) === "keepB";
9324
9678
  const isManual = (resolution == null ? void 0 : resolution.type) === "manual";
@@ -9384,7 +9738,7 @@ const ConflictRow = React.memo(function ConflictRow2({ row, depth, bindingPathSt
9384
9738
  {
9385
9739
  selected: isManual,
9386
9740
  leafType: row.leafType,
9387
- value: isManual ? resolution.value : row.after,
9741
+ value: (resolution == null ? void 0 : resolution.type) === "manual" ? resolution.value : row.after,
9388
9742
  onChange: handleManual
9389
9743
  }
9390
9744
  )
@@ -9501,9 +9855,8 @@ function ManualEditor({
9501
9855
  const fire = React.useCallback((parsed) => {
9502
9856
  queueMicrotask(() => onChange2(parsed));
9503
9857
  }, [onChange2]);
9504
- if (!leafType) return null;
9505
9858
  if (leafType.type === "Boolean") {
9506
- const checked = !!value;
9859
+ const checked = value === true;
9507
9860
  return /* @__PURE__ */ jsxRuntime.jsxs(
9508
9861
  react.Box,
9509
9862
  {
@@ -9685,9 +10038,10 @@ const EastChakraDiff = React.memo(function EastChakraDiff2({ value }) {
9685
10038
  const [conflicts, setConflicts] = React.useState(null);
9686
10039
  const [resolutions, setResolutions] = React.useState(/* @__PURE__ */ new Map());
9687
10040
  const [committing, setCommitting] = React.useState(false);
10041
+ const data4 = eastUiComponents.useDataStable(value, diffValueDataEqual);
9688
10042
  const irBindings = React.useMemo(
9689
- () => value.bindings ?? [],
9690
- [value.bindings]
10043
+ () => data4.bindings ?? [],
10044
+ [data4.bindings]
9691
10045
  );
9692
10046
  const interactive = !(getOpt$1(value.readonly) ?? false);
9693
10047
  const onCommittedFn = getOpt$1(value.onCommitted);
@@ -9721,7 +10075,7 @@ const EastChakraDiff = React.memo(function EastChakraDiff2({ value }) {
9721
10075
  React.useEffect(() => {
9722
10076
  setConflicts(null);
9723
10077
  setResolutions(/* @__PURE__ */ new Map());
9724
- }, [value, stagedVersion, datasetVersion]);
10078
+ }, [data4, stagedVersion, datasetVersion]);
9725
10079
  const visibleBindings = React.useMemo(
9726
10080
  () => bindings.filter((b2) => b2.leaves.length > 0),
9727
10081
  [bindings]
@@ -23100,13 +23454,13 @@ var hashIterableInts = function hashIterableInts2(iterator) {
23100
23454
  }
23101
23455
  return hash2;
23102
23456
  };
23103
- var hashInt = function hashInt2(num2) {
23457
+ var hashInt = function hashInt2(num) {
23104
23458
  var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED;
23105
- return seed * K + num2 | 0;
23459
+ return seed * K + num | 0;
23106
23460
  };
23107
- var hashIntAlt = function hashIntAlt2(num2) {
23461
+ var hashIntAlt = function hashIntAlt2(num) {
23108
23462
  var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED_ALT;
23109
- return (seed << 5) + seed + num2 | 0;
23463
+ return (seed << 5) + seed + num | 0;
23110
23464
  };
23111
23465
  var combineHashes = function combineHashes2(hash1, hash2) {
23112
23466
  return hash1 * 2097152 + hash2;
@@ -23136,7 +23490,7 @@ var hashIntsArray = function hashIntsArray2(ints, seed) {
23136
23490
  };
23137
23491
  return hashIterableInts(iterator, seed);
23138
23492
  };
23139
- var hashString$1 = function hashString(str, seed) {
23493
+ var hashString = function hashString2(str, seed) {
23140
23494
  var entry = {
23141
23495
  value: 0,
23142
23496
  done: false
@@ -23163,9 +23517,9 @@ var hashStringsArray = function hashStringsArray2(strs) {
23163
23517
  for (var i = 0; i < strs.length; i++) {
23164
23518
  var str = strs[i];
23165
23519
  if (i === 0) {
23166
- hash2 = hashString$1(str);
23520
+ hash2 = hashString(str);
23167
23521
  } else {
23168
- hash2 = hashString$1(str, hash2);
23522
+ hash2 = hashString(str, hash2);
23169
23523
  }
23170
23524
  }
23171
23525
  return hash2;
@@ -30303,7 +30657,7 @@ var cache = function cache2(fn3, name) {
30303
30657
  var _p = eles[0]._private;
30304
30658
  var tch = _p.traversalCache = _p.traversalCache || {};
30305
30659
  var ch = tch[name] = tch[name] || [];
30306
- var hash2 = hashString$1(key);
30660
+ var hash2 = hashString(key);
30307
30661
  var cacheHit = ch[hash2];
30308
30662
  if (cacheHit) {
30309
30663
  return cacheHit;
@@ -32909,13 +33263,13 @@ function styleCache(key, fn3, ele) {
32909
33263
  }
32910
33264
  }
32911
33265
  function cacheStyleFunction(key, fn3) {
32912
- key = hashString$1(key);
33266
+ key = hashString(key);
32913
33267
  return function cachedStyleFunction(ele) {
32914
33268
  return styleCache(key, fn3, ele);
32915
33269
  };
32916
33270
  }
32917
33271
  function cachePrototypeStyleFunction(key, fn3) {
32918
- key = hashString$1(key);
33272
+ key = hashString(key);
32919
33273
  var selfFn = function selfFn2(ele) {
32920
33274
  return fn3.call(ele);
32921
33275
  };
@@ -43186,7 +43540,7 @@ BRp$9.applyLabelDimensions = function(ele) {
43186
43540
  BRp$9.applyPrefixedLabelDimensions = function(ele, prefix2) {
43187
43541
  var _p = ele._private;
43188
43542
  var text = this.getLabelText(ele, prefix2);
43189
- var cacheKey = hashString$1(text, ele._private.labelDimsKey);
43543
+ var cacheKey = hashString(text, ele._private.labelDimsKey);
43190
43544
  if (getPrefixedProperty(_p.rscratch, "prefixedLabelDimsKey", prefix2) === cacheKey) {
43191
43545
  return;
43192
43546
  }
@@ -47582,7 +47936,7 @@ CRp$9.drawArrowShape = function(edge, context, fill, edgeWidth, shape, shapeWidt
47582
47936
  var shapeImpl = r2.arrowShapes[shape];
47583
47937
  if (usePaths) {
47584
47938
  var cache3 = r2.arrowPathCache = r2.arrowPathCache || [];
47585
- var key = hashString$1(shape);
47939
+ var key = hashString(shape);
47586
47940
  var cachedPath = cache3[key];
47587
47941
  if (cachedPath != null) {
47588
47942
  path2 = context = cachedPath;
@@ -57964,7 +58318,7 @@ function useBindingValue(binding) {
57964
58318
  try {
57965
58319
  return { value: handle.read(), pending: handle.pending(), error: void 0 };
57966
58320
  } catch (e3) {
57967
- console.error(`[Experiment] failed to read binding "${String(sourcePath)}":`, e3);
58321
+ console.error(`[Experiment] failed to read binding "${e3Types.pathToString(sourcePath)}":`, e3);
57968
58322
  return { value: null, pending: false, error: e3 };
57969
58323
  }
57970
58324
  }, [handle, version2]);
@@ -57980,20 +58334,25 @@ function useBindingValue(binding) {
57980
58334
  }, [handle]);
57981
58335
  return { value, mode, pending, error: error3, mutate, commit, discard };
57982
58336
  }
58337
+ function sameType(a2, b2) {
58338
+ return east.isTypeValueEqual(east.toEastTypeValue(a2), east.toEastTypeValue(b2));
58339
+ }
58340
+ function sameSignature(a2, b2) {
58341
+ const ai = a2.inputs;
58342
+ const bi = b2.inputs;
58343
+ return sameType(a2.output, b2.output) && (ai === null || bi === null ? ai === bi : ai.length === bi.length && ai.every((t4, i) => sameType(t4, bi[i])));
58344
+ }
57983
58345
  const IDLE = { call: () => {
57984
58346
  }, result: null, status: "idle", pending: false, error: null };
57985
58347
  function useFuncCall(name, inputs, output2) {
57986
58348
  const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
57987
58349
  const ready4 = !!name && !!inputs && workspace !== "";
57988
- const sigKey = React.useMemo(
57989
- () => ready4 ? JSON.stringify([inputs.map((t4) => east.toEastTypeValue(t4)), east.toEastTypeValue(output2)]) : "",
57990
- [ready4, inputs, output2]
57991
- );
58350
+ const signature = eastUiComponents.useDataStable({ inputs, output: output2 }, sameSignature);
57992
58351
  const handle = React.useMemo(() => {
57993
- if (!ready4) return null;
57994
- const handleType = east.toEastTypeValue(internal$1.FuncBindHandleType(inputs, output2));
58352
+ if (!ready4 || signature.inputs === null) return null;
58353
+ const handleType = east.toEastTypeValue(internal$1.FuncBindHandleType(signature.inputs, signature.output));
57995
58354
  return defaultFuncRuntime.buildHandle(handleType, name);
57996
- }, [ready4, name, workspace, sigKey]);
58355
+ }, [ready4, name, workspace, signature]);
57997
58356
  const channelKey = ready4 ? funcChannelKey(workspace, name) : "";
57998
58357
  const version2 = React.useSyncExternalStore(
57999
58358
  React.useCallback(
@@ -58004,7 +58363,6 @@ function useFuncCall(name, inputs, output2) {
58004
58363
  React.useCallback(() => channelKey ? defaultFuncRuntime.getKeyVersion(channelKey) : 0, [channelKey])
58005
58364
  );
58006
58365
  return React.useMemo(() => {
58007
- var _a2;
58008
58366
  if (!handle) return IDLE;
58009
58367
  let result = null;
58010
58368
  let status = "idle";
@@ -58016,10 +58374,7 @@ function useFuncCall(name, inputs, output2) {
58016
58374
  status = handle.status().type;
58017
58375
  pending = handle.pending();
58018
58376
  const err = handle.error();
58019
- if (err.type === "some") {
58020
- const v3 = err.value;
58021
- error3 = { kind: ((_a2 = v3.kind) == null ? void 0 : _a2.type) ?? "failed", message: v3.message ?? "", stdout: v3.stdout ?? "", stderr: v3.stderr ?? "" };
58022
- }
58377
+ if (err.type === "some") error3 = err.value;
58023
58378
  } catch {
58024
58379
  }
58025
58380
  return { call: (...args) => {
@@ -60637,207 +60992,14 @@ function GuidanceToggle({ on: on3, onToggle }) {
60637
60992
  }
60638
60993
  ) });
60639
60994
  }
60640
- function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
60641
- const span = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
60642
- return /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: span }) : span }) });
60643
- }
60644
- function svgText(size3, fill, family, weight8) {
60645
- return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
60646
- }
60647
- function useMeasuredWidth() {
60648
- const ref = React.useRef(null);
60649
- const [w2, setW] = React.useState(0);
60650
- React.useLayoutEffect(() => {
60651
- const el = ref.current;
60652
- if (!el) return;
60653
- const update2 = () => setW(el.clientWidth);
60654
- update2();
60655
- const ro = new ResizeObserver(update2);
60656
- ro.observe(el);
60657
- return () => ro.disconnect();
60658
- }, []);
60659
- return [ref, w2];
60660
- }
60661
- function useChartTheme() {
60662
- const system = react.useChakraContext();
60663
- const tok = (k2, fallback) => system.token(k2, fallback);
60664
- const tones = {
60665
- neg: tok("colors.fg.danger", "#b85a4a"),
60666
- pos: tok("colors.fg.success", "#2f7a5b"),
60667
- warn: tok("colors.fg.warning", "#b8862d"),
60668
- muted: tok("colors.fg.muted", "#6b8080"),
60669
- brand: tok("colors.brand.solid", "#3a7780")
60670
- };
60671
- return {
60672
- mono: tok("fonts.mono", '"JetBrains Mono", ui-monospace, monospace'),
60673
- body: tok("fonts.body", "system-ui, -apple-system, sans-serif"),
60674
- ink: tok("colors.fg", "#111b22"),
60675
- muted: tones.muted,
60676
- faint: tok("colors.fg.subtle", "#9bb0b0"),
60677
- rule: tok("colors.border.subtle", "#e2e8e8"),
60678
- ruleStrong: tok("colors.border.strong", "#cbd5d5"),
60679
- surface: tok("colors.bg.surface", "#ffffff"),
60680
- tone: (t4) => tones[t4] ?? tones.brand,
60681
- labelSize: 10,
60682
- titleSize: 11,
60683
- lineWidth: 1.8,
60684
- barWidth: 2.5,
60685
- dotRadius: 2.6,
60686
- scatterRadius: 3.4,
60687
- areaOpacity: 0.16
60688
- };
60689
- }
60690
- const fmt$2 = (n2) => Number.isInteger(n2) ? String(n2) : n2.toFixed(1);
60691
- const signed$1 = (n2) => (n2 > 0 ? "+" : "") + fmt$2(n2);
60692
- const niceCeil = (v3) => {
60693
- if (v3 <= 1e-9) return 0;
60694
- const mag = Math.pow(10, Math.floor(Math.log10(v3)));
60695
- const n2 = v3 / mag;
60696
- const step3 = n2 <= 1 ? 1 : n2 <= 1.5 ? 1.5 : n2 <= 2 ? 2 : n2 <= 3 ? 3 : n2 <= 4 ? 4 : n2 <= 5 ? 5 : n2 <= 6 ? 6 : n2 <= 8 ? 8 : 10;
60697
- return step3 * mag;
60698
- };
60699
- function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
60700
- const t4 = useChartTheme();
60701
- const [ref, w2] = useMeasuredWidth();
60702
- const n2 = rows.length;
60703
- const hPx = height2 ?? 26 + n2 * 38;
60704
- const padL = 124, padR = 46, padT = 20, padB = 30;
60705
- const innerW = Math.max(0, w2 - padL - padR);
60706
- const innerH = hPx - padT - padB;
60707
- const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
60708
- const rowH = innerH / Math.max(1, n2);
60709
- const zeroX = Math.max(0, Math.min(innerW, x2(0)));
60710
- const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
60711
- const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
60712
- const zf = frac(0);
60713
- const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
60714
- return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
60715
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
60716
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
60717
- rows.map((r2, i) => {
60718
- const cy = rowH * i + rowH / 2;
60719
- const col = t4.tone(r2.tone);
60720
- return /* @__PURE__ */ jsxRuntime.jsxs(Group, { top: cy, children: [
60721
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: 0 }, to: { x: x2(r2.hi), y: 0 }, stroke: col, strokeWidth: t4.barWidth, strokeOpacity: 0.9, strokeLinecap: "round" }),
60722
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
60723
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
60724
- /* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
60725
- /* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: -padL + 2, y: -13, w: padL - 6, align: "left", size: t4.labelSize, weight: 600, color: t4.ink, family: t4.body, ...(rowHelp == null ? void 0 : rowHelp[i]) ? { help: rowHelp[i] } : {}, children: r2.label }),
60726
- r2.note && /* @__PURE__ */ jsxRuntime.jsx("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
60727
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed$1(r2.est) })
60728
- ] }, i);
60729
- }),
60730
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60731
- ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
60732
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
60733
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed$1(tk) })
60734
- ] }, `t${i}`)),
60735
- unit2 && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
60736
- ] }) }) });
60737
- }
60738
- function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
60739
- const t4 = useChartTheme();
60740
- const [ref, w2] = useMeasuredWidth();
60741
- const hPx = height2 ?? 100;
60742
- const col = t4.tone(toneName ?? "brand");
60743
- const padL = yTicks.length ? 52 : 6;
60744
- const padR = 12;
60745
- const padT = marks.length ? 22 : 8;
60746
- const padB = xTicks.length ? 22 : 12;
60747
- const innerW = Math.max(0, w2 - padL - padR);
60748
- const innerH = hPx - padT - padB;
60749
- const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
60750
- const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
60751
- const data4 = mid.map((m2, i) => {
60752
- const mm = fin(m2, 0);
60753
- return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
60754
- });
60755
- const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
60756
- const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
60757
- const yHiN = niceCeil(Math.max(dataMax, 0));
60758
- const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
60759
- const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
60760
- const yfmt = (v3) => yFormat === "percent" ? `${Math.round(v3)}%` : signed$1(v3);
60761
- const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
60762
- return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
60763
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60764
- yTicks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
60765
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: 10, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[0] }),
60766
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: innerH / 2 + 3, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[1] }),
60767
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: innerH - 1, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[2] })
60768
- ] }),
60769
- zero2 != null && /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: yScale(zero2) }, to: { x: innerW, y: yScale(zero2) }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
60770
- /* @__PURE__ */ jsxRuntime.jsx(Area, { data: data4, x: (d2) => xScale(d2.i), y0: (d2) => yScale(d2.lo), y1: (d2) => yScale(d2.hi), curve: monotoneX, fill: col, fillOpacity: t4.areaOpacity, stroke: "transparent" }),
60771
- /* @__PURE__ */ jsxRuntime.jsx(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
60772
- marks.map((m2, i) => {
60773
- const mc = t4.tone(m2.tone);
60774
- const dotY = yScale(fin(mid[m2.at], 0));
60775
- const labY = Math.max(9, dotY - 9);
60776
- const atEnd = m2.at >= mid.length - 2;
60777
- const atStart = m2.at <= 1;
60778
- const align = atEnd ? "right" : atStart ? "left" : "center";
60779
- const labW = m2.label.length * 6.5 + 8;
60780
- const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
60781
- return /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: xScale(m2.at), children: [
60782
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
60783
- /* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
60784
- /* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: labX, y: labY - 11, w: labW, align, size: t4.labelSize, weight: 500, color: mc, family: t4.body, ...m2.help ? { help: m2.help } : {}, children: m2.label })
60785
- ] }, `mk${i}`);
60786
- }),
60787
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60788
- xTicks.map((tk, i) => {
60789
- const word = Number.isNaN(Number(tk));
60790
- return /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW * i / Math.max(1, xTicks.length - 1), y: innerH + 14, textAnchor: i === 0 ? "start" : i === xTicks.length - 1 ? "end" : "middle", style: svgText(word ? 9 : t4.labelSize, word ? t4.faint : t4.muted, word ? t4.body : t4.mono), children: tk }, `x${i}`);
60791
- })
60792
- ] }) }) });
60793
- }
60794
- function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
60795
- const t4 = useChartTheme();
60796
- const [ref, w2] = useMeasuredWidth();
60797
- const hPx = height2 ?? 150;
60798
- const padL = 8, padR = 8, padT = 16, padB = 26;
60799
- const innerW = Math.max(0, w2 - padL - padR);
60800
- const innerH = hPx - padT - padB;
60801
- const centerY = innerH / 2;
60802
- const halfH = centerY - 4;
60803
- const bins = Math.max(treated.length, control.length);
60804
- const x2 = createLinearScale({ domain, range: [0, innerW] });
60805
- const span = domain[1] - domain[0];
60806
- const binW = bins > 0 ? innerW / bins : innerW;
60807
- const barW = Math.max(1, binW * 0.78);
60808
- const maxVal = Math.max(1e-9, ...treated, ...control);
60809
- const tCol = t4.tone("pos");
60810
- const cCol = t4.tone("muted");
60811
- const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
60812
- const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
60813
- return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
60814
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
60815
- supportLabel && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
60816
- Array.from({ length: bins }, (_2, i) => {
60817
- const ht = Number(treated[i] ?? 0) / maxVal * halfH;
60818
- const hc = Number(control[i] ?? 0) / maxVal * halfH;
60819
- const left = cx2(i) - barW / 2;
60820
- return /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
60821
- ht > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
60822
- hc > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
60823
- ] }, i);
60824
- }),
60825
- /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
60826
- /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
60827
- ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsx("text", { x: x2(tk), y: innerH + 16, textAnchor: i === 0 ? "start" : i === ticks2.length - 1 ? "end" : "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: fmt$2(tk) }, `x${i}`))
60828
- ] }) }) });
60829
- }
60830
60995
  const arr = (v3) => v3 ? Array.from(v3, Number) : [];
60831
60996
  const arrI = (v3) => v3 ? Array.from(v3, Number) : [];
60832
- const fmt$1 = (x2) => {
60833
- const r2 = Number.isInteger(x2) ? String(x2) : x2.toFixed(1);
60834
- return r2 === "-0.0" ? "0.0" : r2;
60835
- };
60836
- const signed = (x2) => {
60837
- const f2 = fmt$1(x2);
60838
- return f2 === "0" || f2 === "0.0" || f2.startsWith("-") ? f2 : `+${f2}`;
60839
- };
60997
+ const ONE_DECIMAL = east.variant("number", { minimumFractionDigits: east.some(1n), maximumFractionDigits: east.some(1n), signDisplay: east.none });
60998
+ const formatOf = (x2) => Number.isInteger(x2) ? void 0 : ONE_DECIMAL;
60999
+ const fmt$1 = (x2, words) => words.value(Math.abs(x2) < 0.05 ? 0 : x2, formatOf(x2));
61000
+ const signed = (x2, words) => words.value(x2, formatOf(x2), true);
60840
61001
  const clamp01 = (x2) => x2 < 0 ? 0 : x2 > 1 ? 1 : x2;
61002
+ const compareFloats = east.compareFor(east.FloatType);
60841
61003
  function band(absStd) {
60842
61004
  if (absStd >= 0.66) return { level: "large gap", tone: "neg" };
60843
61005
  if (absStd >= 0.33) return { level: "some", tone: "warn" };
@@ -60887,7 +61049,7 @@ function treatmentKind(cols, treatment) {
60887
61049
  return "value";
60888
61050
  }
60889
61051
  }
60890
- function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
61052
+ function deriveSpec(config2, cols, result, meta3, dataLen, subject, words) {
60891
61053
  const outUnit = unitOf(meta3, config2.outcome);
60892
61054
  const balByCol = /* @__PURE__ */ new Map();
60893
61055
  for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
@@ -60925,24 +61087,24 @@ function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
60925
61087
  // n_total belongs to the Answer-tab counts strip; labelling the header
60926
61088
  // with it desyncs from the data after a population edit (and reads
60927
61089
  // wrong whenever the two legitimately differ).
60928
- dataLabel: `${dataLen} ${subject.many}`
61090
+ dataLabel: `${words.number(dataLen)} ${subject.many}`
60929
61091
  };
60930
61092
  }
60931
- function balanceDisplay(b2, categorical) {
61093
+ function balanceDisplay(b2, categorical, words) {
60932
61094
  const isProp = categorical.has(b2.base_column) && b2.treated_mean >= 0 && b2.treated_mean <= 1 && b2.control_mean >= 0 && b2.control_mean <= 1;
60933
- return isProp ? `${Math.round(b2.treated_mean * 100)}% vs ${Math.round(b2.control_mean * 100)}%` : `${b2.treated_mean.toFixed(1)} vs ${b2.control_mean.toFixed(1)}`;
61095
+ return isProp ? `${words.percent(b2.treated_mean)} vs ${words.percent(b2.control_mean)}` : `${words.value(b2.treated_mean, ONE_DECIMAL)} vs ${words.value(b2.control_mean, ONE_DECIMAL)}`;
60934
61096
  }
60935
- function deriveAnswer(config2, result, adj, meta3) {
61097
+ function deriveAnswer(config2, result, adj, meta3, words) {
60936
61098
  const ci = eastUiComponents.getSomeorUndefined(adj.ci);
60937
61099
  const nci = eastUiComponents.getSomeorUndefined(result.naive_ci);
60938
61100
  const categorical = new Set(eastUiComponents.getSomeorUndefined(config2.categorical) ?? []);
60939
- const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
61101
+ const balance = [...result.balance].sort((a2, b2) => compareFloats(Math.abs(b2.std_diff), Math.abs(a2.std_diff))).map((b2) => ({
60940
61102
  // One-hot rows are `col=level` — label the BASE confounder, keep the level.
60941
61103
  col: b2.column,
60942
61104
  label: b2.column === b2.base_column ? labelOf(meta3, b2.base_column) : `${labelOf(meta3, b2.base_column)}${b2.column.startsWith(b2.base_column) ? b2.column.slice(b2.base_column.length) : ` (${b2.column})`}`,
60943
61105
  treated: b2.treated_mean,
60944
61106
  control: b2.control_mean,
60945
- display: balanceDisplay(b2, categorical),
61107
+ display: balanceDisplay(b2, categorical, words),
60946
61108
  frac: clamp01(Math.abs(b2.std_diff)),
60947
61109
  tone: band(Math.abs(b2.std_diff)).tone
60948
61110
  }));
@@ -60973,12 +61135,12 @@ function deriveAnswer(config2, result, adj, meta3) {
60973
61135
  balance
60974
61136
  };
60975
61137
  }
60976
- function deriveNarrative(result, a2, refute, higherBetter, subject) {
61138
+ function deriveNarrative(result, a2, refute, higherBetter, subject, words) {
60977
61139
  if (a2 === null) return null;
60978
61140
  const unit2 = a2.unit ? ` ${a2.unit}` : "";
60979
61141
  const dirUp = a2.effect > 0;
60980
61142
  const verb = higherBetter === void 0 ? dirUp ? "raises" : "lowers" : dirUp === higherBetter ? "improves" : "worsens";
60981
- const range = ` (likely between ${signed(a2.lo)} and ${signed(a2.hi)})`;
61143
+ const range = ` (likely between ${signed(a2.lo, words)} and ${signed(a2.hi, words)})`;
60982
61144
  const passed = (refute == null ? void 0 : refute.checks.filter((c2) => c2.passed).length) ?? 0;
60983
61145
  const total = (refute == null ? void 0 : refute.checks.length) ?? 0;
60984
61146
  const trustClause = total === 0 ? "" : passed === total ? ` It held up against all ${total} ways we tried to break it.` : ` It passed ${passed} of ${total} stress tests — see “Can we trust it?”.`;
@@ -60989,13 +61151,13 @@ function deriveNarrative(result, a2, refute, higherBetter, subject) {
60989
61151
  segments: [
60990
61152
  { text: "Yes — ", strong: true },
60991
61153
  { text: `${a2.treatment} genuinely ${verb} ${a2.outcome}: about ` },
60992
- { text: `${signed(a2.effect)}${unit2}`, strong: true },
61154
+ { text: `${signed(a2.effect, words)}${unit2}`, strong: true },
60993
61155
  { text: ` per ${subject.one}${range}.${trustClause}` }
60994
61156
  ]
60995
61157
  };
60996
61158
  case "modest": {
60997
61159
  const tags = new Set(result.verdict_reasons.map((v3) => v3.type));
60998
- const why = tags.has("not_material") ? ` — the change (${signed(a2.effect)}${unit2}) is smaller than what would matter here (±${fmt$1(result.materiality_threshold)}${unit2}).` : tags.has("ci_spans_zero") ? ` — the likely range (${signed(a2.lo)} to ${signed(a2.hi)}) includes zero.` : tags.has("thin_support") ? " — too few treated and untreated look-alikes overlap to be confident." : tags.has("low_robustness") ? " — a fairly weak hidden factor could overturn it." : a2.lo <= 0 && a2.hi >= 0 ? ` — the likely range (${signed(a2.lo)} to ${signed(a2.hi)}) includes zero.` : ".";
61160
+ const why = tags.has("not_material") ? ` — the change (${signed(a2.effect, words)}${unit2}) is smaller than what would matter here (±${fmt$1(result.materiality_threshold, words)}${unit2}).` : tags.has("ci_spans_zero") ? ` — the likely range (${signed(a2.lo, words)} to ${signed(a2.hi, words)}) includes zero.` : tags.has("thin_support") ? " — too few treated and untreated look-alikes overlap to be confident." : tags.has("low_robustness") ? " — a fairly weak hidden factor could overturn it." : a2.lo <= 0 && a2.hi >= 0 ? ` — the likely range (${signed(a2.lo, words)} to ${signed(a2.hi, words)}) includes zero.` : ".";
60999
61161
  return {
61000
61162
  tone: "warn",
61001
61163
  segments: [
@@ -61010,14 +61172,14 @@ function deriveNarrative(result, a2, refute, higherBetter, subject) {
61010
61172
  tone: "warn",
61011
61173
  segments: [
61012
61174
  { text: "Don’t act on this yet — ", strong: true },
61013
- { text: `we got a number (${signed(a2.effect)}${unit2}), but a stress test failed: something we didn’t adjust for may still be driving it.` }
61175
+ { text: `we got a number (${signed(a2.effect, words)}${unit2}), but a stress test failed: something we didn’t adjust for may still be driving it.` }
61014
61176
  ]
61015
61177
  };
61016
61178
  default:
61017
61179
  return null;
61018
61180
  }
61019
61181
  }
61020
- function deriveRefusal(config2, result, meta3, subject) {
61182
+ function deriveRefusal(config2, result, meta3, subject, words) {
61021
61183
  const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
61022
61184
  const treatment = labelOf(meta3, config2.treatment);
61023
61185
  if (result.verdict.type === "not_estimable") {
@@ -61027,22 +61189,22 @@ function deriveRefusal(config2, result, meta3, subject) {
61027
61189
  title: `Can’t estimate the effect of ${treatment}`,
61028
61190
  body: reason,
61029
61191
  evidence: [
61030
- { label: "treated", value: String(nT) },
61031
- { label: "untreated", value: String(nC) },
61032
- { label: subject.many, value: String(nTot) }
61192
+ { label: "treated", value: words.number(nT) },
61193
+ { label: "untreated", value: words.number(nC) },
61194
+ { label: subject.many, value: words.number(nTot) }
61033
61195
  ]
61034
61196
  };
61035
61197
  }
61036
61198
  if (result.verdict.type === "non_identifiable_positivity") {
61037
- const pct = Math.round(result.overlap.common_support_frac * 100);
61199
+ const pct = words.percent(result.overlap.common_support_frac);
61038
61200
  return {
61039
61201
  kind: "positivity",
61040
61202
  title: "No like-for-like comparison exists",
61041
- body: `The treated and untreated groups barely overlap on the confounders — only ${pct}% sit in a range where both occur — so there is no fair comparison to adjust toward.`,
61203
+ body: `The treated and untreated groups barely overlap on the confounders — only ${pct} sit in a range where both occur — so there is no fair comparison to adjust toward.`,
61042
61204
  evidence: [
61043
- { label: "common support", value: `${pct}%` },
61044
- { label: "treated", value: String(nT) },
61045
- { label: "untreated", value: String(nC) }
61205
+ { label: "common support", value: pct },
61206
+ { label: "treated", value: words.number(nT) },
61207
+ { label: "untreated", value: words.number(nC) }
61046
61208
  ]
61047
61209
  };
61048
61210
  }
@@ -61051,20 +61213,19 @@ function deriveRefusal(config2, result, meta3, subject) {
61051
61213
  title: `Can’t estimate the effect of ${treatment}`,
61052
61214
  body: "The engine could not produce a like-for-like estimate for this configuration.",
61053
61215
  evidence: [
61054
- { label: "treated", value: String(nT) },
61055
- { label: "untreated", value: String(nC) },
61056
- { label: subject.many, value: String(nTot) }
61216
+ { label: "treated", value: words.number(nT) },
61217
+ { label: "untreated", value: words.number(nC) },
61218
+ { label: subject.many, value: words.number(nTot) }
61057
61219
  ]
61058
61220
  };
61059
61221
  }
61060
- function deriveOverlap(o2) {
61061
- const pct = Math.round(o2.common_support_frac * 100);
61222
+ function deriveOverlap(o2, words) {
61062
61223
  return {
61063
61224
  treated: arr(o2.treated_propensity),
61064
61225
  control: arr(o2.control_propensity),
61065
61226
  commonSupportFrac: o2.common_support_frac,
61066
61227
  positivityOk: o2.positivity_ok,
61067
- supportLabel: `${pct}% common support`
61228
+ supportLabel: `${words.percent(o2.common_support_frac)} common support`
61068
61229
  };
61069
61230
  }
61070
61231
  function zeroCrossing(xs, ys) {
@@ -61077,7 +61238,7 @@ function zeroCrossing(xs, ys) {
61077
61238
  }
61078
61239
  return null;
61079
61240
  }
61080
- function deriveRefute(r2, adj, meta3) {
61241
+ function deriveRefute(r2, adj, meta3, words) {
61081
61242
  const checks = [];
61082
61243
  const est = (adj == null ? void 0 : adj.effect) ?? 0;
61083
61244
  const placeboEffect = eastUiComponents.getSomeorUndefined(r2.placebo_effect);
@@ -61090,7 +61251,7 @@ function deriveRefute(r2, adj, meta3) {
61090
61251
  desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
61091
61252
  // Speak the outcome, not the statistic: on fake labels the effect
61092
61253
  // should VANISH — say whether it did.
61093
- value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect)})` : `didn’t vanish (${signed(placeboEffect)})` : passed ? "vanished" : "didn’t vanish",
61254
+ value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect, words)})` : `didn’t vanish (${signed(placeboEffect, words)})` : passed ? "vanished" : "didn’t vanish",
61094
61255
  passed,
61095
61256
  tip: east.some("Randomly re-label which rows were treated. A genuine effect should vanish."),
61096
61257
  help: "check_shuffle"
@@ -61104,7 +61265,7 @@ function deriveRefute(r2, adj, meta3) {
61104
61265
  name: "Drop-some test",
61105
61266
  short: "drop-some",
61106
61267
  desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
61107
- value: passed ? `stayed at ${signed(ds)} ± ${fmt$1(dstd)}` : `moved to ${signed(ds)} ± ${fmt$1(dstd)}`,
61268
+ value: passed ? `stayed at ${signed(ds, words)} ± ${fmt$1(dstd, words)}` : `moved to ${signed(ds, words)} ± ${fmt$1(dstd, words)}`,
61108
61269
  passed,
61109
61270
  tip: east.none,
61110
61271
  help: "check_dropsome"
@@ -61124,14 +61285,14 @@ function deriveRefute(r2, adj, meta3) {
61124
61285
  }
61125
61286
  const sens = eastUiComponents.getSomeorUndefined(r2.sensitivity);
61126
61287
  const evalue = eastUiComponents.getSomeorUndefined(r2.robustness_value);
61127
- const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => b2.strength - a2.strength) : [];
61288
+ const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => compareFloats(b2.strength, a2.strength)) : [];
61128
61289
  if (sens !== void 0 || evalue !== void 0) {
61129
61290
  const strengths = sens ? arr(sens.strengths) : [];
61130
61291
  const effects = sens ? arr(sens.effects) : [];
61131
61292
  const tip = sens ? zeroCrossing(strengths, effects) : null;
61132
61293
  const strongest = benchmarks[0];
61133
- const value = tip !== null ? strongest !== void 0 ? tip > strongest.strength ? `flips only beyond ${labelOf(meta3, strongest.column)}` : `a cause weaker than ${labelOf(meta3, strongest.column)} could flip it` : `would flip at ${fmt$1(tip)}` : evalue !== void 0 ? `holds (E-value ${fmt$1(evalue)})` : "holds throughout";
61134
- const scaleNote = strongest !== void 0 ? ` For scale, your strongest known factor (${labelOf(meta3, strongest.column)}) sits at ${fmt$1(strongest.strength)} on this axis.` : "";
61294
+ const value = tip !== null ? strongest !== void 0 ? tip > strongest.strength ? `flips only beyond ${labelOf(meta3, strongest.column)}` : `a cause weaker than ${labelOf(meta3, strongest.column)} could flip it` : `would flip at ${fmt$1(tip, words)}` : evalue !== void 0 ? `holds (E-value ${fmt$1(evalue, words)})` : "holds throughout";
61295
+ const scaleNote = strongest !== void 0 ? ` For scale, your strongest known factor (${labelOf(meta3, strongest.column)}) sits at ${fmt$1(strongest.strength, words)} on this axis.` : "";
61135
61296
  checks.push({
61136
61297
  name: "Hidden cause",
61137
61298
  short: "hidden cause",
@@ -61167,12 +61328,12 @@ function deriveRefute(r2, adj, meta3) {
61167
61328
  const at = atOf(b2.strength);
61168
61329
  return at === null ? [] : [{ at, label: `as strong as ${labelOf(meta3, b2.column)}`, tone: "muted", help: "sensitivity" }];
61169
61330
  });
61170
- sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)], marks };
61331
+ sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo, words), fmt$1((yLo + yHi) / 2, words), fmt$1(yHi, words)], marks };
61171
61332
  }
61172
61333
  }
61173
61334
  return { checks, sens: sensVM };
61174
61335
  }
61175
- function deriveDose(dose, config2, meta3) {
61336
+ function deriveDose(dose, config2, meta3, words) {
61176
61337
  const grid = arr(dose.grid);
61177
61338
  const mid = arr(dose.effect);
61178
61339
  const lo2 = eastUiComponents.getSomeorUndefined(dose.lower) ? arr(eastUiComponents.getSomeorUndefined(dose.lower)) : mid.slice();
@@ -61190,17 +61351,17 @@ function deriveDose(dose, config2, meta3) {
61190
61351
  }
61191
61352
  }
61192
61353
  const xi = grid.length ? [0, Math.floor(grid.length / 2), grid.length - 1] : [];
61193
- const xTicks = xi.map((i) => fmt$1(grid[i]));
61354
+ const xTicks = xi.map((i) => fmt$1(grid[i], words));
61194
61355
  const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
61195
- const yTicks = [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)];
61356
+ const yTicks = [fmt$1(yLo, words), fmt$1((yLo + yHi) / 2, words), fmt$1(yHi, words)];
61196
61357
  const margSteps = marg.slice(1);
61197
61358
  let lastStep = margSteps.length - 1;
61198
61359
  while (lastStep > 0 && Math.abs(margSteps[lastStep]) < 0.12 * maxMarg) lastStep--;
61199
- const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: `${fmt$1(grid[i + 1])}`, value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
61360
+ const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: fmt$1(grid[i + 1], words), value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
61200
61361
  const stepFrom = here < sweet ? here : Math.max(0, sweet - 1);
61201
61362
  const gainToSweet = (mid[sweet] ?? 0) - (mid[stepFrom] ?? 0);
61202
61363
  const nextGain = sweet + 1 < mid.length ? mid[sweet + 1] - mid[sweet] : 0;
61203
- const tradeoff = `Going from ${fmt$1(grid[stepFrom] ?? 0)} to ${fmt$1(grid[sweet] ?? 0)} adds ${signed(gainToSweet)}; the next step adds only ${signed(nextGain)}.`;
61364
+ const tradeoff = `Going from ${fmt$1(grid[stepFrom] ?? 0, words)} to ${fmt$1(grid[sweet] ?? 0, words)} adds ${signed(gainToSweet, words)}; the next step adds only ${signed(nextGain, words)}.`;
61204
61365
  const marks = [];
61205
61366
  if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
61206
61367
  marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
@@ -61217,7 +61378,7 @@ function deriveDose(dose, config2, meta3) {
61217
61378
  marks,
61218
61379
  // Name the feature in the recommendation — a bare "≈ 5.5" doesn't say
61219
61380
  // 5.5 of WHAT.
61220
- recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
61381
+ recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0, words)}${featureUnit ? " " + featureUnit : ""}`,
61221
61382
  recoEffect: mid[sweet] ?? 0,
61222
61383
  recoLo: lo2[sweet] ?? 0,
61223
61384
  recoHi: hi[sweet] ?? 0,
@@ -61226,11 +61387,11 @@ function deriveDose(dose, config2, meta3) {
61226
61387
  framing: `A different question from the headline: how ${outcomeLabel} responds as ${featureLabel} itself moves.`
61227
61388
  };
61228
61389
  }
61229
- function relTime(d2, now2) {
61390
+ function relTime(d2, now2, words) {
61230
61391
  const days = Math.floor((now2.getTime() - d2.getTime()) / 864e5);
61231
61392
  if (days <= 0) return "today";
61232
61393
  if (days === 1) return "yesterday";
61233
- if (days < 7) return d2.toLocaleDateString(void 0, { weekday: "short" });
61394
+ if (days < 7) return words.weekday(d2);
61234
61395
  return "last wk";
61235
61396
  }
61236
61397
  const VERDICT_WORD = {
@@ -61240,43 +61401,43 @@ const VERDICT_WORD = {
61240
61401
  non_identifiable_positivity: "no overlap",
61241
61402
  not_estimable: "n/a"
61242
61403
  };
61243
- function deriveJournalRow(row, now2, meta3) {
61404
+ function deriveJournalRow(row, now2, meta3, words) {
61244
61405
  const adj = eastUiComponents.getSomeorUndefined(row.adjusted);
61245
61406
  return {
61246
61407
  treatment: labelOf(meta3, row.config.treatment),
61247
61408
  outcome: labelOf(meta3, row.config.outcome),
61248
61409
  confounders: row.config.common_causes.map((c2) => labelOf(meta3, c2)).join(", "),
61249
- effect: adj !== void 0 ? signed(adj) : signed(row.naive),
61410
+ effect: adj !== void 0 ? signed(adj, words) : signed(row.naive, words),
61250
61411
  verdict: VERDICT_WORD[row.verdict.type],
61251
61412
  verdictTone: VERDICT_TONE[row.verdict.type],
61252
61413
  who: row.committed_by,
61253
- when: relTime(row.committed_at, now2),
61414
+ when: relTime(row.committed_at, now2, words),
61254
61415
  // The originating preset id (resolved to a label by the surface); `none` for a
61255
61416
  // free-form / pre-presets row.
61256
61417
  preset: row.preset
61257
61418
  };
61258
61419
  }
61259
- function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }) {
61420
+ function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }, words = eastUiComponents.formatters()) {
61260
61421
  var _a2;
61261
61422
  const adj = result ? eastUiComponents.getSomeorUndefined(result.adjusted) : void 0;
61262
61423
  const refutation = result ? eastUiComponents.getSomeorUndefined(result.refutation) : void 0;
61263
61424
  const doseR = result ? eastUiComponents.getSomeorUndefined(result.dose_response) : void 0;
61264
- const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null;
61265
- const refute = refutation ? deriveRefute(refutation, adj, meta3) : null;
61425
+ const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3, words) : null;
61426
+ const refute = refutation ? deriveRefute(refutation, adj, meta3, words) : null;
61266
61427
  const higherBetter = eastUiComponents.getSomeorUndefined((_a2 = colMeta(meta3, ranConfig.outcome)) == null ? void 0 : _a2.higherIsBetter);
61267
61428
  return {
61268
61429
  // The set-up rail reflects the LIVE config (the editor); the result deck
61269
61430
  // reflects RANCONFIG — the config that produced `result` — so the result
61270
61431
  // strings never drift ahead of the numbers on a live picker edit.
61271
- spec: deriveSpec(config2, cols, result, meta3, dataLen, subject),
61432
+ spec: deriveSpec(config2, cols, result, meta3, dataLen, subject, words),
61272
61433
  verdict: result ? deriveVerdict(result.verdict) : null,
61273
- narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject) : null,
61434
+ narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject, words) : null,
61274
61435
  answer,
61275
- refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject) : null,
61276
- overlap: result ? deriveOverlap(result.overlap) : null,
61436
+ refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject, words) : null,
61437
+ overlap: result ? deriveOverlap(result.overlap, words) : null,
61277
61438
  refute,
61278
- dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
61279
- journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3)) : null
61439
+ dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3, words) : null,
61440
+ journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3, words)) : null
61280
61441
  };
61281
61442
  }
61282
61443
  const DESIGN_HEADLINE = {
@@ -61295,7 +61456,7 @@ function deriveDesignOption(o2) {
61295
61456
  treatedShare: o2.treated_share
61296
61457
  };
61297
61458
  }
61298
- function deriveDesign(d2, result, ranConfig, meta3) {
61459
+ function deriveDesign(d2, result, ranConfig, meta3, words = eastUiComponents.formatters()) {
61299
61460
  const basis = d2.basis.type;
61300
61461
  const outcome = ranConfig.outcome;
61301
61462
  const unit2 = unitOf(meta3, outcome);
@@ -61327,9 +61488,9 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61327
61488
  }
61328
61489
  return bi;
61329
61490
  };
61330
- const xTicks = len ? [String(ns[0]), String(ns[Math.floor((len - 1) / 2)]), String(ns[len - 1])] : [];
61491
+ const xTicks = len ? [words.number(ns[0]), words.number(ns[Math.floor((len - 1) / 2)]), words.number(ns[len - 1])] : [];
61331
61492
  const marks = [];
61332
- if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${(d2.target_power * 100).toFixed(0)}%`, tone: "pos", help: "validate_power" });
61493
+ if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${words.percent(d2.target_power)}`, tone: "pos", help: "validate_power" });
61333
61494
  const cp = eastUiComponents.getSomeorUndefined(d2.current_power);
61334
61495
  if (cp !== void 0 && len) {
61335
61496
  const curN = result ? Number(result.n_total) : 0;
@@ -61337,7 +61498,7 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61337
61498
  const offScale = curN >= (ns[len - 1] ?? 0);
61338
61499
  marks.push({
61339
61500
  at: offScale ? len - 1 : nearest(curN),
61340
- label: offScale ? `you’re here · ${(cp * 100).toFixed(0)}% (off-scale)` : `you’re here ${(cp * 100).toFixed(0)}%`,
61501
+ label: offScale ? `you’re here · ${words.percent(cp)} (off-scale)` : `you’re here ${words.percent(cp)}`,
61341
61502
  tone: "muted",
61342
61503
  help: "validate_power"
61343
61504
  });
@@ -61351,13 +61512,204 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61351
61512
  alternates: options2.slice(1),
61352
61513
  matchOn,
61353
61514
  curve: { mid: powers.slice(0, len).map((p2) => p2 * 100), xTicks, marks },
61354
- targetLabel: `${signed(d2.target_effect)}${unit2 ? ` ${unit2}` : ""}`,
61355
- targetPctLabel: `${(d2.target_power * 100).toFixed(0)}%`,
61515
+ targetLabel: `${signed(d2.target_effect, words)}${unit2 ? ` ${unit2}` : ""}`,
61516
+ targetPctLabel: words.percent(d2.target_power),
61356
61517
  holdback: basis === "create_control",
61357
61518
  showOverlap: basis === "restrict_to_overlap",
61358
61519
  faint
61359
61520
  };
61360
61521
  }
61522
+ function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
61523
+ const span = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
61524
+ return /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: span }) : span }) });
61525
+ }
61526
+ function svgText(size3, fill, family, weight8) {
61527
+ return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
61528
+ }
61529
+ function useMeasuredWidth() {
61530
+ const ref = React.useRef(null);
61531
+ const [w2, setW] = React.useState(0);
61532
+ React.useLayoutEffect(() => {
61533
+ const el = ref.current;
61534
+ if (!el) return;
61535
+ const update2 = () => setW(el.clientWidth);
61536
+ update2();
61537
+ const ro = new ResizeObserver(update2);
61538
+ ro.observe(el);
61539
+ return () => ro.disconnect();
61540
+ }, []);
61541
+ return [ref, w2];
61542
+ }
61543
+ function useChartTheme() {
61544
+ const system = react.useChakraContext();
61545
+ const tok = (k2, fallback) => system.token(k2, fallback);
61546
+ const tones = {
61547
+ neg: tok("colors.fg.danger", "#b85a4a"),
61548
+ pos: tok("colors.fg.success", "#2f7a5b"),
61549
+ warn: tok("colors.fg.warning", "#b8862d"),
61550
+ muted: tok("colors.fg.muted", "#6b8080"),
61551
+ brand: tok("colors.brand.solid", "#3a7780")
61552
+ };
61553
+ return {
61554
+ mono: tok("fonts.mono", '"JetBrains Mono", ui-monospace, monospace'),
61555
+ body: tok("fonts.body", "system-ui, -apple-system, sans-serif"),
61556
+ ink: tok("colors.fg", "#111b22"),
61557
+ muted: tones.muted,
61558
+ faint: tok("colors.fg.subtle", "#9bb0b0"),
61559
+ rule: tok("colors.border.subtle", "#e2e8e8"),
61560
+ ruleStrong: tok("colors.border.strong", "#cbd5d5"),
61561
+ surface: tok("colors.bg.surface", "#ffffff"),
61562
+ tone: (t4) => tones[t4] ?? tones.brand,
61563
+ labelSize: 10,
61564
+ titleSize: 11,
61565
+ lineWidth: 1.8,
61566
+ barWidth: 2.5,
61567
+ dotRadius: 2.6,
61568
+ scatterRadius: 3.4,
61569
+ areaOpacity: 0.16
61570
+ };
61571
+ }
61572
+ const niceCeil = (v3) => {
61573
+ if (v3 <= 1e-9) return 0;
61574
+ const mag = Math.pow(10, Math.floor(Math.log10(v3)));
61575
+ const n2 = v3 / mag;
61576
+ const step3 = n2 <= 1 ? 1 : n2 <= 1.5 ? 1.5 : n2 <= 2 ? 2 : n2 <= 3 ? 3 : n2 <= 4 ? 4 : n2 <= 5 ? 5 : n2 <= 6 ? 6 : n2 <= 8 ? 8 : 10;
61577
+ return step3 * mag;
61578
+ };
61579
+ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
61580
+ const t4 = useChartTheme();
61581
+ const words = eastUiComponents.useFormatters();
61582
+ const [ref, w2] = useMeasuredWidth();
61583
+ const n2 = rows.length;
61584
+ const hPx = height2 ?? 26 + n2 * 38;
61585
+ const padL = 124, padR = 46, padT = 20, padB = 30;
61586
+ const innerW = Math.max(0, w2 - padL - padR);
61587
+ const innerH = hPx - padT - padB;
61588
+ const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
61589
+ const rowH = innerH / Math.max(1, n2);
61590
+ const zeroX = Math.max(0, Math.min(innerW, x2(0)));
61591
+ const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
61592
+ const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
61593
+ const zf = frac(0);
61594
+ const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
61595
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
61596
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
61597
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
61598
+ rows.map((r2, i) => {
61599
+ const cy = rowH * i + rowH / 2;
61600
+ const col = t4.tone(r2.tone);
61601
+ return /* @__PURE__ */ jsxRuntime.jsxs(Group, { top: cy, children: [
61602
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: 0 }, to: { x: x2(r2.hi), y: 0 }, stroke: col, strokeWidth: t4.barWidth, strokeOpacity: 0.9, strokeLinecap: "round" }),
61603
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
61604
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
61605
+ /* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
61606
+ /* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: -padL + 2, y: -13, w: padL - 6, align: "left", size: t4.labelSize, weight: 600, color: t4.ink, family: t4.body, ...(rowHelp == null ? void 0 : rowHelp[i]) ? { help: rowHelp[i] } : {}, children: r2.label }),
61607
+ r2.note && /* @__PURE__ */ jsxRuntime.jsx("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
61608
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed(r2.est, words) })
61609
+ ] }, i);
61610
+ }),
61611
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61612
+ ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
61613
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
61614
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed(tk, words) })
61615
+ ] }, `t${i}`)),
61616
+ unit2 && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
61617
+ ] }) }) });
61618
+ }
61619
+ function AreaRange({ lo: lo2, mid, hi, xTicks = [], xTickWords = false, yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
61620
+ const t4 = useChartTheme();
61621
+ const words = eastUiComponents.useFormatters();
61622
+ const [ref, w2] = useMeasuredWidth();
61623
+ const hPx = height2 ?? 100;
61624
+ const col = t4.tone(toneName ?? "brand");
61625
+ const padL = yTicks.length ? 52 : 6;
61626
+ const padR = 12;
61627
+ const padT = marks.length ? 22 : 8;
61628
+ const padB = xTicks.length ? 22 : 12;
61629
+ const innerW = Math.max(0, w2 - padL - padR);
61630
+ const innerH = hPx - padT - padB;
61631
+ const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
61632
+ const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
61633
+ const data4 = mid.map((m2, i) => {
61634
+ const mm = fin(m2, 0);
61635
+ return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
61636
+ });
61637
+ const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
61638
+ const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
61639
+ const yHiN = niceCeil(Math.max(dataMax, 0));
61640
+ const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
61641
+ const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
61642
+ const yfmt = (v3) => yFormat === "percent" ? words.percent(v3 / 100) : signed(v3, words);
61643
+ const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
61644
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
61645
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61646
+ yTicks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61647
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: 10, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[0] }),
61648
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: innerH / 2 + 3, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[1] }),
61649
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: -8, y: innerH - 1, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[2] })
61650
+ ] }),
61651
+ zero2 != null && /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: yScale(zero2) }, to: { x: innerW, y: yScale(zero2) }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
61652
+ /* @__PURE__ */ jsxRuntime.jsx(Area, { data: data4, x: (d2) => xScale(d2.i), y0: (d2) => yScale(d2.lo), y1: (d2) => yScale(d2.hi), curve: monotoneX, fill: col, fillOpacity: t4.areaOpacity, stroke: "transparent" }),
61653
+ /* @__PURE__ */ jsxRuntime.jsx(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
61654
+ marks.map((m2, i) => {
61655
+ const mc = t4.tone(m2.tone);
61656
+ const dotY = yScale(fin(mid[m2.at], 0));
61657
+ const labY = Math.max(9, dotY - 9);
61658
+ const atEnd = m2.at >= mid.length - 2;
61659
+ const atStart = m2.at <= 1;
61660
+ const align = atEnd ? "right" : atStart ? "left" : "center";
61661
+ const labW = m2.label.length * 6.5 + 8;
61662
+ const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
61663
+ return /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: xScale(m2.at), children: [
61664
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
61665
+ /* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
61666
+ /* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: labX, y: labY - 11, w: labW, align, size: t4.labelSize, weight: 500, color: mc, family: t4.body, ...m2.help ? { help: m2.help } : {}, children: m2.label })
61667
+ ] }, `mk${i}`);
61668
+ }),
61669
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61670
+ xTicks.map((tk, i) => {
61671
+ const word = xTickWords;
61672
+ return /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW * i / Math.max(1, xTicks.length - 1), y: innerH + 14, textAnchor: i === 0 ? "start" : i === xTicks.length - 1 ? "end" : "middle", style: svgText(word ? 9 : t4.labelSize, word ? t4.faint : t4.muted, word ? t4.body : t4.mono), children: tk }, `x${i}`);
61673
+ })
61674
+ ] }) }) });
61675
+ }
61676
+ function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
61677
+ const t4 = useChartTheme();
61678
+ const words = eastUiComponents.useFormatters();
61679
+ const [ref, w2] = useMeasuredWidth();
61680
+ const hPx = height2 ?? 150;
61681
+ const padL = 8, padR = 8, padT = 16, padB = 26;
61682
+ const innerW = Math.max(0, w2 - padL - padR);
61683
+ const innerH = hPx - padT - padB;
61684
+ const centerY = innerH / 2;
61685
+ const halfH = centerY - 4;
61686
+ const bins = Math.max(treated.length, control.length);
61687
+ const x2 = createLinearScale({ domain, range: [0, innerW] });
61688
+ const span = domain[1] - domain[0];
61689
+ const binW = bins > 0 ? innerW / bins : innerW;
61690
+ const barW = Math.max(1, binW * 0.78);
61691
+ const maxVal = Math.max(1e-9, ...treated, ...control);
61692
+ const tCol = t4.tone("pos");
61693
+ const cCol = t4.tone("muted");
61694
+ const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
61695
+ const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
61696
+ return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
61697
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
61698
+ supportLabel && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
61699
+ Array.from({ length: bins }, (_2, i) => {
61700
+ const ht = Number(treated[i] ?? 0) / maxVal * halfH;
61701
+ const hc = Number(control[i] ?? 0) / maxVal * halfH;
61702
+ const left = cx2(i) - barW / 2;
61703
+ return /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
61704
+ ht > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
61705
+ hc > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
61706
+ ] }, i);
61707
+ }),
61708
+ /* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
61709
+ /* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
61710
+ ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsx("text", { x: x2(tk), y: innerH + 16, textAnchor: i === 0 ? "start" : i === ticks2.length - 1 ? "end" : "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: fmt$1(tk, words) }, `x${i}`))
61711
+ ] }) }) });
61712
+ }
61361
61713
  const SUBJECT_DEFAULT = { one: "record", many: "records" };
61362
61714
  const AUTORUN_MAX_ROWS = 5e4;
61363
61715
  const TONE_TOKEN = {
@@ -61385,7 +61737,7 @@ function kindOfTypeValue(t4) {
61385
61737
  }
61386
61738
  }
61387
61739
  function useColumns(workspace, source) {
61388
- const types = source ? getBindingTypes(workspace, source) : void 0;
61740
+ const types = getBindingTypes(workspace, source);
61389
61741
  return React.useMemo(() => {
61390
61742
  const st = types == null ? void 0 : types.sourceType;
61391
61743
  if (!st || st.type !== "Array") return { columns: [], rowArrayType: null };
@@ -61397,35 +61749,9 @@ function useColumns(workspace, source) {
61397
61749
  };
61398
61750
  }, [types == null ? void 0 : types.sourceType]);
61399
61751
  }
61400
- function rowMatchesAll(row, preds) {
61401
- return preds.every((p2) => {
61402
- const { fieldId, op } = p2.value;
61403
- const v3 = row[fieldId];
61404
- const ov = op.value;
61405
- switch (op.type) {
61406
- case "eq":
61407
- case "is":
61408
- return v3 === ov;
61409
- case "neq":
61410
- return v3 !== ov;
61411
- case "lt":
61412
- return num(v3) != null && num(ov) != null ? num(v3) < num(ov) : true;
61413
- case "lte":
61414
- return num(v3) != null && num(ov) != null ? num(v3) <= num(ov) : true;
61415
- case "gt":
61416
- return num(v3) != null && num(ov) != null ? num(v3) > num(ov) : true;
61417
- case "gte":
61418
- return num(v3) != null && num(ov) != null ? num(v3) >= num(ov) : true;
61419
- case "in":
61420
- return east.isEastSet(ov) ? ov.has(v3) : true;
61421
- case "notIn":
61422
- return east.isEastSet(ov) ? !ov.has(v3) : true;
61423
- default:
61424
- return true;
61425
- }
61426
- });
61752
+ function inPopulation(row, population) {
61753
+ return population.every((p2) => eastUi.slicePredicateMatches(p2, row));
61427
61754
  }
61428
- const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
61429
61755
  function Cap({ children: children2, help }) {
61430
61756
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
61431
61757
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: children2 }) : children2 }),
@@ -61538,7 +61864,7 @@ function LoadingSkeleton() {
61538
61864
  ] })
61539
61865
  ] });
61540
61866
  }
61541
- const experimentValueEqual = east.equalFor(internal$1.Experiment.Component.schema);
61867
+ const experimentValueEqual = east.equivalentFor(internal$1.Experiment.Component.schema);
61542
61868
  const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }) {
61543
61869
  var _a2, _b2, _c2;
61544
61870
  const v3 = value;
@@ -61548,6 +61874,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61548
61874
  const bs = react.useSlotRecipe({ key: "barStrip" })({});
61549
61875
  const statusR = react.useSlotRecipe({ key: "status" });
61550
61876
  const es = react.useSlotRecipe({ key: "eyebrowRow" })({});
61877
+ const words = eastUiComponents.useFormatters();
61551
61878
  const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
61552
61879
  const data4 = useBindingValue(v3.data);
61553
61880
  const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
@@ -61595,7 +61922,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61595
61922
  const population = localPop;
61596
61923
  const filteredRows = React.useMemo(() => {
61597
61924
  if (!data4.value) return null;
61598
- return population.length ? data4.value.filter((r2) => rowMatchesAll(r2, population)) : data4.value;
61925
+ return population.length ? data4.value.filter((r2) => inPopulation(r2, population)) : data4.value;
61599
61926
  }, [data4.value, population]);
61600
61927
  const fields = React.useMemo(
61601
61928
  () => columns.filter((c2) => c2.kind !== "other").map((c2) => {
@@ -61697,8 +62024,8 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61697
62024
  }, [ranConfig, config2, meta3, subject]);
61698
62025
  const view = React.useMemo(() => {
61699
62026
  if (!config2) return null;
61700
- return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject);
61701
- }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject]);
62027
+ return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject, words);
62028
+ }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject, words]);
61702
62029
  const designConfigValue = React.useMemo(() => ({
61703
62030
  alpha: east.none,
61704
62031
  target_power: east.none,
@@ -61711,15 +62038,15 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61711
62038
  var _a3;
61712
62039
  if (tab !== "validate" || !hasDesign || design.pending || precomputedDesign !== null) return;
61713
62040
  if (!filteredRows || !config2 || !shownResult) return;
61714
- if (((_a3 = designSnapRef.current) == null ? void 0 : _a3.result) === shownResult) return;
62041
+ if (Object.is((_a3 = designSnapRef.current) == null ? void 0 : _a3.result, shownResult)) return;
61715
62042
  designSnapRef.current = { result: shownResult, config: ranConfig ?? config2 };
61716
62043
  design.call(filteredRows, ranConfig ?? config2, shownResult, designConfigValue);
61717
62044
  }, [tab, hasDesign, precomputedDesign, filteredRows, config2, ranConfig, shownResult, design, designConfigValue]);
61718
62045
  const vmDesign = React.useMemo(() => {
61719
- if (precomputedDesign !== null && shownResult !== null && config2) return deriveDesign(precomputedDesign, shownResult, ranConfig ?? config2, meta3);
61720
- return design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3) : null;
61721
- }, [precomputedDesign, shownResult, config2, ranConfig, design.result, meta3]);
61722
- const designFresh = precomputedDesign !== null && shownResult !== null || design.result !== null && ((_b2 = designSnapRef.current) == null ? void 0 : _b2.result) === shownResult && !design.pending;
62046
+ if (precomputedDesign !== null && shownResult !== null && config2) return deriveDesign(precomputedDesign, shownResult, ranConfig ?? config2, meta3, words);
62047
+ return design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3, words) : null;
62048
+ }, [precomputedDesign, shownResult, config2, ranConfig, design.result, meta3, words]);
62049
+ const designFresh = precomputedDesign !== null && shownResult !== null || design.result !== null && Object.is((_b2 = designSnapRef.current) == null ? void 0 : _b2.result, shownResult) && !design.pending;
61723
62050
  if (!config2 || !view) {
61724
62051
  const failed2 = experiment.status === "failed";
61725
62052
  const noConfigs = !configsBind.error && configs.length === 0;
@@ -61787,7 +62114,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61787
62114
  readonly && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: badge({ variant: "plain", size: "sm" }), textTransform: "none", letterSpacing: "normal", children: "View only" }),
61788
62115
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: dataStatus.root, children: [
61789
62116
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.indicator }),
61790
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: stale ? `${nRows} rows` : vs.dataLabel })
62117
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: stale ? `${words.number(nRows)} rows` : vs.dataLabel })
61791
62118
  ] }),
61792
62119
  /* @__PURE__ */ jsxRuntime.jsx(GuidanceToggle, { on: guidance, onToggle: () => setGuidance((g2) => !g2) }),
61793
62120
  canRun && /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { button, variant: "solid", label: "Run", onClick: onRun, disabled: runDisabled, pulse: stale }),
@@ -61820,7 +62147,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61820
62147
  "add another"
61821
62148
  ] }) })
61822
62149
  ] }) }),
61823
- /* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many }) }),
62150
+ /* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many, words }) }),
61824
62151
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61825
62152
  /* @__PURE__ */ jsxRuntime.jsxs(
61826
62153
  react.Box,
@@ -61938,10 +62265,11 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61938
62265
  subjectMany: subject.many,
61939
62266
  higherBetter,
61940
62267
  badge,
61941
- barList
62268
+ barList,
62269
+ words
61942
62270
  }
61943
62271
  ),
61944
- activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel }),
62272
+ activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel, words }),
61945
62273
  activeTab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
61946
62274
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
61947
62275
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
@@ -61969,6 +62297,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
61969
62297
  zero: 0,
61970
62298
  tone: "brand",
61971
62299
  xTicks: vr.sens.xTicks,
62300
+ xTickWords: true,
61972
62301
  yTicks: vr.sens.yTicks,
61973
62302
  marks: vr.sens.marks,
61974
62303
  height: vr.sens.marks.length ? 148 : 132
@@ -62014,13 +62343,13 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
62014
62343
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
62015
62344
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: vd.recoLabel }),
62016
62345
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2", children: [
62017
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
62346
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect, words) }),
62018
62347
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
62019
62348
  vd.outcome,
62020
62349
  " · ",
62021
- signed(vd.recoLo),
62350
+ signed(vd.recoLo, words),
62022
62351
  " … ",
62023
- signed(vd.recoHi)
62352
+ signed(vd.recoHi, words)
62024
62353
  ] })
62025
62354
  ] })
62026
62355
  ] }),
@@ -62032,7 +62361,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
62032
62361
  vd.outcome,
62033
62362
  " per step"
62034
62363
  ] }),
62035
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", maxH: "200px", overflowY: "auto", children: barList(vd.marginal.map((m2, i) => ({ label: m2.label, frac: m2.frac, tone: i < 2 ? "brand" : "muted", value: signed(m2.value) }))) })
62364
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", maxH: "200px", overflowY: "auto", children: barList(vd.marginal.map((m2, i) => ({ label: m2.label, frac: m2.frac, tone: i < 2 ? "brand" : "muted", value: signed(m2.value, words) }))) })
62036
62365
  ] })
62037
62366
  ] })
62038
62367
  ] }),
@@ -62050,7 +62379,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
62050
62379
  " tab for why)."
62051
62380
  ] })
62052
62381
  ] }),
62053
- activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
62382
+ activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList, words }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
62054
62383
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
62055
62384
  /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
62056
62385
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No trial recipe for this question" })
@@ -62067,7 +62396,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
62067
62396
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
62068
62397
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "journal", children: "Committed experiments" }) }),
62069
62398
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.meta, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", children: [
62070
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
62399
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: words.number(journal.length) }),
62071
62400
  " on record",
62072
62401
  journal.length > 50 ? " · showing newest 50" : ""
62073
62402
  ] }) })
@@ -62109,7 +62438,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
62109
62438
  ] })
62110
62439
  ] }) });
62111
62440
  }, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
62112
- function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList }) {
62441
+ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList, words }) {
62113
62442
  const dirUp = a2.effect > 0;
62114
62443
  const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
62115
62444
  const lowerWord = a2.naive < 0 ? "lower" : "higher";
@@ -62124,7 +62453,7 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62124
62453
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
62125
62454
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
62126
62455
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_effect", children: [
62127
- signed(a2.effect),
62456
+ signed(a2.effect, words),
62128
62457
  a2.unit ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontSize: "16px", color: "fg.muted", children: [
62129
62458
  " ",
62130
62459
  a2.unit
@@ -62132,9 +62461,9 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62132
62461
  ] }) }),
62133
62462
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_ci", children: [
62134
62463
  "likely between ",
62135
- signed(a2.lo),
62464
+ signed(a2.lo, words),
62136
62465
  " and ",
62137
- signed(a2.hi)
62466
+ signed(a2.hi, words)
62138
62467
  ] }) })
62139
62468
  ] })
62140
62469
  ] }),
@@ -62198,7 +62527,7 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62198
62527
  " on ",
62199
62528
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: a2.outcome }),
62200
62529
  " (",
62201
- signed(a2.naive),
62530
+ signed(a2.naive, words),
62202
62531
  ") — but they also differ most on ",
62203
62532
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: top.label }),
62204
62533
  " (",
@@ -62257,13 +62586,13 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62257
62586
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "3.5", pb: "3.5", py: "0.5", maxH: "208px", overflowY: "auto", children: barList(a2.balance.map((b2) => ({ label: b2.label, frac: b2.frac, tone: b2.tone, value: b2.display }))) })
62258
62587
  ] }),
62259
62588
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { mt: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "counts", display: "inline-flex", gap: "4", children: [[a2.nTotal, subjectMany], [a2.nCompared, "compared like-for-like"], [a2.nDropped, "had no fair match"]].map(([n2, label], i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
62260
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
62589
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: words.number(n2) }),
62261
62590
  " ",
62262
62591
  label
62263
62592
  ] }, i)) }) })
62264
62593
  ] });
62265
62594
  }
62266
- function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
62595
+ function RefusalZone({ refusal, overlap, naiveValue, outcome, words }) {
62267
62596
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
62268
62597
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.warning", children: [
62269
62598
  /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "14px" } }),
@@ -62286,11 +62615,11 @@ function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
62286
62615
  "Raw average difference in ",
62287
62616
  outcome,
62288
62617
  " (context only, not an answer): ",
62289
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue) })
62618
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue, words) })
62290
62619
  ] })
62291
62620
  ] });
62292
62621
  }
62293
- function ValidatePanel({ vm, barList }) {
62622
+ function ValidatePanel({ vm, barList, words }) {
62294
62623
  const stat = react.useSlotRecipe({ key: "stat" })({ size: "lg" });
62295
62624
  const meter = react.useSlotRecipe({ key: "segmentedMeter" })({});
62296
62625
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
@@ -62298,7 +62627,7 @@ function ValidatePanel({ vm, barList }) {
62298
62627
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
62299
62628
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: stat.root, children: [
62300
62629
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: stat.label, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
62301
- vm.faint ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.warning", maxW: "220px", children: "Effect too faint to size — set a materiality threshold to size a trial." }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: stat.valueText, color: "brand.solid", children: vm.primary.nTotal.toLocaleString() })
62630
+ vm.faint ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.warning", maxW: "220px", children: "Effect too faint to size — set a materiality threshold to size a trial." }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: stat.valueText, color: "brand.solid", children: words.number(vm.primary.nTotal) })
62302
62631
  ] }),
62303
62632
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.root, flex: "1", minW: "220px", children: [
62304
62633
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: meter.label, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }) }),
@@ -62310,7 +62639,7 @@ function ValidatePanel({ vm, barList }) {
62310
62639
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.keyItem, children: [
62311
62640
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.keyDot, bg: "brand.solid" }),
62312
62641
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
62313
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: vm.primary.nTreated.toLocaleString() }),
62642
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: words.number(vm.primary.nTreated) }),
62314
62643
  " ",
62315
62644
  vm.holdback ? "treated" : "get it"
62316
62645
  ] })
@@ -62318,7 +62647,7 @@ function ValidatePanel({ vm, barList }) {
62318
62647
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.keyItem, children: [
62319
62648
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.keyDot, bg: "bg.emphasized" }),
62320
62649
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
62321
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: vm.primary.nControl.toLocaleString() }),
62650
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: words.number(vm.primary.nControl) }),
62322
62651
  " ",
62323
62652
  vm.holdback ? "held back" : "left alone"
62324
62653
  ] })
@@ -62351,16 +62680,16 @@ function ValidatePanel({ vm, barList }) {
62351
62680
  vm.alternates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", flexDirection: "column", gap: "1.5", mt: "3", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: vm.alternates.map((o2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.muted", children: [
62352
62681
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: o2.label }),
62353
62682
  " · ",
62354
- o2.nTotal.toLocaleString(),
62683
+ words.number(o2.nTotal),
62355
62684
  " total (",
62356
- o2.nTreated.toLocaleString(),
62685
+ words.number(o2.nTreated),
62357
62686
  " / ",
62358
- o2.nControl.toLocaleString(),
62687
+ words.number(o2.nControl),
62359
62688
  ")"
62360
62689
  ] }, i)) })
62361
62690
  ] });
62362
62691
  }
62363
- function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany }) {
62692
+ function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany, words }) {
62364
62693
  const [open, setOpen] = React.useState(null);
62365
62694
  const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
62366
62695
  const removeAt = (i) => onChange2(population.filter((_2, j2) => j2 !== i));
@@ -62369,7 +62698,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
62369
62698
  return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: population.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: chip({ tone: "plain", numeric: true, shape: "rounded" }), children: [
62370
62699
  "all ",
62371
62700
  subjectMany
62372
- ] }) : population.map((pred, i) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", whiteSpace: "nowrap", children: eastUiComponents.formatPredicate(pred) }) }, i)) });
62701
+ ] }) : population.map((pred, i) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", whiteSpace: "nowrap", children: eastUiComponents.formatPredicate(pred, words) }) }, i)) });
62373
62702
  }
62374
62703
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: [
62375
62704
  population.map((pred, i) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -62383,7 +62712,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
62383
62712
  ] }),
62384
62713
  footActions: done,
62385
62714
  trigger: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), cursor: "pointer", flexShrink: 0, children: [
62386
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", whiteSpace: "nowrap", children: eastUiComponents.formatPredicate(pred) }),
62715
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", whiteSpace: "nowrap", children: eastUiComponents.formatPredicate(pred, words) }),
62387
62716
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", display: "inline-flex", alignItems: "center", cursor: "pointer", color: "brand.fg", flexShrink: "0", fontSize: "10px", onClick: (e3) => {
62388
62717
  e3.stopPropagation();
62389
62718
  removeAt(i);
@@ -62509,20 +62838,8 @@ function viewFor(binding) {
62509
62838
  const patch = binding.patch.type === "some" ? binding.patch.value : void 0;
62510
62839
  return defaultBindRuntime.buildBindHandle(types.sourceType, source, patch, binding.mode.type);
62511
62840
  }
62512
- function stableStringify(v3) {
62513
- return JSON.stringify(v3, (_k, x2) => {
62514
- if (typeof x2 === "bigint") return `${x2}n`;
62515
- if (x2 instanceof Date) return x2.toISOString();
62516
- if (east.isEastSet(x2)) return [...x2].sort();
62517
- if (east.isEastDict(x2)) return [...x2.entries()].sort((a2, b2) => String(a2[0]).localeCompare(String(b2[0])));
62518
- return x2;
62519
- });
62520
- }
62521
- function hashString2(s2) {
62522
- let h2 = 5381;
62523
- for (let i = 0; i < s2.length; i++) h2 = (h2 << 5) + h2 + s2.charCodeAt(i) | 0;
62524
- return (h2 >>> 0).toString(36);
62525
- }
62841
+ const printDescriptors = east.printFor(east.StructType({ decisions: east.ArrayType(internal$1.DiffBindingType), judgements: internal$1.DiffBindingType }));
62842
+ const printTypeValue = east.printFor(east.EastTypeValueType);
62526
62843
  function readSelectionAt(selectionKey) {
62527
62844
  platform.StateRuntime.trackKey(selectionKey);
62528
62845
  const bytes = platform.StateRuntime.getStore().read(selectionKey);
@@ -62564,16 +62881,6 @@ function removeFromOwningView(decisions, caseId) {
62564
62881
  }
62565
62882
  }
62566
62883
  const decisionHandleCache = /* @__PURE__ */ new Map();
62567
- function typeToken(tv) {
62568
- const seen = /* @__PURE__ */ new WeakSet();
62569
- return JSON.stringify(tv, (_k, v3) => {
62570
- if (typeof v3 === "object" && v3 !== null) {
62571
- if (seen.has(v3)) return "<cyc>";
62572
- seen.add(v3);
62573
- }
62574
- return typeof v3 === "bigint" ? `${v3}n` : v3;
62575
- });
62576
- }
62577
62884
  function compileDecisionMethods(constraintType, decisions, judgements, selectionKey) {
62578
62885
  const platform$1 = platform.getRegisteredPlatformImplementations();
62579
62886
  const c2 = east.fromEastTypeValue(constraintType);
@@ -62641,7 +62948,7 @@ function buildDecisionHandle(decisions, judgements, constraintType) {
62641
62948
  const at = (j2) => j2.resolvedAt.type === "some" ? j2.resolvedAt.value.getTime() : 0;
62642
62949
  return entries.sort((a2, b2) => at(b2) - at(a2));
62643
62950
  };
62644
- const cacheKey = `${hashString2(stableStringify({ decisions, judgements }))}|${typeToken(cType)}`;
62951
+ const cacheKey = `${printDescriptors({ decisions, judgements })}|${printTypeValue(east.canonicalTypeValue(cType))}`;
62645
62952
  let methods = decisionHandleCache.get(cacheKey);
62646
62953
  if (methods === void 0) {
62647
62954
  methods = compileDecisionMethods(cType, decisions, judgements, selectionKey);
@@ -62831,91 +63138,71 @@ const OP_WORDS = {
62831
63138
  notIn: "not in",
62832
63139
  is: "is"
62833
63140
  };
62834
- function formatScalar(v3) {
62835
- if (v3 instanceof Date) {
62836
- return `${v3.toLocaleString("en", { month: "short" })} ${v3.getDate()}`;
62837
- }
62838
- if (east.isEastSet(v3)) {
62839
- return `{${[...v3].map(formatScalar).join(", ")}}`;
63141
+ const PRINTERS = /* @__PURE__ */ new WeakMap();
63142
+ function printerOf(contract) {
63143
+ let printer = PRINTERS.get(contract);
63144
+ if (printer === void 0) {
63145
+ const context = /* @__PURE__ */ new Map();
63146
+ east.printFor(contract.type, context);
63147
+ printer = (type) => east.printFor(type, context);
63148
+ PRINTERS.set(contract, printer);
62840
63149
  }
62841
- if (v3 !== null && typeof v3 === "object" && !v3.type) {
62842
- return Object.entries(v3).map(([k2, x2]) => `${k2} ${formatScalar(x2)}`).join(" · ");
62843
- }
62844
- return String(v3);
63150
+ return printer;
62845
63151
  }
62846
- function formatConstraint(constraint, payloadType, levers) {
62847
- var _a2, _b2;
62848
- const c2 = constraint;
62849
- const lever = ((_a2 = levers == null ? void 0 : levers.find((l2) => l2.case === c2.type)) == null ? void 0 : _a2.label) ?? c2.type;
62850
- if ((payloadType == null ? void 0 : payloadType.type) === "Variant") {
62851
- const op = c2.value;
62852
- const word = OP_WORDS[op.type] ?? op.type;
62853
- const opType = (_b2 = payloadType.cases) == null ? void 0 : _b2[op.type];
62854
- if ((opType == null ? void 0 : opType.type) === "Struct" && op.value !== null && typeof op.value === "object") {
62855
- const r2 = op.value;
62856
- if ("min" in r2 && "max" in r2) {
62857
- return { lever, op: word, value: `${formatScalar(r2.min)} – ${formatScalar(r2.max)}` };
62858
- }
63152
+ function formatValue(value, type, words, print) {
63153
+ switch (type.type) {
63154
+ case "DateTime":
63155
+ return words.monthDay(value);
63156
+ case "Float":
63157
+ return words.float(value);
63158
+ case "String":
63159
+ return value;
63160
+ case "Set": {
63161
+ const member = type.value;
63162
+ return `{${[...value].map((m2) => formatValue(m2, member, words, print)).join(", ")}}`;
62859
63163
  }
62860
- return { lever, op: word, value: formatScalar(op.value) };
62861
- }
62862
- return { lever, op: "·", value: formatScalar(c2.value) };
62863
- }
62864
- const PRIMITIVES = /* @__PURE__ */ new Set(["String", "Integer", "Float", "DateTime", "Boolean"]);
62865
- function normalizeTypeValue(tv) {
62866
- var _a2;
62867
- const n2 = tv;
62868
- if (n2.cases !== void 0 || n2.fields !== void 0) {
62869
- const out = { type: n2.type };
62870
- if (n2.cases) out.cases = Object.fromEntries(Object.entries(n2.cases).map(([k2, v3]) => [k2, normalizeTypeValue(v3)]));
62871
- if (n2.fields) out.fields = Object.fromEntries(Object.entries(n2.fields).map(([k2, v3]) => [k2, normalizeTypeValue(v3)]));
62872
- if (n2.key) out.key = normalizeTypeValue(n2.key);
62873
- if (n2.value !== void 0 && (n2.type === "Array" || n2.type === "Dict")) out.value = normalizeTypeValue(n2.value);
62874
- return out;
62875
- }
62876
- switch (n2.type) {
62877
63164
  case "Struct":
62878
- return { type: "Struct", fields: Object.fromEntries(n2.value.map((f2) => [f2.name, normalizeTypeValue(f2.type)])) };
62879
- case "Variant":
62880
- return { type: "Variant", cases: Object.fromEntries(n2.value.map((f2) => [f2.name, normalizeTypeValue(f2.type)])) };
62881
- case "Array":
62882
- return { type: "Array", value: normalizeTypeValue(n2.value) };
62883
- case "Dict": {
62884
- const d2 = n2.value;
62885
- return { type: "Dict", key: normalizeTypeValue(d2.key), value: normalizeTypeValue(d2.value) };
62886
- }
62887
- case "Recursive": {
62888
- const w2 = n2.value;
62889
- if ((w2 == null ? void 0 : w2.type) === "wrapper" && ((_a2 = w2.value) == null ? void 0 : _a2.inner)) return normalizeTypeValue(w2.value.inner);
62890
- return { type: n2.type };
62891
- }
63165
+ return type.value.map((f2) => `${f2.name} ${formatValue(value[f2.name], f2.type, words, print)}`).join(" · ");
62892
63166
  default:
62893
- return { type: n2.type };
63167
+ return print(type)(value);
62894
63168
  }
62895
63169
  }
62896
- function emptyFor(node2) {
62897
- switch (node2.type) {
62898
- case "Integer":
62899
- return 0n;
62900
- case "Float":
62901
- return 0;
62902
- case "DateTime":
62903
- return /* @__PURE__ */ new Date();
62904
- case "Boolean":
62905
- return false;
62906
- case "String":
62907
- return "";
62908
- case "Struct":
62909
- return Object.fromEntries(Object.entries(node2.fields ?? {}).map(([k2, f2]) => [k2, emptyFor(f2)]));
62910
- default:
62911
- return null;
63170
+ function formatConstraint(constraint, contract, levers, words = eastUiComponents.formatters()) {
63171
+ var _a2;
63172
+ const lever = ((_a2 = levers == null ? void 0 : levers.find((l2) => l2.case === constraint.type)) == null ? void 0 : _a2.label) ?? constraint.type;
63173
+ const payload = contract == null ? void 0 : contract.payloads.get(constraint.type);
63174
+ if (contract === void 0 || payload === void 0) return { lever, op: "·", value: "" };
63175
+ const print = printerOf(contract);
63176
+ if (payload.type === "Variant") {
63177
+ const op = constraint.value;
63178
+ const word = OP_WORDS[op.type] ?? op.type;
63179
+ const opType = payload.value.find((c2) => c2.name === op.type).type;
63180
+ if (opType.type === "Struct") {
63181
+ const fields = opType.value;
63182
+ const min4 = fields.find((f2) => f2.name === "min");
63183
+ const max5 = fields.find((f2) => f2.name === "max");
63184
+ if (min4 !== void 0 && max5 !== void 0) {
63185
+ const range = op.value;
63186
+ return { lever, op: word, value: `${formatValue(range.min, min4.type, words, print)} – ${formatValue(range.max, max5.type, words, print)}` };
63187
+ }
63188
+ }
63189
+ return { lever, op: word, value: formatValue(op.value, opType, words, print) };
62912
63190
  }
63191
+ return { lever, op: "·", value: formatValue(constraint.value, payload, words, print) };
62913
63192
  }
62914
- function leverPayloadEditable(node2) {
62915
- if (!node2) return false;
62916
- if (PRIMITIVES.has(node2.type)) return true;
62917
- if (node2.type === "Struct") return Object.values(node2.fields ?? {}).every((f2) => PRIMITIVES.has(f2.type));
62918
- if (node2.type === "Variant") return Object.values(node2.cases ?? {}).every((c2) => PRIMITIVES.has(c2.type) || c2.type === "Struct" && Object.values(c2.fields ?? {}).every((f2) => PRIMITIVES.has(f2.type)));
63193
+ const PRIMITIVES = /* @__PURE__ */ new Set(["String", "Integer", "Float", "DateTime", "Boolean"]);
63194
+ const NULL_TYPE = east.toEastTypeValue(east.NullType);
63195
+ function emptyFor(type) {
63196
+ if (type.type === "DateTime") return /* @__PURE__ */ new Date();
63197
+ if (type.type === "Struct") return Object.fromEntries(type.value.map((f2) => [f2.name, emptyFor(f2.type)]));
63198
+ return east.defaultValue(type);
63199
+ }
63200
+ function leverPayloadEditable(payload) {
63201
+ if (payload === void 0) return false;
63202
+ const primitive = (t4) => PRIMITIVES.has(t4.type);
63203
+ const structOfPrimitives = (t4) => t4.type === "Struct" && t4.value.every((f2) => primitive(f2.type));
63204
+ if (primitive(payload) || structOfPrimitives(payload)) return true;
63205
+ if (payload.type === "Variant") return payload.value.every((c2) => primitive(c2.type) || structOfPrimitives(c2.type));
62919
63206
  return false;
62920
63207
  }
62921
63208
  const inputStyle = east.some({ size: east.some(east.variant("sm", null)) });
@@ -62923,17 +63210,17 @@ function LeverEditor({ leverCase, payload, onInject }) {
62923
63210
  var _a2;
62924
63211
  const button = react.useRecipe({ key: "button" });
62925
63212
  const isOpVariant = payload.type === "Variant";
62926
- const ops = React.useMemo(() => isOpVariant ? Object.keys(payload.cases ?? {}) : [], [isOpVariant, payload]);
63213
+ const ops = React.useMemo(() => payload.type === "Variant" ? payload.value.map((c2) => c2.name) : [], [payload]);
62927
63214
  const [opTag, setOpTag] = React.useState(ops[0] ?? "");
62928
- const active = isOpVariant ? ((_a2 = payload.cases) == null ? void 0 : _a2[opTag]) ?? { type: "Null" } : payload;
63215
+ const active = payload.type === "Variant" ? ((_a2 = payload.value.find((c2) => c2.name === opTag)) == null ? void 0 : _a2.type) ?? NULL_TYPE : payload;
62929
63216
  const valRef = React.useRef(emptyFor(active));
62930
63217
  const keyRef = React.useRef(`${leverCase}:${opTag}`);
62931
63218
  if (keyRef.current !== `${leverCase}:${opTag}`) {
62932
63219
  keyRef.current = `${leverCase}:${opTag}`;
62933
63220
  valRef.current = emptyFor(active);
62934
63221
  }
62935
- const primitiveInput = (node2, key, get3, set3) => {
62936
- switch (node2.type) {
63222
+ const primitiveInput = (type, key, get3, set3) => {
63223
+ switch (type.type) {
62937
63224
  case "Integer":
62938
63225
  return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraIntegerInput, { value: { value: get3(), onChange: east.some(set3), style: inputStyle } }, key);
62939
63226
  case "Float":
@@ -62946,7 +63233,7 @@ function LeverEditor({ leverCase, payload, onInject }) {
62946
63233
  {
62947
63234
  ariaLabel: key,
62948
63235
  value: {
62949
- value: east.some(String(get3())),
63236
+ value: east.some(get3() === true ? "true" : "false"),
62950
63237
  items: [{ value: "true", label: "true", disabled: east.none }, { value: "false", label: "false", disabled: east.none }],
62951
63238
  placeholder: east.none,
62952
63239
  multiple: east.none,
@@ -62959,17 +63246,19 @@ function LeverEditor({ leverCase, payload, onInject }) {
62959
63246
  },
62960
63247
  key
62961
63248
  );
63249
+ case "String":
63250
+ return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraStringInput, { value: { value: get3(), onChange: east.some(set3), style: inputStyle } }, key);
62962
63251
  default:
62963
- return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraStringInput, { value: { value: String(get3() ?? ""), onChange: east.some(set3), style: inputStyle } }, key);
63252
+ return null;
62964
63253
  }
62965
63254
  };
62966
63255
  const valueControls = React.useMemo(() => {
62967
63256
  const k2 = keyRef.current;
62968
63257
  if (active.type === "Struct") {
62969
- return Object.entries(active.fields ?? {}).map(([field, node2]) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "6px", minW: 0, children: [
63258
+ return active.value.map(({ name: field, type }) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "6px", minW: 0, children: [
62970
63259
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", textStyle: "caption.eyebrow", color: "fg.subtle", flexShrink: 0, children: field }),
62971
63260
  primitiveInput(
62972
- node2,
63261
+ type,
62973
63262
  `${k2}.${field}.in`,
62974
63263
  () => valRef.current[field],
62975
63264
  (v3) => {
@@ -63025,11 +63314,12 @@ function LeverEditor({ leverCase, payload, onInject }) {
63025
63314
  }
63026
63315
  );
63027
63316
  }
63028
- function fmt(d2, n2, showSign = false) {
63029
- return eastUiComponents.formatTick(n2, eastUiComponents.getSomeorUndefined(d2.format), showSign);
63317
+ function fmt(words, d2, n2, showSign = false) {
63318
+ return words.value(n2, eastUiComponents.getSomeorUndefined(d2.format), showSign);
63030
63319
  }
63031
63320
  const caption = { textStyle: "caption.eyebrow", color: "fg.muted" };
63032
63321
  const EvidenceFacet = React.memo(function EvidenceFacet2({ decision, children: children2 }) {
63322
+ const words = eastUiComponents.useFormatters();
63033
63323
  const stakes = eastUiComponents.getSomeorUndefined(decision.stakes);
63034
63324
  const downside = eastUiComponents.getSomeorUndefined(decision.downside);
63035
63325
  const confidence = eastUiComponents.getSomeorUndefined(decision.confidence);
@@ -63045,15 +63335,12 @@ const EvidenceFacet = React.memo(function EvidenceFacet2({ decision, children: c
63045
63335
  ] }),
63046
63336
  downside !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
63047
63337
  "if wrong ",
63048
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", color: "fg.danger", children: fmt(decision, downside, true) }),
63338
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", color: "fg.danger", children: fmt(words, decision, downside, true) }),
63049
63339
  " · "
63050
63340
  ] }),
63051
63341
  confidence !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
63052
63342
  "confidence ",
63053
- /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: [
63054
- Math.round(confidence * 100),
63055
- "%"
63056
- ] })
63343
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: words.percent(confidence) })
63057
63344
  ] })
63058
63345
  ] })
63059
63346
  ] }),
@@ -63069,8 +63356,9 @@ const EvidenceFacet = React.memo(function EvidenceFacet2({ decision, children: c
63069
63356
  children2
63070
63357
  ] });
63071
63358
  });
63359
+ const compareValues$1 = east.compareFor(east.FloatType);
63072
63360
  function rankOptions(d2) {
63073
- const alternatives = [...d2.alternatives].sort((a2, b2) => b2.value - a2.value).map((a2) => ({
63361
+ const alternatives = [...d2.alternatives].sort((a2, b2) => compareValues$1(b2.value, a2.value)).map((a2) => ({
63074
63362
  label: a2.label,
63075
63363
  value: a2.value,
63076
63364
  downside: eastUiComponents.getSomeorUndefined(a2.downside),
@@ -63090,14 +63378,15 @@ function rankOptions(d2) {
63090
63378
  ...alternatives
63091
63379
  ].map((o2, i) => ({ ...o2, rank: i + 1 }));
63092
63380
  }
63093
- function metaLine(o2) {
63381
+ function metaLine(o2, words) {
63094
63382
  const parts = [];
63095
63383
  if (o2.recommended) parts.push("recommended");
63096
- if (o2.confidence !== void 0) parts.push(`${Math.round(o2.confidence * 100)}% conf`);
63384
+ if (o2.confidence !== void 0) parts.push(`${words.percent(o2.confidence)} conf`);
63097
63385
  if (!o2.recommended && o2.note !== void 0) parts.push(o2.note);
63098
63386
  return parts.join(" · ");
63099
63387
  }
63100
63388
  const OptionsFacet = React.memo(function OptionsFacet2({ decision, narrow }) {
63389
+ const words = eastUiComponents.useFormatters();
63101
63390
  const options2 = rankOptions(decision);
63102
63391
  const maxUp = Math.max(...options2.map((o2) => Math.max(o2.value, 0)), 1e-9);
63103
63392
  const maxDown = Math.max(...options2.map((o2) => Math.abs(o2.downside ?? 0)), 1e-9);
@@ -63113,7 +63402,7 @@ const OptionsFacet = React.memo(function OptionsFacet2({ decision, narrow }) {
63113
63402
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "10px", children: [
63114
63403
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { ...caption, color: "fg", children: [
63115
63404
  "Options · ",
63116
- options2.length,
63405
+ words.number(options2.length),
63117
63406
  " evaluated"
63118
63407
  ] }),
63119
63408
  options2.map((o2) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "4px", pl: "8px", ...o2.recommended ? { boxShadow: "inset 2px 0 0 var(--chakra-colors-accent-brand)" } : {}, children: [
@@ -63122,8 +63411,8 @@ const OptionsFacet = React.memo(function OptionsFacet2({ decision, narrow }) {
63122
63411
  " ",
63123
63412
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: o2.recommended ? "semibold" : "normal", children: o2.label })
63124
63413
  ] }),
63125
- meter(upLabel, o2.value / maxUp, fmt(decision, o2.value, signed2), signed2 ? "success" : "neutral"),
63126
- meter("If wrong", (o2.downside ?? 0) / maxDown, o2.downside !== void 0 ? fmt(decision, o2.downside, true) : "—", "danger")
63414
+ meter(upLabel, o2.value / maxUp, fmt(words, decision, o2.value, signed2), signed2 ? "success" : "neutral"),
63415
+ meter("If wrong", (o2.downside ?? 0) / maxDown, o2.downside !== void 0 ? fmt(words, decision, o2.downside, true) : "—", "danger")
63127
63416
  ] }, o2.rank))
63128
63417
  ] });
63129
63418
  }
@@ -63131,7 +63420,7 @@ const OptionsFacet = React.memo(function OptionsFacet2({ decision, narrow }) {
63131
63420
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "minmax(180px, 1.1fr) 1fr 1fr", alignItems: "baseline", gap: "8px", children: [
63132
63421
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { ...caption, color: "fg", children: [
63133
63422
  "Options · ",
63134
- options2.length,
63423
+ words.number(options2.length),
63135
63424
  " evaluated"
63136
63425
  ] }),
63137
63426
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { ...caption, textAlign: "center", children: "If wrong" }),
@@ -63153,15 +63442,15 @@ const OptionsFacet = React.memo(function OptionsFacet2({ decision, narrow }) {
63153
63442
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { minW: 0, pl: o2.recommended ? "6px" : "0", display: "flex", alignItems: "baseline", gap: "6px", children: [
63154
63443
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontSize: "11px", color: "fg.subtle", flexShrink: 0, children: o2.rank }),
63155
63444
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontSize: "12.5px", fontWeight: o2.recommended ? "semibold" : "normal", flexShrink: 0, children: o2.label }),
63156
- metaLine(o2) !== "" && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontSize: "10.5px", color: "fg.muted", minW: 0, truncate: true, children: metaLine(o2) })
63445
+ metaLine(o2, words) !== "" && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontSize: "10.5px", color: "fg.muted", minW: 0, truncate: true, children: metaLine(o2, words) })
63157
63446
  ] }),
63158
63447
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: "6px", height: "16px", children: o2.downside !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
63159
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "10.5px", fontFamily: "mono", color: "fg.danger", flexShrink: 0, children: fmt(decision, o2.downside, true) }),
63448
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "10.5px", fontFamily: "mono", color: "fg.danger", flexShrink: 0, children: fmt(words, decision, o2.downside, true) }),
63160
63449
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { height: "12px", width: `${Math.round(downFrac * 100)}%`, bg: "fg.danger", opacity: 0.55 })
63161
63450
  ] }) }),
63162
63451
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", alignItems: "center", gap: "6px", height: "16px", borderLeftWidth: "1px", borderColor: "border.strong", children: o2.value > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
63163
63452
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { height: "12px", width: `${Math.round(upFrac * 100)}%`, bg: signed2 ? "fg.success" : "fg.subtle", opacity: 0.6 }),
63164
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "10.5px", fontFamily: "mono", color: signed2 ? "fg.success" : "fg.muted", flexShrink: 0, children: fmt(decision, o2.value, signed2) })
63453
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "10.5px", fontFamily: "mono", color: signed2 ? "fg.success" : "fg.muted", flexShrink: 0, children: fmt(words, decision, o2.value, signed2) })
63165
63454
  ] }) })
63166
63455
  ]
63167
63456
  },
@@ -63175,8 +63464,9 @@ const ANSWERS = [
63175
63464
  { literal: "no", label: "No" },
63176
63465
  { literal: "unknown", label: "Don't know" }
63177
63466
  ];
63178
- const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, leverPayloads }) {
63467
+ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, contract }) {
63179
63468
  var _a2;
63469
+ const words = eastUiComponents.useFormatters();
63180
63470
  const button = react.useRecipe({ key: "button" });
63181
63471
  const input = react.useRecipe({ key: "input" });
63182
63472
  const judgement = handle.judgementFor(decision.id);
@@ -63189,11 +63479,12 @@ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, l
63189
63479
  if (knowledge !== stagedKnowledge) handle.addKnowledge(decision.id, knowledge);
63190
63480
  }, [knowledge, stagedKnowledge, handle, decision.id]);
63191
63481
  const levers = React.useMemo(
63192
- () => decision.levers.filter((l2) => leverPayloadEditable(leverPayloads[l2.case])),
63193
- [decision.levers, leverPayloads]
63482
+ () => decision.levers.filter((l2) => leverPayloadEditable(contract == null ? void 0 : contract.payloads.get(l2.case))),
63483
+ [decision.levers, contract]
63194
63484
  );
63195
63485
  const [leverCase, setLeverCase] = React.useState(((_a2 = levers[0]) == null ? void 0 : _a2.case) ?? "");
63196
63486
  const activeLever = levers.find((l2) => l2.case === leverCase) ?? levers[0];
63487
+ const activePayload = activeLever === void 0 ? void 0 : contract == null ? void 0 : contract.payloads.get(activeLever.case);
63197
63488
  const gate = handle.commitStateFor(decision.id);
63198
63489
  const gateHint = (gate == null ? void 0 : gate.type) === "gated" ? gate.value === 1n ? "Answer the judgement question to enable apply" : `Answer the ${gate.value} judgement questions to enable apply` : (gate == null ? void 0 : gate.type) === "blocked" ? "Commit blocked — an answer contradicts the recommendation" : (gate == null ? void 0 : gate.type) === "handoff" ? "Awaiting handoff — an answer was don't know" : null;
63199
63490
  const onInject = React.useCallback((constraint) => {
@@ -63239,8 +63530,7 @@ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, l
63239
63530
  (levers.length > 0 || judgement.constraints.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "6px", children: [
63240
63531
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { ...caption, children: "Inject constraint · next run folds it in" }),
63241
63532
  judgement.constraints.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", gap: "12px", flexWrap: "wrap", children: judgement.constraints.map((c2, i) => {
63242
- const tag = c2.type;
63243
- const f2 = formatConstraint(c2, leverPayloads[tag], decision.levers);
63533
+ const f2 = formatConstraint(c2, contract, decision.levers, words);
63244
63534
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontFamily: "mono", fontSize: "11px", children: [
63245
63535
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "accent.brand", fontWeight: "semibold", children: f2.lever }),
63246
63536
  " ",
@@ -63249,7 +63539,7 @@ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, l
63249
63539
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: f2.value })
63250
63540
  ] }, i);
63251
63541
  }) }),
63252
- activeLever !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", children: [
63542
+ activeLever !== void 0 && activePayload !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", children: [
63253
63543
  levers.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flexShrink: 0, css: { "& [data-scope=select][data-part=root]": { width: "auto", minWidth: "160px" } }, children: /* @__PURE__ */ jsxRuntime.jsx(
63254
63544
  eastUiComponents.EastChakraSelect,
63255
63545
  {
@@ -63271,7 +63561,7 @@ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, l
63271
63561
  LeverEditor,
63272
63562
  {
63273
63563
  leverCase: activeLever.case,
63274
- payload: leverPayloads[activeLever.case],
63564
+ payload: activePayload,
63275
63565
  onInject
63276
63566
  },
63277
63567
  activeLever.case
@@ -63294,11 +63584,54 @@ const JudgementFacet = React.memo(function JudgementFacet2({ decision, handle, l
63294
63584
  ] })
63295
63585
  ] });
63296
63586
  });
63587
+ function body(type) {
63588
+ return type.type === "Recursive" && type.value.type === "wrapper" ? type.value.value.inner : type;
63589
+ }
63590
+ function constraintContractOf(judgementsType) {
63591
+ const dict = body(judgementsType);
63592
+ if (dict.type !== "Dict") return void 0;
63593
+ const record = body(dict.value.value);
63594
+ if (record.type !== "Struct") return void 0;
63595
+ const field = record.value.find((f2) => f2.name === "constraints");
63596
+ if (field === void 0) return void 0;
63597
+ const constraints = body(field.type);
63598
+ if (constraints.type !== "Array") return void 0;
63599
+ const contract = constraints.value;
63600
+ const cases = body(contract);
63601
+ if (cases.type !== "Variant") return void 0;
63602
+ return {
63603
+ type: contract,
63604
+ payloads: new Map(cases.value.map((c2) => [c2.name, c2.type]))
63605
+ };
63606
+ }
63607
+ function useConstraintContract(ref) {
63608
+ const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
63609
+ return React.useMemo(() => {
63610
+ if (ref === null) return void 0;
63611
+ const judgements = getBindingTypes(workspace, ref.judgements.source);
63612
+ return judgements === void 0 ? void 0 : constraintContractOf(judgements.sourceType);
63613
+ }, [ref, workspace]);
63614
+ }
63297
63615
  const URGENCY_RANK = {
63298
63616
  overdue: 0,
63299
63617
  due: 1,
63300
63618
  routine: 2
63301
63619
  };
63620
+ const compareInstants = east.compareFor(east.DateTimeType);
63621
+ const compareValues = east.compareFor(east.FloatType);
63622
+ function compareByUrgency(a2, b2) {
63623
+ const rank = URGENCY_RANK[a2.urgency.type] - URGENCY_RANK[b2.urgency.type];
63624
+ if (rank !== 0) return rank;
63625
+ const da = a2.deadline.type === "some" ? a2.deadline.value : void 0;
63626
+ const db = b2.deadline.type === "some" ? b2.deadline.value : void 0;
63627
+ if (da !== void 0 && db !== void 0) {
63628
+ const byDeadline = compareInstants(da, db);
63629
+ if (byDeadline !== 0) return byDeadline;
63630
+ } else if (da !== db) {
63631
+ return da === void 0 ? 1 : -1;
63632
+ }
63633
+ return compareValues(b2.value, a2.value);
63634
+ }
63302
63635
  const URGENCY_GROUP_LABEL = {
63303
63636
  overdue: "Overdue",
63304
63637
  due: "Due today",
@@ -63345,10 +63678,10 @@ const URGENCY_TONE = {
63345
63678
  };
63346
63679
  const EXIT_MS = 560;
63347
63680
  const NARROW_PX = 560;
63348
- function decisionValue(d2, n2, showSign = false) {
63349
- return eastUiComponents.formatTick(n2, eastUiComponents.getSomeorUndefined(d2.format), showSign);
63681
+ function decisionValue(words, d2, n2, showSign = false) {
63682
+ return words.value(n2, eastUiComponents.getSomeorUndefined(d2.format), showSign);
63350
63683
  }
63351
- function deadlineSuffix(d2) {
63684
+ function deadlineSuffix(d2, words) {
63352
63685
  const deadline = eastUiComponents.getSomeorUndefined(d2.deadline);
63353
63686
  if (deadline === void 0) return "";
63354
63687
  if (d2.urgency.type === "overdue") {
@@ -63357,34 +63690,16 @@ function deadlineSuffix(d2) {
63357
63690
  if (hours < 48) return ` ${hours}h`;
63358
63691
  return ` ${Math.round(hours / 24)}d`;
63359
63692
  }
63360
- if (d2.urgency.type === "due") {
63361
- const h2 = deadline.getHours();
63362
- const m2 = deadline.getMinutes();
63363
- const meridiem = h2 >= 12 ? "pm" : "am";
63364
- const clock2 = h2 % 12 === 0 ? 12 : h2 % 12;
63365
- return m2 === 0 ? ` ${clock2}${meridiem}` : ` ${clock2}:${String(m2).padStart(2, "0")}${meridiem}`;
63366
- }
63693
+ if (d2.urgency.type === "due") return ` ${words.time(deadline)}`;
63367
63694
  return "";
63368
63695
  }
63369
- function useLeverPayloads(ref) {
63370
- const cache3 = getReactiveDatasetCache();
63371
- const workspace = cache3.getConfig().workspace ?? "";
63372
- return React.useMemo(() => {
63373
- var _a2, _b2, _c2;
63374
- if (!ref) return {};
63375
- const judgements = getBindingTypes(workspace, ref.judgements.source);
63376
- if (!(judgements == null ? void 0 : judgements.sourceType)) return {};
63377
- const root2 = normalizeTypeValue(judgements.sourceType);
63378
- const constraint = (_c2 = (_b2 = (_a2 = root2.value) == null ? void 0 : _a2.fields) == null ? void 0 : _b2["constraints"]) == null ? void 0 : _c2.value;
63379
- return (constraint == null ? void 0 : constraint.type) === "Variant" ? constraint.cases ?? {} : {};
63380
- }, [ref, workspace]);
63381
- }
63382
- const Row = React.memo(function Row2({ decision, handle, selected: selected2, narrow, leverPayloads, modify, evidence, defaultFacet, facetInclude, apply: apply2, reject: reject2, leaving, storageKey }) {
63696
+ const Row = React.memo(function Row2({ decision, handle, selected: selected2, narrow, contract, modify, evidence, defaultFacet, facetInclude, apply: apply2, reject: reject2, leaving, storageKey }) {
63383
63697
  var _a2, _b2;
63384
63698
  const dq = react.useSlotRecipe({ key: "decisionQueue" });
63385
63699
  const status = react.useSlotRecipe({ key: "status" });
63386
63700
  const tabs = react.useSlotRecipe({ key: "facetTabs" });
63387
63701
  const button = react.useRecipe({ key: "button" });
63702
+ const words = eastUiComponents.useFormatters();
63388
63703
  const rs = dq({});
63389
63704
  const ts = tabs({});
63390
63705
  const st = status({ status: URGENCY_TONE[decision.urgency.type], size: "md" });
@@ -63410,10 +63725,15 @@ const Row = React.memo(function Row2({ decision, handle, selected: selected2, na
63410
63725
  const handleReject = React.useCallback(() => {
63411
63726
  reject2(decision);
63412
63727
  }, [reject2, decision]);
63728
+ const stageUpdate = handle.update;
63729
+ const updateDecision = React.useCallback((next2) => {
63730
+ stageUpdate(next2);
63731
+ return null;
63732
+ }, [stageUpdate]);
63413
63733
  const probe = React.useMemo(() => {
63414
63734
  if (!selected2 || facet !== "modify" || !modify) return null;
63415
- return modify(decision, handle.update);
63416
- }, [selected2, facet, modify, decision, handle.update]);
63735
+ return modify(decision, updateDecision);
63736
+ }, [selected2, facet, modify, decision, updateDecision]);
63417
63737
  const canvas = React.useMemo(() => {
63418
63738
  if (!selected2 || facet !== "evidence" || !evidence) return null;
63419
63739
  return evidence(decision);
@@ -63462,11 +63782,11 @@ const Row = React.memo(function Row2({ decision, handle, selected: selected2, na
63462
63782
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: st.indicator }),
63463
63783
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: st.label, children: [
63464
63784
  decision.urgency.type,
63465
- deadlineSuffix(decision)
63785
+ deadlineSuffix(decision, words)
63466
63786
  ] })
63467
63787
  ] });
63468
63788
  const valueSigned = ((_b2 = eastUiComponents.getSomeorUndefined(decision.valueAxis)) == null ? void 0 : _b2.signed) ?? true;
63469
- const valueText = /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontFamily: "mono", fontWeight: "semibold", textAlign: "right", ...valueSigned && decision.value >= 0 && selected2 ? { color: "fg.success" } : {}, children: decisionValue(decision, decision.value, valueSigned && selected2) });
63789
+ const valueText = /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontFamily: "mono", fontWeight: "semibold", textAlign: "right", ...valueSigned && decision.value >= 0 && selected2 ? { color: "fg.success" } : {}, children: decisionValue(words, decision, decision.value, valueSigned && selected2) });
63470
63790
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: rs.rowShell, ...leaving ? { "data-leaving": leaving } : {}, children: [
63471
63791
  narrow ? /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: "14px", py: "10px", children: [
63472
63792
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", justifyContent: "space-between", alignItems: "baseline", children: [
@@ -63490,7 +63810,7 @@ const Row = React.memo(function Row2({ decision, handle, selected: selected2, na
63490
63810
  selected2 && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: narrow ? "14px" : "18px", py: "12px", borderTopWidth: "1px", borderColor: "border.subtle", bg: "bg.canvas", children: [
63491
63811
  facet === "evidence" && /* @__PURE__ */ jsxRuntime.jsx(EvidenceFacet, { decision, children: canvas != null && /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraComponent, { value: canvas, storageKey: `${storageKey}-evidence-${decision.id}` }) }),
63492
63812
  facet === "options" && /* @__PURE__ */ jsxRuntime.jsx(OptionsFacet, { decision, narrow }),
63493
- facet === "judgement" && /* @__PURE__ */ jsxRuntime.jsx(JudgementFacet, { decision, handle, leverPayloads }),
63813
+ facet === "judgement" && /* @__PURE__ */ jsxRuntime.jsx(JudgementFacet, { decision, handle, contract }),
63494
63814
  facet === "modify" && probe != null && /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraComponent, { value: probe, storageKey: `${storageKey}-modify-${decision.id}` })
63495
63815
  ] })
63496
63816
  ] });
@@ -63499,6 +63819,7 @@ const RoutineGroup = React.memo(function RoutineGroup2({ routine, acceptAll, lea
63499
63819
  const dq = react.useSlotRecipe({ key: "decisionQueue" });
63500
63820
  const status = react.useSlotRecipe({ key: "status" });
63501
63821
  const button = react.useRecipe({ key: "button" });
63822
+ const words = eastUiComponents.useFormatters();
63502
63823
  const rs = dq({});
63503
63824
  const st = status({ status: "neutral", size: "md" });
63504
63825
  const total = React.useMemo(() => routine.reduce((acc, d2) => acc + d2.value, 0), [routine]);
@@ -63520,15 +63841,15 @@ const RoutineGroup = React.memo(function RoutineGroup2({ routine, acceptAll, lea
63520
63841
  d2.kind
63521
63842
  ] })
63522
63843
  ] }),
63523
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: rs.routineValue, children: decisionValue(d2, d2.value) })
63844
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: rs.routineValue, children: decisionValue(words, d2, d2.value) })
63524
63845
  ] }) }, d2.id)),
63525
63846
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: rs.summary, ...narrow ? { marginLeft: "0", flexWrap: "wrap" } : {}, children: [
63526
63847
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: rs.summaryCap, children: [
63527
- routine.length,
63848
+ words.number(routine.length),
63528
63849
  " routine"
63529
63850
  ] }),
63530
63851
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.subtle", children: "·" }),
63531
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: eastUiComponents.formatTick(total, format2) }),
63852
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: words.value(total, format2) }),
63532
63853
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.muted", children: "total" }),
63533
63854
  acceptAll && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: button({ variant: "solid", size: "xs" }), ml: "auto", onClick: handleAcceptAll, children: "Accept all" })
63534
63855
  ] })
@@ -63537,6 +63858,7 @@ const RoutineGroup = React.memo(function RoutineGroup2({ routine, acceptAll, lea
63537
63858
  const GroupHead = React.memo(function GroupHead2({ group: group2, collapsible, collapsed, onToggle, acceptAll }) {
63538
63859
  const dq = react.useSlotRecipe({ key: "decisionQueue" });
63539
63860
  const button = react.useRecipe({ key: "button" });
63861
+ const words = eastUiComponents.useFormatters();
63540
63862
  const rs = dq({});
63541
63863
  const format2 = group2.decisions[0] ? eastUiComponents.getSomeorUndefined(group2.decisions[0].format) : void 0;
63542
63864
  const handleToggle = React.useCallback(() => {
@@ -63551,11 +63873,11 @@ const GroupHead = React.memo(function GroupHead2({ group: group2, collapsible, c
63551
63873
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: rs.groupLabel, children: [
63552
63874
  group2.label,
63553
63875
  " · ",
63554
- group2.decisions.length
63876
+ words.number(group2.decisions.length)
63555
63877
  ] }),
63556
63878
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: rs.groupSummary, children: [
63557
- eastUiComponents.formatTick(group2.total, format2),
63558
- group2.pastSla > 0 ? ` · ${group2.pastSla} past SLA` : ""
63879
+ words.value(group2.total, format2),
63880
+ group2.pastSla > 0 ? ` · ${words.number(group2.pastSla)} past SLA` : ""
63559
63881
  ] }),
63560
63882
  acceptAll && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "button", css: button({ variant: "solid", size: "xs" }), onClick: handleAcceptAll, children: "Accept all" })
63561
63883
  ] });
@@ -63567,10 +63889,11 @@ const EastChakraDecisionQueue = React.memo(function EastChakraDecisionQueue2({ v
63567
63889
  const dq = react.useSlotRecipe({ key: "decisionQueue" });
63568
63890
  const qs = dq({});
63569
63891
  const chip = react.useRecipe({ key: "chip" });
63892
+ const words = eastUiComponents.useFormatters();
63570
63893
  const handleRef = value.handle;
63571
63894
  const handle = useDecisionHandle(handleRef);
63572
63895
  const decisions = handle.decisions;
63573
- const leverPayloads = useLeverPayloads(handleRef);
63896
+ const contract = useConstraintContract(handleRef);
63574
63897
  const railAffordances = eastUiComponents.getSomeorUndefined(value.affordances);
63575
63898
  const sliceHandle = eastUiComponents.getSomeorUndefined(value.slice) ?? null;
63576
63899
  eastUiComponents.useSliceReactivity(sliceHandle == null ? void 0 : sliceHandle.key);
@@ -63610,14 +63933,8 @@ const EastChakraDecisionQueue = React.memo(function EastChakraDecisionQueue2({ v
63610
63933
  const heading = eastUiComponents.getSomeorUndefined(value.heading) ?? "Decisions waiting";
63611
63934
  const onApply = React.useMemo(() => eastUiComponents.getSomeorUndefined(value.onApply), [value.onApply]);
63612
63935
  const onReject = React.useMemo(() => eastUiComponents.getSomeorUndefined(value.onReject), [value.onReject]);
63613
- const modify = React.useMemo(
63614
- () => eastUiComponents.getSomeorUndefined(value.modify),
63615
- [value.modify]
63616
- );
63617
- const evidence = React.useMemo(
63618
- () => eastUiComponents.getSomeorUndefined(value.evidence),
63619
- [value.evidence]
63620
- );
63936
+ const modify = React.useMemo(() => eastUiComponents.getSomeorUndefined(value.modify), [value.modify]);
63937
+ const evidence = React.useMemo(() => eastUiComponents.getSomeorUndefined(value.evidence), [value.evidence]);
63621
63938
  const defaultFacet = ((_a2 = eastUiComponents.getSomeorUndefined(value.defaultFacet)) == null ? void 0 : _a2.type) ?? "evidence";
63622
63939
  const facetInclude = React.useMemo(() => {
63623
63940
  const facets = eastUiComponents.getSomeorUndefined(value.facets);
@@ -63666,15 +63983,7 @@ const EastChakraDecisionQueue = React.memo(function EastChakraDecisionQueue2({ v
63666
63983
  for (const { decision } of exiting.values()) {
63667
63984
  if (!merged2.some((d2) => d2.id === decision.id)) merged2.push(decision);
63668
63985
  }
63669
- merged2.sort((a2, b2) => {
63670
- var _a3, _b2;
63671
- const r2 = URGENCY_RANK[a2.urgency.type] - URGENCY_RANK[b2.urgency.type];
63672
- if (r2 !== 0) return r2;
63673
- const da = ((_a3 = eastUiComponents.getSomeorUndefined(a2.deadline)) == null ? void 0 : _a3.getTime()) ?? Infinity;
63674
- const db = ((_b2 = eastUiComponents.getSomeorUndefined(b2.deadline)) == null ? void 0 : _b2.getTime()) ?? Infinity;
63675
- if (da !== db) return da - db;
63676
- return b2.value - a2.value;
63677
- });
63986
+ merged2.sort(compareByUrgency);
63678
63987
  return {
63679
63988
  merged: merged2,
63680
63989
  active: merged2.filter((d2) => d2.urgency.type !== "routine"),
@@ -63713,7 +64022,7 @@ const EastChakraDecisionQueue = React.memo(function EastChakraDecisionQueue2({ v
63713
64022
  handle,
63714
64023
  selected: selectedId === d2.id,
63715
64024
  narrow,
63716
- leverPayloads,
64025
+ contract,
63717
64026
  modify,
63718
64027
  evidence,
63719
64028
  defaultFacet,
@@ -63740,13 +64049,13 @@ const EastChakraDecisionQueue = React.memo(function EastChakraDecisionQueue2({ v
63740
64049
  ) }),
63741
64050
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.meta, gap: "10px", children: [
63742
64051
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", children: [
63743
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg", fontWeight: "semibold", children: visible }),
64052
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg", fontWeight: "semibold", children: words.number(visible) }),
63744
64053
  narrow ? "" : " decisions"
63745
64054
  ] }),
63746
64055
  pastSla > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
63747
64056
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: es.sep, children: "·" }),
63748
64057
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", children: [
63749
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.danger", fontWeight: "semibold", children: pastSla }),
64058
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.danger", fontWeight: "semibold", children: words.number(pastSla) }),
63750
64059
  narrow ? " SLA" : " past SLA"
63751
64060
  ] })
63752
64061
  ] })
@@ -63808,34 +64117,24 @@ function verdictPresentation(verdict) {
63808
64117
  return { tone: "warning", verb: "handed off", detail: verdict.value !== "" ? verdict.value : null };
63809
64118
  }
63810
64119
  }
63811
- function formatResolvedAt(j2) {
64120
+ function formatResolvedAt(j2, words) {
63812
64121
  if (j2.resolvedAt.type !== "some") return null;
63813
64122
  const d2 = j2.resolvedAt.value;
63814
- const month = d2.toLocaleString("en", { month: "short" });
63815
- const time2 = `${String(d2.getHours()).padStart(2, "0")}:${String(d2.getMinutes()).padStart(2, "0")}`;
63816
- return `${month} ${d2.getDate()} · ${time2}`;
64123
+ return `${words.monthDay(d2)} · ${words.time(d2)}`;
63817
64124
  }
63818
64125
  const EastChakraDecisionJournal = React.memo(function EastChakraDecisionJournal2({ value }) {
63819
64126
  const eyebrow = react.useSlotRecipe({ key: "eyebrowRow" });
63820
64127
  const status = react.useSlotRecipe({ key: "status" });
63821
64128
  const es = eyebrow({});
64129
+ const words = eastUiComponents.useFormatters();
63822
64130
  const handle = useDecisionHandle(value.handle);
63823
64131
  const heading = eastUiComponents.getSomeorUndefined(value.heading) ?? "Decision journal";
63824
- const cache3 = getReactiveDatasetCache();
63825
- const workspace = cache3.getConfig().workspace ?? "";
63826
- const leverPayloads = React.useMemo(() => {
63827
- var _a2, _b2, _c2;
63828
- const judgements = getBindingTypes(workspace, value.handle.judgements.source);
63829
- if (!(judgements == null ? void 0 : judgements.sourceType)) return {};
63830
- const root2 = normalizeTypeValue(judgements.sourceType);
63831
- const constraint = (_c2 = (_b2 = (_a2 = root2.value) == null ? void 0 : _a2.fields) == null ? void 0 : _b2["constraints"]) == null ? void 0 : _c2.value;
63832
- return (constraint == null ? void 0 : constraint.type) === "Variant" ? constraint.cases ?? {} : {};
63833
- }, [workspace, value.handle]);
64132
+ const contract = useConstraintContract(value.handle);
63834
64133
  const entries = React.useMemo(() => handle.journal ?? [], [handle.journal]);
63835
64134
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { borderWidth: "1px", borderColor: "border.strong", borderRadius: "6px", background: "bg.canvas", overflow: "hidden", children: [
63836
64135
  /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.root, children: [
63837
64136
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: heading }),
63838
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.meta, children: entries.length === 1 ? "1 entry" : `${entries.length} entries` })
64137
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.meta, children: entries.length === 1 ? "1 entry" : `${words.number(entries.length)} entries` })
63839
64138
  ] }),
63840
64139
  /* @__PURE__ */ jsxRuntime.jsx(
63841
64140
  react.Box,
@@ -63847,7 +64146,7 @@ const EastChakraDecisionJournal = React.memo(function EastChakraDecisionJournal2
63847
64146
  if (!verdict) return null;
63848
64147
  const { tone, verb, detail } = verdictPresentation(verdict);
63849
64148
  const st = status({ status: tone, size: "md" });
63850
- const when = formatResolvedAt(j2);
64149
+ const when = formatResolvedAt(j2, words);
63851
64150
  const knowledge = j2.knowledge.type === "some" && j2.knowledge.value !== "" ? j2.knowledge.value : null;
63852
64151
  return /* @__PURE__ */ jsxRuntime.jsxs(
63853
64152
  react.Box,
@@ -63886,8 +64185,7 @@ const EastChakraDecisionJournal = React.memo(function EastChakraDecisionJournal2
63886
64185
  }
63887
64186
  ),
63888
64187
  j2.constraints.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", gap: "12px", flexWrap: "wrap", children: j2.constraints.map((c2, k2) => {
63889
- const tag = c2.type;
63890
- const f2 = formatConstraint(c2, leverPayloads[tag]);
64188
+ const f2 = formatConstraint(c2, contract, void 0, words);
63891
64189
  return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.ClauseChip, { field: f2.lever, op: f2.op, value: f2.value }, k2);
63892
64190
  }) })
63893
64191
  ]
@@ -64206,6 +64504,7 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
64206
64504
  const { requestOptions, queryOverrides, enabled = true, hash: hash2, platforms, type } = options2;
64207
64505
  const platformImpls = platforms ?? defaultPlatformImplementations;
64208
64506
  const reqOpts = requestOptions ?? { token: null };
64507
+ const queryClient = reactQuery.useQueryClient();
64209
64508
  const pathParts = React.useMemo(
64210
64509
  () => (datasetPath == null ? void 0 : datasetPath.split(".").filter(Boolean).map((v3) => east.variant("field", v3))) ?? [],
64211
64510
  [datasetPath]
@@ -64215,8 +64514,14 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
64215
64514
  queryFn: async () => {
64216
64515
  const result = await e3ApiClient.datasetGet(apiUrl, repo, workspace, pathParts, reqOpts);
64217
64516
  const decoded = east.decodeBeast2For(type, { platform: platformImpls })(result.data);
64218
- return { decoded, sizeBytes: result.data.length };
64517
+ const value = { decoded, sizeBytes: result.data.length, hash: result.hash ?? hash2 ?? null };
64518
+ if (value.hash !== hash2) {
64519
+ queryClient.setQueryData(["datasetValue", apiUrl, repo, workspace, datasetPath, value.hash], value);
64520
+ void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, datasetPath] });
64521
+ }
64522
+ return value;
64219
64523
  },
64524
+ select: (value) => value.hash === (hash2 ?? null) ? value : void 0,
64220
64525
  enabled: enabled && !!workspace && !!datasetPath && hash2 != null,
64221
64526
  ...queryOverrides
64222
64527
  });
@@ -64282,14 +64587,6 @@ function useTaskDetails(apiUrl, repo, workspace, taskName, options2) {
64282
64587
  ...options2 == null ? void 0 : options2.queryOverrides
64283
64588
  });
64284
64589
  }
64285
- function getTaskKind(details) {
64286
- var _a2;
64287
- return ((_a2 = details.kind) == null ? void 0 : _a2.type) === "some" ? details.kind.value : null;
64288
- }
64289
- function getTaskMetadata(details) {
64290
- var _a2;
64291
- return ((_a2 = details.metadata) == null ? void 0 : _a2.type) === "some" ? details.metadata.value : null;
64292
- }
64293
64590
  const variantStyles = {
64294
64591
  // Mode-aware semantic tokens (the raw `red.50` / `gray.50` washes rendered
64295
64592
  // as bright light boxes in dark mode — #362).
@@ -64427,6 +64724,10 @@ function pagingDebug(...args) {
64427
64724
  console.info("[e3-paging]", ...args);
64428
64725
  }
64429
64726
  const PAGE_SIZE = 500;
64727
+ function servedPageSize(requested, page) {
64728
+ const trimmed = page.count < requested && page.offset + page.count < page.totalElements;
64729
+ return trimmed ? Math.max(1, page.count) : requested;
64730
+ }
64430
64731
  const MAX_RETAINED_PAGES = 8;
64431
64732
  function pageRows(type, decoded, offset) {
64432
64733
  if (type.type === "Array") {
@@ -64466,6 +64767,7 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64466
64767
  onDownload,
64467
64768
  onNotIndexed
64468
64769
  }) {
64770
+ const words = eastUiComponents.useFormatters();
64469
64771
  const queryClient = reactQuery.useQueryClient();
64470
64772
  const [pages, setPages] = React.useState(/* @__PURE__ */ new Map());
64471
64773
  const [totals, setTotals] = React.useState(null);
@@ -64473,48 +64775,69 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64473
64775
  const [error3, setError] = React.useState(null);
64474
64776
  const [jumpRow, setJumpRow] = React.useState(void 0);
64475
64777
  const inflightRef = React.useRef(/* @__PURE__ */ new Set());
64778
+ const shownRef = React.useRef({ path: path2, hash: hash2 });
64779
+ const [pageSize, setPageSize] = React.useState(PAGE_SIZE);
64780
+ const pageSizeRef = React.useRef(PAGE_SIZE);
64476
64781
  React.useEffect(() => {
64477
64782
  pagingDebug(`reset (path=${path2}, hash=${hash2.slice(0, 8)})`);
64783
+ shownRef.current = { path: path2, hash: hash2 };
64478
64784
  setPages(/* @__PURE__ */ new Map());
64479
64785
  setTotals(null);
64480
64786
  setError(null);
64481
64787
  setJumpRow(void 0);
64482
- inflightRef.current.clear();
64788
+ inflightRef.current = /* @__PURE__ */ new Set();
64789
+ pageSizeRef.current = PAGE_SIZE;
64790
+ setPageSize(PAGE_SIZE);
64483
64791
  }, [path2, hash2]);
64484
64792
  const loadPage = React.useCallback((pageIdx) => {
64485
- if (inflightRef.current.has(pageIdx)) return;
64486
- inflightRef.current.add(pageIdx);
64793
+ const inflight = inflightRef.current;
64794
+ if (inflight.has(pageIdx)) return;
64795
+ inflight.add(pageIdx);
64796
+ const current = () => shownRef.current.path === path2 && shownRef.current.hash === hash2;
64797
+ const size3 = pageSizeRef.current;
64487
64798
  setLoadingCount((n2) => n2 + 1);
64488
- pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * PAGE_SIZE}, limit=${PAGE_SIZE}, hash=${hash2.slice(0, 8)})`);
64799
+ pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * size3}, limit=${size3}, hash=${hash2.slice(0, 8)})`);
64489
64800
  const pathParts2 = path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3));
64490
64801
  const reqOpts = requestOptions ?? { token: null };
64491
64802
  queryClient.fetchQuery({
64492
- queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}`],
64803
+ queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}@${size3}`],
64493
64804
  // Hash-pinned: the URL is a pure function of the bytes, so any
64494
64805
  // HTTP cache between here and the server can hold it immutably.
64495
- queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts2, { offset: pageIdx * PAGE_SIZE, limit: PAGE_SIZE, hash: hash2 }, reqOpts),
64806
+ queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts2, { offset: pageIdx * size3, limit: size3, hash: hash2 }, reqOpts),
64496
64807
  staleTime: Infinity
64497
64808
  // pages of one content hash are immutable
64498
64809
  }).then((page) => {
64810
+ if (!current()) return;
64811
+ if (size3 !== pageSizeRef.current) return;
64499
64812
  const decoded = east.decodeBeast2For(type)(page.data);
64500
64813
  const rows = pageRows(type, decoded, page.offset);
64501
64814
  pagingDebug(
64502
64815
  `p${pageIdx} ok: count=${page.count} rows=${rows.length} offset=${page.offset} total=${page.totalElements} segs=${page.segmentCount}`,
64503
64816
  rows.length > 0 ? `first=${JSON.stringify(rows[0].label ?? "(derived)")}` : "(empty)"
64504
64817
  );
64505
- if (page.count < PAGE_SIZE && page.offset + page.count < page.totalElements) {
64506
- pagingDebug(`p${pageIdx} SHORT page: server clamped ${PAGE_SIZE} → ${page.count}`);
64507
- }
64508
64818
  setTotals({ elements: page.totalElements, bytes: page.totalBytes });
64509
- setPages((prev2) => new Map(prev2).set(pageIdx, rows));
64819
+ const served = servedPageSize(size3, page);
64820
+ if (served === size3) {
64821
+ setPages((prev2) => new Map(prev2).set(pageIdx, rows));
64822
+ return;
64823
+ }
64824
+ pagingDebug(`p${pageIdx} SHORT page: served ${page.count} of ${size3} — addressing pages by ${served}`);
64825
+ pageSizeRef.current = served;
64826
+ setPageSize(served);
64827
+ setPages(pageIdx === 0 ? /* @__PURE__ */ new Map([[0, rows]]) : /* @__PURE__ */ new Map());
64510
64828
  }).catch((err) => {
64511
64829
  pagingDebug(`p${pageIdx} FAILED:`, err);
64830
+ if (!current()) return;
64831
+ if (err instanceof e3ApiClient.DatasetHashMismatchError) {
64832
+ void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
64833
+ return;
64834
+ }
64512
64835
  if (err instanceof e3ApiClient.ApiError && err.code === "dataset_not_indexed") {
64513
64836
  onNotIndexed == null ? void 0 : onNotIndexed();
64514
64837
  }
64515
64838
  setError(err instanceof Error ? err : new Error(String(err)));
64516
64839
  }).finally(() => {
64517
- inflightRef.current.delete(pageIdx);
64840
+ inflight.delete(pageIdx);
64518
64841
  setLoadingCount((n2) => n2 - 1);
64519
64842
  });
64520
64843
  }, [queryClient, apiUrl, repo, workspace, path2, hash2, requestOptions, type, onNotIndexed]);
@@ -64522,8 +64845,8 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64522
64845
  if (totals === null && error3 === null) loadPage(0);
64523
64846
  }, [totals, error3, loadPage]);
64524
64847
  const onNeedRows = React.useCallback((startRow, endRow) => {
64525
- const first2 = Math.max(0, Math.floor(startRow / PAGE_SIZE));
64526
- const last2 = Math.max(first2, Math.ceil(endRow / PAGE_SIZE) - 1);
64848
+ const first2 = Math.max(0, Math.floor(startRow / pageSize));
64849
+ const last2 = Math.max(first2, Math.ceil(endRow / pageSize) - 1);
64527
64850
  const missing = [];
64528
64851
  for (let p2 = first2; p2 <= last2; p2++) {
64529
64852
  if (!pages.has(p2)) missing.push(p2);
@@ -64531,7 +64854,7 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64531
64854
  pagingDebug(`need rows [${startRow}, ${endRow}) → pages ${first2}..${last2}, missing=[${missing.join(",")}], inflight=[${[...inflightRef.current].join(",")}]`);
64532
64855
  setPages((prev2) => internal$2.pruneRetainedPages(prev2, first2, last2, MAX_RETAINED_PAGES));
64533
64856
  for (const p2 of missing) loadPage(p2);
64534
- }, [pages, loadPage]);
64857
+ }, [pages, loadPage, pageSize]);
64535
64858
  const keyType = React.useMemo(() => type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64536
64859
  const pathParts = React.useMemo(() => path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3)), [path2]);
64537
64860
  const onFindKey = React.useCallback(async (query) => {
@@ -64568,11 +64891,11 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64568
64891
  }), [type]);
64569
64892
  const paging = React.useMemo(() => totals === null ? null : {
64570
64893
  totalRows: totals.elements,
64571
- pageSize: PAGE_SIZE,
64894
+ pageSize,
64572
64895
  pages,
64573
64896
  onNeedRows,
64574
64897
  scrollToRow: jumpRow
64575
- }, [totals, pages, onNeedRows, jumpRow]);
64898
+ }, [totals, pageSize, pages, onNeedRows, jumpRow]);
64576
64899
  if (error3 !== null) {
64577
64900
  if (error3 instanceof e3ApiClient.ApiError && error3.code === "dataset_too_large") {
64578
64901
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { height: "100%", direction: "column", align: "center", justify: "center", gap: 3, p: 6, children: [
@@ -64590,11 +64913,11 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64590
64913
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64591
64914
  /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64592
64915
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: [
64593
- totals.elements.toLocaleString(),
64916
+ words.number(totals.elements),
64594
64917
  " ",
64595
64918
  itemNoun,
64596
64919
  " · ",
64597
- formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes)
64920
+ formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes, words)
64598
64921
  ] }),
64599
64922
  loadingCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "Loading…" }),
64600
64923
  keyType !== null && /* @__PURE__ */ jsxRuntime.jsx(
@@ -64611,12 +64934,17 @@ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64611
64934
  ] }),
64612
64935
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: `${path2}:page`, paging }) })
64613
64936
  ] });
64614
- }, (prev2, next2) => prev2.path === next2.path && prev2.workspace === next2.workspace && prev2.hash === next2.hash && prev2.sizeBytes === next2.sizeBytes);
64937
+ }, (prev2, next2) => {
64938
+ var _a2, _b2;
64939
+ return prev2.apiUrl === next2.apiUrl && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.path === next2.path && prev2.hash === next2.hash && prev2.sizeBytes === next2.sizeBytes && ((_a2 = prev2.requestOptions) == null ? void 0 : _a2.token) === ((_b2 = next2.requestOptions) == null ? void 0 : _b2.token);
64940
+ });
64615
64941
  const DEFAULT_SIZE_LIMIT = 200 * 1024;
64616
- function formatSize(bytes) {
64617
- if (bytes < 1024) return `${bytes} B`;
64618
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
64619
- return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
64942
+ const KB_FORMAT = east.variant("number", { minimumFractionDigits: east.some(1n), maximumFractionDigits: east.some(1n), signDisplay: east.none });
64943
+ const MB_FORMAT = east.variant("number", { minimumFractionDigits: east.some(2n), maximumFractionDigits: east.some(2n), signDisplay: east.none });
64944
+ function formatSize(bytes, words = eastUiComponents.formatters()) {
64945
+ if (bytes < 1024) return `${words.number(bytes)} B`;
64946
+ if (bytes < 1024 * 1024) return `${words.value(bytes / 1024, KB_FORMAT)} KB`;
64947
+ return `${words.value(bytes / 1024 / 1024, MB_FORMAT)} MB`;
64620
64948
  }
64621
64949
  function DownloadButton({ onClick, label }) {
64622
64950
  const [downloading, setDownloading] = React.useState(false);
@@ -64644,6 +64972,7 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64644
64972
  editable = false
64645
64973
  }) {
64646
64974
  var _a2;
64975
+ const words = eastUiComponents.useFormatters();
64647
64976
  const statusQuery = useDatasetStatus(apiUrl, repo, workspace, path2, {
64648
64977
  ...requestOptions != null && { requestOptions },
64649
64978
  ...pollInterval !== void 0 && { pollInterval }
@@ -64671,12 +65000,29 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64671
65000
  const setMutation = useDatasetSet(apiUrl, repo, workspace, requestOptions);
64672
65001
  const queryClient = reactQuery.useQueryClient();
64673
65002
  const decoded = (_a2 = valueQuery.data) == null ? void 0 : _a2.decoded;
64674
- const write = React.useCallback(async (next2) => {
64675
- if (workspace == null || path2 == null || type === void 0) return;
65003
+ const edited = React.useRef(null);
65004
+ const writing = React.useRef(0);
65005
+ const writes = React.useRef(Promise.resolve());
65006
+ React.useEffect(() => {
65007
+ if (writing.current === 0) edited.current = null;
65008
+ }, [decoded]);
65009
+ const write = React.useCallback((next2) => {
65010
+ if (workspace == null || path2 == null || type === void 0) return Promise.resolve();
64676
65011
  const treePath = path2.split(".").filter(Boolean).map((p2) => east.variant("field", p2));
64677
65012
  const data4 = east.encodeBeast2For(type)(next2);
64678
- await setMutation.mutateAsync({ path: treePath, data: data4 });
64679
- await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
65013
+ edited.current = { value: next2 };
65014
+ writing.current++;
65015
+ const run = writes.current.then(async () => {
65016
+ await setMutation.mutateAsync({ path: treePath, data: data4 });
65017
+ await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
65018
+ });
65019
+ writes.current = run.catch(() => void 0);
65020
+ return run.catch((err) => {
65021
+ edited.current = null;
65022
+ throw err;
65023
+ }).finally(() => {
65024
+ writing.current--;
65025
+ });
64680
65026
  }, [apiUrl, repo, workspace, path2, type, setMutation, queryClient]);
64681
65027
  const keyType = React.useMemo(() => type === void 0 ? null : type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64682
65028
  const inlineKeys = React.useMemo(() => decoded === void 0 || type === void 0 ? null : type.type === "Dict" ? [...decoded.keys()] : type.type === "Set" ? [...decoded.values()] : null, [type, decoded]);
@@ -64712,11 +65058,12 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64712
65058
  const treeValue = React.useMemo(() => {
64713
65059
  if (type === void 0 || decoded === void 0) return null;
64714
65060
  const root2 = eastUi.ValueTree.materialize(type, decoded);
65061
+ const base = () => edited.current !== null ? edited.current.value : decoded;
64715
65062
  const wire = editable ? {
64716
- onEdit: east.some((p2, leaf) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "edit", leaf }))),
64717
- onInsert: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "insert" }))),
64718
- onRemove: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "remove" }))),
64719
- onTag: east.some((p2, tag) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "tag", tag })))
65063
+ onEdit: east.some((p2, leaf) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "edit", leaf }))),
65064
+ onInsert: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "insert" }))),
65065
+ onRemove: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "remove" }))),
65066
+ onTag: east.some((p2, tag) => write(eastUi.ValueTree.applyEdit(type, base(), p2, { kind: "tag", tag })))
64720
65067
  } : { onEdit: east.none, onInsert: east.none, onRemove: east.none, onTag: east.none };
64721
65068
  return { root: root2, ...wire, style: east.some({ height: east.some("100%"), maxHeight: east.none, openDepth: east.none, toolbar: east.some(true) }) };
64722
65069
  }, [type, decoded, editable, write]);
@@ -64750,18 +65097,21 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64750
65097
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "lg", color: "fg.warning", fontWeight: "bold", children: "Value too large to display" }),
64751
65098
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { color: "fg.muted", fontSize: "sm", children: [
64752
65099
  "The data is ",
64753
- formatSize(sizeBytes),
65100
+ formatSize(sizeBytes, words),
64754
65101
  ", which exceeds the ",
64755
- formatSize(sizeLimit),
65102
+ formatSize(sizeLimit, words),
64756
65103
  " display limit."
64757
65104
  ] }),
64758
65105
  /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download, label: "Download value" })
64759
65106
  ] });
64760
65107
  }
65108
+ if (valueQuery.error) {
65109
+ const { message, details } = formatApiError(valueQuery.error);
65110
+ return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Load failed", message, details: details ?? formatError(valueQuery.error) });
65111
+ }
64761
65112
  if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64762
- if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
64763
65113
  const count = type === void 0 || decoded === void 0 ? null : type.type === "Array" ? decoded.length : type.type === "Set" || type.type === "Dict" ? decoded.size : null;
64764
- const countText = count === null ? "" : `${count.toLocaleString()} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
65114
+ const countText = count === null ? "" : `${words.number(count)} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
64765
65115
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64766
65116
  /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64767
65117
  keyType !== null && inlineKeys !== null && /* @__PURE__ */ jsxRuntime.jsx(
@@ -64777,7 +65127,7 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64777
65127
  /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { flex: 1, justify: "flex-end", align: "center", gap: 2, children: [
64778
65128
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", children: [
64779
65129
  countText,
64780
- formatSize(sizeBytes)
65130
+ formatSize(sizeBytes, words)
64781
65131
  ] }),
64782
65132
  /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download })
64783
65133
  ] })
@@ -64830,19 +65180,15 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64830
65180
  const workspace = (config2 == null ? void 0 : config2.workspace) ?? (e3 == null ? void 0 : e3.workspace) ?? null;
64831
65181
  const token2 = (config2 == null ? void 0 : config2.token) ?? (e3 == null ? void 0 : e3.token) ?? null;
64832
65182
  const requestOptions = { token: token2 };
65183
+ const bound3 = cache3 == null ? void 0 : cache3.getConfig().workspace;
65184
+ const foreign = (config2 == null ? void 0 : config2.workspace) !== void 0 && bound3 !== void 0 && config2.workspace !== bound3;
64833
65185
  const detailsQuery = useTaskDetails(apiUrl ?? "", repo, workspace, task, { requestOptions });
64834
65186
  const details = detailsQuery.data;
64835
- const kind = details ? getTaskKind(details) : null;
64836
- const isUI = kind === "ui";
64837
- const manifest = React.useMemo(() => {
64838
- if (!details || !isUI) return null;
64839
- const meta3 = getTaskMetadata(details);
64840
- return meta3 ? internal$1.decodeManifest(meta3) : { paths: [], functions: [], records: [], pages: [] };
64841
- }, [details, isUI]);
64842
- const outputPath = details ? treePathToString$1(details.output) : null;
65187
+ const manifest = React.useMemo(() => (details == null ? void 0 : details.role.type) === "ui" ? details.role.value : null, [details]);
65188
+ const outputPath = details ? treePathToString$1(details.output.path) : null;
64843
65189
  const preloads = React.useMemo(
64844
- () => manifest && workspace ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
64845
- [manifest, workspace]
65190
+ () => manifest && workspace && !foreign ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
65191
+ [manifest, workspace, foreign]
64846
65192
  );
64847
65193
  const { loading: preloading, error: preloadError } = usePreloadReactiveDatasets(preloads);
64848
65194
  const scopedPlatforms = React.useMemo(
@@ -64868,7 +65214,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64868
65214
  [manifest]
64869
65215
  );
64870
65216
  React.useEffect(() => {
64871
- if (!cache3 || !manifest || !workspace) return;
65217
+ if (!cache3 || !manifest || !workspace || foreign) return;
64872
65218
  for (const path2 of manifest.paths) {
64873
65219
  cache3.setRefetchInterval(workspace, path2, pollInterval);
64874
65220
  }
@@ -64877,7 +65223,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64877
65223
  cache3.clearRefetchInterval(workspace, path2);
64878
65224
  }
64879
65225
  };
64880
- }, [cache3, manifest, workspace, pollInterval]);
65226
+ }, [cache3, manifest, workspace, pollInterval, foreign]);
64881
65227
  const statusQuery = useDatasetStatus(apiUrl ?? "", repo, workspace, outputPath, { requestOptions });
64882
65228
  const valueQuery = useDatasetValue(apiUrl ?? "", repo, workspace, outputPath, {
64883
65229
  requestOptions,
@@ -64887,16 +65233,20 @@ const UITaskPreview = React.memo(function UITaskPreview2({
64887
65233
  });
64888
65234
  const store = React.useMemo(() => eastUiComponents.createUIStore(), [task]);
64889
65235
  if (!apiUrl) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Configuration error", message: "No apiUrl available — render UITaskPreview inside an <E3Provider> or pass a config prop." });
65236
+ if (foreign) {
65237
+ return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Workspace mismatch", message: `config names workspace "${config2 == null ? void 0 : config2.workspace}", and the data bindings read workspace "${bound3}", the one the surrounding <ReactiveDatasetProvider> serves: a page previews one workspace.` });
65238
+ }
64890
65239
  if (detailsQuery.isLoading) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." });
64891
65240
  if (detailsQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message });
64892
65241
  if (!details) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No task", message: `Task "${task}" not found` });
64893
- if (!isUI) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Not a UI task", message: `Task "${task}" has kind "${kind ?? "(none)"}"` });
65242
+ if (!manifest) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Not a UI task", message: `Task "${task}" is a ${details.role.type} task` });
64894
65243
  if (preloadError) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Preload failed", message: preloadError.message });
64895
65244
  if (preloading) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading datasets..." });
65245
+ if (statusQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: statusQuery.error.message });
64896
65246
  if (statusQuery.isLoading || !statusQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64897
65247
  if (statusQuery.data.refType !== "value") return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No output yet", message: "Task has not produced a value" });
65248
+ if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Load failed", message: valueQuery.error.message });
64898
65249
  if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64899
- if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
64900
65250
  return /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.UIStoreProvider, { store, children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { height: "100%", overflow: bare ? void 0 : "auto", p: bare ? void 0 : "4", minH: bare ? 0 : void 0, children: /* @__PURE__ */ jsxRuntime.jsx(
64901
65251
  eastUiComponents.EastChakraComponent,
64902
65252
  {
@@ -65353,7 +65703,7 @@ const DataTaskPreview = React.memo(function DataTaskPreview2({
65353
65703
  const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
65354
65704
  ...requestOptions != null && { requestOptions }
65355
65705
  });
65356
- const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output) : null;
65706
+ const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output.path) : null;
65357
65707
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
65358
65708
  /* @__PURE__ */ jsxRuntime.jsx(
65359
65709
  react.Flex,
@@ -65417,13 +65767,14 @@ const TaskPreview = React.memo(function TaskPreview2({
65417
65767
  requestOptions,
65418
65768
  bare = false
65419
65769
  }) {
65770
+ var _a2;
65420
65771
  const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
65421
65772
  ...requestOptions != null && { requestOptions }
65422
65773
  });
65423
- const kind = detailsQuery.data ? getTaskKind(detailsQuery.data) : null;
65774
+ const isUI = ((_a2 = detailsQuery.data) == null ? void 0 : _a2.role.type) === "ui";
65424
65775
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
65425
65776
  !bare && /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { px: 4, py: 2, borderBottom: "1px solid", borderColor: "border.subtle", bg: "bg.surface", align: "center", flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg", children: task }) }),
65426
- /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) : kind === "ui" ? /* @__PURE__ */ jsxRuntime.jsx(
65777
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) : isUI ? /* @__PURE__ */ jsxRuntime.jsx(
65427
65778
  UITaskPreview,
65428
65779
  {
65429
65780
  task,
@@ -65441,33 +65792,16 @@ const TaskPreview = React.memo(function TaskPreview2({
65441
65792
  }
65442
65793
  ) })
65443
65794
  ] });
65444
- }, (prev2, next2) => prev2.task === next2.task && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.bare === next2.bare);
65795
+ }, (prev2, next2) => {
65796
+ var _a2, _b2;
65797
+ return prev2.task === next2.task && prev2.apiUrl === next2.apiUrl && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.bare === next2.bare && ((_a2 = prev2.requestOptions) == null ? void 0 : _a2.token) === ((_b2 = next2.requestOptions) == null ? void 0 : _b2.token);
65798
+ });
65445
65799
  function formatPrimitive(type, value) {
65446
- switch (type.type) {
65447
- case "Null":
65448
- return "null";
65449
- case "Boolean":
65450
- return value ? "true" : "false";
65451
- case "Integer":
65452
- return String(value);
65453
- case "Float": {
65454
- const num2 = value;
65455
- if (Number.isNaN(num2)) return "NaN";
65456
- if (!Number.isFinite(num2)) return num2 > 0 ? "Infinity" : "-Infinity";
65457
- return String(num2);
65458
- }
65459
- case "String":
65460
- return `"${value}"`;
65461
- case "DateTime":
65462
- return value.toISOString();
65463
- case "Blob":
65464
- return `Blob[${value.length} bytes]`;
65465
- default:
65466
- return String(value);
65467
- }
65800
+ if (type.type === "Blob") return `Blob[${value.length} bytes]`;
65801
+ return east.printFor(type)(value);
65468
65802
  }
65469
65803
  function isPrimitive(type) {
65470
- return ["Null", "Boolean", "Integer", "Float", "String", "DateTime", "Blob", "Never"].includes(type.type);
65804
+ return ["Null", "Boolean", "Integer", "Float", "String", "DateTime", "Blob"].includes(type.type);
65471
65805
  }
65472
65806
  function getTypeLabel(type, value) {
65473
65807
  switch (type.type) {
@@ -65589,8 +65923,8 @@ function ValueNode({
65589
65923
  }
65590
65924
  if (type.type === "Variant") {
65591
65925
  const cases = type.value;
65592
- const variant = value;
65593
- const activeCase = cases.find((c2) => c2.name === variant.type);
65926
+ const chosen = value;
65927
+ const activeCase = cases.find((c2) => c2.name === chosen.type);
65594
65928
  if (!activeCase) {
65595
65929
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { py: 0.5, display: "flex", alignItems: "center", gap: 2, children: [
65596
65930
  label && /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", color: "accent.purple", fontWeight: "medium", children: [
@@ -65599,7 +65933,7 @@ function ValueNode({
65599
65933
  ] }),
65600
65934
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", color: "fg.danger", children: [
65601
65935
  "Unknown variant: ",
65602
- variant.type
65936
+ chosen.type
65603
65937
  ] })
65604
65938
  ] });
65605
65939
  }
@@ -65611,7 +65945,7 @@ function ValueNode({
65611
65945
  ] }),
65612
65946
  /* @__PURE__ */ jsxRuntime.jsxs(react.Badge, { size: "xs", colorPalette: "brand", variant: "subtle", children: [
65613
65947
  ".",
65614
- variant.type
65948
+ chosen.type
65615
65949
  ] })
65616
65950
  ] });
65617
65951
  }
@@ -65623,14 +65957,14 @@ function ValueNode({
65623
65957
  ] }),
65624
65958
  /* @__PURE__ */ jsxRuntime.jsxs(react.Badge, { size: "xs", colorPalette: "brand", variant: "subtle", children: [
65625
65959
  ".",
65626
- variant.type
65960
+ chosen.type
65627
65961
  ] })
65628
65962
  ] }),
65629
65963
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { pl: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
65630
65964
  ValueNode,
65631
65965
  {
65632
65966
  type: activeCase.type,
65633
- value: variant.value,
65967
+ value: chosen.value,
65634
65968
  depth: depth + 1
65635
65969
  }
65636
65970
  ) })
@@ -65838,8 +66172,6 @@ exports.funcChannelKey = funcChannelKey;
65838
66172
  exports.getBindingTypes = getBindingTypes;
65839
66173
  exports.getReactiveDatasetCache = getReactiveDatasetCache;
65840
66174
  exports.getStagedStore = getStagedStore;
65841
- exports.getTaskKind = getTaskKind;
65842
- exports.getTaskMetadata = getTaskMetadata;
65843
66175
  exports.initializeFunctionApi = initializeFunctionApi;
65844
66176
  exports.initializePagedApi = initializePagedApi;
65845
66177
  exports.initializeReactiveDatasetCache = initializeReactiveDatasetCache;
@@ -65847,6 +66179,7 @@ exports.initializeRecordApi = initializeRecordApi;
65847
66179
  exports.initializeStagedStore = initializeStagedStore;
65848
66180
  exports.isBindingTracking = isBindingTracking;
65849
66181
  exports.onWriteError = onWriteError;
66182
+ exports.pagedRevisionKey = pagedRevisionKey;
65850
66183
  exports.pagedTotalKey = pagedTotalKey;
65851
66184
  exports.pagedWindowKey = pagedWindowKey;
65852
66185
  exports.preloadReactiveDatasetList = preloadReactiveDatasetList;