@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.js CHANGED
@@ -18,22 +18,22 @@ var __privateWrapper = (obj, member, setter, getter) => ({
18
18
  }
19
19
  });
20
20
  var _focused, _cleanup, _setup, _a, _provider, _providerCalled, _b, _online, _cleanup2, _setup2, _c, _gcTimeout, _d, _initialState, _revertState, _cache, _client, _retryer, _defaultOptions, _abortSignalConsumed, _Query_instances, isInitialPausedFetch_fn, dispatch_fn, _e, _client2, _observers, _mutationCache, _retryer2, _Mutation_instances, dispatch_fn2, _f, _mutations, _scopes, _mutationId, _g, _queries, _h, _queryCache, _mutationCache2, _defaultOptions2, _queryDefaults, _mutationDefaults, _mountCount, _unsubscribeFocus, _unsubscribeOnline, _i, _j;
21
- import { variant, SortedMap, compareFor, EastError, fromEastTypeValue, East, none as none$1, some, OptionType, BooleanType, NullType, ConflictError, equalFor, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, IntegerType, StructType, StringType, walkPatch, pathToString, pathDisplay, printFor, isVariant, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, isEastSet, isEastDict, ArrayType, parseFor } from "@elaraai/east";
22
- import { EastTypeType } from "@elaraai/east/internal";
23
- import { DataBindModeType, DataBindPrimitives, bindPlatformFn, DataPagedPrimitives, bindPagedPlatformFn, FuncBindPrimitives, FuncErrorType, FuncStatusType, funcBindPlatformFn, RecordBindPrimitives, recordBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionBindPrimitives, DecisionConstraintType, DiffBindingType, CommitStateType, VerdictType, AnswerType, judgementInputType, DecisionType, DecisionQueue, DecisionJournal, decodeManifest } from "@elaraai/e3-ui/internal";
21
+ import { variant, SortedMap, compareFor, EastError, fromEastTypeValue, East, none as none$1, some, OptionType, BooleanType, NullType, ConflictError, equalFor, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, StringType, IntegerType, isTypeValueEqual, StructType, walkPatch, pathToString, pathDisplay, parseFor, printFor, FloatType, equivalentFor, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, canonicalTypeValue, ArrayType, EastTypeValueType, defaultValue, DateTimeType } from "@elaraai/east";
22
+ import { EastTypeType, formatDateTime, parseDateTimeFormatted, tokenizeDateTimeFormat } from "@elaraai/east/internal";
23
+ import { DataBindModeType, DataBindPrimitives, bindPlatformFn, DataPagedPrimitives, bindPagedPlatformFn, bindPagedPinnedPlatformFn, FuncBindPrimitives, FuncErrorType, FuncStatusType, funcBindPlatformFn, RecordBindPrimitives, recordBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionBindPrimitives, DecisionConstraintType, DiffBindingType, CommitStateType, VerdictType, AnswerType, judgementInputType, DecisionType, DecisionQueue, DecisionJournal } from "@elaraai/e3-ui/internal";
24
24
  import { registerReactiveTracker, registerPlatformImplementation, StateRuntime, getRegisteredPlatformImplementations, boundSliceConfig } from "@elaraai/east-ui-components/platform";
25
- import { TreePathType, DatasetStatusType } from "@elaraai/e3-types";
26
- import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, ApiError, datasetFindKey, datasetGetPage, workspaceFunctionCall, workspaceFunctionList, workspaceRecordHistory, workspaceRecordMutate, workspaceRecordDescribe, repoCreate, repoGc, repoGcStart, repoGcStatus, repoList, repoRemove, repoStatus, packageExport, packageGet, packageImport, packageList, packageRemove, workspaceCreate, workspaceDeploy, workspaceExport, workspaceGet, workspaceList, workspaceRemove, datasetGetStatus, datasetListRecursive, datasetListRecursivePaths, datasetListWithStatus, taskExecutionList, taskGet, taskList, dataflowCancel, dataflowExecute, dataflowExecutePoll, dataflowGraph, taskLogs } from "@elaraai/e3-api-client";
27
- import { SeekQueryType, SeekRangeType, ValueTree } from "@elaraai/east-ui";
25
+ import { TreePathType, DatasetStatusType, pathToString as pathToString$1 } from "@elaraai/e3-types";
26
+ import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, ApiError, DatasetHashMismatchError, datasetGetStatus, datasetFindKey, datasetGetPage, workspaceFunctionCall, workspaceFunctionList, workspaceRecordHistory, workspaceRecordMutate, workspaceRecordDescribe, repoCreate, repoGc, repoGcStart, repoGcStatus, repoList, repoRemove, repoStatus, packageExport, packageGet, packageImport, packageList, packageRemove, workspaceCreate, workspaceDeploy, workspaceExport, workspaceGet, workspaceList, workspaceRemove, datasetListRecursive, datasetListRecursivePaths, datasetListWithStatus, taskExecutionList, taskGet, taskList, dataflowCancel, dataflowExecute, dataflowExecutePoll, dataflowGraph, taskLogs } from "@elaraai/e3-api-client";
27
+ import { SeekQueryType, SeekRangeType, slicePredicateMatches, ValueTree } from "@elaraai/east-ui";
28
28
  import { jsx as jsx$1, Fragment, jsxs } from "react/jsx-runtime";
29
29
  import * as React from "react";
30
- import React__default, { useId, useMemo, useContext, createContext, useRef, useEffect, useState, useCallback, useSyncExternalStore, memo, forwardRef, useLayoutEffect, Fragment as Fragment$1, Component } from "react";
30
+ import React__default, { useId, useMemo, useContext, createContext, useRef, useState, useEffect, useCallback, useSyncExternalStore, memo, forwardRef, useLayoutEffect, Fragment as Fragment$1, Component } from "react";
31
31
  import { Box, Flex, Text, HStack, Input, useSlotRecipe, useRecipe, VStack, useToken, Drawer, Portal, Field, NativeSelect, Textarea, chakra, Tooltip, SegmentGroup, Menu, HoverCard, useChakraContext, Spinner, Code, Clipboard, IconButton, Button, Tabs, Badge, useTabs } from "@chakra-ui/react";
32
- import { implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, useContainerBelow, usePersistedState, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, LoadingIcon, DatasetKeySearch, EastChakraValueTree, keyRangePredicates, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
32
+ import { formatters, useFormatters, useDataStable, implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, useSliceReactivity, useContainerBelow, usePersistedState, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, LoadingIcon, DatasetKeySearch, EastChakraValueTree, keyRangePredicates, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
33
33
  import { DatasetKeySearch as DatasetKeySearch2 } from "@elaraai/east-ui-components";
34
34
  import { createPortal } from "react-dom";
35
35
  import { sliceMatches, pruneRetainedPages } from "@elaraai/east-ui/internal";
36
- import { QueryClient as QueryClient$1, QueryClientProvider, useMutation, useQuery, keepPreviousData, useQueryClient } from "@tanstack/react-query";
36
+ import { QueryClient as QueryClient$1, QueryClientProvider, useMutation, useQuery, useQueryClient, keepPreviousData } from "@tanstack/react-query";
37
37
  import { useVirtualizer } from "@tanstack/react-virtual";
38
38
  var Subscribable = class {
39
39
  constructor() {
@@ -2060,6 +2060,7 @@ const realClock = {
2060
2060
  return { clear: () => clearInterval(id3) };
2061
2061
  }
2062
2062
  };
2063
+ const HASH_POLL_MS = 1e3;
2063
2064
  function createDefaultDatasetApi(apiUrl, repo, getToken) {
2064
2065
  const opts = () => ({ token: getToken() });
2065
2066
  return {
@@ -2125,12 +2126,17 @@ class ReactiveDatasetCache {
2125
2126
  __publicField(this, "keyVersions", /* @__PURE__ */ new Map());
2126
2127
  // Workspace status polling — groups subscriptions by workspace for
2127
2128
  // efficiency. Each entry holds the active interval handle from the
2128
- // injected {@link Clock} so tests can drive ticking deterministically.
2129
+ // injected {@link Clock} so tests can drive ticking deterministically,
2130
+ // and each polled path with how many watchers poll it.
2129
2131
  __publicField(this, "workspacePollers", /* @__PURE__ */ new Map());
2130
2132
  __publicField(this, "clock");
2131
2133
  // In-flight poll dedup — concurrent callers (interval tick + a
2132
2134
  // post-write trigger) share a single round-trip rather than racing.
2133
2135
  __publicField(this, "inFlightPolls", /* @__PURE__ */ new Map());
2136
+ // Hash-only watches, by workspace and path: each callback with the hash it
2137
+ // was last told. A watched path rides its workspace's poll, but its
2138
+ // content is never fetched — a paged source reads windows, never the value.
2139
+ __publicField(this, "hashWatches", /* @__PURE__ */ new Map());
2134
2140
  // Batching
2135
2141
  __publicField(this, "batchDepth", 0);
2136
2142
  __publicField(this, "changedKeys", /* @__PURE__ */ new Set());
@@ -2219,9 +2225,7 @@ class ReactiveDatasetCache {
2219
2225
  hash: void 0,
2220
2226
  status: variant("stale", null)
2221
2227
  });
2222
- if (this.workspacePollers.has(workspace)) {
2223
- void this.pollWorkspaceStatus(workspace);
2224
- }
2228
+ void this.refresh(workspace);
2225
2229
  } catch (error3) {
2226
2230
  if (!this.destroyed && this.writeEpochs.get(key) === myEpoch) {
2227
2231
  const baseline = this.writeBaselines.get(key);
@@ -2327,15 +2331,15 @@ class ReactiveDatasetCache {
2327
2331
  * 3. Only fetches full content when hash changes
2328
2332
  *
2329
2333
  * Multiple subscriptions to the same workspace share a single poller.
2330
- * Pair with {@link clearRefetchInterval} on unmount so the poller can
2331
- * stop when nothing is watching.
2334
+ * Pair each call with one {@link clearRefetchInterval} on unmount so the
2335
+ * poller can stop when nothing is watching.
2332
2336
  */
2333
2337
  setRefetchInterval(workspace, path2, intervalMs) {
2334
2338
  var _a2;
2335
2339
  const pathStr = datasetPathToString(path2);
2336
2340
  let poller = this.workspacePollers.get(workspace);
2337
2341
  if (poller) {
2338
- poller.paths.add(pathStr);
2342
+ poller.paths.set(pathStr, (poller.paths.get(pathStr) ?? 0) + 1);
2339
2343
  if (intervalMs < poller.intervalMs) {
2340
2344
  (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2341
2345
  poller.intervalMs = intervalMs;
@@ -2347,7 +2351,7 @@ class ReactiveDatasetCache {
2347
2351
  } else {
2348
2352
  poller = {
2349
2353
  intervalMs,
2350
- paths: /* @__PURE__ */ new Set([pathStr]),
2354
+ paths: /* @__PURE__ */ new Map([[pathStr, 1]]),
2351
2355
  handle: this.clock.setInterval(
2352
2356
  () => this.pollWorkspaceStatus(workspace),
2353
2357
  intervalMs
@@ -2358,23 +2362,79 @@ class ReactiveDatasetCache {
2358
2362
  }
2359
2363
  }
2360
2364
  /**
2361
- * Stop polling a dataset; the workspace poller is torn down when its
2362
- * last watched path is cleared.
2365
+ * Stop polling a dataset for one watcher; the path goes when its last
2366
+ * watcher clears it, and the workspace poller is torn down when its last
2367
+ * path goes.
2363
2368
  */
2364
2369
  clearRefetchInterval(workspace, path2) {
2365
- var _a2;
2366
2370
  const poller = this.workspacePollers.get(workspace);
2367
2371
  if (!poller) return;
2368
- poller.paths.delete(datasetPathToString(path2));
2369
- if (poller.paths.size === 0) {
2372
+ const pathStr = datasetPathToString(path2);
2373
+ const watchers = (poller.paths.get(pathStr) ?? 0) - 1;
2374
+ if (watchers > 0) {
2375
+ poller.paths.set(pathStr, watchers);
2376
+ return;
2377
+ }
2378
+ poller.paths.delete(pathStr);
2379
+ this.stopIdlePoller(workspace);
2380
+ }
2381
+ /**
2382
+ * Follow a dataset's content hash without fetching its content; see the
2383
+ * interface doc.
2384
+ */
2385
+ watchHash(workspace, path2, onHash, intervalMs = HASH_POLL_MS) {
2386
+ var _a2;
2387
+ const pathStr = datasetPathToString(path2);
2388
+ let byPath = this.hashWatches.get(workspace);
2389
+ if (!byPath) {
2390
+ byPath = /* @__PURE__ */ new Map();
2391
+ this.hashWatches.set(workspace, byPath);
2392
+ }
2393
+ let callbacks = byPath.get(pathStr);
2394
+ if (!callbacks) {
2395
+ callbacks = /* @__PURE__ */ new Map();
2396
+ byPath.set(pathStr, callbacks);
2397
+ }
2398
+ callbacks.set(onHash, void 0);
2399
+ const poller = this.workspacePollers.get(workspace);
2400
+ if (!poller) {
2401
+ this.workspacePollers.set(workspace, {
2402
+ intervalMs,
2403
+ paths: /* @__PURE__ */ new Map(),
2404
+ handle: this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs)
2405
+ });
2406
+ void this.pollWorkspaceStatus(workspace);
2407
+ } else if (intervalMs < poller.intervalMs) {
2370
2408
  (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2371
- this.workspacePollers.delete(workspace);
2409
+ poller.intervalMs = intervalMs;
2410
+ poller.handle = this.clock.setInterval(() => this.pollWorkspaceStatus(workspace), intervalMs);
2372
2411
  }
2412
+ return () => {
2413
+ const watched = this.hashWatches.get(workspace);
2414
+ const set3 = watched == null ? void 0 : watched.get(pathStr);
2415
+ if (!watched || !set3) return;
2416
+ set3.delete(onHash);
2417
+ if (set3.size === 0) watched.delete(pathStr);
2418
+ if (watched.size === 0) this.hashWatches.delete(workspace);
2419
+ this.stopIdlePoller(workspace);
2420
+ };
2421
+ }
2422
+ /** Stops a workspace's poller once it has no path to reconcile and no hash
2423
+ * to follow — without this, a long-lived session polls forever. */
2424
+ stopIdlePoller(workspace) {
2425
+ var _a2;
2426
+ const poller = this.workspacePollers.get(workspace);
2427
+ if (!poller || poller.paths.size > 0 || this.hashWatches.has(workspace)) return;
2428
+ (_a2 = poller.handle) == null ? void 0 : _a2.clear();
2429
+ this.workspacePollers.delete(workspace);
2373
2430
  }
2374
2431
  refresh(workspace) {
2375
2432
  if (this.destroyed) return Promise.resolve();
2376
2433
  if (!this.workspacePollers.has(workspace)) return Promise.resolve();
2377
- return this.pollWorkspaceStatus(workspace);
2434
+ const inFlight = this.inFlightPolls.get(workspace);
2435
+ if (inFlight === void 0) return this.pollWorkspaceStatus(workspace);
2436
+ const again = () => this.destroyed || !this.workspacePollers.has(workspace) ? void 0 : this.pollWorkspaceStatus(workspace);
2437
+ return inFlight.then(again, again);
2378
2438
  }
2379
2439
  /**
2380
2440
  * Poll workspace status and reconcile the cache with the server.
@@ -2390,11 +2450,12 @@ class ReactiveDatasetCache {
2390
2450
  return promise4;
2391
2451
  }
2392
2452
  async doPollWorkspaceStatus(workspace) {
2453
+ var _a2;
2393
2454
  if (this.destroyed) return;
2394
2455
  const poller = this.workspacePollers.get(workspace);
2395
- if (!poller || poller.paths.size === 0) return;
2456
+ if (!poller || poller.paths.size === 0 && !this.hashWatches.has(workspace)) return;
2396
2457
  const epochsAtStart = /* @__PURE__ */ new Map();
2397
- for (const pathStr of poller.paths) {
2458
+ for (const pathStr of poller.paths.keys()) {
2398
2459
  const key = pathStr ? `${workspace}.${pathStr}` : workspace;
2399
2460
  epochsAtStart.set(key, this.writeEpochs.get(key) ?? 0);
2400
2461
  }
@@ -2408,15 +2469,28 @@ class ReactiveDatasetCache {
2408
2469
  if (this.destroyed) return;
2409
2470
  const infoByPath = /* @__PURE__ */ new Map();
2410
2471
  for (const info of status.datasets) infoByPath.set(info.path, info);
2472
+ for (const [pathStr, callbacks] of this.hashWatches.get(workspace) ?? []) {
2473
+ const info = infoByPath.get(pathStr ? `.${pathStr}` : "");
2474
+ const hash2 = ((_a2 = info == null ? void 0 : info.hash) == null ? void 0 : _a2.type) === "some" ? info.hash.value : null;
2475
+ for (const [onHash, told] of [...callbacks]) {
2476
+ if (told === hash2 || !callbacks.has(onHash)) continue;
2477
+ callbacks.set(onHash, hash2);
2478
+ try {
2479
+ onHash(hash2);
2480
+ } catch (error3) {
2481
+ console.error(`A hash watch on ${workspace}.${pathStr} failed:`, error3);
2482
+ }
2483
+ }
2484
+ }
2411
2485
  const pending = [];
2412
2486
  this.batch(() => {
2413
- var _a2;
2414
- for (const pathStr of poller.paths) {
2487
+ var _a3;
2488
+ for (const pathStr of poller.paths.keys()) {
2415
2489
  const e3Path = pathStr ? `.${pathStr}` : "";
2416
2490
  const info = infoByPath.get(e3Path);
2417
2491
  const key = pathStr ? `${workspace}.${pathStr}` : workspace;
2418
2492
  const path2 = this.stringToPath(pathStr);
2419
- const currentHash = ((_a2 = info == null ? void 0 : info.hash) == null ? void 0 : _a2.type) === "some" ? info.hash.value : null;
2493
+ const currentHash = ((_a3 = info == null ? void 0 : info.hash) == null ? void 0 : _a3.type) === "some" ? info.hash.value : null;
2420
2494
  const knownHash = this.knownHashes.get(key);
2421
2495
  if (info == null ? void 0 : info.status) {
2422
2496
  const previous = this.statuses.get(key);
@@ -2582,6 +2656,7 @@ class ReactiveDatasetCache {
2582
2656
  }
2583
2657
  this.workspacePollers.clear();
2584
2658
  this.inFlightPolls.clear();
2659
+ this.hashWatches.clear();
2585
2660
  this.keySubscribers.clear();
2586
2661
  this.globalSubscribers.clear();
2587
2662
  this.knownHashes.clear();
@@ -2696,9 +2771,16 @@ class MemoryStagedAdapter {
2696
2771
  this.map.clear();
2697
2772
  }
2698
2773
  }
2774
+ const SCOPE_SEPARATOR = "\0";
2775
+ function cacheKeyOf(key) {
2776
+ return key.slice(key.indexOf(SCOPE_SEPARATOR) + 1);
2777
+ }
2699
2778
  class StagedStore {
2700
2779
  constructor(adapter) {
2780
+ // By scope and `datasetCacheKey`; subscribers and versions go by the
2781
+ // `datasetCacheKey` alone, which is what a reactive view tracks.
2701
2782
  __publicField(this, "entries", /* @__PURE__ */ new Map());
2783
+ __publicField(this, "scope", null);
2702
2784
  __publicField(this, "subscribers", /* @__PURE__ */ new Map());
2703
2785
  __publicField(this, "versions", /* @__PURE__ */ new Map());
2704
2786
  __publicField(this, "adapter");
@@ -2722,25 +2804,25 @@ class StagedStore {
2722
2804
  }
2723
2805
  }
2724
2806
  hasPending(workspace, path2) {
2725
- return this.entries.has(datasetCacheKey(workspace, path2));
2807
+ return this.entries.has(this.keyOf(workspace, path2));
2726
2808
  }
2727
2809
  getBuffered(workspace, path2) {
2728
2810
  var _a2;
2729
- return (_a2 = this.entries.get(datasetCacheKey(workspace, path2))) == null ? void 0 : _a2.buffered;
2811
+ return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.buffered;
2730
2812
  }
2731
2813
  getSnapshot(workspace, path2) {
2732
2814
  var _a2;
2733
- return (_a2 = this.entries.get(datasetCacheKey(workspace, path2))) == null ? void 0 : _a2.snapshot;
2815
+ return (_a2 = this.entries.get(this.keyOf(workspace, path2))) == null ? void 0 : _a2.snapshot;
2734
2816
  }
2735
2817
  getEntry(workspace, path2) {
2736
- return this.entries.get(datasetCacheKey(workspace, path2));
2818
+ return this.entries.get(this.keyOf(workspace, path2));
2737
2819
  }
2738
2820
  write(workspace, path2, snapshotIfNew, buffered) {
2739
- const key = datasetCacheKey(workspace, path2);
2821
+ const key = this.keyOf(workspace, path2);
2740
2822
  const existing = this.entries.get(key);
2741
2823
  const next2 = existing ? { snapshot: existing.snapshot, buffered } : { snapshot: snapshotIfNew, buffered };
2742
2824
  this.entries.set(key, next2);
2743
- this.notify(key);
2825
+ this.notify(datasetCacheKey(workspace, path2));
2744
2826
  const persisted = {
2745
2827
  snapshot: next2.snapshot,
2746
2828
  buffered: next2.buffered
@@ -2748,15 +2830,26 @@ class StagedStore {
2748
2830
  this.persist(key, () => this.adapter.save(key, persisted));
2749
2831
  }
2750
2832
  discard(workspace, path2) {
2751
- const key = datasetCacheKey(workspace, path2);
2833
+ const key = this.keyOf(workspace, path2);
2752
2834
  if (!this.entries.has(key)) return false;
2753
2835
  this.entries.delete(key);
2754
- this.notify(key);
2836
+ this.notify(datasetCacheKey(workspace, path2));
2755
2837
  this.persist(key, () => this.adapter.remove(key));
2756
2838
  return true;
2757
2839
  }
2758
2840
  listKeys() {
2759
- return [...this.entries.keys()];
2841
+ const prefix2 = `${this.scope ?? ""}${SCOPE_SEPARATOR}`;
2842
+ return [...this.entries.keys()].filter((key) => key.startsWith(prefix2)).map(cacheKeyOf);
2843
+ }
2844
+ setScope(scope) {
2845
+ const before = this.scope;
2846
+ this.scope = scope;
2847
+ this.notifyScopes(before, scope);
2848
+ return () => {
2849
+ if (this.scope !== scope) return;
2850
+ this.scope = null;
2851
+ this.notifyScopes(scope, null);
2852
+ };
2760
2853
  }
2761
2854
  subscribe(key, callback) {
2762
2855
  let set3 = this.subscribers.get(key);
@@ -2779,15 +2872,29 @@ class StagedStore {
2779
2872
  this.errorListeners.delete(cb);
2780
2873
  };
2781
2874
  }
2782
- /** Test-only: clear in-memory + persisted state. */
2875
+ /** Test-only: clear in-memory + persisted state, in every scope. */
2783
2876
  async clear() {
2784
2877
  const keys = [...this.entries.keys()];
2785
2878
  this.entries.clear();
2786
- for (const key of keys) this.notify(key);
2879
+ for (const key of new Set(keys.map(cacheKeyOf))) this.notify(key);
2787
2880
  await this.flushPending();
2788
2881
  await this.adapter.clear();
2789
2882
  }
2790
2883
  // ----- internals -------------------------------------------------------
2884
+ /** The key a path's entry is held under in the current scope. */
2885
+ keyOf(workspace, path2) {
2886
+ return `${this.scope ?? ""}${SCOPE_SEPARATOR}${datasetCacheKey(workspace, path2)}`;
2887
+ }
2888
+ /** Tells every path with an entry in either scope that what it reads has
2889
+ * changed: the scope's move shows one's entries and hides the other's. */
2890
+ notifyScopes(from2, to) {
2891
+ const keys = /* @__PURE__ */ new Set();
2892
+ for (const key of this.entries.keys()) {
2893
+ const scope = key.slice(0, key.indexOf(SCOPE_SEPARATOR));
2894
+ if (scope === (from2 ?? "") || scope === (to ?? "")) keys.add(cacheKeyOf(key));
2895
+ }
2896
+ for (const key of keys) this.notify(key);
2897
+ }
2791
2898
  notify(key) {
2792
2899
  this.versions.set(key, (this.versions.get(key) ?? 0) + 1);
2793
2900
  const subs = this.subscribers.get(key);
@@ -2804,7 +2911,7 @@ class StagedStore {
2804
2911
  const settled = prev2.then(op, op).then(
2805
2912
  () => void 0,
2806
2913
  (err) => {
2807
- this.emitPersistError(key, err);
2914
+ this.emitPersistError(cacheKeyOf(key), err);
2808
2915
  }
2809
2916
  ).then(() => {
2810
2917
  if (this.saveChains.get(key) === settled) this.saveChains.delete(key);
@@ -2835,11 +2942,18 @@ class StagedStore {
2835
2942
  return;
2836
2943
  }
2837
2944
  for (const [key, p2] of persisted) {
2945
+ if (!key.includes(SCOPE_SEPARATOR)) {
2946
+ this.persist(key, () => this.adapter.remove(key));
2947
+ this.emitPersistError(key, new Error(
2948
+ `a staged edit of ${key} that an older build left names no server or repository, and was dropped`
2949
+ ));
2950
+ continue;
2951
+ }
2838
2952
  this.entries.set(key, {
2839
2953
  snapshot: p2.snapshot,
2840
2954
  buffered: p2.buffered
2841
2955
  });
2842
- this.notify(key);
2956
+ this.notify(cacheKeyOf(key));
2843
2957
  }
2844
2958
  }
2845
2959
  }
@@ -2867,7 +2981,7 @@ function clearStagedStoreSingleton() {
2867
2981
  }
2868
2982
  const UNCHANGED_VARIANT = variant("unchanged", null);
2869
2983
  const blobEqual = equalFor(BlobType);
2870
- const eastTypeEqual$2 = equalFor(EastTypeType);
2984
+ const eastTypeEqual$1 = equalFor(EastTypeType);
2871
2985
  const helpersCache = new SortedMap(
2872
2986
  void 0,
2873
2987
  compareFor(EastTypeType)
@@ -2956,8 +3070,14 @@ class BindRuntime {
2956
3070
  }
2957
3071
  this.cache = cache3;
2958
3072
  }
2959
- /** Clear the active cache and drop any queued writes. */
2960
- clearCache() {
3073
+ /**
3074
+ * Clear the active cache and drop any queued writes.
3075
+ *
3076
+ * @param cache - Clear only while this is the active cache, so a provider
3077
+ * tearing down after another installed its own leaves that one in place
3078
+ */
3079
+ clearCache(cache3) {
3080
+ if (cache3 !== void 0 && this.cache !== cache3) return;
2961
3081
  this.cache = null;
2962
3082
  this.clearPendingWrites();
2963
3083
  this.handleCache.clear();
@@ -3070,11 +3190,11 @@ class BindRuntime {
3070
3190
  const key = datasetCacheKey(workspace, sourcePath);
3071
3191
  const existing = this.bindingRegistry.get(key);
3072
3192
  if (existing) {
3073
- if (existing.mode === info.mode && existing.hasPatchDataset === info.hasPatchDataset && existing.sourceType === info.sourceType) {
3193
+ if (existing.mode === info.mode && existing.hasPatchDataset === info.hasPatchDataset && Object.is(existing.sourceType, info.sourceType)) {
3074
3194
  return;
3075
3195
  }
3076
3196
  const shapeChanged = existing.mode !== info.mode || existing.hasPatchDataset !== info.hasPatchDataset;
3077
- const typeChanged = !eastTypeEqual$2(existing.sourceType, info.sourceType);
3197
+ const typeChanged = !eastTypeEqual$1(existing.sourceType, info.sourceType);
3078
3198
  if (!shapeChanged && !typeChanged) {
3079
3199
  this.bindingRegistry.set(key, info);
3080
3200
  return;
@@ -3264,7 +3384,7 @@ class BindRuntime {
3264
3384
  };
3265
3385
  const bindingDescriptor = {
3266
3386
  source: sourcePath,
3267
- patch: patchPath ? variant("some", patchPath) : variant("none", null),
3387
+ patch: patchPath ? some(patchPath) : none$1,
3268
3388
  mode: variant(mode, null)
3269
3389
  };
3270
3390
  const writeStaged = (value) => {
@@ -3282,6 +3402,10 @@ class BindRuntime {
3282
3402
  this.queueWrite(() => cache3.launchDataflow(ws));
3283
3403
  return null;
3284
3404
  };
3405
+ const hasSource = () => {
3406
+ this.trackPath(ws, sourcePath);
3407
+ return cache3.has(ws, sourcePath);
3408
+ };
3285
3409
  if (mode === "direct" && !patchPath) {
3286
3410
  return {
3287
3411
  read: () => {
@@ -3304,7 +3428,7 @@ class BindRuntime {
3304
3428
  pending: () => false,
3305
3429
  commit: () => null,
3306
3430
  discard: () => null,
3307
- has: () => cache3.has(ws, sourcePath),
3431
+ has: hasSource,
3308
3432
  status: () => {
3309
3433
  this.trackPath(ws, sourcePath);
3310
3434
  return cache3.getStatus(ws, sourcePath);
@@ -3353,7 +3477,7 @@ class BindRuntime {
3353
3477
  this.queueWrite(() => cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT)));
3354
3478
  return null;
3355
3479
  },
3356
- has: () => cache3.has(ws, sourcePath),
3480
+ has: hasSource,
3357
3481
  status: () => {
3358
3482
  this.trackPath(ws, sourcePath);
3359
3483
  return cache3.getStatus(ws, sourcePath);
@@ -3397,7 +3521,7 @@ class BindRuntime {
3397
3521
  staged.discard(ws, sourcePath);
3398
3522
  return null;
3399
3523
  },
3400
- has: () => cache3.has(ws, sourcePath),
3524
+ has: hasSource,
3401
3525
  status: () => {
3402
3526
  this.trackPath(ws, sourcePath);
3403
3527
  return cache3.getStatus(ws, sourcePath);
@@ -3444,7 +3568,7 @@ class BindRuntime {
3444
3568
  staged.discard(ws, sourcePath);
3445
3569
  return null;
3446
3570
  },
3447
- has: () => cache3.has(ws, sourcePath),
3571
+ has: hasSource,
3448
3572
  status: () => {
3449
3573
  this.trackPath(ws, sourcePath);
3450
3574
  return cache3.getStatus(ws, sourcePath);
@@ -3502,8 +3626,8 @@ function getReactiveDatasetCache() {
3502
3626
  function initializeReactiveDatasetCache(cache3) {
3503
3627
  defaultBindRuntime.initializeCache(cache3);
3504
3628
  }
3505
- function clearReactiveDatasetCache() {
3506
- defaultBindRuntime.clearCache();
3629
+ function clearReactiveDatasetCache(cache3) {
3630
+ defaultBindRuntime.clearCache(cache3);
3507
3631
  }
3508
3632
  function enableBindingTracking() {
3509
3633
  return defaultBindRuntime.enableTracking();
@@ -3683,7 +3807,11 @@ class TrackedChannelStore {
3683
3807
  this.keyVersions.clear();
3684
3808
  }
3685
3809
  }
3686
- function createDefaultPagedApi(apiUrl, repo, getToken) {
3810
+ const NO_INDEX = { index: null, join: false };
3811
+ function selectorKey(selector2) {
3812
+ return selector2.index === null ? "" : `@${selector2.index}${selector2.join ? "+join" : ""}`;
3813
+ }
3814
+ function createDefaultPagedApi(apiUrl, repo, getToken, datasets) {
3687
3815
  const opts = () => ({ token: getToken() });
3688
3816
  return {
3689
3817
  async getPage(workspace, path2, window3) {
@@ -3691,58 +3819,115 @@ function createDefaultPagedApi(apiUrl, repo, getToken) {
3691
3819
  },
3692
3820
  async findKey(workspace, path2, query) {
3693
3821
  return datasetFindKey(apiUrl, repo, workspace, path2, query, opts());
3822
+ },
3823
+ async getRevision(workspace, path2) {
3824
+ const status = await datasetGetStatus(apiUrl, repo, workspace, path2, opts());
3825
+ return status.hash.type === "some" ? status.hash.value : null;
3826
+ },
3827
+ watchRevision(workspace, path2, onChange2) {
3828
+ return datasets.watchHash(workspace, path2, onChange2);
3694
3829
  }
3695
3830
  };
3696
3831
  }
3697
3832
  const RETRY_AFTER_MS = 2e3;
3698
3833
  const MAX_RETAINED_WINDOWS = 24;
3699
- const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set(["dataset_not_pageable"]);
3700
- function isPermanentPageError(err) {
3834
+ const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set([
3835
+ "bad_request",
3836
+ "dataset_not_canonical",
3837
+ "dataset_not_indexed",
3838
+ "dataset_not_pageable",
3839
+ "dataset_not_searchable",
3840
+ "dataset_not_segmented",
3841
+ "index_not_found",
3842
+ "key_parse_error"
3843
+ ]);
3844
+ const NO_VALUE_ERRORS = /* @__PURE__ */ new Set(["dataset_unassigned", "dataset_null"]);
3845
+ function toSelector(indexArg, joinArg) {
3846
+ const index2 = indexArg;
3847
+ return {
3848
+ index: index2 !== void 0 && index2.type === "some" ? index2.value : null,
3849
+ join: joinArg === true
3850
+ };
3851
+ }
3852
+ function checkDeclared(allowed, path2) {
3853
+ if (allowed === null) return;
3854
+ const pathStr = datasetPathToString(path2);
3855
+ if (!allowed.has(pathStr)) {
3856
+ throw new Error(
3857
+ `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
3858
+ );
3859
+ }
3860
+ }
3861
+ function errorCode(err) {
3701
3862
  const code = err == null ? void 0 : err.code;
3702
- return typeof code === "string" && PERMANENT_PAGE_ERRORS.has(code);
3863
+ return typeof code === "string" ? code : void 0;
3864
+ }
3865
+ function isPermanentPageError(err) {
3866
+ const code = errorCode(err);
3867
+ return code !== void 0 && PERMANENT_PAGE_ERRORS.has(code);
3868
+ }
3869
+ function failureOf(path2, err) {
3870
+ const code = errorCode(err);
3871
+ const details = err == null ? void 0 : err.details;
3872
+ const reason = code === void 0 ? err instanceof Error ? err.message : String(err) : typeof details === "string" && details !== "" ? `${code}: ${details}` : code;
3873
+ return `Data.bindPaged: ${datasetPathToString(path2)}: ${reason}`;
3703
3874
  }
3704
3875
  function keyTypeOf(sourceType) {
3705
3876
  if (sourceType.type === "Dict") return sourceType.value.key;
3706
3877
  if (sourceType.type === "Set") return sourceType.value;
3707
3878
  return null;
3708
3879
  }
3709
- function buildSeek(sourceType, T2, pathExpr, platform) {
3710
- if (keyTypeOf(sourceType) === null) return none$1;
3880
+ function buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform) {
3881
+ if (selector2.index === null && keyTypeOf(sourceType) === null) return none$1;
3711
3882
  const { seek } = DataPagedPrimitives;
3712
3883
  return some(East.compile(
3713
3884
  East.function([SeekQueryType], OptionType(SeekRangeType), ($3, query) => {
3714
- $3.return(seek([T2], pathExpr, query));
3885
+ $3.return(seek([T2], pathExpr, indexExpr, joinExpr, query));
3715
3886
  }),
3716
3887
  platform
3717
3888
  ));
3718
3889
  }
3719
- function pagedWindowKey(workspace, path2, offset, limit2) {
3720
- return `paged:${workspace}:${datasetPathToString(path2)}#${offset}+${limit2}`;
3890
+ function pagedRevisionKey(workspace, path2) {
3891
+ return `paged:${workspace}:${datasetPathToString(path2)}#revision`;
3892
+ }
3893
+ function pagedWindowKey(workspace, path2, revision, offset, limit2, selector2 = NO_INDEX) {
3894
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#${offset}+${limit2}`;
3721
3895
  }
3722
- function pagedTotalKey(workspace, path2) {
3723
- return `paged:${workspace}:${datasetPathToString(path2)}#total`;
3896
+ function pagedTotalKey(workspace, path2, revision, selector2 = NO_INDEX) {
3897
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#total`;
3724
3898
  }
3725
- function pagedSeekKey(workspace, path2, query) {
3726
- const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : `p=${query.prefix}`;
3727
- return `paged:${workspace}:${datasetPathToString(path2)}#seek:${q}`;
3899
+ function pagedSeekKey(workspace, path2, revision, query, selector2 = NO_INDEX) {
3900
+ 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")}`;
3901
+ return `paged:${workspace}:${datasetPathToString(path2)}${selectorKey(selector2)}@${revision}#seek:${q}`;
3728
3902
  }
3729
- function toFindQuery(query) {
3903
+ function toFindQuery(query, selector2 = NO_INDEX) {
3730
3904
  const q = query;
3731
- if (q.type === "key") return { key: q.value };
3732
- if (q.type === "prefix") return { prefix: q.value };
3905
+ const scope = selector2.index === null ? {} : { index: selector2.index };
3906
+ if (q.type === "key") return { key: q.value, ...scope };
3907
+ if (q.type === "prefix") return { prefix: q.value, ...scope };
3908
+ if (q.type === "range") {
3909
+ const r2 = q.value;
3910
+ const from2 = [...r2.from];
3911
+ const to = [...r2.to];
3912
+ if (from2.length === 0 && to.length === 0) {
3913
+ throw new Error("Data.bindPaged: a range seek names no end — give it a `from`, a `to`, or both");
3914
+ }
3915
+ return from2.length === 0 ? { to, ...scope } : to.length === 0 ? { from: from2, ...scope } : { from: from2, to, ...scope };
3916
+ }
3733
3917
  const f2 = q.value;
3734
3918
  const fields = [...f2.values];
3735
- return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value } : { fields };
3919
+ return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value, ...scope } : { fields, ...scope };
3736
3920
  }
3737
3921
  class PagedRuntime extends TrackedChannelStore {
3738
3922
  constructor() {
3739
3923
  super(...arguments);
3740
3924
  __publicField(this, "api", null);
3741
3925
  __publicField(this, "workspace", null);
3742
- // Compiled-handle cache (issue #106 perf): buildHandle compiles 2
3926
+ // Compiled-handle cache (issue #106 perf): buildHandle compiles several
3743
3927
  // East.functions per bind, and binds re-run every reactive frame. The
3744
- // method IR is a pure function of (sourceType, sourcePath), and the methods
3745
- // resolve api/workspace LIVE, so a cached handle still re-binds.
3928
+ // method IR is a pure function of (sourceType, path, selector, shape), and
3929
+ // the methods resolve api/workspace LIVE, so a cached handle still
3930
+ // re-binds.
3746
3931
  //
3747
3932
  // Keyed by TYPE first, then path — never by path alone. The window decoder
3748
3933
  // is baked from `sourceType`, so a path re-bound at a different type (a
@@ -3757,6 +3942,10 @@ class PagedRuntime extends TrackedChannelStore {
3757
3942
  /** Loaded window keys in least-recently-READ order (a Map preserves
3758
3943
  * insertion order; a read re-inserts). Bounds the decoded-window cache. */
3759
3944
  __publicField(this, "loadedWindows", /* @__PURE__ */ new Map());
3945
+ /** Each dataset's snapshot, by workspace and path. */
3946
+ __publicField(this, "snapshots", /* @__PURE__ */ new Map());
3947
+ /** Fetches launched so far — the source of every channel's `launchSeq`. */
3948
+ __publicField(this, "launches", 0);
3760
3949
  }
3761
3950
  /** Monotonic clock seam so tests can drive the retry gate. */
3762
3951
  now() {
@@ -3767,17 +3956,33 @@ class PagedRuntime extends TrackedChannelStore {
3767
3956
  }
3768
3957
  // ----- wiring ----------------------------------------------------------
3769
3958
  /** Install the API adapter + workspace — called by the React provider
3770
- * (or a test/showcase harness) before any handle is used. */
3959
+ * (or a test/showcase harness) before any handle is used. Another adapter
3960
+ * or workspace starts from nothing: no snapshot of the old one's carries
3961
+ * over. */
3771
3962
  initialize(api3, workspace) {
3963
+ if (this.api !== api3 || this.workspace !== workspace) this.reset();
3772
3964
  this.api = api3;
3773
3965
  this.workspace = workspace;
3774
3966
  }
3775
- /** Tear down the adapter and all window state. */
3776
- clear() {
3967
+ /**
3968
+ * Tear down the adapter and all window state.
3969
+ *
3970
+ * @param api - Clear only while this is the installed adapter, so a
3971
+ * provider tearing down after another installed its own leaves that one
3972
+ * in place
3973
+ */
3974
+ clear(api3) {
3975
+ if (api3 !== void 0 && this.api !== api3) return;
3777
3976
  this.api = null;
3778
3977
  this.workspace = null;
3779
- this.clearChannels();
3978
+ this.reset();
3780
3979
  this.handleCache.clear();
3980
+ }
3981
+ /** Stop following every dataset, and drop what every snapshot delivered. */
3982
+ reset() {
3983
+ for (const snapshot of this.snapshots.values()) snapshot.unwatch();
3984
+ this.snapshots.clear();
3985
+ this.clearChannels();
3781
3986
  this.loadedWindows.clear();
3782
3987
  }
3783
3988
  resolveWorkspace() {
@@ -3788,7 +3993,178 @@ class PagedRuntime extends TrackedChannelStore {
3788
3993
  }
3789
3994
  return this.workspace;
3790
3995
  }
3791
- // ----- window loading --------------------------------------------------
3996
+ // ----- snapshots -------------------------------------------------------
3997
+ /** A dataset's snapshot, followed from its first read until the runtime
3998
+ * is cleared. */
3999
+ snapshotOf(workspace, path2) {
4000
+ const key = `${workspace}:${datasetPathToString(path2)}`;
4001
+ const existing = this.snapshots.get(key);
4002
+ if (existing !== void 0) return existing;
4003
+ const snapshot = {
4004
+ key,
4005
+ revision: void 0,
4006
+ moves: 0,
4007
+ findSeq: 0,
4008
+ finding: false,
4009
+ channels: /* @__PURE__ */ new Set(),
4010
+ unwatch: () => {
4011
+ }
4012
+ };
4013
+ this.snapshots.set(key, snapshot);
4014
+ if (this.api !== null) {
4015
+ snapshot.unwatch = this.api.watchRevision(workspace, path2, (hash2) => this.moveTo(workspace, path2, snapshot, hash2));
4016
+ }
4017
+ return snapshot;
4018
+ }
4019
+ /** Whether a snapshot is still the runtime's, rather than one a clear or a
4020
+ * re-initialization dropped. */
4021
+ isLive(snapshot) {
4022
+ return this.snapshots.get(snapshot.key) === snapshot;
4023
+ }
4024
+ /** Whether a live snapshot is still pinned to `revision`. */
4025
+ pinnedTo(snapshot, revision) {
4026
+ return this.isLive(snapshot) && snapshot.revision === revision;
4027
+ }
4028
+ /**
4029
+ * The revision a read of `path` is pinned to — a hash, `null` while the
4030
+ * dataset has no value, or `undefined` while it is being found — tracked so
4031
+ * the read re-fires when the source moves.
4032
+ *
4033
+ * @throws The failed lookup's reason, until a read after the retry gap
4034
+ * looks again
4035
+ */
4036
+ revisionFor(workspace, path2) {
4037
+ this.track(pagedRevisionKey(workspace, path2));
4038
+ const snapshot = this.snapshotOf(workspace, path2);
4039
+ if (snapshot.revision !== void 0 || snapshot.finding) return snapshot.revision;
4040
+ if (snapshot.findError !== void 0 && this.now() - (snapshot.findFailedAtMs ?? 0) < RETRY_AFTER_MS) {
4041
+ throw new Error(snapshot.findError);
4042
+ }
4043
+ this.find(workspace, path2, snapshot);
4044
+ return void 0;
4045
+ }
4046
+ /**
4047
+ * Ask the server which content the dataset holds, and move there — unless
4048
+ * a move lands first, which came from something no older than the answer.
4049
+ */
4050
+ find(workspace, path2, snapshot) {
4051
+ const seq = ++snapshot.findSeq;
4052
+ const moves = snapshot.moves;
4053
+ snapshot.finding = true;
4054
+ const api3 = this.api;
4055
+ void (async () => {
4056
+ let revision;
4057
+ try {
4058
+ if (api3 === null) throw new Error("no PagedApi installed");
4059
+ revision = await api3.getRevision(workspace, path2);
4060
+ } catch (err) {
4061
+ if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
4062
+ snapshot.finding = false;
4063
+ if (snapshot.revision !== void 0) return;
4064
+ snapshot.findError = failureOf(path2, err);
4065
+ snapshot.findFailedAtMs = this.now();
4066
+ console.error(`Data.bindPaged: could not find which content ${datasetPathToString(path2)} holds:`, err);
4067
+ this.notify(pagedRevisionKey(workspace, path2));
4068
+ return;
4069
+ }
4070
+ if (!this.isLive(snapshot) || snapshot.findSeq !== seq) return;
4071
+ snapshot.finding = false;
4072
+ if (snapshot.moves !== moves) return;
4073
+ this.moveTo(workspace, path2, snapshot, revision);
4074
+ })();
4075
+ }
4076
+ /**
4077
+ * Move a dataset's source to `revision`: drop what the old snapshot
4078
+ * delivered and re-fire every read of the dataset, so the windows and
4079
+ * searches still in use are fetched again, pinned to the new one. A move to
4080
+ * the revision the source already has changes nothing.
4081
+ */
4082
+ moveTo(workspace, path2, snapshot, revision) {
4083
+ if (!this.isLive(snapshot) || snapshot.revision === revision) return;
4084
+ snapshot.revision = revision;
4085
+ snapshot.moves += 1;
4086
+ delete snapshot.findError;
4087
+ delete snapshot.findFailedAtMs;
4088
+ for (const key of snapshot.channels) {
4089
+ this.entries.delete(key);
4090
+ this.loadedWindows.delete(key);
4091
+ }
4092
+ snapshot.channels.clear();
4093
+ this.notify(pagedRevisionKey(workspace, path2));
4094
+ }
4095
+ /**
4096
+ * Handle what a pinned fetch threw when it is not the read's failure: a
4097
+ * refusal naming other content moves the source there, and "no value"
4098
+ * moves it to no snapshot. Returns whether the error was one of those, or
4099
+ * arrived after the source had already moved on.
4100
+ */
4101
+ followed(workspace, path2, snapshot, revision, err) {
4102
+ if (!this.pinnedTo(snapshot, revision)) return true;
4103
+ if (err instanceof DatasetHashMismatchError) {
4104
+ if (err.currentHash !== null && err.currentHash !== revision) {
4105
+ this.moveTo(workspace, path2, snapshot, err.currentHash);
4106
+ return true;
4107
+ }
4108
+ this.find(workspace, path2, snapshot);
4109
+ return false;
4110
+ }
4111
+ const code = errorCode(err);
4112
+ if (code !== void 0 && NO_VALUE_ERRORS.has(code)) {
4113
+ this.moveTo(workspace, path2, snapshot, null);
4114
+ return true;
4115
+ }
4116
+ return false;
4117
+ }
4118
+ // ----- channels ----------------------------------------------------------
4119
+ /** A channel of the snapshot's current revision, dropped when it moves. */
4120
+ channel(snapshot, key) {
4121
+ snapshot.channels.add(key);
4122
+ return this.entry(key);
4123
+ }
4124
+ /** Whether a channel is due a fetch: never fetched, evicted, or failed
4125
+ * for a reason a retry can fix, longer ago than the retry gap. */
4126
+ due(entry) {
4127
+ if (entry.status === "idle") return true;
4128
+ if (entry.status !== "failed" || entry.permanent === true) return false;
4129
+ return this.now() - (entry.failedAtMs ?? 0) >= RETRY_AFTER_MS;
4130
+ }
4131
+ /**
4132
+ * Mark a channel's fetch as started, returning the fetch's sequence number.
4133
+ *
4134
+ * Deliberately does NOT notify — the read that triggers it runs inside a
4135
+ * render pass, and notifying there would re-enter the renderer. Only the
4136
+ * settle notifies.
4137
+ */
4138
+ launch(entry) {
4139
+ entry.status = "running";
4140
+ entry.launchSeq = ++this.launches;
4141
+ return entry.launchSeq;
4142
+ }
4143
+ /** Apply a fetch's outcome if the channel still waits on it, re-firing the
4144
+ * channel's reads. Returns whether it applied. */
4145
+ settle(key, seq, mutate) {
4146
+ const entry = this.entries.get(key);
4147
+ if (entry === void 0 || entry.launchSeq !== seq) return false;
4148
+ mutate(entry);
4149
+ this.notify(key);
4150
+ return true;
4151
+ }
4152
+ /** Settle a fetch as failed, with the reason a read will throw. */
4153
+ fail(key, seq, reason, permanent) {
4154
+ this.settle(key, seq, (e3) => {
4155
+ e3.status = "failed";
4156
+ e3.error = reason;
4157
+ e3.failedAtMs = this.now();
4158
+ e3.permanent = permanent;
4159
+ });
4160
+ }
4161
+ /** What a read of a channel answers: its value once loaded, `none` while
4162
+ * in flight, or its failure, thrown. */
4163
+ answer(entry, value) {
4164
+ if (entry.status === "loaded" && value !== void 0) return some(value);
4165
+ if (entry.status === "failed") throw new Error(entry.error ?? "Data.bindPaged: the read failed");
4166
+ return none$1;
4167
+ }
3792
4168
  /** Note a window as most-recently-read, and drop the coldest decoded
3793
4169
  * windows once the cache exceeds its cap. An evicted window returns to
3794
4170
  * `idle`, so the next read refetches it rather than reading a hole. */
@@ -3806,131 +4182,83 @@ class PagedRuntime extends TrackedChannelStore {
3806
4182
  delete entry.window;
3807
4183
  }
3808
4184
  }
3809
- /**
3810
- * Start the fetch for a window if it isn't loaded or already in flight.
3811
- *
3812
- * Deliberately does NOT notify on launch — the read that triggers it runs
3813
- * inside a render pass, and notifying there would re-enter the renderer.
3814
- * Only the settle notifies.
3815
- */
3816
- ensureWindow(sourceType, workspace, path2, offset, limit2) {
3817
- const key = pagedWindowKey(workspace, path2, offset, limit2);
3818
- const entry = this.entry(key);
3819
- if (entry.status === "running" || entry.status === "loaded") return;
3820
- if (entry.status === "failed") {
3821
- if (entry.permanent) return;
3822
- const since = this.now() - (entry.failedAtMs ?? 0);
3823
- if (since < RETRY_AFTER_MS) return;
3824
- }
3825
- entry.status = "running";
3826
- entry.launchSeq += 1;
3827
- const mySeq = entry.launchSeq;
4185
+ // ----- window loading --------------------------------------------------
4186
+ /** Start the pinned fetch for a window if it is due one, returning its
4187
+ * channel. */
4188
+ ensureWindow(sourceType, workspace, path2, snapshot, revision, offset, limit2, selector2, key) {
4189
+ const entry = this.channel(snapshot, key);
4190
+ if (!this.due(entry)) return entry;
4191
+ const seq = this.launch(entry);
3828
4192
  const api3 = this.api;
3829
4193
  void (async () => {
3830
- const settle = (mutate) => {
3831
- const current = this.entries.get(key);
3832
- if (!current || current.launchSeq !== mySeq) return;
3833
- mutate(current);
3834
- this.notify(key);
3835
- };
3836
- if (!api3) {
3837
- settle((e3) => {
3838
- e3.status = "failed";
3839
- e3.failedAtMs = this.now();
3840
- });
3841
- console.error("Data.bindPaged: no PagedApi installed");
3842
- return;
3843
- }
3844
4194
  let page;
3845
4195
  try {
3846
- page = await api3.getPage(workspace, path2, { offset, limit: limit2 });
3847
- } catch (err) {
3848
- const permanent = isPermanentPageError(err);
3849
- settle((e3) => {
3850
- e3.status = "failed";
3851
- e3.failedAtMs = this.now();
3852
- e3.permanent = permanent;
4196
+ if (api3 === null) throw new Error("no PagedApi installed");
4197
+ page = await api3.getPage(workspace, path2, {
4198
+ offset,
4199
+ limit: limit2,
4200
+ hash: revision,
4201
+ ...selector2.index !== null && { index: selector2.index, join: selector2.join }
3853
4202
  });
3854
- console.error(
3855
- 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}:`,
3856
- err
3857
- );
4203
+ } catch (err) {
4204
+ if (this.followed(workspace, path2, snapshot, revision, err)) return;
4205
+ this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
4206
+ console.error(`Data.bindPaged: fetch failed for ${key}:`, err);
3858
4207
  return;
3859
4208
  }
4209
+ if (!this.pinnedTo(snapshot, revision)) return;
3860
4210
  let decoded;
3861
4211
  try {
3862
4212
  decoded = decodeBeast2For(sourceType)(page.data);
3863
4213
  } catch (err) {
3864
- settle((e3) => {
3865
- e3.status = "failed";
3866
- e3.failedAtMs = this.now();
3867
- });
4214
+ this.fail(key, seq, `${failureOf(path2, err)} (the window does not decode as the source's type)`, true);
3868
4215
  console.error(`Data.bindPaged: decode failed for ${key}:`, err);
3869
4216
  return;
3870
4217
  }
3871
- settle((e3) => {
4218
+ const landed = this.settle(key, seq, (e3) => {
3872
4219
  e3.status = "loaded";
3873
4220
  e3.window = decoded;
3874
4221
  e3.total = page.totalElements;
4222
+ delete e3.error;
3875
4223
  });
4224
+ if (!landed) return;
3876
4225
  this.touchWindow(key);
3877
- const totalKey = pagedTotalKey(workspace, path2);
3878
- const totalEntry = this.entry(totalKey);
4226
+ const totalKey = pagedTotalKey(workspace, path2, revision, selector2);
4227
+ const totalEntry = this.channel(snapshot, totalKey);
3879
4228
  if (totalEntry.total !== page.totalElements) {
3880
4229
  totalEntry.total = page.totalElements;
3881
4230
  totalEntry.status = "loaded";
3882
4231
  this.notify(totalKey);
3883
4232
  }
3884
4233
  })();
4234
+ return entry;
3885
4235
  }
3886
- /**
3887
- * Start the fence search for one key query if it isn't answered or already
3888
- * in flight — the seek sibling of {@link ensureWindow}, with the same
3889
- * launch-does-not-notify rule (the read runs inside a render pass).
3890
- */
3891
- ensureSeek(workspace, path2, query, key) {
3892
- const entry = this.entry(key);
3893
- if (entry.status === "running" || entry.status === "loaded") return;
3894
- if (entry.status === "failed") {
3895
- if (entry.permanent) return;
3896
- const since = this.now() - (entry.failedAtMs ?? 0);
3897
- if (since < RETRY_AFTER_MS) return;
3898
- }
3899
- entry.status = "running";
3900
- entry.launchSeq += 1;
3901
- const mySeq = entry.launchSeq;
4236
+ /** Start the pinned fence search for one key query if it is due one,
4237
+ * returning its channel — the seek sibling of {@link ensureWindow}. */
4238
+ ensureSeek(workspace, path2, snapshot, revision, query, key) {
4239
+ const entry = this.channel(snapshot, key);
4240
+ if (!this.due(entry)) return entry;
4241
+ const seq = this.launch(entry);
3902
4242
  const api3 = this.api;
3903
4243
  void (async () => {
3904
- const settle = (mutate) => {
3905
- const current = this.entries.get(key);
3906
- if (!current || current.launchSeq !== mySeq) return;
3907
- mutate(current);
3908
- this.notify(key);
3909
- };
3910
- if (!api3) {
3911
- settle((e3) => {
3912
- e3.status = "failed";
3913
- e3.failedAtMs = this.now();
3914
- });
3915
- console.error("Data.bindPaged: no PagedApi installed");
3916
- return;
3917
- }
4244
+ let range;
3918
4245
  try {
3919
- const range = await api3.findKey(workspace, path2, query);
3920
- settle((e3) => {
3921
- e3.status = "loaded";
3922
- e3.range = range;
3923
- });
4246
+ if (api3 === null) throw new Error("no PagedApi installed");
4247
+ range = await api3.findKey(workspace, path2, { ...query, hash: revision });
3924
4248
  } catch (err) {
3925
- const permanent = isPermanentPageError(err);
3926
- settle((e3) => {
3927
- e3.status = "failed";
3928
- e3.failedAtMs = this.now();
3929
- e3.permanent = permanent;
3930
- });
4249
+ if (this.followed(workspace, path2, snapshot, revision, err)) return;
4250
+ this.fail(key, seq, failureOf(path2, err), isPermanentPageError(err));
3931
4251
  console.error(`Data.bindPaged: key search failed for ${key}:`, err);
4252
+ return;
3932
4253
  }
4254
+ if (!this.pinnedTo(snapshot, revision)) return;
4255
+ this.settle(key, seq, (e3) => {
4256
+ e3.status = "loaded";
4257
+ e3.range = range;
4258
+ delete e3.error;
4259
+ });
3933
4260
  })();
4261
+ return entry;
3934
4262
  }
3935
4263
  /**
3936
4264
  * The low-level primitives backing handle methods, bound to THIS runtime.
@@ -3940,53 +4268,92 @@ class PagedRuntime extends TrackedChannelStore {
3940
4268
  */
3941
4269
  buildPrimitives() {
3942
4270
  return [
3943
- DataPagedPrimitives.page.implement((sourceType) => (pathArg, offsetArg, limitArg) => {
4271
+ DataPagedPrimitives.page.implement((sourceType) => (pathArg, indexArg, joinArg, offsetArg, limitArg) => {
3944
4272
  const workspace = this.resolveWorkspace();
3945
4273
  const path2 = pathArg;
4274
+ const revision = this.revisionFor(workspace, path2);
4275
+ if (typeof revision !== "string") return none$1;
4276
+ const selector2 = toSelector(indexArg, joinArg);
3946
4277
  const offset = Number(offsetArg);
3947
4278
  const limit2 = Number(limitArg);
3948
- const key = pagedWindowKey(workspace, path2, offset, limit2);
4279
+ const key = pagedWindowKey(workspace, path2, revision, offset, limit2, selector2);
3949
4280
  this.track(key);
3950
- this.ensureWindow(sourceType, workspace, path2, offset, limit2);
3951
- const entry = this.entry(key);
3952
- if (entry.status === "loaded" && entry.window !== void 0) {
3953
- this.touchWindow(key);
3954
- return variant("some", entry.window);
3955
- }
3956
- return variant("none", null);
4281
+ const entry = this.ensureWindow(
4282
+ sourceType,
4283
+ workspace,
4284
+ path2,
4285
+ this.snapshotOf(workspace, path2),
4286
+ revision,
4287
+ offset,
4288
+ limit2,
4289
+ selector2,
4290
+ key
4291
+ );
4292
+ if (entry.status === "loaded" && entry.window !== void 0) this.touchWindow(key);
4293
+ return this.answer(entry, entry.window);
3957
4294
  }),
3958
- DataPagedPrimitives.total.implement((_sourceType) => (pathArg) => {
4295
+ DataPagedPrimitives.total.implement((_sourceType) => (pathArg, indexArg, joinArg) => {
3959
4296
  const workspace = this.resolveWorkspace();
3960
- const key = pagedTotalKey(workspace, pathArg);
4297
+ const path2 = pathArg;
4298
+ const revision = this.revisionFor(workspace, path2);
4299
+ if (typeof revision !== "string") return none$1;
4300
+ const key = pagedTotalKey(workspace, path2, revision, toSelector(indexArg, joinArg));
3961
4301
  this.track(key);
3962
- const entry = this.entry(key);
3963
- return entry.total !== void 0 ? variant("some", BigInt(entry.total)) : variant("none", null);
4302
+ const entry = this.channel(this.snapshotOf(workspace, path2), key);
4303
+ return entry.total !== void 0 ? some(BigInt(entry.total)) : none$1;
3964
4304
  }),
3965
- DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, queryArg) => {
4305
+ DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, indexArg, joinArg, queryArg) => {
3966
4306
  const workspace = this.resolveWorkspace();
3967
4307
  const path2 = pathArg;
3968
- const query = toFindQuery(queryArg);
3969
- const key = pagedSeekKey(workspace, path2, query);
4308
+ const revision = this.revisionFor(workspace, path2);
4309
+ if (typeof revision !== "string") return none$1;
4310
+ const selector2 = toSelector(indexArg, joinArg);
4311
+ const query = toFindQuery(queryArg, selector2);
4312
+ const key = pagedSeekKey(workspace, path2, revision, query, selector2);
3970
4313
  this.track(key);
3971
- this.ensureSeek(workspace, path2, query, key);
3972
- const entry = this.entry(key);
3973
- if (entry.status !== "loaded" || entry.range === void 0) return none$1;
3974
- const r2 = entry.range;
3975
- return some({ found: r2.found, row: BigInt(r2.row), count: BigInt(r2.count) });
4314
+ const entry = this.ensureSeek(workspace, path2, this.snapshotOf(workspace, path2), revision, query, key);
4315
+ const range = entry.range;
4316
+ return this.answer(entry, range === void 0 ? void 0 : { found: range.found, row: BigInt(range.row), count: BigInt(range.count) });
4317
+ }),
4318
+ DataPagedPrimitives.revision.implement((_sourceType) => (pathArg, _indexArg, _joinArg) => {
4319
+ const revision = this.revisionFor(this.resolveWorkspace(), pathArg);
4320
+ return typeof revision === "string" ? some(revision) : none$1;
4321
+ }),
4322
+ DataPagedPrimitives.refresh.implement((_sourceType) => (pathArg, _indexArg, _joinArg, targetArg) => {
4323
+ const workspace = this.resolveWorkspace();
4324
+ const path2 = pathArg;
4325
+ const target = targetArg;
4326
+ queueMicrotask(() => {
4327
+ if (this.workspace !== workspace) return;
4328
+ const snapshot = this.snapshotOf(workspace, path2);
4329
+ if (target.type === "some") this.moveTo(workspace, path2, snapshot, target.value);
4330
+ else this.find(workspace, path2, snapshot);
4331
+ });
4332
+ return null;
3976
4333
  })
3977
4334
  ];
3978
4335
  }
3979
4336
  /**
3980
- * Build the handle value for one `Data.bindPaged` evaluation. Both methods
3981
- * are thin IR-bearing `East.function`s over {@link buildPrimitives},
3982
- * capturing only the plain-data source path (the value type rides as a
3983
- * type-arg) — so the handle is ordinary serializable East data (issue #106).
4337
+ * Build the handle value for one paged bind. Every method is a thin
4338
+ * IR-bearing `East.function` over {@link buildPrimitives}, capturing only
4339
+ * the plain-data source path and selector (the value type rides as a
4340
+ * type-arg) — so the handle is ordinary serializable East data (issue
4341
+ * #106).
4342
+ *
4343
+ * @param sourceType - The dataset's type, or an index's window type
4344
+ * @param path - The dataset's path
4345
+ * @param selector - The rows the bind serves
4346
+ * @param shape - `released`: the handle `data_bind_paged` returns, without
4347
+ * `revision` or `refresh`; `pinned`: the handle `data_bind_paged_pinned`
4348
+ * returns. Both read through the same snapshot.
4349
+ * @returns The handle
3984
4350
  */
3985
- buildHandle(sourceType, path2) {
3986
- const pathKey = datasetPathToString(path2);
4351
+ buildHandle(sourceType, path2, selector2, shape) {
4352
+ const id3 = `${datasetPathToString(path2)}${selectorKey(selector2)}`;
4353
+ const cacheKey = `${id3}#${shape}`;
3987
4354
  let byPath = this.handleCache.get(sourceType);
3988
4355
  if (byPath) {
3989
- const hit = byPath.get(pathKey);
4356
+ const hit = byPath.get(cacheKey);
3990
4357
  if (hit) return hit;
3991
4358
  } else {
3992
4359
  byPath = /* @__PURE__ */ new Map();
@@ -3994,24 +4361,27 @@ class PagedRuntime extends TrackedChannelStore {
3994
4361
  }
3995
4362
  const T2 = fromEastTypeValue(sourceType);
3996
4363
  const pathExpr = East.value(path2, TreePathType);
4364
+ const indexExpr = East.value(selector2.index === null ? none$1 : some(selector2.index), OptionType(StringType));
4365
+ const joinExpr = East.value(selector2.join, BooleanType);
3997
4366
  const platform = this.buildPrimitives();
3998
- const { page, total } = DataPagedPrimitives;
4367
+ const { page, total, revision, refresh } = DataPagedPrimitives;
3999
4368
  const handle = {
4000
4369
  // The comparable identity east-ui's `PagedSourceType` requires:
4001
4370
  // East compares every function as EQUAL, so a struct of nothing but
4002
4371
  // closures is indistinguishable from any other and a memoized
4003
4372
  // component would never re-render on a source swap (#567 D4). The
4004
- // dataset path is the natural identity — same path, same rows.
4005
- id: pathKey,
4373
+ // dataset path and selector are the natural identity — same path,
4374
+ // same rows. The snapshot they serve is `revision`'s, not the id's.
4375
+ id: id3,
4006
4376
  page: East.compile(
4007
4377
  East.function([IntegerType, IntegerType], OptionType(T2), ($3, offset, limit2) => {
4008
- $3.return(page([T2], pathExpr, offset, limit2));
4378
+ $3.return(page([T2], pathExpr, indexExpr, joinExpr, offset, limit2));
4009
4379
  }),
4010
4380
  platform
4011
4381
  ),
4012
4382
  total: East.compile(
4013
4383
  East.function([], OptionType(IntegerType), ($3) => {
4014
- $3.return(total([T2], pathExpr));
4384
+ $3.return(total([T2], pathExpr, indexExpr, joinExpr));
4015
4385
  }),
4016
4386
  platform
4017
4387
  ),
@@ -4020,28 +4390,50 @@ class PagedRuntime extends TrackedChannelStore {
4020
4390
  // segments) against the stored fences; an Array's stream order has
4021
4391
  // nothing to search. Resolved at bind time from the dataset's own
4022
4392
  // type, so a component renders the affordance only when it works.
4023
- seek: buildSeek(sourceType, T2, pathExpr, platform)
4393
+ seek: buildSeek(sourceType, T2, pathExpr, indexExpr, joinExpr, selector2, platform)
4024
4394
  };
4025
- byPath.set(pathKey, handle);
4395
+ if (shape === "pinned") {
4396
+ handle.revision = East.compile(
4397
+ East.function([], OptionType(StringType), ($3) => {
4398
+ $3.return(revision([T2], pathExpr, indexExpr, joinExpr));
4399
+ }),
4400
+ platform
4401
+ );
4402
+ handle.refresh = East.compile(
4403
+ East.function([OptionType(StringType)], NullType, ($3, target) => {
4404
+ $3.return(refresh([T2], pathExpr, indexExpr, joinExpr, target));
4405
+ }),
4406
+ platform
4407
+ );
4408
+ }
4409
+ byPath.set(cacheKey, handle);
4026
4410
  return handle;
4027
4411
  }
4028
4412
  // ----- platform building -------------------------------------------------
4029
- /** Build a `Data.bindPaged` PlatformFunction bound to this runtime. Pass
4030
- * `allowed=null` for an unscoped impl; pass a Set of path strings for
4031
- * manifest scoping. */
4413
+ /** Build the PlatformFunction behind `data_bind_paged` — the bind a UI
4414
+ * exported before pinned reads calls — bound to this runtime. Its handle
4415
+ * has no `revision` or `refresh`, but its reads are pinned, follow the
4416
+ * dataset and fail visibly all the same. Pass `allowed=null` for an
4417
+ * unscoped impl, or a Set of path strings for manifest scoping. */
4032
4418
  buildPlatform(allowed) {
4033
4419
  return bindPagedPlatformFn.implement(
4034
4420
  (sourceType) => (pathArg) => {
4035
4421
  const path2 = pathArg;
4036
- if (allowed) {
4037
- const pathStr = datasetPathToString(path2);
4038
- if (!allowed.has(pathStr)) {
4039
- throw new Error(
4040
- `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
4041
- );
4042
- }
4043
- }
4044
- return this.buildHandle(sourceType, path2);
4422
+ checkDeclared(allowed, path2);
4423
+ return this.buildHandle(sourceType, path2, NO_INDEX, "released");
4424
+ }
4425
+ );
4426
+ }
4427
+ /** Build the PlatformFunction behind `data_bind_paged_pinned` — what
4428
+ * `Data.bindPaged` emits, for a dataset's own rows or a record's index —
4429
+ * bound to this runtime and scoped by `allowed` exactly as
4430
+ * {@link buildPlatform} is: an index read is a read of the record's path. */
4431
+ buildPinnedPlatform(allowed) {
4432
+ return bindPagedPinnedPlatformFn.implement(
4433
+ (sourceType) => (pathArg, indexArg, joinArg) => {
4434
+ const path2 = pathArg;
4435
+ checkDeclared(allowed, path2);
4436
+ return this.buildHandle(sourceType, path2, toSelector(indexArg, joinArg), "pinned");
4045
4437
  }
4046
4438
  );
4047
4439
  }
@@ -4050,17 +4442,19 @@ const defaultPagedRuntime = new PagedRuntime();
4050
4442
  function initializePagedApi(api3, workspace) {
4051
4443
  defaultPagedRuntime.initialize(api3, workspace);
4052
4444
  }
4053
- function clearPagedApi() {
4054
- defaultPagedRuntime.clear();
4445
+ function clearPagedApi(api3) {
4446
+ defaultPagedRuntime.clear(api3);
4055
4447
  }
4056
4448
  const PagedPlatform = [
4057
4449
  defaultPagedRuntime.buildPlatform(null),
4450
+ defaultPagedRuntime.buildPinnedPlatform(null),
4058
4451
  ...defaultPagedRuntime.buildPrimitives()
4059
4452
  ];
4060
4453
  function createScopedPagedPlatform(pages) {
4061
4454
  const allowed = new Set(pages.map((p2) => datasetPathToString(p2)));
4062
4455
  return [
4063
4456
  defaultPagedRuntime.buildPlatform(allowed),
4457
+ defaultPagedRuntime.buildPinnedPlatform(allowed),
4064
4458
  ...defaultPagedRuntime.buildPrimitives()
4065
4459
  ];
4066
4460
  }
@@ -4083,13 +4477,12 @@ function createDefaultFunctionApi(apiUrl, repo, getToken) {
4083
4477
  async call(workspace, fn3, req) {
4084
4478
  return workspaceFunctionCall(apiUrl, repo, workspace, fn3, {
4085
4479
  args: req.args,
4086
- runner: variant("none", null),
4087
- limits: variant("none", null)
4480
+ runner: none$1,
4481
+ limits: none$1
4088
4482
  }, opts());
4089
4483
  }
4090
4484
  };
4091
4485
  }
4092
- const eastTypeEqual$1 = equalFor(EastTypeType);
4093
4486
  function signatureOfFuncHandleType(handleType) {
4094
4487
  var _a2, _b2;
4095
4488
  if (handleType.type !== "Struct") {
@@ -4106,11 +4499,11 @@ function signatureOfFuncHandleType(handleType) {
4106
4499
  if (readOut.type !== "Variant") {
4107
4500
  throw new Error("Func.bind: read() must return Option(Output)");
4108
4501
  }
4109
- const some3 = readOut.value.find((a2) => a2.name === "some");
4110
- if (!some3) {
4502
+ const some22 = readOut.value.find((a2) => a2.name === "some");
4503
+ if (!some22) {
4111
4504
  throw new Error("Func.bind: read() must return Option(Output)");
4112
4505
  }
4113
- return { inputs, output: some3.type };
4506
+ return { inputs, output: some22.type };
4114
4507
  }
4115
4508
  function errorOfExecuteResult(result) {
4116
4509
  const outcome = result.outcome;
@@ -4186,8 +4579,15 @@ class FuncRuntime extends TrackedChannelStore {
4186
4579
  this.api = api3;
4187
4580
  this.workspace = workspace;
4188
4581
  }
4189
- /** Tear down the adapter and all call state. */
4190
- clear() {
4582
+ /**
4583
+ * Tear down the adapter and all call state.
4584
+ *
4585
+ * @param api - Clear only while this is the installed adapter, so a
4586
+ * provider tearing down after another installed its own leaves that one
4587
+ * in place
4588
+ */
4589
+ clear(api3) {
4590
+ if (api3 !== void 0 && this.api !== api3) return;
4191
4591
  this.api = null;
4192
4592
  this.workspace = null;
4193
4593
  this.clearChannels();
@@ -4226,8 +4626,8 @@ class FuncRuntime extends TrackedChannelStore {
4226
4626
  stderr: ""
4227
4627
  };
4228
4628
  }
4229
- const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual$1(t4, sig.inputs[i]));
4230
- const outputMatches = eastTypeEqual$1(deployed.outputType, sig.output);
4629
+ const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => isTypeValueEqual(t4, sig.inputs[i]));
4630
+ const outputMatches = isTypeValueEqual(deployed.outputType, sig.output);
4231
4631
  if (!inputsMatch || !outputMatches) {
4232
4632
  const message = `signature mismatch for "${name}": bound (${sig.inputs.length} inputs) disagrees with the deployed package`;
4233
4633
  return {
@@ -4330,7 +4730,7 @@ class FuncRuntime extends TrackedChannelStore {
4330
4730
  FuncBindPrimitives.read.implement((_outputType) => (nameArg) => {
4331
4731
  const { key, entry } = channel(nameArg);
4332
4732
  runtime.track(key);
4333
- return entry.result !== void 0 ? variant("some", entry.result) : variant("none", null);
4733
+ return entry.result !== void 0 ? some(entry.result) : none$1;
4334
4734
  }),
4335
4735
  FuncBindPrimitives.status.implement((nameArg) => {
4336
4736
  const { key, entry } = channel(nameArg);
@@ -4340,7 +4740,7 @@ class FuncRuntime extends TrackedChannelStore {
4340
4740
  FuncBindPrimitives.error.implement((nameArg) => {
4341
4741
  const { key, entry } = channel(nameArg);
4342
4742
  runtime.track(key);
4343
- return entry.status === "failed" && entry.error !== void 0 ? variant("some", entry.error) : variant("none", null);
4743
+ return entry.status === "failed" && entry.error !== void 0 ? some(entry.error) : none$1;
4344
4744
  }),
4345
4745
  FuncBindPrimitives.pending.implement((nameArg) => {
4346
4746
  const { key, entry } = channel(nameArg);
@@ -4420,8 +4820,8 @@ const defaultFuncRuntime = new FuncRuntime();
4420
4820
  function initializeFunctionApi(api3, workspace) {
4421
4821
  defaultFuncRuntime.initialize(api3, workspace);
4422
4822
  }
4423
- function clearFunctionApi() {
4424
- defaultFuncRuntime.clear();
4823
+ function clearFunctionApi(api3) {
4824
+ defaultFuncRuntime.clear(api3);
4425
4825
  }
4426
4826
  const FuncPlatform = [
4427
4827
  defaultFuncRuntime.buildPlatform(null),
@@ -8194,12 +8594,6 @@ function errorOfMutationResult(result) {
8194
8594
  message: `reducer exited with code ${outcome.value.exitCode}`,
8195
8595
  stderr: outcome.value.stderr
8196
8596
  };
8197
- case "too_large":
8198
- return {
8199
- kind: variant("too_large", { bytes: outcome.value.bytes, limit: outcome.value.limit }),
8200
- message: `new state too large (${outcome.value.bytes} bytes, limit ${outcome.value.limit})`,
8201
- stderr: outcome.value.stderr
8202
- };
8203
8597
  case "timed_out":
8204
8598
  return {
8205
8599
  kind: variant("timed_out", { ms: outcome.value.ms }),
@@ -8259,8 +8653,15 @@ class RecordRuntime extends TrackedChannelStore {
8259
8653
  this.cache = cache3;
8260
8654
  this.workspace = workspace;
8261
8655
  }
8262
- /** Tear down the adapter and all record state. */
8263
- clear() {
8656
+ /**
8657
+ * Tear down the adapter and all record state.
8658
+ *
8659
+ * @param api - Clear only while this is the installed adapter, so a
8660
+ * provider tearing down after another installed its own leaves that one
8661
+ * in place
8662
+ */
8663
+ clear(api3) {
8664
+ if (api3 !== void 0 && this.api !== api3) return;
8264
8665
  this.api = null;
8265
8666
  this.cache = null;
8266
8667
  this.workspace = null;
@@ -8568,8 +8969,8 @@ const defaultRecordRuntime = new RecordRuntime();
8568
8969
  function initializeRecordApi(api3, cache3, workspace) {
8569
8970
  defaultRecordRuntime.initialize(api3, cache3, workspace);
8570
8971
  }
8571
- function clearRecordApi() {
8572
- defaultRecordRuntime.clear();
8972
+ function clearRecordApi(api3) {
8973
+ defaultRecordRuntime.clear(api3);
8573
8974
  }
8574
8975
  const RecordPlatform = [
8575
8976
  defaultRecordRuntime.buildPlatform(null),
@@ -8595,7 +8996,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
8595
8996
  state: `${def.name}-state-0`.padEnd(64, "0"),
8596
8997
  mutation: "$init",
8597
8998
  actor: "memory",
8598
- at: /* @__PURE__ */ new Date(0)
8999
+ at: /* @__PURE__ */ new Date(0),
9000
+ delta: none$1
8599
9001
  };
8600
9002
  compiled.set(def.name, { stateType, mutations, commits: [genesis], seq: 0 });
8601
9003
  void cache3.write(workspace, recordPath(def.name), encodeBeast2For(stateType)(def.initial));
@@ -8627,7 +9029,8 @@ function createInMemoryRecordApi(cache3, workspace, defs2) {
8627
9029
  state: `${record}-state-${c2.seq}`.padEnd(64, "0"),
8628
9030
  mutation,
8629
9031
  actor: "memory",
8630
- at: /* @__PURE__ */ new Date(0)
9032
+ at: /* @__PURE__ */ new Date(0),
9033
+ delta: none$1
8631
9034
  });
8632
9035
  return { outcome: variant("committed", { commitHash: hash2, stateHash: `${record}-state-${c2.seq}`.padEnd(64, "0") }) };
8633
9036
  },
@@ -8676,68 +9079,49 @@ function ReactiveDatasetProvider({
8676
9079
  children: children2
8677
9080
  }) {
8678
9081
  const e3 = useE3Config();
9082
+ const { apiUrl, workspace } = e3;
9083
+ const repo = e3.repo ?? "default";
8679
9084
  const tokenRef = useRef(e3.token ?? null);
8680
9085
  tokenRef.current = e3.token ?? null;
8681
- const api3 = useMemo(
8682
- () => createDefaultDatasetApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8683
- [e3.apiUrl, e3.repo]
8684
- );
8685
- const cache3 = useMemo(() => {
8686
- const cfg = {};
8687
- if (e3.workspace !== void 0) cfg.workspace = e3.workspace;
8688
- return new ReactiveDatasetCache(cfg, api3);
8689
- }, [api3, e3.workspace]);
8690
- useMemo(() => {
8691
- initializeReactiveDatasetCache(cache3);
8692
- cache3.setScheduler((notify3) => queueMicrotask(notify3));
8693
- }, [cache3]);
8694
- useMemo(() => {
8695
- if (e3.workspace !== void 0) {
8696
- initializeFunctionApi(
8697
- createDefaultFunctionApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8698
- e3.workspace
8699
- );
8700
- }
8701
- }, [e3.apiUrl, e3.repo, e3.workspace]);
8702
- useMemo(() => {
8703
- if (e3.workspace !== void 0) {
8704
- initializePagedApi(
8705
- createDefaultPagedApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8706
- e3.workspace
8707
- );
8708
- }
8709
- }, [e3.apiUrl, e3.repo, e3.workspace]);
8710
- useMemo(() => {
8711
- if (e3.workspace !== void 0) {
8712
- initializeRecordApi(
8713
- createDefaultRecordApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8714
- cache3,
8715
- e3.workspace
8716
- );
8717
- }
8718
- }, [e3.apiUrl, e3.repo, e3.workspace, cache3]);
9086
+ const [installed, setInstalled] = useState(null);
8719
9087
  useEffect(() => {
9088
+ const getToken = () => tokenRef.current;
9089
+ const cache22 = new ReactiveDatasetCache(
9090
+ workspace !== void 0 ? { workspace } : {},
9091
+ createDefaultDatasetApi(apiUrl, repo, getToken)
9092
+ );
9093
+ cache22.setScheduler((notify3) => queueMicrotask(notify3));
9094
+ initializeReactiveDatasetCache(cache22);
9095
+ const functionApi = createDefaultFunctionApi(apiUrl, repo, getToken);
9096
+ const pagedApi = createDefaultPagedApi(apiUrl, repo, getToken, cache22);
9097
+ const recordApi = createDefaultRecordApi(apiUrl, repo, getToken);
9098
+ if (workspace !== void 0) {
9099
+ initializeFunctionApi(functionApi, workspace);
9100
+ initializePagedApi(pagedApi, workspace);
9101
+ initializeRecordApi(recordApi, cache22, workspace);
9102
+ }
9103
+ const unscopeStaged = getStagedStore().setScope(JSON.stringify([apiUrl, repo]));
9104
+ setInstalled({ apiUrl, repo, workspace, cache: cache22 });
8720
9105
  return () => {
8721
- const ws = cache3.getConfig().workspace;
8722
- clearReactiveDatasetCache();
8723
- clearFunctionApi();
8724
- clearPagedApi();
8725
- clearRecordApi();
8726
- cache3.destroy();
8727
- if (ws) clearBindingRegistry(ws);
9106
+ clearReactiveDatasetCache(cache22);
9107
+ clearFunctionApi(functionApi);
9108
+ clearPagedApi(pagedApi);
9109
+ clearRecordApi(recordApi);
9110
+ unscopeStaged();
9111
+ cache22.destroy();
9112
+ if (workspace) clearBindingRegistry(workspace);
8728
9113
  else clearBindingRegistry();
8729
9114
  };
8730
- }, [cache3]);
9115
+ }, [apiUrl, repo, workspace]);
9116
+ const cache3 = installed !== null && installed.apiUrl === apiUrl && installed.repo === repo && installed.workspace === workspace ? installed.cache : null;
8731
9117
  useEffect(() => {
8732
- if (typeof window !== "undefined") {
8733
- window.__EAST_REACTIVE_DATASET_CACHE__ = cache3;
8734
- }
9118
+ if (cache3 === null || typeof window === "undefined") return;
9119
+ window.__EAST_REACTIVE_DATASET_CACHE__ = cache3;
8735
9120
  return () => {
8736
- if (typeof window !== "undefined") {
8737
- delete window.__EAST_REACTIVE_DATASET_CACHE__;
8738
- }
9121
+ delete window.__EAST_REACTIVE_DATASET_CACHE__;
8739
9122
  };
8740
9123
  }, [cache3]);
9124
+ if (cache3 === null) return null;
8741
9125
  return /* @__PURE__ */ jsx$1(ReactiveDatasetCacheContext.Provider, { value: cache3, children: children2 });
8742
9126
  }
8743
9127
  function useReactiveDatasetCache() {
@@ -8856,12 +9240,11 @@ function collectLeaves(node2, into = []) {
8856
9240
  else for (const c2 of node2.children) collectLeaves(c2, into);
8857
9241
  return into;
8858
9242
  }
9243
+ const readString = parseFor(StringType);
8859
9244
  function leafDisplayLabel(seg) {
8860
- if (seg.kind === "key" && typeof seg.key === "string" && seg.key.length >= 2 && seg.key.startsWith('"') && seg.key.endsWith('"')) {
8861
- try {
8862
- return JSON.parse(seg.key);
8863
- } catch {
8864
- }
9245
+ if (seg.kind === "key") {
9246
+ const read = readString(seg.key);
9247
+ if (read.success) return read.value;
8865
9248
  }
8866
9249
  return pathDisplay(seg);
8867
9250
  }
@@ -8913,57 +9296,28 @@ function walkPatchToTree(typeValue, patch, rootLabel) {
8913
9296
  return root2;
8914
9297
  }
8915
9298
  const MAX_INLINE_CHARS = 48;
8916
- function formatLeafValue(typeValue, value) {
8917
- if (value === null || value === void 0) return "null";
8918
- if (!typeValue) return "<value>";
8919
- switch (typeValue.type) {
8920
- case "Null":
8921
- return "null";
8922
- case "Boolean":
8923
- return value ? "true" : "false";
8924
- case "Integer":
8925
- return typeof value === "bigint" ? value.toString() : String(value);
8926
- case "Float":
8927
- return formatFloat(value);
8928
- case "String":
8929
- return value === "" ? '""' : String(value);
8930
- case "DateTime":
8931
- return value instanceof Date ? value.toISOString() : String(value);
8932
- case "Blob":
8933
- return value instanceof Uint8Array ? `${value.length} bytes` : truncate(printFor(typeValue)(value));
8934
- case "Variant":
8935
- if (isVariant(value)) {
8936
- const cases = typeValue.value;
8937
- const sub = cases.find((c2) => c2.name === value.type);
8938
- if (value.value === null || value.value === void 0 || !sub) return value.type;
8939
- return `${value.type}(${truncate(printFor(sub.type)(value.value))})`;
8940
- }
8941
- return "<value>";
8942
- default:
8943
- return truncate(printFor(typeValue)(value));
8944
- }
8945
- }
8946
- function formatFloat(v3) {
8947
- if (typeof v3 !== "number") return String(v3);
8948
- if (Number.isInteger(v3)) return v3.toFixed(1);
8949
- return Math.abs(v3) >= 1e3 ? v3.toLocaleString("en-US", { maximumFractionDigits: 2 }) : String(Number(v3.toFixed(4)));
9299
+ function formatLeafValue(leafType, value, words = formatters()) {
9300
+ if (leafType.type === "Float") return words.float(value);
9301
+ const text = printFor(leafType)(value);
9302
+ return leafType.type === "String" ? text : truncate(text);
8950
9303
  }
8951
9304
  function truncate(s2) {
8952
9305
  return s2.length > MAX_INLINE_CHARS ? s2.slice(0, MAX_INLINE_CHARS - 1) + "…" : s2;
8953
9306
  }
8954
- function formatBindingLabel(segments) {
8955
- if (segments.length === 0) return "(root)";
8956
- const last2 = segments[segments.length - 1];
8957
- if (last2 && typeof last2 === "object" && "type" in last2) {
8958
- if (last2.type === "field") return String(last2.value);
8959
- if (last2.type === "index") return `[${String(last2.value)}]`;
8960
- if (last2.type === "key") return String(last2.value);
9307
+ function formatBindingLabel(path2) {
9308
+ const last2 = path2[path2.length - 1];
9309
+ if (last2 === void 0) return "(root)";
9310
+ switch (last2.type) {
9311
+ case "field":
9312
+ return last2.value;
8961
9313
  }
8962
- return String(last2);
8963
9314
  }
8964
- function isPrimitiveLeafType(t4) {
8965
- if (!t4) return false;
8966
- switch (t4.type) {
9315
+ const DATETIME_LOCAL = tokenizeDateTimeFormat("YYYY-MM-DDTHH:mm");
9316
+ const readBoolean = parseFor(BooleanType);
9317
+ const readInteger = parseFor(IntegerType);
9318
+ const readFloat = parseFor(FloatType);
9319
+ function isPrimitiveLeafType(leafType) {
9320
+ switch (leafType.type) {
8967
9321
  case "Boolean":
8968
9322
  case "Integer":
8969
9323
  case "Float":
@@ -8975,49 +9329,46 @@ function isPrimitiveLeafType(t4) {
8975
9329
  }
8976
9330
  }
8977
9331
  function formatManualDraft(leafType, value) {
8978
- if (!leafType || value === void 0 || value === null) return "";
9332
+ if (value === void 0) return "";
8979
9333
  switch (leafType.type) {
8980
9334
  case "Boolean":
8981
- return value ? "true" : "false";
8982
9335
  case "Integer":
8983
- return typeof value === "bigint" ? value.toString() : String(value);
8984
9336
  case "Float":
8985
- return typeof value === "number" ? String(value) : String(value);
9337
+ return printFor(leafType)(value);
8986
9338
  case "String":
8987
- return String(value);
8988
- case "DateTime": {
8989
- if (!(value instanceof Date)) return String(value);
8990
- return value.toISOString().slice(0, 16);
8991
- }
9339
+ return value;
9340
+ case "DateTime":
9341
+ return formatDateTime(value, DATETIME_LOCAL);
8992
9342
  default:
8993
9343
  return "";
8994
9344
  }
8995
9345
  }
8996
9346
  function parseManualDraft(leafType, draft) {
8997
- try {
8998
- switch (leafType.type) {
8999
- case "Integer":
9000
- return { ok: true, value: BigInt(draft) };
9001
- case "Float": {
9002
- const n2 = Number(draft);
9003
- if (Number.isNaN(n2)) return { ok: false };
9004
- return { ok: true, value: n2 };
9005
- }
9006
- case "String":
9007
- return { ok: true, value: draft };
9008
- case "DateTime": {
9009
- const d2 = new Date(draft);
9010
- if (Number.isNaN(d2.getTime())) return { ok: false };
9011
- return { ok: true, value: d2 };
9012
- }
9013
- default:
9014
- return { ok: false };
9347
+ switch (leafType.type) {
9348
+ case "Boolean": {
9349
+ const read = readBoolean(draft);
9350
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9015
9351
  }
9016
- } catch {
9017
- return { ok: false };
9352
+ case "Integer": {
9353
+ const read = readInteger(draft);
9354
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9355
+ }
9356
+ case "Float": {
9357
+ const read = readFloat(draft);
9358
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9359
+ }
9360
+ case "String":
9361
+ return { ok: true, value: draft };
9362
+ case "DateTime": {
9363
+ const read = parseDateTimeFormatted(draft, DATETIME_LOCAL);
9364
+ return read.success ? { ok: true, value: read.value } : { ok: false };
9365
+ }
9366
+ default:
9367
+ return { ok: false };
9018
9368
  }
9019
9369
  }
9020
- const diffValueEqual = equalFor(Diff.Component.schema);
9370
+ const diffValueEqual = equivalentFor(Diff.Component.schema);
9371
+ const diffValueDataEqual = equalFor(Diff.Component.schema);
9021
9372
  function getOpt$1(opt) {
9022
9373
  return opt.type === "some" ? opt.value : void 0;
9023
9374
  }
@@ -9243,6 +9594,7 @@ function ActionBtn({
9243
9594
  );
9244
9595
  }
9245
9596
  const DiffRow = memo(function DiffRow2({ row, depth, bindingPathStr, showActions, onDiscard, annotation, metrics }) {
9597
+ const words = useFormatters();
9246
9598
  const handleDiscard = useCallback(() => onDiscard(bindingPathStr, row.path), [onDiscard, bindingPathStr, row.path]);
9247
9599
  return /* @__PURE__ */ jsxs(
9248
9600
  Box,
@@ -9275,7 +9627,7 @@ const DiffRow = memo(function DiffRow2({ row, depth, bindingPathStr, showActions
9275
9627
  row.stale && /* @__PURE__ */ jsxs(Text, { fontSize: "11px", color: "fg.warning", fontWeight: 500, children: [
9276
9628
  /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }),
9277
9629
  " ",
9278
- row.stale.actual === void 0 ? "stale — source no longer has this entry" : `stale — source actually has ${formatLeafValue(row.leafType, row.stale.actual)}`
9630
+ row.stale.actual === void 0 ? "stale — source no longer has this entry" : `stale — source actually has ${formatLeafValue(row.leafType, row.stale.actual, words)}`
9279
9631
  ] }),
9280
9632
  annotation && /* @__PURE__ */ jsx$1(Text, { fontSize: "11px", color: `${annotation.tone}.700`, fontWeight: 500, children: annotation.text })
9281
9633
  ] }),
@@ -9284,13 +9636,13 @@ const DiffRow = memo(function DiffRow2({ row, depth, bindingPathStr, showActions
9284
9636
  row.before !== void 0 && /* @__PURE__ */ jsxs(Chip$1, { fontSize: metrics.chipFontSize, children: [
9285
9637
  /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.danger", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faMinus }) }),
9286
9638
  " ",
9287
- formatLeafValue(row.leafType, row.before)
9639
+ formatLeafValue(row.leafType, row.before, words)
9288
9640
  ] }),
9289
9641
  row.before !== void 0 && row.after !== void 0 && /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.subtle", fontSize: "11px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faArrowRight }) }),
9290
9642
  row.after !== void 0 && /* @__PURE__ */ jsxs(Chip$1, { fontSize: metrics.chipFontSize, children: [
9291
9643
  /* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.success", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faPlus }) }),
9292
9644
  " ",
9293
- formatLeafValue(row.leafType, row.after)
9645
+ formatLeafValue(row.leafType, row.after, words)
9294
9646
  ] })
9295
9647
  ] }),
9296
9648
  showActions && /* @__PURE__ */ jsx$1(ActionBtn, { icon: faXmark, title: "Discard this change", onClick: handleDiscard, size: metrics.actionBtnSize })
@@ -9299,9 +9651,11 @@ const DiffRow = memo(function DiffRow2({ row, depth, bindingPathStr, showActions
9299
9651
  }
9300
9652
  );
9301
9653
  });
9654
+ const REMOVED = "(removed)";
9302
9655
  const ConflictRow = memo(function ConflictRow2({ row, depth, bindingPathStr, serverValue, resolution, onResolve, metrics }) {
9303
- const yoursStr = formatLeafValue(row.leafType, row.after);
9304
- const theirsStr = formatLeafValue(row.leafType, serverValue);
9656
+ const words = useFormatters();
9657
+ const yoursStr = row.after === void 0 ? REMOVED : formatLeafValue(row.leafType, row.after, words);
9658
+ const theirsStr = serverValue === void 0 ? REMOVED : formatLeafValue(row.leafType, serverValue, words);
9305
9659
  const isYours = (resolution == null ? void 0 : resolution.type) === "keepA";
9306
9660
  const isTheirs = (resolution == null ? void 0 : resolution.type) === "keepB";
9307
9661
  const isManual = (resolution == null ? void 0 : resolution.type) === "manual";
@@ -9367,7 +9721,7 @@ const ConflictRow = memo(function ConflictRow2({ row, depth, bindingPathStr, ser
9367
9721
  {
9368
9722
  selected: isManual,
9369
9723
  leafType: row.leafType,
9370
- value: isManual ? resolution.value : row.after,
9724
+ value: (resolution == null ? void 0 : resolution.type) === "manual" ? resolution.value : row.after,
9371
9725
  onChange: handleManual
9372
9726
  }
9373
9727
  )
@@ -9484,9 +9838,8 @@ function ManualEditor({
9484
9838
  const fire = useCallback((parsed) => {
9485
9839
  queueMicrotask(() => onChange2(parsed));
9486
9840
  }, [onChange2]);
9487
- if (!leafType) return null;
9488
9841
  if (leafType.type === "Boolean") {
9489
- const checked = !!value;
9842
+ const checked = value === true;
9490
9843
  return /* @__PURE__ */ jsxs(
9491
9844
  Box,
9492
9845
  {
@@ -9668,9 +10021,10 @@ const EastChakraDiff = memo(function EastChakraDiff2({ value }) {
9668
10021
  const [conflicts, setConflicts] = useState(null);
9669
10022
  const [resolutions, setResolutions] = useState(/* @__PURE__ */ new Map());
9670
10023
  const [committing, setCommitting] = useState(false);
10024
+ const data4 = useDataStable(value, diffValueDataEqual);
9671
10025
  const irBindings = useMemo(
9672
- () => value.bindings ?? [],
9673
- [value.bindings]
10026
+ () => data4.bindings ?? [],
10027
+ [data4.bindings]
9674
10028
  );
9675
10029
  const interactive = !(getOpt$1(value.readonly) ?? false);
9676
10030
  const onCommittedFn = getOpt$1(value.onCommitted);
@@ -9704,7 +10058,7 @@ const EastChakraDiff = memo(function EastChakraDiff2({ value }) {
9704
10058
  useEffect(() => {
9705
10059
  setConflicts(null);
9706
10060
  setResolutions(/* @__PURE__ */ new Map());
9707
- }, [value, stagedVersion, datasetVersion]);
10061
+ }, [data4, stagedVersion, datasetVersion]);
9708
10062
  const visibleBindings = useMemo(
9709
10063
  () => bindings.filter((b2) => b2.leaves.length > 0),
9710
10064
  [bindings]
@@ -23083,13 +23437,13 @@ var hashIterableInts = function hashIterableInts2(iterator) {
23083
23437
  }
23084
23438
  return hash2;
23085
23439
  };
23086
- var hashInt = function hashInt2(num2) {
23440
+ var hashInt = function hashInt2(num) {
23087
23441
  var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED;
23088
- return seed * K + num2 | 0;
23442
+ return seed * K + num | 0;
23089
23443
  };
23090
- var hashIntAlt = function hashIntAlt2(num2) {
23444
+ var hashIntAlt = function hashIntAlt2(num) {
23091
23445
  var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED_ALT;
23092
- return (seed << 5) + seed + num2 | 0;
23446
+ return (seed << 5) + seed + num | 0;
23093
23447
  };
23094
23448
  var combineHashes = function combineHashes2(hash1, hash2) {
23095
23449
  return hash1 * 2097152 + hash2;
@@ -23119,7 +23473,7 @@ var hashIntsArray = function hashIntsArray2(ints, seed) {
23119
23473
  };
23120
23474
  return hashIterableInts(iterator, seed);
23121
23475
  };
23122
- var hashString$1 = function hashString(str, seed) {
23476
+ var hashString = function hashString2(str, seed) {
23123
23477
  var entry = {
23124
23478
  value: 0,
23125
23479
  done: false
@@ -23146,9 +23500,9 @@ var hashStringsArray = function hashStringsArray2(strs) {
23146
23500
  for (var i = 0; i < strs.length; i++) {
23147
23501
  var str = strs[i];
23148
23502
  if (i === 0) {
23149
- hash2 = hashString$1(str);
23503
+ hash2 = hashString(str);
23150
23504
  } else {
23151
- hash2 = hashString$1(str, hash2);
23505
+ hash2 = hashString(str, hash2);
23152
23506
  }
23153
23507
  }
23154
23508
  return hash2;
@@ -28714,9 +29068,9 @@ function requireGet() {
28714
29068
  if (hasRequiredGet) return get_1;
28715
29069
  hasRequiredGet = 1;
28716
29070
  var baseGet = require_baseGet();
28717
- function get3(object3, path2, defaultValue) {
29071
+ function get3(object3, path2, defaultValue2) {
28718
29072
  var result = object3 == null ? void 0 : baseGet(object3, path2);
28719
- return result === void 0 ? defaultValue : result;
29073
+ return result === void 0 ? defaultValue2 : result;
28720
29074
  }
28721
29075
  get_1 = get3;
28722
29076
  return get_1;
@@ -30286,7 +30640,7 @@ var cache = function cache2(fn3, name) {
30286
30640
  var _p = eles[0]._private;
30287
30641
  var tch = _p.traversalCache = _p.traversalCache || {};
30288
30642
  var ch = tch[name] = tch[name] || [];
30289
- var hash2 = hashString$1(key);
30643
+ var hash2 = hashString(key);
30290
30644
  var cacheHit = ch[hash2];
30291
30645
  if (cacheHit) {
30292
30646
  return cacheHit;
@@ -32892,13 +33246,13 @@ function styleCache(key, fn3, ele) {
32892
33246
  }
32893
33247
  }
32894
33248
  function cacheStyleFunction(key, fn3) {
32895
- key = hashString$1(key);
33249
+ key = hashString(key);
32896
33250
  return function cachedStyleFunction(ele) {
32897
33251
  return styleCache(key, fn3, ele);
32898
33252
  };
32899
33253
  }
32900
33254
  function cachePrototypeStyleFunction(key, fn3) {
32901
- key = hashString$1(key);
33255
+ key = hashString(key);
32902
33256
  var selfFn = function selfFn2(ele) {
32903
33257
  return fn3.call(ele);
32904
33258
  };
@@ -43169,7 +43523,7 @@ BRp$9.applyLabelDimensions = function(ele) {
43169
43523
  BRp$9.applyPrefixedLabelDimensions = function(ele, prefix2) {
43170
43524
  var _p = ele._private;
43171
43525
  var text = this.getLabelText(ele, prefix2);
43172
- var cacheKey = hashString$1(text, ele._private.labelDimsKey);
43526
+ var cacheKey = hashString(text, ele._private.labelDimsKey);
43173
43527
  if (getPrefixedProperty(_p.rscratch, "prefixedLabelDimsKey", prefix2) === cacheKey) {
43174
43528
  return;
43175
43529
  }
@@ -47565,7 +47919,7 @@ CRp$9.drawArrowShape = function(edge, context, fill, edgeWidth, shape, shapeWidt
47565
47919
  var shapeImpl = r2.arrowShapes[shape];
47566
47920
  if (usePaths) {
47567
47921
  var cache3 = r2.arrowPathCache = r2.arrowPathCache || [];
47568
- var key = hashString$1(shape);
47922
+ var key = hashString(shape);
47569
47923
  var cachedPath = cache3[key];
47570
47924
  if (cachedPath != null) {
47571
47925
  path2 = context = cachedPath;
@@ -54578,25 +54932,25 @@ function requireLayoutBase() {
54578
54932
  }
54579
54933
  }
54580
54934
  };
54581
- Layout2.transform = function(sliderValue, defaultValue, minDiv, maxMul) {
54935
+ Layout2.transform = function(sliderValue, defaultValue2, minDiv, maxMul) {
54582
54936
  if (minDiv != void 0 && maxMul != void 0) {
54583
- var value = defaultValue;
54937
+ var value = defaultValue2;
54584
54938
  if (sliderValue <= 50) {
54585
- var minValue = defaultValue / minDiv;
54586
- value -= (defaultValue - minValue) / 50 * (50 - sliderValue);
54939
+ var minValue = defaultValue2 / minDiv;
54940
+ value -= (defaultValue2 - minValue) / 50 * (50 - sliderValue);
54587
54941
  } else {
54588
- var maxValue = defaultValue * maxMul;
54589
- value += (maxValue - defaultValue) / 50 * (sliderValue - 50);
54942
+ var maxValue = defaultValue2 * maxMul;
54943
+ value += (maxValue - defaultValue2) / 50 * (sliderValue - 50);
54590
54944
  }
54591
54945
  return value;
54592
54946
  } else {
54593
54947
  var a2, b2;
54594
54948
  if (sliderValue <= 50) {
54595
- a2 = 9 * defaultValue / 500;
54596
- b2 = defaultValue / 10;
54949
+ a2 = 9 * defaultValue2 / 500;
54950
+ b2 = defaultValue2 / 10;
54597
54951
  } else {
54598
- a2 = 9 * defaultValue / 50;
54599
- b2 = -8 * defaultValue;
54952
+ a2 = 9 * defaultValue2 / 50;
54953
+ b2 = -8 * defaultValue2;
54600
54954
  }
54601
54955
  return a2 * sliderValue + b2;
54602
54956
  }
@@ -57947,7 +58301,7 @@ function useBindingValue(binding) {
57947
58301
  try {
57948
58302
  return { value: handle.read(), pending: handle.pending(), error: void 0 };
57949
58303
  } catch (e3) {
57950
- console.error(`[Experiment] failed to read binding "${String(sourcePath)}":`, e3);
58304
+ console.error(`[Experiment] failed to read binding "${pathToString$1(sourcePath)}":`, e3);
57951
58305
  return { value: null, pending: false, error: e3 };
57952
58306
  }
57953
58307
  }, [handle, version2]);
@@ -57963,20 +58317,25 @@ function useBindingValue(binding) {
57963
58317
  }, [handle]);
57964
58318
  return { value, mode, pending, error: error3, mutate, commit, discard };
57965
58319
  }
58320
+ function sameType(a2, b2) {
58321
+ return isTypeValueEqual(toEastTypeValue(a2), toEastTypeValue(b2));
58322
+ }
58323
+ function sameSignature(a2, b2) {
58324
+ const ai = a2.inputs;
58325
+ const bi = b2.inputs;
58326
+ return sameType(a2.output, b2.output) && (ai === null || bi === null ? ai === bi : ai.length === bi.length && ai.every((t4, i) => sameType(t4, bi[i])));
58327
+ }
57966
58328
  const IDLE = { call: () => {
57967
58329
  }, result: null, status: "idle", pending: false, error: null };
57968
58330
  function useFuncCall(name, inputs, output2) {
57969
58331
  const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
57970
58332
  const ready4 = !!name && !!inputs && workspace !== "";
57971
- const sigKey = useMemo(
57972
- () => ready4 ? JSON.stringify([inputs.map((t4) => toEastTypeValue(t4)), toEastTypeValue(output2)]) : "",
57973
- [ready4, inputs, output2]
57974
- );
58333
+ const signature = useDataStable({ inputs, output: output2 }, sameSignature);
57975
58334
  const handle = useMemo(() => {
57976
- if (!ready4) return null;
57977
- const handleType = toEastTypeValue(FuncBindHandleType(inputs, output2));
58335
+ if (!ready4 || signature.inputs === null) return null;
58336
+ const handleType = toEastTypeValue(FuncBindHandleType(signature.inputs, signature.output));
57978
58337
  return defaultFuncRuntime.buildHandle(handleType, name);
57979
- }, [ready4, name, workspace, sigKey]);
58338
+ }, [ready4, name, workspace, signature]);
57980
58339
  const channelKey = ready4 ? funcChannelKey(workspace, name) : "";
57981
58340
  const version2 = useSyncExternalStore(
57982
58341
  useCallback(
@@ -57987,7 +58346,6 @@ function useFuncCall(name, inputs, output2) {
57987
58346
  useCallback(() => channelKey ? defaultFuncRuntime.getKeyVersion(channelKey) : 0, [channelKey])
57988
58347
  );
57989
58348
  return useMemo(() => {
57990
- var _a2;
57991
58349
  if (!handle) return IDLE;
57992
58350
  let result = null;
57993
58351
  let status = "idle";
@@ -57999,10 +58357,7 @@ function useFuncCall(name, inputs, output2) {
57999
58357
  status = handle.status().type;
58000
58358
  pending = handle.pending();
58001
58359
  const err = handle.error();
58002
- if (err.type === "some") {
58003
- const v3 = err.value;
58004
- error3 = { kind: ((_a2 = v3.kind) == null ? void 0 : _a2.type) ?? "failed", message: v3.message ?? "", stdout: v3.stdout ?? "", stderr: v3.stderr ?? "" };
58005
- }
58360
+ if (err.type === "some") error3 = err.value;
58006
58361
  } catch {
58007
58362
  }
58008
58363
  return { call: (...args) => {
@@ -60620,207 +60975,14 @@ function GuidanceToggle({ on: on3, onToggle }) {
60620
60975
  }
60621
60976
  ) });
60622
60977
  }
60623
- function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
60624
- const span = /* @__PURE__ */ jsx$1("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
60625
- return /* @__PURE__ */ jsx$1("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsx$1("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: span }) : span }) });
60626
- }
60627
- function svgText(size3, fill, family, weight8) {
60628
- return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
60629
- }
60630
- function useMeasuredWidth() {
60631
- const ref = useRef(null);
60632
- const [w2, setW] = useState(0);
60633
- useLayoutEffect(() => {
60634
- const el = ref.current;
60635
- if (!el) return;
60636
- const update2 = () => setW(el.clientWidth);
60637
- update2();
60638
- const ro = new ResizeObserver(update2);
60639
- ro.observe(el);
60640
- return () => ro.disconnect();
60641
- }, []);
60642
- return [ref, w2];
60643
- }
60644
- function useChartTheme() {
60645
- const system = useChakraContext();
60646
- const tok = (k2, fallback) => system.token(k2, fallback);
60647
- const tones = {
60648
- neg: tok("colors.fg.danger", "#b85a4a"),
60649
- pos: tok("colors.fg.success", "#2f7a5b"),
60650
- warn: tok("colors.fg.warning", "#b8862d"),
60651
- muted: tok("colors.fg.muted", "#6b8080"),
60652
- brand: tok("colors.brand.solid", "#3a7780")
60653
- };
60654
- return {
60655
- mono: tok("fonts.mono", '"JetBrains Mono", ui-monospace, monospace'),
60656
- body: tok("fonts.body", "system-ui, -apple-system, sans-serif"),
60657
- ink: tok("colors.fg", "#111b22"),
60658
- muted: tones.muted,
60659
- faint: tok("colors.fg.subtle", "#9bb0b0"),
60660
- rule: tok("colors.border.subtle", "#e2e8e8"),
60661
- ruleStrong: tok("colors.border.strong", "#cbd5d5"),
60662
- surface: tok("colors.bg.surface", "#ffffff"),
60663
- tone: (t4) => tones[t4] ?? tones.brand,
60664
- labelSize: 10,
60665
- titleSize: 11,
60666
- lineWidth: 1.8,
60667
- barWidth: 2.5,
60668
- dotRadius: 2.6,
60669
- scatterRadius: 3.4,
60670
- areaOpacity: 0.16
60671
- };
60672
- }
60673
- const fmt$2 = (n2) => Number.isInteger(n2) ? String(n2) : n2.toFixed(1);
60674
- const signed$1 = (n2) => (n2 > 0 ? "+" : "") + fmt$2(n2);
60675
- const niceCeil = (v3) => {
60676
- if (v3 <= 1e-9) return 0;
60677
- const mag = Math.pow(10, Math.floor(Math.log10(v3)));
60678
- const n2 = v3 / mag;
60679
- 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;
60680
- return step3 * mag;
60681
- };
60682
- function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
60683
- const t4 = useChartTheme();
60684
- const [ref, w2] = useMeasuredWidth();
60685
- const n2 = rows.length;
60686
- const hPx = height2 ?? 26 + n2 * 38;
60687
- const padL = 124, padR = 46, padT = 20, padB = 30;
60688
- const innerW = Math.max(0, w2 - padL - padR);
60689
- const innerH = hPx - padT - padB;
60690
- const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
60691
- const rowH = innerH / Math.max(1, n2);
60692
- const zeroX = Math.max(0, Math.min(innerW, x2(0)));
60693
- const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
60694
- const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
60695
- const zf = frac(0);
60696
- const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
60697
- return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
60698
- /* @__PURE__ */ jsx$1(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
60699
- /* @__PURE__ */ jsx$1("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
60700
- rows.map((r2, i) => {
60701
- const cy = rowH * i + rowH / 2;
60702
- const col = t4.tone(r2.tone);
60703
- return /* @__PURE__ */ jsxs(Group, { top: cy, children: [
60704
- /* @__PURE__ */ jsx$1(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" }),
60705
- /* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
60706
- /* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
60707
- /* @__PURE__ */ jsx$1(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
60708
- /* @__PURE__ */ jsx$1(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 }),
60709
- r2.note && /* @__PURE__ */ jsx$1("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
60710
- /* @__PURE__ */ jsx$1("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed$1(r2.est) })
60711
- ] }, i);
60712
- }),
60713
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60714
- ticks2.map((tk, i) => /* @__PURE__ */ jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
60715
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
60716
- /* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed$1(tk) })
60717
- ] }, `t${i}`)),
60718
- unit2 && /* @__PURE__ */ jsx$1("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
60719
- ] }) }) });
60720
- }
60721
- function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
60722
- const t4 = useChartTheme();
60723
- const [ref, w2] = useMeasuredWidth();
60724
- const hPx = height2 ?? 100;
60725
- const col = t4.tone(toneName ?? "brand");
60726
- const padL = yTicks.length ? 52 : 6;
60727
- const padR = 12;
60728
- const padT = marks.length ? 22 : 8;
60729
- const padB = xTicks.length ? 22 : 12;
60730
- const innerW = Math.max(0, w2 - padL - padR);
60731
- const innerH = hPx - padT - padB;
60732
- const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
60733
- const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
60734
- const data4 = mid.map((m2, i) => {
60735
- const mm = fin(m2, 0);
60736
- return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
60737
- });
60738
- const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
60739
- const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
60740
- const yHiN = niceCeil(Math.max(dataMax, 0));
60741
- const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
60742
- const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
60743
- const yfmt = (v3) => yFormat === "percent" ? `${Math.round(v3)}%` : signed$1(v3);
60744
- const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
60745
- return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
60746
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60747
- yTicks.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
60748
- /* @__PURE__ */ jsx$1("text", { x: -8, y: 10, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[0] }),
60749
- /* @__PURE__ */ jsx$1("text", { x: -8, y: innerH / 2 + 3, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[1] }),
60750
- /* @__PURE__ */ jsx$1("text", { x: -8, y: innerH - 1, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[2] })
60751
- ] }),
60752
- zero2 != null && /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: yScale(zero2) }, to: { x: innerW, y: yScale(zero2) }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
60753
- /* @__PURE__ */ jsx$1(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" }),
60754
- /* @__PURE__ */ jsx$1(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
60755
- marks.map((m2, i) => {
60756
- const mc = t4.tone(m2.tone);
60757
- const dotY = yScale(fin(mid[m2.at], 0));
60758
- const labY = Math.max(9, dotY - 9);
60759
- const atEnd = m2.at >= mid.length - 2;
60760
- const atStart = m2.at <= 1;
60761
- const align = atEnd ? "right" : atStart ? "left" : "center";
60762
- const labW = m2.label.length * 6.5 + 8;
60763
- const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
60764
- return /* @__PURE__ */ jsxs(Group, { left: xScale(m2.at), children: [
60765
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
60766
- /* @__PURE__ */ jsx$1(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
60767
- /* @__PURE__ */ jsx$1(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 })
60768
- ] }, `mk${i}`);
60769
- }),
60770
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
60771
- xTicks.map((tk, i) => {
60772
- const word = Number.isNaN(Number(tk));
60773
- return /* @__PURE__ */ jsx$1("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}`);
60774
- })
60775
- ] }) }) });
60776
- }
60777
- function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
60778
- const t4 = useChartTheme();
60779
- const [ref, w2] = useMeasuredWidth();
60780
- const hPx = height2 ?? 150;
60781
- const padL = 8, padR = 8, padT = 16, padB = 26;
60782
- const innerW = Math.max(0, w2 - padL - padR);
60783
- const innerH = hPx - padT - padB;
60784
- const centerY = innerH / 2;
60785
- const halfH = centerY - 4;
60786
- const bins = Math.max(treated.length, control.length);
60787
- const x2 = createLinearScale({ domain, range: [0, innerW] });
60788
- const span = domain[1] - domain[0];
60789
- const binW = bins > 0 ? innerW / bins : innerW;
60790
- const barW = Math.max(1, binW * 0.78);
60791
- const maxVal = Math.max(1e-9, ...treated, ...control);
60792
- const tCol = t4.tone("pos");
60793
- const cCol = t4.tone("muted");
60794
- const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
60795
- const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
60796
- return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
60797
- /* @__PURE__ */ jsx$1("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
60798
- supportLabel && /* @__PURE__ */ jsx$1("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
60799
- Array.from({ length: bins }, (_2, i) => {
60800
- const ht = Number(treated[i] ?? 0) / maxVal * halfH;
60801
- const hc = Number(control[i] ?? 0) / maxVal * halfH;
60802
- const left = cx2(i) - barW / 2;
60803
- return /* @__PURE__ */ jsxs(Group, { children: [
60804
- ht > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
60805
- hc > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
60806
- ] }, i);
60807
- }),
60808
- /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
60809
- /* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
60810
- ticks2.map((tk, i) => /* @__PURE__ */ jsx$1("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}`))
60811
- ] }) }) });
60812
- }
60813
60978
  const arr = (v3) => v3 ? Array.from(v3, Number) : [];
60814
60979
  const arrI = (v3) => v3 ? Array.from(v3, Number) : [];
60815
- const fmt$1 = (x2) => {
60816
- const r2 = Number.isInteger(x2) ? String(x2) : x2.toFixed(1);
60817
- return r2 === "-0.0" ? "0.0" : r2;
60818
- };
60819
- const signed = (x2) => {
60820
- const f2 = fmt$1(x2);
60821
- return f2 === "0" || f2 === "0.0" || f2.startsWith("-") ? f2 : `+${f2}`;
60822
- };
60980
+ const ONE_DECIMAL = variant("number", { minimumFractionDigits: some(1n), maximumFractionDigits: some(1n), signDisplay: none$1 });
60981
+ const formatOf = (x2) => Number.isInteger(x2) ? void 0 : ONE_DECIMAL;
60982
+ const fmt$1 = (x2, words) => words.value(Math.abs(x2) < 0.05 ? 0 : x2, formatOf(x2));
60983
+ const signed = (x2, words) => words.value(x2, formatOf(x2), true);
60823
60984
  const clamp01 = (x2) => x2 < 0 ? 0 : x2 > 1 ? 1 : x2;
60985
+ const compareFloats = compareFor(FloatType);
60824
60986
  function band(absStd) {
60825
60987
  if (absStd >= 0.66) return { level: "large gap", tone: "neg" };
60826
60988
  if (absStd >= 0.33) return { level: "some", tone: "warn" };
@@ -60870,7 +61032,7 @@ function treatmentKind(cols, treatment) {
60870
61032
  return "value";
60871
61033
  }
60872
61034
  }
60873
- function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
61035
+ function deriveSpec(config2, cols, result, meta3, dataLen, subject, words) {
60874
61036
  const outUnit = unitOf(meta3, config2.outcome);
60875
61037
  const balByCol = /* @__PURE__ */ new Map();
60876
61038
  for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
@@ -60908,24 +61070,24 @@ function deriveSpec(config2, cols, result, meta3, dataLen, subject) {
60908
61070
  // n_total belongs to the Answer-tab counts strip; labelling the header
60909
61071
  // with it desyncs from the data after a population edit (and reads
60910
61072
  // wrong whenever the two legitimately differ).
60911
- dataLabel: `${dataLen} ${subject.many}`
61073
+ dataLabel: `${words.number(dataLen)} ${subject.many}`
60912
61074
  };
60913
61075
  }
60914
- function balanceDisplay(b2, categorical) {
61076
+ function balanceDisplay(b2, categorical, words) {
60915
61077
  const isProp = categorical.has(b2.base_column) && b2.treated_mean >= 0 && b2.treated_mean <= 1 && b2.control_mean >= 0 && b2.control_mean <= 1;
60916
- 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)}`;
61078
+ 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)}`;
60917
61079
  }
60918
- function deriveAnswer(config2, result, adj, meta3) {
61080
+ function deriveAnswer(config2, result, adj, meta3, words) {
60919
61081
  const ci = getSomeorUndefined(adj.ci);
60920
61082
  const nci = getSomeorUndefined(result.naive_ci);
60921
61083
  const categorical = new Set(getSomeorUndefined(config2.categorical) ?? []);
60922
- const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
61084
+ const balance = [...result.balance].sort((a2, b2) => compareFloats(Math.abs(b2.std_diff), Math.abs(a2.std_diff))).map((b2) => ({
60923
61085
  // One-hot rows are `col=level` — label the BASE confounder, keep the level.
60924
61086
  col: b2.column,
60925
61087
  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})`}`,
60926
61088
  treated: b2.treated_mean,
60927
61089
  control: b2.control_mean,
60928
- display: balanceDisplay(b2, categorical),
61090
+ display: balanceDisplay(b2, categorical, words),
60929
61091
  frac: clamp01(Math.abs(b2.std_diff)),
60930
61092
  tone: band(Math.abs(b2.std_diff)).tone
60931
61093
  }));
@@ -60956,12 +61118,12 @@ function deriveAnswer(config2, result, adj, meta3) {
60956
61118
  balance
60957
61119
  };
60958
61120
  }
60959
- function deriveNarrative(result, a2, refute, higherBetter, subject) {
61121
+ function deriveNarrative(result, a2, refute, higherBetter, subject, words) {
60960
61122
  if (a2 === null) return null;
60961
61123
  const unit2 = a2.unit ? ` ${a2.unit}` : "";
60962
61124
  const dirUp = a2.effect > 0;
60963
61125
  const verb = higherBetter === void 0 ? dirUp ? "raises" : "lowers" : dirUp === higherBetter ? "improves" : "worsens";
60964
- const range = ` (likely between ${signed(a2.lo)} and ${signed(a2.hi)})`;
61126
+ const range = ` (likely between ${signed(a2.lo, words)} and ${signed(a2.hi, words)})`;
60965
61127
  const passed = (refute == null ? void 0 : refute.checks.filter((c2) => c2.passed).length) ?? 0;
60966
61128
  const total = (refute == null ? void 0 : refute.checks.length) ?? 0;
60967
61129
  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?”.`;
@@ -60972,13 +61134,13 @@ function deriveNarrative(result, a2, refute, higherBetter, subject) {
60972
61134
  segments: [
60973
61135
  { text: "Yes — ", strong: true },
60974
61136
  { text: `${a2.treatment} genuinely ${verb} ${a2.outcome}: about ` },
60975
- { text: `${signed(a2.effect)}${unit2}`, strong: true },
61137
+ { text: `${signed(a2.effect, words)}${unit2}`, strong: true },
60976
61138
  { text: ` per ${subject.one}${range}.${trustClause}` }
60977
61139
  ]
60978
61140
  };
60979
61141
  case "modest": {
60980
61142
  const tags = new Set(result.verdict_reasons.map((v3) => v3.type));
60981
- 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.` : ".";
61143
+ 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.` : ".";
60982
61144
  return {
60983
61145
  tone: "warn",
60984
61146
  segments: [
@@ -60993,14 +61155,14 @@ function deriveNarrative(result, a2, refute, higherBetter, subject) {
60993
61155
  tone: "warn",
60994
61156
  segments: [
60995
61157
  { text: "Don’t act on this yet — ", strong: true },
60996
- { 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.` }
61158
+ { 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.` }
60997
61159
  ]
60998
61160
  };
60999
61161
  default:
61000
61162
  return null;
61001
61163
  }
61002
61164
  }
61003
- function deriveRefusal(config2, result, meta3, subject) {
61165
+ function deriveRefusal(config2, result, meta3, subject, words) {
61004
61166
  const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
61005
61167
  const treatment = labelOf(meta3, config2.treatment);
61006
61168
  if (result.verdict.type === "not_estimable") {
@@ -61010,22 +61172,22 @@ function deriveRefusal(config2, result, meta3, subject) {
61010
61172
  title: `Can’t estimate the effect of ${treatment}`,
61011
61173
  body: reason,
61012
61174
  evidence: [
61013
- { label: "treated", value: String(nT) },
61014
- { label: "untreated", value: String(nC) },
61015
- { label: subject.many, value: String(nTot) }
61175
+ { label: "treated", value: words.number(nT) },
61176
+ { label: "untreated", value: words.number(nC) },
61177
+ { label: subject.many, value: words.number(nTot) }
61016
61178
  ]
61017
61179
  };
61018
61180
  }
61019
61181
  if (result.verdict.type === "non_identifiable_positivity") {
61020
- const pct = Math.round(result.overlap.common_support_frac * 100);
61182
+ const pct = words.percent(result.overlap.common_support_frac);
61021
61183
  return {
61022
61184
  kind: "positivity",
61023
61185
  title: "No like-for-like comparison exists",
61024
- 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.`,
61186
+ 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.`,
61025
61187
  evidence: [
61026
- { label: "common support", value: `${pct}%` },
61027
- { label: "treated", value: String(nT) },
61028
- { label: "untreated", value: String(nC) }
61188
+ { label: "common support", value: pct },
61189
+ { label: "treated", value: words.number(nT) },
61190
+ { label: "untreated", value: words.number(nC) }
61029
61191
  ]
61030
61192
  };
61031
61193
  }
@@ -61034,20 +61196,19 @@ function deriveRefusal(config2, result, meta3, subject) {
61034
61196
  title: `Can’t estimate the effect of ${treatment}`,
61035
61197
  body: "The engine could not produce a like-for-like estimate for this configuration.",
61036
61198
  evidence: [
61037
- { label: "treated", value: String(nT) },
61038
- { label: "untreated", value: String(nC) },
61039
- { label: subject.many, value: String(nTot) }
61199
+ { label: "treated", value: words.number(nT) },
61200
+ { label: "untreated", value: words.number(nC) },
61201
+ { label: subject.many, value: words.number(nTot) }
61040
61202
  ]
61041
61203
  };
61042
61204
  }
61043
- function deriveOverlap(o2) {
61044
- const pct = Math.round(o2.common_support_frac * 100);
61205
+ function deriveOverlap(o2, words) {
61045
61206
  return {
61046
61207
  treated: arr(o2.treated_propensity),
61047
61208
  control: arr(o2.control_propensity),
61048
61209
  commonSupportFrac: o2.common_support_frac,
61049
61210
  positivityOk: o2.positivity_ok,
61050
- supportLabel: `${pct}% common support`
61211
+ supportLabel: `${words.percent(o2.common_support_frac)} common support`
61051
61212
  };
61052
61213
  }
61053
61214
  function zeroCrossing(xs, ys) {
@@ -61060,7 +61221,7 @@ function zeroCrossing(xs, ys) {
61060
61221
  }
61061
61222
  return null;
61062
61223
  }
61063
- function deriveRefute(r2, adj, meta3) {
61224
+ function deriveRefute(r2, adj, meta3, words) {
61064
61225
  const checks = [];
61065
61226
  const est = (adj == null ? void 0 : adj.effect) ?? 0;
61066
61227
  const placeboEffect = getSomeorUndefined(r2.placebo_effect);
@@ -61073,7 +61234,7 @@ function deriveRefute(r2, adj, meta3) {
61073
61234
  desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
61074
61235
  // Speak the outcome, not the statistic: on fake labels the effect
61075
61236
  // should VANISH — say whether it did.
61076
- value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect)})` : `didn’t vanish (${signed(placeboEffect)})` : passed ? "vanished" : "didn’t vanish",
61237
+ value: placeboEffect !== void 0 ? passed ? `vanished (${signed(placeboEffect, words)})` : `didn’t vanish (${signed(placeboEffect, words)})` : passed ? "vanished" : "didn’t vanish",
61077
61238
  passed,
61078
61239
  tip: some("Randomly re-label which rows were treated. A genuine effect should vanish."),
61079
61240
  help: "check_shuffle"
@@ -61087,7 +61248,7 @@ function deriveRefute(r2, adj, meta3) {
61087
61248
  name: "Drop-some test",
61088
61249
  short: "drop-some",
61089
61250
  desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
61090
- value: passed ? `stayed at ${signed(ds)} ± ${fmt$1(dstd)}` : `moved to ${signed(ds)} ± ${fmt$1(dstd)}`,
61251
+ value: passed ? `stayed at ${signed(ds, words)} ± ${fmt$1(dstd, words)}` : `moved to ${signed(ds, words)} ± ${fmt$1(dstd, words)}`,
61091
61252
  passed,
61092
61253
  tip: none$1,
61093
61254
  help: "check_dropsome"
@@ -61107,14 +61268,14 @@ function deriveRefute(r2, adj, meta3) {
61107
61268
  }
61108
61269
  const sens = getSomeorUndefined(r2.sensitivity);
61109
61270
  const evalue = getSomeorUndefined(r2.robustness_value);
61110
- const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => b2.strength - a2.strength) : [];
61271
+ const benchmarks = sens ? [...sens.benchmarks].sort((a2, b2) => compareFloats(b2.strength, a2.strength)) : [];
61111
61272
  if (sens !== void 0 || evalue !== void 0) {
61112
61273
  const strengths = sens ? arr(sens.strengths) : [];
61113
61274
  const effects = sens ? arr(sens.effects) : [];
61114
61275
  const tip = sens ? zeroCrossing(strengths, effects) : null;
61115
61276
  const strongest = benchmarks[0];
61116
- 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";
61117
- const scaleNote = strongest !== void 0 ? ` For scale, your strongest known factor (${labelOf(meta3, strongest.column)}) sits at ${fmt$1(strongest.strength)} on this axis.` : "";
61277
+ 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";
61278
+ 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.` : "";
61118
61279
  checks.push({
61119
61280
  name: "Hidden cause",
61120
61281
  short: "hidden cause",
@@ -61150,12 +61311,12 @@ function deriveRefute(r2, adj, meta3) {
61150
61311
  const at = atOf(b2.strength);
61151
61312
  return at === null ? [] : [{ at, label: `as strong as ${labelOf(meta3, b2.column)}`, tone: "muted", help: "sensitivity" }];
61152
61313
  });
61153
- sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)], marks };
61314
+ sensVM = { lo: lo2, mid, hi, xTicks: ["none", "", "stronger"], yTicks: [fmt$1(yLo, words), fmt$1((yLo + yHi) / 2, words), fmt$1(yHi, words)], marks };
61154
61315
  }
61155
61316
  }
61156
61317
  return { checks, sens: sensVM };
61157
61318
  }
61158
- function deriveDose(dose, config2, meta3) {
61319
+ function deriveDose(dose, config2, meta3, words) {
61159
61320
  const grid = arr(dose.grid);
61160
61321
  const mid = arr(dose.effect);
61161
61322
  const lo2 = getSomeorUndefined(dose.lower) ? arr(getSomeorUndefined(dose.lower)) : mid.slice();
@@ -61173,17 +61334,17 @@ function deriveDose(dose, config2, meta3) {
61173
61334
  }
61174
61335
  }
61175
61336
  const xi = grid.length ? [0, Math.floor(grid.length / 2), grid.length - 1] : [];
61176
- const xTicks = xi.map((i) => fmt$1(grid[i]));
61337
+ const xTicks = xi.map((i) => fmt$1(grid[i], words));
61177
61338
  const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
61178
- const yTicks = [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)];
61339
+ const yTicks = [fmt$1(yLo, words), fmt$1((yLo + yHi) / 2, words), fmt$1(yHi, words)];
61179
61340
  const margSteps = marg.slice(1);
61180
61341
  let lastStep = margSteps.length - 1;
61181
61342
  while (lastStep > 0 && Math.abs(margSteps[lastStep]) < 0.12 * maxMarg) lastStep--;
61182
- const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: `${fmt$1(grid[i + 1])}`, value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
61343
+ 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) }));
61183
61344
  const stepFrom = here < sweet ? here : Math.max(0, sweet - 1);
61184
61345
  const gainToSweet = (mid[sweet] ?? 0) - (mid[stepFrom] ?? 0);
61185
61346
  const nextGain = sweet + 1 < mid.length ? mid[sweet + 1] - mid[sweet] : 0;
61186
- 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)}.`;
61347
+ 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)}.`;
61187
61348
  const marks = [];
61188
61349
  if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
61189
61350
  marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
@@ -61200,7 +61361,7 @@ function deriveDose(dose, config2, meta3) {
61200
61361
  marks,
61201
61362
  // Name the feature in the recommendation — a bare "≈ 5.5" doesn't say
61202
61363
  // 5.5 of WHAT.
61203
- recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
61364
+ recoLabel: `aim for ${featureLabel} ≈ ${fmt$1(grid[sweet] ?? 0, words)}${featureUnit ? " " + featureUnit : ""}`,
61204
61365
  recoEffect: mid[sweet] ?? 0,
61205
61366
  recoLo: lo2[sweet] ?? 0,
61206
61367
  recoHi: hi[sweet] ?? 0,
@@ -61209,11 +61370,11 @@ function deriveDose(dose, config2, meta3) {
61209
61370
  framing: `A different question from the headline: how ${outcomeLabel} responds as ${featureLabel} itself moves.`
61210
61371
  };
61211
61372
  }
61212
- function relTime(d2, now2) {
61373
+ function relTime(d2, now2, words) {
61213
61374
  const days = Math.floor((now2.getTime() - d2.getTime()) / 864e5);
61214
61375
  if (days <= 0) return "today";
61215
61376
  if (days === 1) return "yesterday";
61216
- if (days < 7) return d2.toLocaleDateString(void 0, { weekday: "short" });
61377
+ if (days < 7) return words.weekday(d2);
61217
61378
  return "last wk";
61218
61379
  }
61219
61380
  const VERDICT_WORD = {
@@ -61223,43 +61384,43 @@ const VERDICT_WORD = {
61223
61384
  non_identifiable_positivity: "no overlap",
61224
61385
  not_estimable: "n/a"
61225
61386
  };
61226
- function deriveJournalRow(row, now2, meta3) {
61387
+ function deriveJournalRow(row, now2, meta3, words) {
61227
61388
  const adj = getSomeorUndefined(row.adjusted);
61228
61389
  return {
61229
61390
  treatment: labelOf(meta3, row.config.treatment),
61230
61391
  outcome: labelOf(meta3, row.config.outcome),
61231
61392
  confounders: row.config.common_causes.map((c2) => labelOf(meta3, c2)).join(", "),
61232
- effect: adj !== void 0 ? signed(adj) : signed(row.naive),
61393
+ effect: adj !== void 0 ? signed(adj, words) : signed(row.naive, words),
61233
61394
  verdict: VERDICT_WORD[row.verdict.type],
61234
61395
  verdictTone: VERDICT_TONE[row.verdict.type],
61235
61396
  who: row.committed_by,
61236
- when: relTime(row.committed_at, now2),
61397
+ when: relTime(row.committed_at, now2, words),
61237
61398
  // The originating preset id (resolved to a label by the surface); `none` for a
61238
61399
  // free-form / pre-presets row.
61239
61400
  preset: row.preset
61240
61401
  };
61241
61402
  }
61242
- function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }) {
61403
+ function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2, subject = { one: "record", many: "records" }, words = formatters()) {
61243
61404
  var _a2;
61244
61405
  const adj = result ? getSomeorUndefined(result.adjusted) : void 0;
61245
61406
  const refutation = result ? getSomeorUndefined(result.refutation) : void 0;
61246
61407
  const doseR = result ? getSomeorUndefined(result.dose_response) : void 0;
61247
- const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null;
61248
- const refute = refutation ? deriveRefute(refutation, adj, meta3) : null;
61408
+ const answer = result && adj ? deriveAnswer(ranConfig, result, adj, meta3, words) : null;
61409
+ const refute = refutation ? deriveRefute(refutation, adj, meta3, words) : null;
61249
61410
  const higherBetter = getSomeorUndefined((_a2 = colMeta(meta3, ranConfig.outcome)) == null ? void 0 : _a2.higherIsBetter);
61250
61411
  return {
61251
61412
  // The set-up rail reflects the LIVE config (the editor); the result deck
61252
61413
  // reflects RANCONFIG — the config that produced `result` — so the result
61253
61414
  // strings never drift ahead of the numbers on a live picker edit.
61254
- spec: deriveSpec(config2, cols, result, meta3, dataLen, subject),
61415
+ spec: deriveSpec(config2, cols, result, meta3, dataLen, subject, words),
61255
61416
  verdict: result ? deriveVerdict(result.verdict) : null,
61256
- narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject) : null,
61417
+ narrative: result ? deriveNarrative(result, answer, refute, higherBetter, subject, words) : null,
61257
61418
  answer,
61258
- refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject) : null,
61259
- overlap: result ? deriveOverlap(result.overlap) : null,
61419
+ refusal: result && !adj ? deriveRefusal(ranConfig, result, meta3, subject, words) : null,
61420
+ overlap: result ? deriveOverlap(result.overlap, words) : null,
61260
61421
  refute,
61261
- dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
61262
- journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3)) : null
61422
+ dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3, words) : null,
61423
+ journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2, meta3, words)) : null
61263
61424
  };
61264
61425
  }
61265
61426
  const DESIGN_HEADLINE = {
@@ -61278,7 +61439,7 @@ function deriveDesignOption(o2) {
61278
61439
  treatedShare: o2.treated_share
61279
61440
  };
61280
61441
  }
61281
- function deriveDesign(d2, result, ranConfig, meta3) {
61442
+ function deriveDesign(d2, result, ranConfig, meta3, words = formatters()) {
61282
61443
  const basis = d2.basis.type;
61283
61444
  const outcome = ranConfig.outcome;
61284
61445
  const unit2 = unitOf(meta3, outcome);
@@ -61310,9 +61471,9 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61310
61471
  }
61311
61472
  return bi;
61312
61473
  };
61313
- const xTicks = len ? [String(ns[0]), String(ns[Math.floor((len - 1) / 2)]), String(ns[len - 1])] : [];
61474
+ const xTicks = len ? [words.number(ns[0]), words.number(ns[Math.floor((len - 1) / 2)]), words.number(ns[len - 1])] : [];
61314
61475
  const marks = [];
61315
- if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${(d2.target_power * 100).toFixed(0)}%`, tone: "pos", help: "validate_power" });
61476
+ if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${words.percent(d2.target_power)}`, tone: "pos", help: "validate_power" });
61316
61477
  const cp = getSomeorUndefined(d2.current_power);
61317
61478
  if (cp !== void 0 && len) {
61318
61479
  const curN = result ? Number(result.n_total) : 0;
@@ -61320,7 +61481,7 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61320
61481
  const offScale = curN >= (ns[len - 1] ?? 0);
61321
61482
  marks.push({
61322
61483
  at: offScale ? len - 1 : nearest(curN),
61323
- label: offScale ? `you’re here · ${(cp * 100).toFixed(0)}% (off-scale)` : `you’re here ${(cp * 100).toFixed(0)}%`,
61484
+ label: offScale ? `you’re here · ${words.percent(cp)} (off-scale)` : `you’re here ${words.percent(cp)}`,
61324
61485
  tone: "muted",
61325
61486
  help: "validate_power"
61326
61487
  });
@@ -61334,13 +61495,204 @@ function deriveDesign(d2, result, ranConfig, meta3) {
61334
61495
  alternates: options2.slice(1),
61335
61496
  matchOn,
61336
61497
  curve: { mid: powers.slice(0, len).map((p2) => p2 * 100), xTicks, marks },
61337
- targetLabel: `${signed(d2.target_effect)}${unit2 ? ` ${unit2}` : ""}`,
61338
- targetPctLabel: `${(d2.target_power * 100).toFixed(0)}%`,
61498
+ targetLabel: `${signed(d2.target_effect, words)}${unit2 ? ` ${unit2}` : ""}`,
61499
+ targetPctLabel: words.percent(d2.target_power),
61339
61500
  holdback: basis === "create_control",
61340
61501
  showOverlap: basis === "restrict_to_overlap",
61341
61502
  faint
61342
61503
  };
61343
61504
  }
61505
+ function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
61506
+ const span = /* @__PURE__ */ jsx$1("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
61507
+ return /* @__PURE__ */ jsx$1("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsx$1("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: span }) : span }) });
61508
+ }
61509
+ function svgText(size3, fill, family, weight8) {
61510
+ return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
61511
+ }
61512
+ function useMeasuredWidth() {
61513
+ const ref = useRef(null);
61514
+ const [w2, setW] = useState(0);
61515
+ useLayoutEffect(() => {
61516
+ const el = ref.current;
61517
+ if (!el) return;
61518
+ const update2 = () => setW(el.clientWidth);
61519
+ update2();
61520
+ const ro = new ResizeObserver(update2);
61521
+ ro.observe(el);
61522
+ return () => ro.disconnect();
61523
+ }, []);
61524
+ return [ref, w2];
61525
+ }
61526
+ function useChartTheme() {
61527
+ const system = useChakraContext();
61528
+ const tok = (k2, fallback) => system.token(k2, fallback);
61529
+ const tones = {
61530
+ neg: tok("colors.fg.danger", "#b85a4a"),
61531
+ pos: tok("colors.fg.success", "#2f7a5b"),
61532
+ warn: tok("colors.fg.warning", "#b8862d"),
61533
+ muted: tok("colors.fg.muted", "#6b8080"),
61534
+ brand: tok("colors.brand.solid", "#3a7780")
61535
+ };
61536
+ return {
61537
+ mono: tok("fonts.mono", '"JetBrains Mono", ui-monospace, monospace'),
61538
+ body: tok("fonts.body", "system-ui, -apple-system, sans-serif"),
61539
+ ink: tok("colors.fg", "#111b22"),
61540
+ muted: tones.muted,
61541
+ faint: tok("colors.fg.subtle", "#9bb0b0"),
61542
+ rule: tok("colors.border.subtle", "#e2e8e8"),
61543
+ ruleStrong: tok("colors.border.strong", "#cbd5d5"),
61544
+ surface: tok("colors.bg.surface", "#ffffff"),
61545
+ tone: (t4) => tones[t4] ?? tones.brand,
61546
+ labelSize: 10,
61547
+ titleSize: 11,
61548
+ lineWidth: 1.8,
61549
+ barWidth: 2.5,
61550
+ dotRadius: 2.6,
61551
+ scatterRadius: 3.4,
61552
+ areaOpacity: 0.16
61553
+ };
61554
+ }
61555
+ const niceCeil = (v3) => {
61556
+ if (v3 <= 1e-9) return 0;
61557
+ const mag = Math.pow(10, Math.floor(Math.log10(v3)));
61558
+ const n2 = v3 / mag;
61559
+ 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;
61560
+ return step3 * mag;
61561
+ };
61562
+ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
61563
+ const t4 = useChartTheme();
61564
+ const words = useFormatters();
61565
+ const [ref, w2] = useMeasuredWidth();
61566
+ const n2 = rows.length;
61567
+ const hPx = height2 ?? 26 + n2 * 38;
61568
+ const padL = 124, padR = 46, padT = 20, padB = 30;
61569
+ const innerW = Math.max(0, w2 - padL - padR);
61570
+ const innerH = hPx - padT - padB;
61571
+ const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
61572
+ const rowH = innerH / Math.max(1, n2);
61573
+ const zeroX = Math.max(0, Math.min(innerW, x2(0)));
61574
+ const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
61575
+ const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
61576
+ const zf = frac(0);
61577
+ const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
61578
+ return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
61579
+ /* @__PURE__ */ jsx$1(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
61580
+ /* @__PURE__ */ jsx$1("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
61581
+ rows.map((r2, i) => {
61582
+ const cy = rowH * i + rowH / 2;
61583
+ const col = t4.tone(r2.tone);
61584
+ return /* @__PURE__ */ jsxs(Group, { top: cy, children: [
61585
+ /* @__PURE__ */ jsx$1(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" }),
61586
+ /* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
61587
+ /* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
61588
+ /* @__PURE__ */ jsx$1(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
61589
+ /* @__PURE__ */ jsx$1(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 }),
61590
+ r2.note && /* @__PURE__ */ jsx$1("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
61591
+ /* @__PURE__ */ jsx$1("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed(r2.est, words) })
61592
+ ] }, i);
61593
+ }),
61594
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61595
+ ticks2.map((tk, i) => /* @__PURE__ */ jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
61596
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
61597
+ /* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed(tk, words) })
61598
+ ] }, `t${i}`)),
61599
+ unit2 && /* @__PURE__ */ jsx$1("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
61600
+ ] }) }) });
61601
+ }
61602
+ function AreaRange({ lo: lo2, mid, hi, xTicks = [], xTickWords = false, yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
61603
+ const t4 = useChartTheme();
61604
+ const words = useFormatters();
61605
+ const [ref, w2] = useMeasuredWidth();
61606
+ const hPx = height2 ?? 100;
61607
+ const col = t4.tone(toneName ?? "brand");
61608
+ const padL = yTicks.length ? 52 : 6;
61609
+ const padR = 12;
61610
+ const padT = marks.length ? 22 : 8;
61611
+ const padB = xTicks.length ? 22 : 12;
61612
+ const innerW = Math.max(0, w2 - padL - padR);
61613
+ const innerH = hPx - padT - padB;
61614
+ const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
61615
+ const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
61616
+ const data4 = mid.map((m2, i) => {
61617
+ const mm = fin(m2, 0);
61618
+ return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
61619
+ });
61620
+ const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
61621
+ const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
61622
+ const yHiN = niceCeil(Math.max(dataMax, 0));
61623
+ const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
61624
+ const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
61625
+ const yfmt = (v3) => yFormat === "percent" ? words.percent(v3 / 100) : signed(v3, words);
61626
+ const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
61627
+ return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
61628
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61629
+ yTicks.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
61630
+ /* @__PURE__ */ jsx$1("text", { x: -8, y: 10, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[0] }),
61631
+ /* @__PURE__ */ jsx$1("text", { x: -8, y: innerH / 2 + 3, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[1] }),
61632
+ /* @__PURE__ */ jsx$1("text", { x: -8, y: innerH - 1, textAnchor: "end", style: svgText(t4.labelSize, t4.muted, t4.mono), children: yLabels[2] })
61633
+ ] }),
61634
+ zero2 != null && /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: yScale(zero2) }, to: { x: innerW, y: yScale(zero2) }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
61635
+ /* @__PURE__ */ jsx$1(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" }),
61636
+ /* @__PURE__ */ jsx$1(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
61637
+ marks.map((m2, i) => {
61638
+ const mc = t4.tone(m2.tone);
61639
+ const dotY = yScale(fin(mid[m2.at], 0));
61640
+ const labY = Math.max(9, dotY - 9);
61641
+ const atEnd = m2.at >= mid.length - 2;
61642
+ const atStart = m2.at <= 1;
61643
+ const align = atEnd ? "right" : atStart ? "left" : "center";
61644
+ const labW = m2.label.length * 6.5 + 8;
61645
+ const labX = atEnd ? -labW - 5 : atStart ? 5 : -labW / 2;
61646
+ return /* @__PURE__ */ jsxs(Group, { left: xScale(m2.at), children: [
61647
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
61648
+ /* @__PURE__ */ jsx$1(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
61649
+ /* @__PURE__ */ jsx$1(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 })
61650
+ ] }, `mk${i}`);
61651
+ }),
61652
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
61653
+ xTicks.map((tk, i) => {
61654
+ const word = xTickWords;
61655
+ return /* @__PURE__ */ jsx$1("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}`);
61656
+ })
61657
+ ] }) }) });
61658
+ }
61659
+ function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
61660
+ const t4 = useChartTheme();
61661
+ const words = useFormatters();
61662
+ const [ref, w2] = useMeasuredWidth();
61663
+ const hPx = height2 ?? 150;
61664
+ const padL = 8, padR = 8, padT = 16, padB = 26;
61665
+ const innerW = Math.max(0, w2 - padL - padR);
61666
+ const innerH = hPx - padT - padB;
61667
+ const centerY = innerH / 2;
61668
+ const halfH = centerY - 4;
61669
+ const bins = Math.max(treated.length, control.length);
61670
+ const x2 = createLinearScale({ domain, range: [0, innerW] });
61671
+ const span = domain[1] - domain[0];
61672
+ const binW = bins > 0 ? innerW / bins : innerW;
61673
+ const barW = Math.max(1, binW * 0.78);
61674
+ const maxVal = Math.max(1e-9, ...treated, ...control);
61675
+ const tCol = t4.tone("pos");
61676
+ const cCol = t4.tone("muted");
61677
+ const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
61678
+ const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
61679
+ return /* @__PURE__ */ jsx$1(Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsx$1("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxs(Group, { left: padL, top: padT, children: [
61680
+ /* @__PURE__ */ jsx$1("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
61681
+ supportLabel && /* @__PURE__ */ jsx$1("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
61682
+ Array.from({ length: bins }, (_2, i) => {
61683
+ const ht = Number(treated[i] ?? 0) / maxVal * halfH;
61684
+ const hc = Number(control[i] ?? 0) / maxVal * halfH;
61685
+ const left = cx2(i) - barW / 2;
61686
+ return /* @__PURE__ */ jsxs(Group, { children: [
61687
+ ht > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
61688
+ hc > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
61689
+ ] }, i);
61690
+ }),
61691
+ /* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
61692
+ /* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
61693
+ ticks2.map((tk, i) => /* @__PURE__ */ jsx$1("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}`))
61694
+ ] }) }) });
61695
+ }
61344
61696
  const SUBJECT_DEFAULT = { one: "record", many: "records" };
61345
61697
  const AUTORUN_MAX_ROWS = 5e4;
61346
61698
  const TONE_TOKEN = {
@@ -61368,7 +61720,7 @@ function kindOfTypeValue(t4) {
61368
61720
  }
61369
61721
  }
61370
61722
  function useColumns(workspace, source) {
61371
- const types = source ? getBindingTypes(workspace, source) : void 0;
61723
+ const types = getBindingTypes(workspace, source);
61372
61724
  return useMemo(() => {
61373
61725
  const st = types == null ? void 0 : types.sourceType;
61374
61726
  if (!st || st.type !== "Array") return { columns: [], rowArrayType: null };
@@ -61380,35 +61732,9 @@ function useColumns(workspace, source) {
61380
61732
  };
61381
61733
  }, [types == null ? void 0 : types.sourceType]);
61382
61734
  }
61383
- function rowMatchesAll(row, preds) {
61384
- return preds.every((p2) => {
61385
- const { fieldId, op } = p2.value;
61386
- const v3 = row[fieldId];
61387
- const ov = op.value;
61388
- switch (op.type) {
61389
- case "eq":
61390
- case "is":
61391
- return v3 === ov;
61392
- case "neq":
61393
- return v3 !== ov;
61394
- case "lt":
61395
- return num(v3) != null && num(ov) != null ? num(v3) < num(ov) : true;
61396
- case "lte":
61397
- return num(v3) != null && num(ov) != null ? num(v3) <= num(ov) : true;
61398
- case "gt":
61399
- return num(v3) != null && num(ov) != null ? num(v3) > num(ov) : true;
61400
- case "gte":
61401
- return num(v3) != null && num(ov) != null ? num(v3) >= num(ov) : true;
61402
- case "in":
61403
- return isEastSet(ov) ? ov.has(v3) : true;
61404
- case "notIn":
61405
- return isEastSet(ov) ? !ov.has(v3) : true;
61406
- default:
61407
- return true;
61408
- }
61409
- });
61735
+ function inPopulation(row, population) {
61736
+ return population.every((p2) => slicePredicateMatches(p2, row));
61410
61737
  }
61411
- const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
61412
61738
  function Cap({ children: children2, help }) {
61413
61739
  return /* @__PURE__ */ jsxs(Fragment, { children: [
61414
61740
  /* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: children2 }) : children2 }),
@@ -61521,7 +61847,7 @@ function LoadingSkeleton() {
61521
61847
  ] })
61522
61848
  ] });
61523
61849
  }
61524
- const experimentValueEqual = equalFor(Experiment.Component.schema);
61850
+ const experimentValueEqual = equivalentFor(Experiment.Component.schema);
61525
61851
  const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61526
61852
  var _a2, _b2, _c2;
61527
61853
  const v3 = value;
@@ -61531,6 +61857,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61531
61857
  const bs = useSlotRecipe({ key: "barStrip" })({});
61532
61858
  const statusR = useSlotRecipe({ key: "status" });
61533
61859
  const es = useSlotRecipe({ key: "eyebrowRow" })({});
61860
+ const words = useFormatters();
61534
61861
  const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
61535
61862
  const data4 = useBindingValue(v3.data);
61536
61863
  const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
@@ -61578,7 +61905,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61578
61905
  const population = localPop;
61579
61906
  const filteredRows = useMemo(() => {
61580
61907
  if (!data4.value) return null;
61581
- return population.length ? data4.value.filter((r2) => rowMatchesAll(r2, population)) : data4.value;
61908
+ return population.length ? data4.value.filter((r2) => inPopulation(r2, population)) : data4.value;
61582
61909
  }, [data4.value, population]);
61583
61910
  const fields = useMemo(
61584
61911
  () => columns.filter((c2) => c2.kind !== "other").map((c2) => {
@@ -61680,8 +62007,8 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61680
62007
  }, [ranConfig, config2, meta3, subject]);
61681
62008
  const view = useMemo(() => {
61682
62009
  if (!config2) return null;
61683
- return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject);
61684
- }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject]);
62010
+ return deriveView(config2, ranConfig ?? config2, columns, shownResult, journalBind.value, meta3, nRows, now2, subject, words);
62011
+ }, [config2, ranConfig, columns, shownResult, journalBind.value, meta3, nRows, now2, subject, words]);
61685
62012
  const designConfigValue = useMemo(() => ({
61686
62013
  alpha: none$1,
61687
62014
  target_power: none$1,
@@ -61694,15 +62021,15 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61694
62021
  var _a3;
61695
62022
  if (tab !== "validate" || !hasDesign || design.pending || precomputedDesign !== null) return;
61696
62023
  if (!filteredRows || !config2 || !shownResult) return;
61697
- if (((_a3 = designSnapRef.current) == null ? void 0 : _a3.result) === shownResult) return;
62024
+ if (Object.is((_a3 = designSnapRef.current) == null ? void 0 : _a3.result, shownResult)) return;
61698
62025
  designSnapRef.current = { result: shownResult, config: ranConfig ?? config2 };
61699
62026
  design.call(filteredRows, ranConfig ?? config2, shownResult, designConfigValue);
61700
62027
  }, [tab, hasDesign, precomputedDesign, filteredRows, config2, ranConfig, shownResult, design, designConfigValue]);
61701
62028
  const vmDesign = useMemo(() => {
61702
- if (precomputedDesign !== null && shownResult !== null && config2) return deriveDesign(precomputedDesign, shownResult, ranConfig ?? config2, meta3);
61703
- return design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3) : null;
61704
- }, [precomputedDesign, shownResult, config2, ranConfig, design.result, meta3]);
61705
- const designFresh = precomputedDesign !== null && shownResult !== null || design.result !== null && ((_b2 = designSnapRef.current) == null ? void 0 : _b2.result) === shownResult && !design.pending;
62029
+ if (precomputedDesign !== null && shownResult !== null && config2) return deriveDesign(precomputedDesign, shownResult, ranConfig ?? config2, meta3, words);
62030
+ return design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3, words) : null;
62031
+ }, [precomputedDesign, shownResult, config2, ranConfig, design.result, meta3, words]);
62032
+ const designFresh = precomputedDesign !== null && shownResult !== null || design.result !== null && Object.is((_b2 = designSnapRef.current) == null ? void 0 : _b2.result, shownResult) && !design.pending;
61706
62033
  if (!config2 || !view) {
61707
62034
  const failed2 = experiment.status === "failed";
61708
62035
  const noConfigs = !configsBind.error && configs.length === 0;
@@ -61770,7 +62097,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61770
62097
  readonly && /* @__PURE__ */ jsx$1(Box, { as: "span", css: badge({ variant: "plain", size: "sm" }), textTransform: "none", letterSpacing: "normal", children: "View only" }),
61771
62098
  /* @__PURE__ */ jsxs(Box, { as: "span", css: dataStatus.root, children: [
61772
62099
  /* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.indicator }),
61773
- /* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.label, children: stale ? `${nRows} rows` : vs.dataLabel })
62100
+ /* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.label, children: stale ? `${words.number(nRows)} rows` : vs.dataLabel })
61774
62101
  ] }),
61775
62102
  /* @__PURE__ */ jsx$1(GuidanceToggle, { on: guidance, onToggle: () => setGuidance((g2) => !g2) }),
61776
62103
  canRun && /* @__PURE__ */ jsx$1(ActionButton, { button, variant: "solid", label: "Run", onClick: onRun, disabled: runDisabled, pulse: stale }),
@@ -61803,7 +62130,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61803
62130
  "add another"
61804
62131
  ] }) })
61805
62132
  ] }) }),
61806
- /* @__PURE__ */ jsx$1(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsx$1(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many }) }),
62133
+ /* @__PURE__ */ jsx$1(Step, { n: 4, title: `Which ${subject.many}?`, help: "step_population", children: /* @__PURE__ */ jsx$1(FilterRail, { fields, population, onChange: editPopulation, chip, button, readonly, subjectMany: subject.many, words }) }),
61807
62134
  /* @__PURE__ */ jsxs(Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
61808
62135
  /* @__PURE__ */ jsxs(
61809
62136
  Box,
@@ -61921,10 +62248,11 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61921
62248
  subjectMany: subject.many,
61922
62249
  higherBetter,
61923
62250
  badge,
61924
- barList
62251
+ barList,
62252
+ words
61925
62253
  }
61926
62254
  ),
61927
- activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsx$1(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel }),
62255
+ activeTab === "answer" && !a2 && ref && /* @__PURE__ */ jsx$1(RefusalZone, { refusal: ref, overlap: ov, naiveValue: (shownResult == null ? void 0 : shownResult.naive) ?? 0, outcome: vs.outcomeLabel, words }),
61928
62256
  activeTab === "trust" && vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
61929
62257
  /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
61930
62258
  /* @__PURE__ */ jsx$1(Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
@@ -61952,6 +62280,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61952
62280
  zero: 0,
61953
62281
  tone: "brand",
61954
62282
  xTicks: vr.sens.xTicks,
62283
+ xTickWords: true,
61955
62284
  yTicks: vr.sens.yTicks,
61956
62285
  marks: vr.sens.marks,
61957
62286
  height: vr.sens.marks.length ? 148 : 132
@@ -61997,13 +62326,13 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
61997
62326
  /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
61998
62327
  /* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", children: vd.recoLabel }),
61999
62328
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2", children: [
62000
- /* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
62329
+ /* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect, words) }),
62001
62330
  /* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
62002
62331
  vd.outcome,
62003
62332
  " · ",
62004
- signed(vd.recoLo),
62333
+ signed(vd.recoLo, words),
62005
62334
  " … ",
62006
- signed(vd.recoHi)
62335
+ signed(vd.recoHi, words)
62007
62336
  ] })
62008
62337
  ] })
62009
62338
  ] }),
@@ -62015,7 +62344,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
62015
62344
  vd.outcome,
62016
62345
  " per step"
62017
62346
  ] }),
62018
- /* @__PURE__ */ jsx$1(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) }))) })
62347
+ /* @__PURE__ */ jsx$1(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) }))) })
62019
62348
  ] })
62020
62349
  ] })
62021
62350
  ] }),
@@ -62033,7 +62362,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
62033
62362
  " tab for why)."
62034
62363
  ] })
62035
62364
  ] }),
62036
- activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsx$1(ValidatePanel, { vm: vmDesign, barList }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
62365
+ activeTab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsx$1(ValidatePanel, { vm: vmDesign, barList, words }) : !hasDesign && precomputedDesign === null ? /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
62037
62366
  /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
62038
62367
  /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
62039
62368
  /* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "No trial recipe for this question" })
@@ -62050,7 +62379,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
62050
62379
  /* @__PURE__ */ jsxs(Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
62051
62380
  /* @__PURE__ */ jsx$1(Box, { css: es.lbl, children: /* @__PURE__ */ jsx$1(Help, { id: "journal", children: "Committed experiments" }) }),
62052
62381
  /* @__PURE__ */ jsx$1(Box, { css: es.meta, children: /* @__PURE__ */ jsxs(Text, { as: "span", children: [
62053
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
62382
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: words.number(journal.length) }),
62054
62383
  " on record",
62055
62384
  journal.length > 50 ? " · showing newest 50" : ""
62056
62385
  ] }) })
@@ -62092,7 +62421,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
62092
62421
  ] })
62093
62422
  ] }) });
62094
62423
  }, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
62095
- function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList }) {
62424
+ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMany, higherBetter, badge, barList, words }) {
62096
62425
  const dirUp = a2.effect > 0;
62097
62426
  const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
62098
62427
  const lowerWord = a2.naive < 0 ? "lower" : "higher";
@@ -62107,7 +62436,7 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62107
62436
  /* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
62108
62437
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
62109
62438
  /* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxs(Help, { id: "answer_effect", children: [
62110
- signed(a2.effect),
62439
+ signed(a2.effect, words),
62111
62440
  a2.unit ? /* @__PURE__ */ jsxs(Text, { as: "span", fontSize: "16px", color: "fg.muted", children: [
62112
62441
  " ",
62113
62442
  a2.unit
@@ -62115,9 +62444,9 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62115
62444
  ] }) }),
62116
62445
  /* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxs(Help, { id: "answer_ci", children: [
62117
62446
  "likely between ",
62118
- signed(a2.lo),
62447
+ signed(a2.lo, words),
62119
62448
  " and ",
62120
- signed(a2.hi)
62449
+ signed(a2.hi, words)
62121
62450
  ] }) })
62122
62451
  ] })
62123
62452
  ] }),
@@ -62181,7 +62510,7 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62181
62510
  " on ",
62182
62511
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: a2.outcome }),
62183
62512
  " (",
62184
- signed(a2.naive),
62513
+ signed(a2.naive, words),
62185
62514
  ") — but they also differ most on ",
62186
62515
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: top.label }),
62187
62516
  " (",
@@ -62240,13 +62569,13 @@ function AnswerNumeric({ a: a2, verdict, narrative, checks, onGoTrust, subjectMa
62240
62569
  /* @__PURE__ */ jsx$1(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 }))) })
62241
62570
  ] }),
62242
62571
  /* @__PURE__ */ jsx$1(Box, { mt: "3.5", children: /* @__PURE__ */ jsx$1(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__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
62243
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
62572
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: words.number(n2) }),
62244
62573
  " ",
62245
62574
  label
62246
62575
  ] }, i)) }) })
62247
62576
  ] });
62248
62577
  }
62249
- function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
62578
+ function RefusalZone({ refusal, overlap, naiveValue, outcome, words }) {
62250
62579
  return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
62251
62580
  /* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.warning", children: [
62252
62581
  /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "14px" } }),
@@ -62269,11 +62598,11 @@ function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
62269
62598
  "Raw average difference in ",
62270
62599
  outcome,
62271
62600
  " (context only, not an answer): ",
62272
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue) })
62601
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue, words) })
62273
62602
  ] })
62274
62603
  ] });
62275
62604
  }
62276
- function ValidatePanel({ vm, barList }) {
62605
+ function ValidatePanel({ vm, barList, words }) {
62277
62606
  const stat = useSlotRecipe({ key: "stat" })({ size: "lg" });
62278
62607
  const meter = useSlotRecipe({ key: "segmentedMeter" })({});
62279
62608
  return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
@@ -62281,7 +62610,7 @@ function ValidatePanel({ vm, barList }) {
62281
62610
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
62282
62611
  /* @__PURE__ */ jsxs(Box, { css: stat.root, children: [
62283
62612
  /* @__PURE__ */ jsx$1(Text, { css: stat.label, children: /* @__PURE__ */ jsx$1(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
62284
- vm.faint ? /* @__PURE__ */ jsx$1(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__ */ jsx$1(Text, { css: stat.valueText, color: "brand.solid", children: vm.primary.nTotal.toLocaleString() })
62613
+ vm.faint ? /* @__PURE__ */ jsx$1(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__ */ jsx$1(Text, { css: stat.valueText, color: "brand.solid", children: words.number(vm.primary.nTotal) })
62285
62614
  ] }),
62286
62615
  /* @__PURE__ */ jsxs(Box, { css: meter.root, flex: "1", minW: "220px", children: [
62287
62616
  /* @__PURE__ */ jsx$1(Text, { css: meter.label, children: /* @__PURE__ */ jsx$1(Help, { id: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }) }),
@@ -62293,7 +62622,7 @@ function ValidatePanel({ vm, barList }) {
62293
62622
  /* @__PURE__ */ jsxs(Box, { css: meter.keyItem, children: [
62294
62623
  /* @__PURE__ */ jsx$1(Box, { css: meter.keyDot, bg: "brand.solid" }),
62295
62624
  /* @__PURE__ */ jsxs("span", { children: [
62296
- /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: vm.primary.nTreated.toLocaleString() }),
62625
+ /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: words.number(vm.primary.nTreated) }),
62297
62626
  " ",
62298
62627
  vm.holdback ? "treated" : "get it"
62299
62628
  ] })
@@ -62301,7 +62630,7 @@ function ValidatePanel({ vm, barList }) {
62301
62630
  /* @__PURE__ */ jsxs(Box, { css: meter.keyItem, children: [
62302
62631
  /* @__PURE__ */ jsx$1(Box, { css: meter.keyDot, bg: "bg.emphasized" }),
62303
62632
  /* @__PURE__ */ jsxs("span", { children: [
62304
- /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: vm.primary.nControl.toLocaleString() }),
62633
+ /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: words.number(vm.primary.nControl) }),
62305
62634
  " ",
62306
62635
  vm.holdback ? "held back" : "left alone"
62307
62636
  ] })
@@ -62334,16 +62663,16 @@ function ValidatePanel({ vm, barList }) {
62334
62663
  vm.alternates.length > 0 && /* @__PURE__ */ jsx$1(Box, { display: "flex", flexDirection: "column", gap: "1.5", mt: "3", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: vm.alternates.map((o2, i) => /* @__PURE__ */ jsxs(Text, { textStyle: "caption", color: "fg.muted", children: [
62335
62664
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: o2.label }),
62336
62665
  " · ",
62337
- o2.nTotal.toLocaleString(),
62666
+ words.number(o2.nTotal),
62338
62667
  " total (",
62339
- o2.nTreated.toLocaleString(),
62668
+ words.number(o2.nTreated),
62340
62669
  " / ",
62341
- o2.nControl.toLocaleString(),
62670
+ words.number(o2.nControl),
62342
62671
  ")"
62343
62672
  ] }, i)) })
62344
62673
  ] });
62345
62674
  }
62346
- function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany }) {
62675
+ function FilterRail({ fields, population, onChange: onChange2, chip, button, readonly, subjectMany, words }) {
62347
62676
  const [open, setOpen] = useState(null);
62348
62677
  const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
62349
62678
  const removeAt = (i) => onChange2(population.filter((_2, j2) => j2 !== i));
@@ -62352,7 +62681,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
62352
62681
  return /* @__PURE__ */ jsx$1(Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: population.length === 0 ? /* @__PURE__ */ jsxs(Box, { css: chip({ tone: "plain", numeric: true, shape: "rounded" }), children: [
62353
62682
  "all ",
62354
62683
  subjectMany
62355
- ] }) : population.map((pred, i) => /* @__PURE__ */ jsx$1(Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsx$1(Box, { as: "span", whiteSpace: "nowrap", children: formatPredicate(pred) }) }, i)) });
62684
+ ] }) : population.map((pred, i) => /* @__PURE__ */ jsx$1(Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), flexShrink: 0, children: /* @__PURE__ */ jsx$1(Box, { as: "span", whiteSpace: "nowrap", children: formatPredicate(pred, words) }) }, i)) });
62356
62685
  }
62357
62686
  return /* @__PURE__ */ jsxs(Box, { display: "flex", flexWrap: "wrap", gap: "1.5", alignItems: "center", children: [
62358
62687
  population.map((pred, i) => /* @__PURE__ */ jsx$1(
@@ -62366,7 +62695,7 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button, rea
62366
62695
  ] }),
62367
62696
  footActions: done,
62368
62697
  trigger: /* @__PURE__ */ jsxs(Box, { css: chip({ tone: "brand", numeric: true, shape: "rounded" }), cursor: "pointer", flexShrink: 0, children: [
62369
- /* @__PURE__ */ jsx$1(Box, { as: "span", whiteSpace: "nowrap", children: formatPredicate(pred) }),
62698
+ /* @__PURE__ */ jsx$1(Box, { as: "span", whiteSpace: "nowrap", children: formatPredicate(pred, words) }),
62370
62699
  /* @__PURE__ */ jsx$1(Box, { as: "button", display: "inline-flex", alignItems: "center", cursor: "pointer", color: "brand.fg", flexShrink: "0", fontSize: "10px", onClick: (e3) => {
62371
62700
  e3.stopPropagation();
62372
62701
  removeAt(i);
@@ -62492,20 +62821,8 @@ function viewFor(binding) {
62492
62821
  const patch = binding.patch.type === "some" ? binding.patch.value : void 0;
62493
62822
  return defaultBindRuntime.buildBindHandle(types.sourceType, source, patch, binding.mode.type);
62494
62823
  }
62495
- function stableStringify(v3) {
62496
- return JSON.stringify(v3, (_k, x2) => {
62497
- if (typeof x2 === "bigint") return `${x2}n`;
62498
- if (x2 instanceof Date) return x2.toISOString();
62499
- if (isEastSet(x2)) return [...x2].sort();
62500
- if (isEastDict(x2)) return [...x2.entries()].sort((a2, b2) => String(a2[0]).localeCompare(String(b2[0])));
62501
- return x2;
62502
- });
62503
- }
62504
- function hashString2(s2) {
62505
- let h2 = 5381;
62506
- for (let i = 0; i < s2.length; i++) h2 = (h2 << 5) + h2 + s2.charCodeAt(i) | 0;
62507
- return (h2 >>> 0).toString(36);
62508
- }
62824
+ const printDescriptors = printFor(StructType({ decisions: ArrayType(DiffBindingType), judgements: DiffBindingType }));
62825
+ const printTypeValue = printFor(EastTypeValueType);
62509
62826
  function readSelectionAt(selectionKey) {
62510
62827
  StateRuntime.trackKey(selectionKey);
62511
62828
  const bytes = StateRuntime.getStore().read(selectionKey);
@@ -62547,16 +62864,6 @@ function removeFromOwningView(decisions, caseId) {
62547
62864
  }
62548
62865
  }
62549
62866
  const decisionHandleCache = /* @__PURE__ */ new Map();
62550
- function typeToken(tv) {
62551
- const seen = /* @__PURE__ */ new WeakSet();
62552
- return JSON.stringify(tv, (_k, v3) => {
62553
- if (typeof v3 === "object" && v3 !== null) {
62554
- if (seen.has(v3)) return "<cyc>";
62555
- seen.add(v3);
62556
- }
62557
- return typeof v3 === "bigint" ? `${v3}n` : v3;
62558
- });
62559
- }
62560
62867
  function compileDecisionMethods(constraintType, decisions, judgements, selectionKey) {
62561
62868
  const platform = getRegisteredPlatformImplementations();
62562
62869
  const c2 = fromEastTypeValue(constraintType);
@@ -62624,7 +62931,7 @@ function buildDecisionHandle(decisions, judgements, constraintType) {
62624
62931
  const at = (j2) => j2.resolvedAt.type === "some" ? j2.resolvedAt.value.getTime() : 0;
62625
62932
  return entries.sort((a2, b2) => at(b2) - at(a2));
62626
62933
  };
62627
- const cacheKey = `${hashString2(stableStringify({ decisions, judgements }))}|${typeToken(cType)}`;
62934
+ const cacheKey = `${printDescriptors({ decisions, judgements })}|${printTypeValue(canonicalTypeValue(cType))}`;
62628
62935
  let methods = decisionHandleCache.get(cacheKey);
62629
62936
  if (methods === void 0) {
62630
62937
  methods = compileDecisionMethods(cType, decisions, judgements, selectionKey);
@@ -62814,91 +63121,71 @@ const OP_WORDS = {
62814
63121
  notIn: "not in",
62815
63122
  is: "is"
62816
63123
  };
62817
- function formatScalar(v3) {
62818
- if (v3 instanceof Date) {
62819
- return `${v3.toLocaleString("en", { month: "short" })} ${v3.getDate()}`;
62820
- }
62821
- if (isEastSet(v3)) {
62822
- return `{${[...v3].map(formatScalar).join(", ")}}`;
63124
+ const PRINTERS = /* @__PURE__ */ new WeakMap();
63125
+ function printerOf(contract) {
63126
+ let printer = PRINTERS.get(contract);
63127
+ if (printer === void 0) {
63128
+ const context = /* @__PURE__ */ new Map();
63129
+ printFor(contract.type, context);
63130
+ printer = (type) => printFor(type, context);
63131
+ PRINTERS.set(contract, printer);
62823
63132
  }
62824
- if (v3 !== null && typeof v3 === "object" && !v3.type) {
62825
- return Object.entries(v3).map(([k2, x2]) => `${k2} ${formatScalar(x2)}`).join(" · ");
62826
- }
62827
- return String(v3);
63133
+ return printer;
62828
63134
  }
62829
- function formatConstraint(constraint, payloadType, levers) {
62830
- var _a2, _b2;
62831
- const c2 = constraint;
62832
- const lever = ((_a2 = levers == null ? void 0 : levers.find((l2) => l2.case === c2.type)) == null ? void 0 : _a2.label) ?? c2.type;
62833
- if ((payloadType == null ? void 0 : payloadType.type) === "Variant") {
62834
- const op = c2.value;
62835
- const word = OP_WORDS[op.type] ?? op.type;
62836
- const opType = (_b2 = payloadType.cases) == null ? void 0 : _b2[op.type];
62837
- if ((opType == null ? void 0 : opType.type) === "Struct" && op.value !== null && typeof op.value === "object") {
62838
- const r2 = op.value;
62839
- if ("min" in r2 && "max" in r2) {
62840
- return { lever, op: word, value: `${formatScalar(r2.min)} – ${formatScalar(r2.max)}` };
62841
- }
63135
+ function formatValue(value, type, words, print) {
63136
+ switch (type.type) {
63137
+ case "DateTime":
63138
+ return words.monthDay(value);
63139
+ case "Float":
63140
+ return words.float(value);
63141
+ case "String":
63142
+ return value;
63143
+ case "Set": {
63144
+ const member = type.value;
63145
+ return `{${[...value].map((m2) => formatValue(m2, member, words, print)).join(", ")}}`;
62842
63146
  }
62843
- return { lever, op: word, value: formatScalar(op.value) };
62844
- }
62845
- return { lever, op: "·", value: formatScalar(c2.value) };
62846
- }
62847
- const PRIMITIVES = /* @__PURE__ */ new Set(["String", "Integer", "Float", "DateTime", "Boolean"]);
62848
- function normalizeTypeValue(tv) {
62849
- var _a2;
62850
- const n2 = tv;
62851
- if (n2.cases !== void 0 || n2.fields !== void 0) {
62852
- const out = { type: n2.type };
62853
- if (n2.cases) out.cases = Object.fromEntries(Object.entries(n2.cases).map(([k2, v3]) => [k2, normalizeTypeValue(v3)]));
62854
- if (n2.fields) out.fields = Object.fromEntries(Object.entries(n2.fields).map(([k2, v3]) => [k2, normalizeTypeValue(v3)]));
62855
- if (n2.key) out.key = normalizeTypeValue(n2.key);
62856
- if (n2.value !== void 0 && (n2.type === "Array" || n2.type === "Dict")) out.value = normalizeTypeValue(n2.value);
62857
- return out;
62858
- }
62859
- switch (n2.type) {
62860
63147
  case "Struct":
62861
- return { type: "Struct", fields: Object.fromEntries(n2.value.map((f2) => [f2.name, normalizeTypeValue(f2.type)])) };
62862
- case "Variant":
62863
- return { type: "Variant", cases: Object.fromEntries(n2.value.map((f2) => [f2.name, normalizeTypeValue(f2.type)])) };
62864
- case "Array":
62865
- return { type: "Array", value: normalizeTypeValue(n2.value) };
62866
- case "Dict": {
62867
- const d2 = n2.value;
62868
- return { type: "Dict", key: normalizeTypeValue(d2.key), value: normalizeTypeValue(d2.value) };
62869
- }
62870
- case "Recursive": {
62871
- const w2 = n2.value;
62872
- if ((w2 == null ? void 0 : w2.type) === "wrapper" && ((_a2 = w2.value) == null ? void 0 : _a2.inner)) return normalizeTypeValue(w2.value.inner);
62873
- return { type: n2.type };
62874
- }
63148
+ return type.value.map((f2) => `${f2.name} ${formatValue(value[f2.name], f2.type, words, print)}`).join(" · ");
62875
63149
  default:
62876
- return { type: n2.type };
63150
+ return print(type)(value);
62877
63151
  }
62878
63152
  }
62879
- function emptyFor(node2) {
62880
- switch (node2.type) {
62881
- case "Integer":
62882
- return 0n;
62883
- case "Float":
62884
- return 0;
62885
- case "DateTime":
62886
- return /* @__PURE__ */ new Date();
62887
- case "Boolean":
62888
- return false;
62889
- case "String":
62890
- return "";
62891
- case "Struct":
62892
- return Object.fromEntries(Object.entries(node2.fields ?? {}).map(([k2, f2]) => [k2, emptyFor(f2)]));
62893
- default:
62894
- return null;
63153
+ function formatConstraint(constraint, contract, levers, words = formatters()) {
63154
+ var _a2;
63155
+ const lever = ((_a2 = levers == null ? void 0 : levers.find((l2) => l2.case === constraint.type)) == null ? void 0 : _a2.label) ?? constraint.type;
63156
+ const payload = contract == null ? void 0 : contract.payloads.get(constraint.type);
63157
+ if (contract === void 0 || payload === void 0) return { lever, op: "·", value: "" };
63158
+ const print = printerOf(contract);
63159
+ if (payload.type === "Variant") {
63160
+ const op = constraint.value;
63161
+ const word = OP_WORDS[op.type] ?? op.type;
63162
+ const opType = payload.value.find((c2) => c2.name === op.type).type;
63163
+ if (opType.type === "Struct") {
63164
+ const fields = opType.value;
63165
+ const min4 = fields.find((f2) => f2.name === "min");
63166
+ const max5 = fields.find((f2) => f2.name === "max");
63167
+ if (min4 !== void 0 && max5 !== void 0) {
63168
+ const range = op.value;
63169
+ return { lever, op: word, value: `${formatValue(range.min, min4.type, words, print)} – ${formatValue(range.max, max5.type, words, print)}` };
63170
+ }
63171
+ }
63172
+ return { lever, op: word, value: formatValue(op.value, opType, words, print) };
62895
63173
  }
63174
+ return { lever, op: "·", value: formatValue(constraint.value, payload, words, print) };
62896
63175
  }
62897
- function leverPayloadEditable(node2) {
62898
- if (!node2) return false;
62899
- if (PRIMITIVES.has(node2.type)) return true;
62900
- if (node2.type === "Struct") return Object.values(node2.fields ?? {}).every((f2) => PRIMITIVES.has(f2.type));
62901
- 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)));
63176
+ const PRIMITIVES = /* @__PURE__ */ new Set(["String", "Integer", "Float", "DateTime", "Boolean"]);
63177
+ const NULL_TYPE = toEastTypeValue(NullType);
63178
+ function emptyFor(type) {
63179
+ if (type.type === "DateTime") return /* @__PURE__ */ new Date();
63180
+ if (type.type === "Struct") return Object.fromEntries(type.value.map((f2) => [f2.name, emptyFor(f2.type)]));
63181
+ return defaultValue(type);
63182
+ }
63183
+ function leverPayloadEditable(payload) {
63184
+ if (payload === void 0) return false;
63185
+ const primitive = (t4) => PRIMITIVES.has(t4.type);
63186
+ const structOfPrimitives = (t4) => t4.type === "Struct" && t4.value.every((f2) => primitive(f2.type));
63187
+ if (primitive(payload) || structOfPrimitives(payload)) return true;
63188
+ if (payload.type === "Variant") return payload.value.every((c2) => primitive(c2.type) || structOfPrimitives(c2.type));
62902
63189
  return false;
62903
63190
  }
62904
63191
  const inputStyle = some({ size: some(variant("sm", null)) });
@@ -62906,17 +63193,17 @@ function LeverEditor({ leverCase, payload, onInject }) {
62906
63193
  var _a2;
62907
63194
  const button = useRecipe({ key: "button" });
62908
63195
  const isOpVariant = payload.type === "Variant";
62909
- const ops = useMemo(() => isOpVariant ? Object.keys(payload.cases ?? {}) : [], [isOpVariant, payload]);
63196
+ const ops = useMemo(() => payload.type === "Variant" ? payload.value.map((c2) => c2.name) : [], [payload]);
62910
63197
  const [opTag, setOpTag] = useState(ops[0] ?? "");
62911
- const active = isOpVariant ? ((_a2 = payload.cases) == null ? void 0 : _a2[opTag]) ?? { type: "Null" } : payload;
63198
+ const active = payload.type === "Variant" ? ((_a2 = payload.value.find((c2) => c2.name === opTag)) == null ? void 0 : _a2.type) ?? NULL_TYPE : payload;
62912
63199
  const valRef = useRef(emptyFor(active));
62913
63200
  const keyRef = useRef(`${leverCase}:${opTag}`);
62914
63201
  if (keyRef.current !== `${leverCase}:${opTag}`) {
62915
63202
  keyRef.current = `${leverCase}:${opTag}`;
62916
63203
  valRef.current = emptyFor(active);
62917
63204
  }
62918
- const primitiveInput = (node2, key, get3, set3) => {
62919
- switch (node2.type) {
63205
+ const primitiveInput = (type, key, get3, set3) => {
63206
+ switch (type.type) {
62920
63207
  case "Integer":
62921
63208
  return /* @__PURE__ */ jsx$1(EastChakraIntegerInput, { value: { value: get3(), onChange: some(set3), style: inputStyle } }, key);
62922
63209
  case "Float":
@@ -62929,7 +63216,7 @@ function LeverEditor({ leverCase, payload, onInject }) {
62929
63216
  {
62930
63217
  ariaLabel: key,
62931
63218
  value: {
62932
- value: some(String(get3())),
63219
+ value: some(get3() === true ? "true" : "false"),
62933
63220
  items: [{ value: "true", label: "true", disabled: none$1 }, { value: "false", label: "false", disabled: none$1 }],
62934
63221
  placeholder: none$1,
62935
63222
  multiple: none$1,
@@ -62942,17 +63229,19 @@ function LeverEditor({ leverCase, payload, onInject }) {
62942
63229
  },
62943
63230
  key
62944
63231
  );
63232
+ case "String":
63233
+ return /* @__PURE__ */ jsx$1(EastChakraStringInput, { value: { value: get3(), onChange: some(set3), style: inputStyle } }, key);
62945
63234
  default:
62946
- return /* @__PURE__ */ jsx$1(EastChakraStringInput, { value: { value: String(get3() ?? ""), onChange: some(set3), style: inputStyle } }, key);
63235
+ return null;
62947
63236
  }
62948
63237
  };
62949
63238
  const valueControls = useMemo(() => {
62950
63239
  const k2 = keyRef.current;
62951
63240
  if (active.type === "Struct") {
62952
- return Object.entries(active.fields ?? {}).map(([field, node2]) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "6px", minW: 0, children: [
63241
+ return active.value.map(({ name: field, type }) => /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "6px", minW: 0, children: [
62953
63242
  /* @__PURE__ */ jsx$1(Box, { as: "span", textStyle: "caption.eyebrow", color: "fg.subtle", flexShrink: 0, children: field }),
62954
63243
  primitiveInput(
62955
- node2,
63244
+ type,
62956
63245
  `${k2}.${field}.in`,
62957
63246
  () => valRef.current[field],
62958
63247
  (v3) => {
@@ -63008,11 +63297,12 @@ function LeverEditor({ leverCase, payload, onInject }) {
63008
63297
  }
63009
63298
  );
63010
63299
  }
63011
- function fmt(d2, n2, showSign = false) {
63012
- return formatTick(n2, getSomeorUndefined(d2.format), showSign);
63300
+ function fmt(words, d2, n2, showSign = false) {
63301
+ return words.value(n2, getSomeorUndefined(d2.format), showSign);
63013
63302
  }
63014
63303
  const caption = { textStyle: "caption.eyebrow", color: "fg.muted" };
63015
63304
  const EvidenceFacet = memo(function EvidenceFacet2({ decision, children: children2 }) {
63305
+ const words = useFormatters();
63016
63306
  const stakes = getSomeorUndefined(decision.stakes);
63017
63307
  const downside = getSomeorUndefined(decision.downside);
63018
63308
  const confidence = getSomeorUndefined(decision.confidence);
@@ -63028,15 +63318,12 @@ const EvidenceFacet = memo(function EvidenceFacet2({ decision, children: childre
63028
63318
  ] }),
63029
63319
  downside !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
63030
63320
  "if wrong ",
63031
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", color: "fg.danger", children: fmt(decision, downside, true) }),
63321
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", color: "fg.danger", children: fmt(words, decision, downside, true) }),
63032
63322
  " · "
63033
63323
  ] }),
63034
63324
  confidence !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
63035
63325
  "confidence ",
63036
- /* @__PURE__ */ jsxs(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: [
63037
- Math.round(confidence * 100),
63038
- "%"
63039
- ] })
63326
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: words.percent(confidence) })
63040
63327
  ] })
63041
63328
  ] })
63042
63329
  ] }),
@@ -63052,8 +63339,9 @@ const EvidenceFacet = memo(function EvidenceFacet2({ decision, children: childre
63052
63339
  children2
63053
63340
  ] });
63054
63341
  });
63342
+ const compareValues$1 = compareFor(FloatType);
63055
63343
  function rankOptions(d2) {
63056
- const alternatives = [...d2.alternatives].sort((a2, b2) => b2.value - a2.value).map((a2) => ({
63344
+ const alternatives = [...d2.alternatives].sort((a2, b2) => compareValues$1(b2.value, a2.value)).map((a2) => ({
63057
63345
  label: a2.label,
63058
63346
  value: a2.value,
63059
63347
  downside: getSomeorUndefined(a2.downside),
@@ -63073,14 +63361,15 @@ function rankOptions(d2) {
63073
63361
  ...alternatives
63074
63362
  ].map((o2, i) => ({ ...o2, rank: i + 1 }));
63075
63363
  }
63076
- function metaLine(o2) {
63364
+ function metaLine(o2, words) {
63077
63365
  const parts = [];
63078
63366
  if (o2.recommended) parts.push("recommended");
63079
- if (o2.confidence !== void 0) parts.push(`${Math.round(o2.confidence * 100)}% conf`);
63367
+ if (o2.confidence !== void 0) parts.push(`${words.percent(o2.confidence)} conf`);
63080
63368
  if (!o2.recommended && o2.note !== void 0) parts.push(o2.note);
63081
63369
  return parts.join(" · ");
63082
63370
  }
63083
63371
  const OptionsFacet = memo(function OptionsFacet2({ decision, narrow }) {
63372
+ const words = useFormatters();
63084
63373
  const options2 = rankOptions(decision);
63085
63374
  const maxUp = Math.max(...options2.map((o2) => Math.max(o2.value, 0)), 1e-9);
63086
63375
  const maxDown = Math.max(...options2.map((o2) => Math.abs(o2.downside ?? 0)), 1e-9);
@@ -63096,7 +63385,7 @@ const OptionsFacet = memo(function OptionsFacet2({ decision, narrow }) {
63096
63385
  return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "10px", children: [
63097
63386
  /* @__PURE__ */ jsxs(Text, { ...caption, color: "fg", children: [
63098
63387
  "Options · ",
63099
- options2.length,
63388
+ words.number(options2.length),
63100
63389
  " evaluated"
63101
63390
  ] }),
63102
63391
  options2.map((o2) => /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "4px", pl: "8px", ...o2.recommended ? { boxShadow: "inset 2px 0 0 var(--chakra-colors-accent-brand)" } : {}, children: [
@@ -63105,8 +63394,8 @@ const OptionsFacet = memo(function OptionsFacet2({ decision, narrow }) {
63105
63394
  " ",
63106
63395
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: o2.recommended ? "semibold" : "normal", children: o2.label })
63107
63396
  ] }),
63108
- meter(upLabel, o2.value / maxUp, fmt(decision, o2.value, signed2), signed2 ? "success" : "neutral"),
63109
- meter("If wrong", (o2.downside ?? 0) / maxDown, o2.downside !== void 0 ? fmt(decision, o2.downside, true) : "—", "danger")
63397
+ meter(upLabel, o2.value / maxUp, fmt(words, decision, o2.value, signed2), signed2 ? "success" : "neutral"),
63398
+ meter("If wrong", (o2.downside ?? 0) / maxDown, o2.downside !== void 0 ? fmt(words, decision, o2.downside, true) : "—", "danger")
63110
63399
  ] }, o2.rank))
63111
63400
  ] });
63112
63401
  }
@@ -63114,7 +63403,7 @@ const OptionsFacet = memo(function OptionsFacet2({ decision, narrow }) {
63114
63403
  /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(180px, 1.1fr) 1fr 1fr", alignItems: "baseline", gap: "8px", children: [
63115
63404
  /* @__PURE__ */ jsxs(Text, { ...caption, color: "fg", children: [
63116
63405
  "Options · ",
63117
- options2.length,
63406
+ words.number(options2.length),
63118
63407
  " evaluated"
63119
63408
  ] }),
63120
63409
  /* @__PURE__ */ jsx$1(Text, { ...caption, textAlign: "center", children: "If wrong" }),
@@ -63136,15 +63425,15 @@ const OptionsFacet = memo(function OptionsFacet2({ decision, narrow }) {
63136
63425
  /* @__PURE__ */ jsxs(Box, { minW: 0, pl: o2.recommended ? "6px" : "0", display: "flex", alignItems: "baseline", gap: "6px", children: [
63137
63426
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontSize: "11px", color: "fg.subtle", flexShrink: 0, children: o2.rank }),
63138
63427
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontSize: "12.5px", fontWeight: o2.recommended ? "semibold" : "normal", flexShrink: 0, children: o2.label }),
63139
- metaLine(o2) !== "" && /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontSize: "10.5px", color: "fg.muted", minW: 0, truncate: true, children: metaLine(o2) })
63428
+ metaLine(o2, words) !== "" && /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontSize: "10.5px", color: "fg.muted", minW: 0, truncate: true, children: metaLine(o2, words) })
63140
63429
  ] }),
63141
63430
  /* @__PURE__ */ jsx$1(Box, { display: "flex", justifyContent: "flex-end", alignItems: "center", gap: "6px", height: "16px", children: o2.downside !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
63142
- /* @__PURE__ */ jsx$1(Text, { fontSize: "10.5px", fontFamily: "mono", color: "fg.danger", flexShrink: 0, children: fmt(decision, o2.downside, true) }),
63431
+ /* @__PURE__ */ jsx$1(Text, { fontSize: "10.5px", fontFamily: "mono", color: "fg.danger", flexShrink: 0, children: fmt(words, decision, o2.downside, true) }),
63143
63432
  /* @__PURE__ */ jsx$1(Box, { height: "12px", width: `${Math.round(downFrac * 100)}%`, bg: "fg.danger", opacity: 0.55 })
63144
63433
  ] }) }),
63145
63434
  /* @__PURE__ */ jsx$1(Box, { display: "flex", alignItems: "center", gap: "6px", height: "16px", borderLeftWidth: "1px", borderColor: "border.strong", children: o2.value > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
63146
63435
  /* @__PURE__ */ jsx$1(Box, { height: "12px", width: `${Math.round(upFrac * 100)}%`, bg: signed2 ? "fg.success" : "fg.subtle", opacity: 0.6 }),
63147
- /* @__PURE__ */ jsx$1(Text, { fontSize: "10.5px", fontFamily: "mono", color: signed2 ? "fg.success" : "fg.muted", flexShrink: 0, children: fmt(decision, o2.value, signed2) })
63436
+ /* @__PURE__ */ jsx$1(Text, { fontSize: "10.5px", fontFamily: "mono", color: signed2 ? "fg.success" : "fg.muted", flexShrink: 0, children: fmt(words, decision, o2.value, signed2) })
63148
63437
  ] }) })
63149
63438
  ]
63150
63439
  },
@@ -63158,8 +63447,9 @@ const ANSWERS = [
63158
63447
  { literal: "no", label: "No" },
63159
63448
  { literal: "unknown", label: "Don't know" }
63160
63449
  ];
63161
- const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPayloads }) {
63450
+ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, contract }) {
63162
63451
  var _a2;
63452
+ const words = useFormatters();
63163
63453
  const button = useRecipe({ key: "button" });
63164
63454
  const input = useRecipe({ key: "input" });
63165
63455
  const judgement = handle.judgementFor(decision.id);
@@ -63172,11 +63462,12 @@ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPa
63172
63462
  if (knowledge !== stagedKnowledge) handle.addKnowledge(decision.id, knowledge);
63173
63463
  }, [knowledge, stagedKnowledge, handle, decision.id]);
63174
63464
  const levers = useMemo(
63175
- () => decision.levers.filter((l2) => leverPayloadEditable(leverPayloads[l2.case])),
63176
- [decision.levers, leverPayloads]
63465
+ () => decision.levers.filter((l2) => leverPayloadEditable(contract == null ? void 0 : contract.payloads.get(l2.case))),
63466
+ [decision.levers, contract]
63177
63467
  );
63178
63468
  const [leverCase, setLeverCase] = useState(((_a2 = levers[0]) == null ? void 0 : _a2.case) ?? "");
63179
63469
  const activeLever = levers.find((l2) => l2.case === leverCase) ?? levers[0];
63470
+ const activePayload = activeLever === void 0 ? void 0 : contract == null ? void 0 : contract.payloads.get(activeLever.case);
63180
63471
  const gate = handle.commitStateFor(decision.id);
63181
63472
  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;
63182
63473
  const onInject = useCallback((constraint) => {
@@ -63222,8 +63513,7 @@ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPa
63222
63513
  (levers.length > 0 || judgement.constraints.length > 0) && /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "6px", children: [
63223
63514
  /* @__PURE__ */ jsx$1(Text, { ...caption, children: "Inject constraint · next run folds it in" }),
63224
63515
  judgement.constraints.length > 0 && /* @__PURE__ */ jsx$1(Box, { display: "flex", gap: "12px", flexWrap: "wrap", children: judgement.constraints.map((c2, i) => {
63225
- const tag = c2.type;
63226
- const f2 = formatConstraint(c2, leverPayloads[tag], decision.levers);
63516
+ const f2 = formatConstraint(c2, contract, decision.levers, words);
63227
63517
  return /* @__PURE__ */ jsxs(Text, { as: "span", fontFamily: "mono", fontSize: "11px", children: [
63228
63518
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "accent.brand", fontWeight: "semibold", children: f2.lever }),
63229
63519
  " ",
@@ -63232,7 +63522,7 @@ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPa
63232
63522
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: f2.value })
63233
63523
  ] }, i);
63234
63524
  }) }),
63235
- activeLever !== void 0 && /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", children: [
63525
+ activeLever !== void 0 && activePayload !== void 0 && /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap", children: [
63236
63526
  levers.length > 1 ? /* @__PURE__ */ jsx$1(Box, { flexShrink: 0, css: { "& [data-scope=select][data-part=root]": { width: "auto", minWidth: "160px" } }, children: /* @__PURE__ */ jsx$1(
63237
63527
  EastChakraSelect,
63238
63528
  {
@@ -63254,7 +63544,7 @@ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPa
63254
63544
  LeverEditor,
63255
63545
  {
63256
63546
  leverCase: activeLever.case,
63257
- payload: leverPayloads[activeLever.case],
63547
+ payload: activePayload,
63258
63548
  onInject
63259
63549
  },
63260
63550
  activeLever.case
@@ -63277,11 +63567,54 @@ const JudgementFacet = memo(function JudgementFacet2({ decision, handle, leverPa
63277
63567
  ] })
63278
63568
  ] });
63279
63569
  });
63570
+ function body(type) {
63571
+ return type.type === "Recursive" && type.value.type === "wrapper" ? type.value.value.inner : type;
63572
+ }
63573
+ function constraintContractOf(judgementsType) {
63574
+ const dict = body(judgementsType);
63575
+ if (dict.type !== "Dict") return void 0;
63576
+ const record = body(dict.value.value);
63577
+ if (record.type !== "Struct") return void 0;
63578
+ const field = record.value.find((f2) => f2.name === "constraints");
63579
+ if (field === void 0) return void 0;
63580
+ const constraints = body(field.type);
63581
+ if (constraints.type !== "Array") return void 0;
63582
+ const contract = constraints.value;
63583
+ const cases = body(contract);
63584
+ if (cases.type !== "Variant") return void 0;
63585
+ return {
63586
+ type: contract,
63587
+ payloads: new Map(cases.value.map((c2) => [c2.name, c2.type]))
63588
+ };
63589
+ }
63590
+ function useConstraintContract(ref) {
63591
+ const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
63592
+ return useMemo(() => {
63593
+ if (ref === null) return void 0;
63594
+ const judgements = getBindingTypes(workspace, ref.judgements.source);
63595
+ return judgements === void 0 ? void 0 : constraintContractOf(judgements.sourceType);
63596
+ }, [ref, workspace]);
63597
+ }
63280
63598
  const URGENCY_RANK = {
63281
63599
  overdue: 0,
63282
63600
  due: 1,
63283
63601
  routine: 2
63284
63602
  };
63603
+ const compareInstants = compareFor(DateTimeType);
63604
+ const compareValues = compareFor(FloatType);
63605
+ function compareByUrgency(a2, b2) {
63606
+ const rank = URGENCY_RANK[a2.urgency.type] - URGENCY_RANK[b2.urgency.type];
63607
+ if (rank !== 0) return rank;
63608
+ const da = a2.deadline.type === "some" ? a2.deadline.value : void 0;
63609
+ const db = b2.deadline.type === "some" ? b2.deadline.value : void 0;
63610
+ if (da !== void 0 && db !== void 0) {
63611
+ const byDeadline = compareInstants(da, db);
63612
+ if (byDeadline !== 0) return byDeadline;
63613
+ } else if (da !== db) {
63614
+ return da === void 0 ? 1 : -1;
63615
+ }
63616
+ return compareValues(b2.value, a2.value);
63617
+ }
63285
63618
  const URGENCY_GROUP_LABEL = {
63286
63619
  overdue: "Overdue",
63287
63620
  due: "Due today",
@@ -63328,10 +63661,10 @@ const URGENCY_TONE = {
63328
63661
  };
63329
63662
  const EXIT_MS = 560;
63330
63663
  const NARROW_PX = 560;
63331
- function decisionValue(d2, n2, showSign = false) {
63332
- return formatTick(n2, getSomeorUndefined(d2.format), showSign);
63664
+ function decisionValue(words, d2, n2, showSign = false) {
63665
+ return words.value(n2, getSomeorUndefined(d2.format), showSign);
63333
63666
  }
63334
- function deadlineSuffix(d2) {
63667
+ function deadlineSuffix(d2, words) {
63335
63668
  const deadline = getSomeorUndefined(d2.deadline);
63336
63669
  if (deadline === void 0) return "";
63337
63670
  if (d2.urgency.type === "overdue") {
@@ -63340,34 +63673,16 @@ function deadlineSuffix(d2) {
63340
63673
  if (hours < 48) return ` ${hours}h`;
63341
63674
  return ` ${Math.round(hours / 24)}d`;
63342
63675
  }
63343
- if (d2.urgency.type === "due") {
63344
- const h2 = deadline.getHours();
63345
- const m2 = deadline.getMinutes();
63346
- const meridiem = h2 >= 12 ? "pm" : "am";
63347
- const clock2 = h2 % 12 === 0 ? 12 : h2 % 12;
63348
- return m2 === 0 ? ` ${clock2}${meridiem}` : ` ${clock2}:${String(m2).padStart(2, "0")}${meridiem}`;
63349
- }
63676
+ if (d2.urgency.type === "due") return ` ${words.time(deadline)}`;
63350
63677
  return "";
63351
63678
  }
63352
- function useLeverPayloads(ref) {
63353
- const cache3 = getReactiveDatasetCache();
63354
- const workspace = cache3.getConfig().workspace ?? "";
63355
- return useMemo(() => {
63356
- var _a2, _b2, _c2;
63357
- if (!ref) return {};
63358
- const judgements = getBindingTypes(workspace, ref.judgements.source);
63359
- if (!(judgements == null ? void 0 : judgements.sourceType)) return {};
63360
- const root2 = normalizeTypeValue(judgements.sourceType);
63361
- const constraint = (_c2 = (_b2 = (_a2 = root2.value) == null ? void 0 : _a2.fields) == null ? void 0 : _b2["constraints"]) == null ? void 0 : _c2.value;
63362
- return (constraint == null ? void 0 : constraint.type) === "Variant" ? constraint.cases ?? {} : {};
63363
- }, [ref, workspace]);
63364
- }
63365
- const Row = memo(function Row2({ decision, handle, selected: selected2, narrow, leverPayloads, modify, evidence, defaultFacet, facetInclude, apply: apply2, reject: reject2, leaving, storageKey }) {
63679
+ const Row = memo(function Row2({ decision, handle, selected: selected2, narrow, contract, modify, evidence, defaultFacet, facetInclude, apply: apply2, reject: reject2, leaving, storageKey }) {
63366
63680
  var _a2, _b2;
63367
63681
  const dq = useSlotRecipe({ key: "decisionQueue" });
63368
63682
  const status = useSlotRecipe({ key: "status" });
63369
63683
  const tabs = useSlotRecipe({ key: "facetTabs" });
63370
63684
  const button = useRecipe({ key: "button" });
63685
+ const words = useFormatters();
63371
63686
  const rs = dq({});
63372
63687
  const ts = tabs({});
63373
63688
  const st = status({ status: URGENCY_TONE[decision.urgency.type], size: "md" });
@@ -63393,10 +63708,15 @@ const Row = memo(function Row2({ decision, handle, selected: selected2, narrow,
63393
63708
  const handleReject = useCallback(() => {
63394
63709
  reject2(decision);
63395
63710
  }, [reject2, decision]);
63711
+ const stageUpdate = handle.update;
63712
+ const updateDecision = useCallback((next2) => {
63713
+ stageUpdate(next2);
63714
+ return null;
63715
+ }, [stageUpdate]);
63396
63716
  const probe = useMemo(() => {
63397
63717
  if (!selected2 || facet !== "modify" || !modify) return null;
63398
- return modify(decision, handle.update);
63399
- }, [selected2, facet, modify, decision, handle.update]);
63718
+ return modify(decision, updateDecision);
63719
+ }, [selected2, facet, modify, decision, updateDecision]);
63400
63720
  const canvas = useMemo(() => {
63401
63721
  if (!selected2 || facet !== "evidence" || !evidence) return null;
63402
63722
  return evidence(decision);
@@ -63445,11 +63765,11 @@ const Row = memo(function Row2({ decision, handle, selected: selected2, narrow,
63445
63765
  /* @__PURE__ */ jsx$1(Box, { as: "span", css: st.indicator }),
63446
63766
  /* @__PURE__ */ jsxs(Box, { as: "span", css: st.label, children: [
63447
63767
  decision.urgency.type,
63448
- deadlineSuffix(decision)
63768
+ deadlineSuffix(decision, words)
63449
63769
  ] })
63450
63770
  ] });
63451
63771
  const valueSigned = ((_b2 = getSomeorUndefined(decision.valueAxis)) == null ? void 0 : _b2.signed) ?? true;
63452
- const valueText = /* @__PURE__ */ jsx$1(Text, { fontFamily: "mono", fontWeight: "semibold", textAlign: "right", ...valueSigned && decision.value >= 0 && selected2 ? { color: "fg.success" } : {}, children: decisionValue(decision, decision.value, valueSigned && selected2) });
63772
+ const valueText = /* @__PURE__ */ jsx$1(Text, { fontFamily: "mono", fontWeight: "semibold", textAlign: "right", ...valueSigned && decision.value >= 0 && selected2 ? { color: "fg.success" } : {}, children: decisionValue(words, decision, decision.value, valueSigned && selected2) });
63453
63773
  return /* @__PURE__ */ jsxs(Box, { css: rs.rowShell, ...leaving ? { "data-leaving": leaving } : {}, children: [
63454
63774
  narrow ? /* @__PURE__ */ jsxs(Box, { px: "14px", py: "10px", children: [
63455
63775
  /* @__PURE__ */ jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "baseline", children: [
@@ -63473,7 +63793,7 @@ const Row = memo(function Row2({ decision, handle, selected: selected2, narrow,
63473
63793
  selected2 && /* @__PURE__ */ jsxs(Box, { px: narrow ? "14px" : "18px", py: "12px", borderTopWidth: "1px", borderColor: "border.subtle", bg: "bg.canvas", children: [
63474
63794
  facet === "evidence" && /* @__PURE__ */ jsx$1(EvidenceFacet, { decision, children: canvas != null && /* @__PURE__ */ jsx$1(EastChakraComponent, { value: canvas, storageKey: `${storageKey}-evidence-${decision.id}` }) }),
63475
63795
  facet === "options" && /* @__PURE__ */ jsx$1(OptionsFacet, { decision, narrow }),
63476
- facet === "judgement" && /* @__PURE__ */ jsx$1(JudgementFacet, { decision, handle, leverPayloads }),
63796
+ facet === "judgement" && /* @__PURE__ */ jsx$1(JudgementFacet, { decision, handle, contract }),
63477
63797
  facet === "modify" && probe != null && /* @__PURE__ */ jsx$1(EastChakraComponent, { value: probe, storageKey: `${storageKey}-modify-${decision.id}` })
63478
63798
  ] })
63479
63799
  ] });
@@ -63482,6 +63802,7 @@ const RoutineGroup = memo(function RoutineGroup2({ routine, acceptAll, leaving,
63482
63802
  const dq = useSlotRecipe({ key: "decisionQueue" });
63483
63803
  const status = useSlotRecipe({ key: "status" });
63484
63804
  const button = useRecipe({ key: "button" });
63805
+ const words = useFormatters();
63485
63806
  const rs = dq({});
63486
63807
  const st = status({ status: "neutral", size: "md" });
63487
63808
  const total = useMemo(() => routine.reduce((acc, d2) => acc + d2.value, 0), [routine]);
@@ -63503,15 +63824,15 @@ const RoutineGroup = memo(function RoutineGroup2({ routine, acceptAll, leaving,
63503
63824
  d2.kind
63504
63825
  ] })
63505
63826
  ] }),
63506
- /* @__PURE__ */ jsx$1(Box, { css: rs.routineValue, children: decisionValue(d2, d2.value) })
63827
+ /* @__PURE__ */ jsx$1(Box, { css: rs.routineValue, children: decisionValue(words, d2, d2.value) })
63507
63828
  ] }) }, d2.id)),
63508
63829
  /* @__PURE__ */ jsxs(Box, { css: rs.summary, ...narrow ? { marginLeft: "0", flexWrap: "wrap" } : {}, children: [
63509
63830
  /* @__PURE__ */ jsxs(Box, { as: "span", css: rs.summaryCap, children: [
63510
- routine.length,
63831
+ words.number(routine.length),
63511
63832
  " routine"
63512
63833
  ] }),
63513
63834
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.subtle", children: "·" }),
63514
- /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: formatTick(total, format2) }),
63835
+ /* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", fontWeight: "semibold", children: words.value(total, format2) }),
63515
63836
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.muted", children: "total" }),
63516
63837
  acceptAll && /* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "solid", size: "xs" }), ml: "auto", onClick: handleAcceptAll, children: "Accept all" })
63517
63838
  ] })
@@ -63520,6 +63841,7 @@ const RoutineGroup = memo(function RoutineGroup2({ routine, acceptAll, leaving,
63520
63841
  const GroupHead = memo(function GroupHead2({ group: group2, collapsible, collapsed, onToggle, acceptAll }) {
63521
63842
  const dq = useSlotRecipe({ key: "decisionQueue" });
63522
63843
  const button = useRecipe({ key: "button" });
63844
+ const words = useFormatters();
63523
63845
  const rs = dq({});
63524
63846
  const format2 = group2.decisions[0] ? getSomeorUndefined(group2.decisions[0].format) : void 0;
63525
63847
  const handleToggle = useCallback(() => {
@@ -63534,11 +63856,11 @@ const GroupHead = memo(function GroupHead2({ group: group2, collapsible, collaps
63534
63856
  /* @__PURE__ */ jsxs(Box, { as: "span", css: rs.groupLabel, children: [
63535
63857
  group2.label,
63536
63858
  " · ",
63537
- group2.decisions.length
63859
+ words.number(group2.decisions.length)
63538
63860
  ] }),
63539
63861
  /* @__PURE__ */ jsxs(Box, { as: "span", css: rs.groupSummary, children: [
63540
- formatTick(group2.total, format2),
63541
- group2.pastSla > 0 ? ` · ${group2.pastSla} past SLA` : ""
63862
+ words.value(group2.total, format2),
63863
+ group2.pastSla > 0 ? ` · ${words.number(group2.pastSla)} past SLA` : ""
63542
63864
  ] }),
63543
63865
  acceptAll && /* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "solid", size: "xs" }), onClick: handleAcceptAll, children: "Accept all" })
63544
63866
  ] });
@@ -63550,10 +63872,11 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63550
63872
  const dq = useSlotRecipe({ key: "decisionQueue" });
63551
63873
  const qs = dq({});
63552
63874
  const chip = useRecipe({ key: "chip" });
63875
+ const words = useFormatters();
63553
63876
  const handleRef = value.handle;
63554
63877
  const handle = useDecisionHandle(handleRef);
63555
63878
  const decisions = handle.decisions;
63556
- const leverPayloads = useLeverPayloads(handleRef);
63879
+ const contract = useConstraintContract(handleRef);
63557
63880
  const railAffordances = getSomeorUndefined(value.affordances);
63558
63881
  const sliceHandle = getSomeorUndefined(value.slice) ?? null;
63559
63882
  useSliceReactivity(sliceHandle == null ? void 0 : sliceHandle.key);
@@ -63593,14 +63916,8 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63593
63916
  const heading = getSomeorUndefined(value.heading) ?? "Decisions waiting";
63594
63917
  const onApply = useMemo(() => getSomeorUndefined(value.onApply), [value.onApply]);
63595
63918
  const onReject = useMemo(() => getSomeorUndefined(value.onReject), [value.onReject]);
63596
- const modify = useMemo(
63597
- () => getSomeorUndefined(value.modify),
63598
- [value.modify]
63599
- );
63600
- const evidence = useMemo(
63601
- () => getSomeorUndefined(value.evidence),
63602
- [value.evidence]
63603
- );
63919
+ const modify = useMemo(() => getSomeorUndefined(value.modify), [value.modify]);
63920
+ const evidence = useMemo(() => getSomeorUndefined(value.evidence), [value.evidence]);
63604
63921
  const defaultFacet = ((_a2 = getSomeorUndefined(value.defaultFacet)) == null ? void 0 : _a2.type) ?? "evidence";
63605
63922
  const facetInclude = useMemo(() => {
63606
63923
  const facets = getSomeorUndefined(value.facets);
@@ -63649,15 +63966,7 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63649
63966
  for (const { decision } of exiting.values()) {
63650
63967
  if (!merged2.some((d2) => d2.id === decision.id)) merged2.push(decision);
63651
63968
  }
63652
- merged2.sort((a2, b2) => {
63653
- var _a3, _b2;
63654
- const r2 = URGENCY_RANK[a2.urgency.type] - URGENCY_RANK[b2.urgency.type];
63655
- if (r2 !== 0) return r2;
63656
- const da = ((_a3 = getSomeorUndefined(a2.deadline)) == null ? void 0 : _a3.getTime()) ?? Infinity;
63657
- const db = ((_b2 = getSomeorUndefined(b2.deadline)) == null ? void 0 : _b2.getTime()) ?? Infinity;
63658
- if (da !== db) return da - db;
63659
- return b2.value - a2.value;
63660
- });
63969
+ merged2.sort(compareByUrgency);
63661
63970
  return {
63662
63971
  merged: merged2,
63663
63972
  active: merged2.filter((d2) => d2.urgency.type !== "routine"),
@@ -63696,7 +64005,7 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63696
64005
  handle,
63697
64006
  selected: selectedId === d2.id,
63698
64007
  narrow,
63699
- leverPayloads,
64008
+ contract,
63700
64009
  modify,
63701
64010
  evidence,
63702
64011
  defaultFacet,
@@ -63723,13 +64032,13 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63723
64032
  ) }),
63724
64033
  /* @__PURE__ */ jsxs(Box, { css: es.meta, gap: "10px", children: [
63725
64034
  /* @__PURE__ */ jsxs(Box, { as: "span", children: [
63726
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg", fontWeight: "semibold", children: visible }),
64035
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg", fontWeight: "semibold", children: words.number(visible) }),
63727
64036
  narrow ? "" : " decisions"
63728
64037
  ] }),
63729
64038
  pastSla > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
63730
64039
  /* @__PURE__ */ jsx$1(Box, { as: "span", css: es.sep, children: "·" }),
63731
64040
  /* @__PURE__ */ jsxs(Box, { as: "span", children: [
63732
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.danger", fontWeight: "semibold", children: pastSla }),
64041
+ /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.danger", fontWeight: "semibold", children: words.number(pastSla) }),
63733
64042
  narrow ? " SLA" : " past SLA"
63734
64043
  ] })
63735
64044
  ] })
@@ -63791,34 +64100,24 @@ function verdictPresentation(verdict) {
63791
64100
  return { tone: "warning", verb: "handed off", detail: verdict.value !== "" ? verdict.value : null };
63792
64101
  }
63793
64102
  }
63794
- function formatResolvedAt(j2) {
64103
+ function formatResolvedAt(j2, words) {
63795
64104
  if (j2.resolvedAt.type !== "some") return null;
63796
64105
  const d2 = j2.resolvedAt.value;
63797
- const month = d2.toLocaleString("en", { month: "short" });
63798
- const time2 = `${String(d2.getHours()).padStart(2, "0")}:${String(d2.getMinutes()).padStart(2, "0")}`;
63799
- return `${month} ${d2.getDate()} · ${time2}`;
64106
+ return `${words.monthDay(d2)} · ${words.time(d2)}`;
63800
64107
  }
63801
64108
  const EastChakraDecisionJournal = memo(function EastChakraDecisionJournal2({ value }) {
63802
64109
  const eyebrow = useSlotRecipe({ key: "eyebrowRow" });
63803
64110
  const status = useSlotRecipe({ key: "status" });
63804
64111
  const es = eyebrow({});
64112
+ const words = useFormatters();
63805
64113
  const handle = useDecisionHandle(value.handle);
63806
64114
  const heading = getSomeorUndefined(value.heading) ?? "Decision journal";
63807
- const cache3 = getReactiveDatasetCache();
63808
- const workspace = cache3.getConfig().workspace ?? "";
63809
- const leverPayloads = useMemo(() => {
63810
- var _a2, _b2, _c2;
63811
- const judgements = getBindingTypes(workspace, value.handle.judgements.source);
63812
- if (!(judgements == null ? void 0 : judgements.sourceType)) return {};
63813
- const root2 = normalizeTypeValue(judgements.sourceType);
63814
- const constraint = (_c2 = (_b2 = (_a2 = root2.value) == null ? void 0 : _a2.fields) == null ? void 0 : _b2["constraints"]) == null ? void 0 : _c2.value;
63815
- return (constraint == null ? void 0 : constraint.type) === "Variant" ? constraint.cases ?? {} : {};
63816
- }, [workspace, value.handle]);
64115
+ const contract = useConstraintContract(value.handle);
63817
64116
  const entries = useMemo(() => handle.journal ?? [], [handle.journal]);
63818
64117
  return /* @__PURE__ */ jsxs(Box, { borderWidth: "1px", borderColor: "border.strong", borderRadius: "6px", background: "bg.canvas", overflow: "hidden", children: [
63819
64118
  /* @__PURE__ */ jsxs(Box, { css: es.root, children: [
63820
64119
  /* @__PURE__ */ jsx$1(Box, { css: es.lbl, children: heading }),
63821
- /* @__PURE__ */ jsx$1(Box, { css: es.meta, children: entries.length === 1 ? "1 entry" : `${entries.length} entries` })
64120
+ /* @__PURE__ */ jsx$1(Box, { css: es.meta, children: entries.length === 1 ? "1 entry" : `${words.number(entries.length)} entries` })
63822
64121
  ] }),
63823
64122
  /* @__PURE__ */ jsx$1(
63824
64123
  Box,
@@ -63830,7 +64129,7 @@ const EastChakraDecisionJournal = memo(function EastChakraDecisionJournal2({ val
63830
64129
  if (!verdict) return null;
63831
64130
  const { tone, verb, detail } = verdictPresentation(verdict);
63832
64131
  const st = status({ status: tone, size: "md" });
63833
- const when = formatResolvedAt(j2);
64132
+ const when = formatResolvedAt(j2, words);
63834
64133
  const knowledge = j2.knowledge.type === "some" && j2.knowledge.value !== "" ? j2.knowledge.value : null;
63835
64134
  return /* @__PURE__ */ jsxs(
63836
64135
  Box,
@@ -63869,8 +64168,7 @@ const EastChakraDecisionJournal = memo(function EastChakraDecisionJournal2({ val
63869
64168
  }
63870
64169
  ),
63871
64170
  j2.constraints.length > 0 && /* @__PURE__ */ jsx$1(Box, { display: "flex", gap: "12px", flexWrap: "wrap", children: j2.constraints.map((c2, k2) => {
63872
- const tag = c2.type;
63873
- const f2 = formatConstraint(c2, leverPayloads[tag]);
64171
+ const f2 = formatConstraint(c2, contract, void 0, words);
63874
64172
  return /* @__PURE__ */ jsx$1(ClauseChip, { field: f2.lever, op: f2.op, value: f2.value }, k2);
63875
64173
  }) })
63876
64174
  ]
@@ -64189,6 +64487,7 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
64189
64487
  const { requestOptions, queryOverrides, enabled = true, hash: hash2, platforms, type } = options2;
64190
64488
  const platformImpls = platforms ?? defaultPlatformImplementations;
64191
64489
  const reqOpts = requestOptions ?? { token: null };
64490
+ const queryClient = useQueryClient();
64192
64491
  const pathParts = useMemo(
64193
64492
  () => (datasetPath == null ? void 0 : datasetPath.split(".").filter(Boolean).map((v3) => variant("field", v3))) ?? [],
64194
64493
  [datasetPath]
@@ -64198,8 +64497,14 @@ function useDatasetValue(apiUrl, repo, workspace, datasetPath, options2) {
64198
64497
  queryFn: async () => {
64199
64498
  const result = await datasetGet(apiUrl, repo, workspace, pathParts, reqOpts);
64200
64499
  const decoded = decodeBeast2For(type, { platform: platformImpls })(result.data);
64201
- return { decoded, sizeBytes: result.data.length };
64500
+ const value = { decoded, sizeBytes: result.data.length, hash: result.hash ?? hash2 ?? null };
64501
+ if (value.hash !== hash2) {
64502
+ queryClient.setQueryData(["datasetValue", apiUrl, repo, workspace, datasetPath, value.hash], value);
64503
+ void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, datasetPath] });
64504
+ }
64505
+ return value;
64202
64506
  },
64507
+ select: (value) => value.hash === (hash2 ?? null) ? value : void 0,
64203
64508
  enabled: enabled && !!workspace && !!datasetPath && hash2 != null,
64204
64509
  ...queryOverrides
64205
64510
  });
@@ -64265,14 +64570,6 @@ function useTaskDetails(apiUrl, repo, workspace, taskName, options2) {
64265
64570
  ...options2 == null ? void 0 : options2.queryOverrides
64266
64571
  });
64267
64572
  }
64268
- function getTaskKind(details) {
64269
- var _a2;
64270
- return ((_a2 = details.kind) == null ? void 0 : _a2.type) === "some" ? details.kind.value : null;
64271
- }
64272
- function getTaskMetadata(details) {
64273
- var _a2;
64274
- return ((_a2 = details.metadata) == null ? void 0 : _a2.type) === "some" ? details.metadata.value : null;
64275
- }
64276
64573
  const variantStyles = {
64277
64574
  // Mode-aware semantic tokens (the raw `red.50` / `gray.50` washes rendered
64278
64575
  // as bright light boxes in dark mode — #362).
@@ -64410,6 +64707,10 @@ function pagingDebug(...args) {
64410
64707
  console.info("[e3-paging]", ...args);
64411
64708
  }
64412
64709
  const PAGE_SIZE = 500;
64710
+ function servedPageSize(requested, page) {
64711
+ const trimmed = page.count < requested && page.offset + page.count < page.totalElements;
64712
+ return trimmed ? Math.max(1, page.count) : requested;
64713
+ }
64413
64714
  const MAX_RETAINED_PAGES = 8;
64414
64715
  function pageRows(type, decoded, offset) {
64415
64716
  if (type.type === "Array") {
@@ -64449,6 +64750,7 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64449
64750
  onDownload,
64450
64751
  onNotIndexed
64451
64752
  }) {
64753
+ const words = useFormatters();
64452
64754
  const queryClient = useQueryClient();
64453
64755
  const [pages, setPages] = useState(/* @__PURE__ */ new Map());
64454
64756
  const [totals, setTotals] = useState(null);
@@ -64456,48 +64758,69 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64456
64758
  const [error3, setError] = useState(null);
64457
64759
  const [jumpRow, setJumpRow] = useState(void 0);
64458
64760
  const inflightRef = useRef(/* @__PURE__ */ new Set());
64761
+ const shownRef = useRef({ path: path2, hash: hash2 });
64762
+ const [pageSize, setPageSize] = useState(PAGE_SIZE);
64763
+ const pageSizeRef = useRef(PAGE_SIZE);
64459
64764
  useEffect(() => {
64460
64765
  pagingDebug(`reset (path=${path2}, hash=${hash2.slice(0, 8)})`);
64766
+ shownRef.current = { path: path2, hash: hash2 };
64461
64767
  setPages(/* @__PURE__ */ new Map());
64462
64768
  setTotals(null);
64463
64769
  setError(null);
64464
64770
  setJumpRow(void 0);
64465
- inflightRef.current.clear();
64771
+ inflightRef.current = /* @__PURE__ */ new Set();
64772
+ pageSizeRef.current = PAGE_SIZE;
64773
+ setPageSize(PAGE_SIZE);
64466
64774
  }, [path2, hash2]);
64467
64775
  const loadPage = useCallback((pageIdx) => {
64468
- if (inflightRef.current.has(pageIdx)) return;
64469
- inflightRef.current.add(pageIdx);
64776
+ const inflight = inflightRef.current;
64777
+ if (inflight.has(pageIdx)) return;
64778
+ inflight.add(pageIdx);
64779
+ const current = () => shownRef.current.path === path2 && shownRef.current.hash === hash2;
64780
+ const size3 = pageSizeRef.current;
64470
64781
  setLoadingCount((n2) => n2 + 1);
64471
- pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * PAGE_SIZE}, limit=${PAGE_SIZE}, hash=${hash2.slice(0, 8)})`);
64782
+ pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * size3}, limit=${size3}, hash=${hash2.slice(0, 8)})`);
64472
64783
  const pathParts2 = path2.split(".").filter(Boolean).map((v3) => variant("field", v3));
64473
64784
  const reqOpts = requestOptions ?? { token: null };
64474
64785
  queryClient.fetchQuery({
64475
- queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}`],
64786
+ queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}@${size3}`],
64476
64787
  // Hash-pinned: the URL is a pure function of the bytes, so any
64477
64788
  // HTTP cache between here and the server can hold it immutably.
64478
- queryFn: () => datasetGetPage(apiUrl, repo, workspace, pathParts2, { offset: pageIdx * PAGE_SIZE, limit: PAGE_SIZE, hash: hash2 }, reqOpts),
64789
+ queryFn: () => datasetGetPage(apiUrl, repo, workspace, pathParts2, { offset: pageIdx * size3, limit: size3, hash: hash2 }, reqOpts),
64479
64790
  staleTime: Infinity
64480
64791
  // pages of one content hash are immutable
64481
64792
  }).then((page) => {
64793
+ if (!current()) return;
64794
+ if (size3 !== pageSizeRef.current) return;
64482
64795
  const decoded = decodeBeast2For(type)(page.data);
64483
64796
  const rows = pageRows(type, decoded, page.offset);
64484
64797
  pagingDebug(
64485
64798
  `p${pageIdx} ok: count=${page.count} rows=${rows.length} offset=${page.offset} total=${page.totalElements} segs=${page.segmentCount}`,
64486
64799
  rows.length > 0 ? `first=${JSON.stringify(rows[0].label ?? "(derived)")}` : "(empty)"
64487
64800
  );
64488
- if (page.count < PAGE_SIZE && page.offset + page.count < page.totalElements) {
64489
- pagingDebug(`p${pageIdx} SHORT page: server clamped ${PAGE_SIZE} → ${page.count}`);
64490
- }
64491
64801
  setTotals({ elements: page.totalElements, bytes: page.totalBytes });
64492
- setPages((prev2) => new Map(prev2).set(pageIdx, rows));
64802
+ const served = servedPageSize(size3, page);
64803
+ if (served === size3) {
64804
+ setPages((prev2) => new Map(prev2).set(pageIdx, rows));
64805
+ return;
64806
+ }
64807
+ pagingDebug(`p${pageIdx} SHORT page: served ${page.count} of ${size3} — addressing pages by ${served}`);
64808
+ pageSizeRef.current = served;
64809
+ setPageSize(served);
64810
+ setPages(pageIdx === 0 ? /* @__PURE__ */ new Map([[0, rows]]) : /* @__PURE__ */ new Map());
64493
64811
  }).catch((err) => {
64494
64812
  pagingDebug(`p${pageIdx} FAILED:`, err);
64813
+ if (!current()) return;
64814
+ if (err instanceof DatasetHashMismatchError) {
64815
+ void queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
64816
+ return;
64817
+ }
64495
64818
  if (err instanceof ApiError && err.code === "dataset_not_indexed") {
64496
64819
  onNotIndexed == null ? void 0 : onNotIndexed();
64497
64820
  }
64498
64821
  setError(err instanceof Error ? err : new Error(String(err)));
64499
64822
  }).finally(() => {
64500
- inflightRef.current.delete(pageIdx);
64823
+ inflight.delete(pageIdx);
64501
64824
  setLoadingCount((n2) => n2 - 1);
64502
64825
  });
64503
64826
  }, [queryClient, apiUrl, repo, workspace, path2, hash2, requestOptions, type, onNotIndexed]);
@@ -64505,8 +64828,8 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64505
64828
  if (totals === null && error3 === null) loadPage(0);
64506
64829
  }, [totals, error3, loadPage]);
64507
64830
  const onNeedRows = useCallback((startRow, endRow) => {
64508
- const first2 = Math.max(0, Math.floor(startRow / PAGE_SIZE));
64509
- const last2 = Math.max(first2, Math.ceil(endRow / PAGE_SIZE) - 1);
64831
+ const first2 = Math.max(0, Math.floor(startRow / pageSize));
64832
+ const last2 = Math.max(first2, Math.ceil(endRow / pageSize) - 1);
64510
64833
  const missing = [];
64511
64834
  for (let p2 = first2; p2 <= last2; p2++) {
64512
64835
  if (!pages.has(p2)) missing.push(p2);
@@ -64514,7 +64837,7 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64514
64837
  pagingDebug(`need rows [${startRow}, ${endRow}) → pages ${first2}..${last2}, missing=[${missing.join(",")}], inflight=[${[...inflightRef.current].join(",")}]`);
64515
64838
  setPages((prev2) => pruneRetainedPages(prev2, first2, last2, MAX_RETAINED_PAGES));
64516
64839
  for (const p2 of missing) loadPage(p2);
64517
- }, [pages, loadPage]);
64840
+ }, [pages, loadPage, pageSize]);
64518
64841
  const keyType = useMemo(() => type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64519
64842
  const pathParts = useMemo(() => path2.split(".").filter(Boolean).map((v3) => variant("field", v3)), [path2]);
64520
64843
  const onFindKey = useCallback(async (query) => {
@@ -64551,11 +64874,11 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64551
64874
  }), [type]);
64552
64875
  const paging = useMemo(() => totals === null ? null : {
64553
64876
  totalRows: totals.elements,
64554
- pageSize: PAGE_SIZE,
64877
+ pageSize,
64555
64878
  pages,
64556
64879
  onNeedRows,
64557
64880
  scrollToRow: jumpRow
64558
- }, [totals, pages, onNeedRows, jumpRow]);
64881
+ }, [totals, pageSize, pages, onNeedRows, jumpRow]);
64559
64882
  if (error3 !== null) {
64560
64883
  if (error3 instanceof ApiError && error3.code === "dataset_too_large") {
64561
64884
  return /* @__PURE__ */ jsxs(Flex, { height: "100%", direction: "column", align: "center", justify: "center", gap: 3, p: 6, children: [
@@ -64573,11 +64896,11 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64573
64896
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64574
64897
  /* @__PURE__ */ jsxs(Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64575
64898
  /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: [
64576
- totals.elements.toLocaleString(),
64899
+ words.number(totals.elements),
64577
64900
  " ",
64578
64901
  itemNoun,
64579
64902
  " · ",
64580
- formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes)
64903
+ formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes, words)
64581
64904
  ] }),
64582
64905
  loadingCount > 0 && /* @__PURE__ */ jsx$1(Text, { fontSize: "xs", color: "fg.muted", children: "Loading…" }),
64583
64906
  keyType !== null && /* @__PURE__ */ jsx$1(
@@ -64594,12 +64917,17 @@ const PagedDatasetPreview = memo(function PagedDatasetPreview2({
64594
64917
  ] }),
64595
64918
  /* @__PURE__ */ jsx$1(Box, { flex: 1, minHeight: 0, overflow: "hidden", children: /* @__PURE__ */ jsx$1(EastChakraValueTree, { value: treeValue, storageKey: `${path2}:page`, paging }) })
64596
64919
  ] });
64597
- }, (prev2, next2) => prev2.path === next2.path && prev2.workspace === next2.workspace && prev2.hash === next2.hash && prev2.sizeBytes === next2.sizeBytes);
64920
+ }, (prev2, next2) => {
64921
+ var _a2, _b2;
64922
+ 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);
64923
+ });
64598
64924
  const DEFAULT_SIZE_LIMIT = 200 * 1024;
64599
- function formatSize(bytes) {
64600
- if (bytes < 1024) return `${bytes} B`;
64601
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
64602
- return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
64925
+ const KB_FORMAT = variant("number", { minimumFractionDigits: some(1n), maximumFractionDigits: some(1n), signDisplay: none$1 });
64926
+ const MB_FORMAT = variant("number", { minimumFractionDigits: some(2n), maximumFractionDigits: some(2n), signDisplay: none$1 });
64927
+ function formatSize(bytes, words = formatters()) {
64928
+ if (bytes < 1024) return `${words.number(bytes)} B`;
64929
+ if (bytes < 1024 * 1024) return `${words.value(bytes / 1024, KB_FORMAT)} KB`;
64930
+ return `${words.value(bytes / 1024 / 1024, MB_FORMAT)} MB`;
64603
64931
  }
64604
64932
  function DownloadButton({ onClick, label }) {
64605
64933
  const [downloading, setDownloading] = useState(false);
@@ -64627,6 +64955,7 @@ const DatasetPreview = memo(function DatasetPreview2({
64627
64955
  editable = false
64628
64956
  }) {
64629
64957
  var _a2;
64958
+ const words = useFormatters();
64630
64959
  const statusQuery = useDatasetStatus(apiUrl, repo, workspace, path2, {
64631
64960
  ...requestOptions != null && { requestOptions },
64632
64961
  ...pollInterval !== void 0 && { pollInterval }
@@ -64654,12 +64983,29 @@ const DatasetPreview = memo(function DatasetPreview2({
64654
64983
  const setMutation = useDatasetSet(apiUrl, repo, workspace, requestOptions);
64655
64984
  const queryClient = useQueryClient();
64656
64985
  const decoded = (_a2 = valueQuery.data) == null ? void 0 : _a2.decoded;
64657
- const write = useCallback(async (next2) => {
64658
- if (workspace == null || path2 == null || type === void 0) return;
64986
+ const edited = useRef(null);
64987
+ const writing = useRef(0);
64988
+ const writes = useRef(Promise.resolve());
64989
+ useEffect(() => {
64990
+ if (writing.current === 0) edited.current = null;
64991
+ }, [decoded]);
64992
+ const write = useCallback((next2) => {
64993
+ if (workspace == null || path2 == null || type === void 0) return Promise.resolve();
64659
64994
  const treePath = path2.split(".").filter(Boolean).map((p2) => variant("field", p2));
64660
64995
  const data4 = encodeBeast2For(type)(next2);
64661
- await setMutation.mutateAsync({ path: treePath, data: data4 });
64662
- await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
64996
+ edited.current = { value: next2 };
64997
+ writing.current++;
64998
+ const run = writes.current.then(async () => {
64999
+ await setMutation.mutateAsync({ path: treePath, data: data4 });
65000
+ await queryClient.invalidateQueries({ queryKey: ["datasetStatus", apiUrl, repo, workspace, path2] });
65001
+ });
65002
+ writes.current = run.catch(() => void 0);
65003
+ return run.catch((err) => {
65004
+ edited.current = null;
65005
+ throw err;
65006
+ }).finally(() => {
65007
+ writing.current--;
65008
+ });
64663
65009
  }, [apiUrl, repo, workspace, path2, type, setMutation, queryClient]);
64664
65010
  const keyType = useMemo(() => type === void 0 ? null : type.type === "Dict" ? type.value.key : type.type === "Set" ? type.value : null, [type]);
64665
65011
  const inlineKeys = useMemo(() => decoded === void 0 || type === void 0 ? null : type.type === "Dict" ? [...decoded.keys()] : type.type === "Set" ? [...decoded.values()] : null, [type, decoded]);
@@ -64695,11 +65041,12 @@ const DatasetPreview = memo(function DatasetPreview2({
64695
65041
  const treeValue = useMemo(() => {
64696
65042
  if (type === void 0 || decoded === void 0) return null;
64697
65043
  const root2 = ValueTree.materialize(type, decoded);
65044
+ const base = () => edited.current !== null ? edited.current.value : decoded;
64698
65045
  const wire = editable ? {
64699
- onEdit: some((p2, leaf) => write(ValueTree.applyEdit(type, decoded, p2, { kind: "edit", leaf }))),
64700
- onInsert: some((p2) => write(ValueTree.applyEdit(type, decoded, p2, { kind: "insert" }))),
64701
- onRemove: some((p2) => write(ValueTree.applyEdit(type, decoded, p2, { kind: "remove" }))),
64702
- onTag: some((p2, tag) => write(ValueTree.applyEdit(type, decoded, p2, { kind: "tag", tag })))
65046
+ onEdit: some((p2, leaf) => write(ValueTree.applyEdit(type, base(), p2, { kind: "edit", leaf }))),
65047
+ onInsert: some((p2) => write(ValueTree.applyEdit(type, base(), p2, { kind: "insert" }))),
65048
+ onRemove: some((p2) => write(ValueTree.applyEdit(type, base(), p2, { kind: "remove" }))),
65049
+ onTag: some((p2, tag) => write(ValueTree.applyEdit(type, base(), p2, { kind: "tag", tag })))
64703
65050
  } : { onEdit: none$1, onInsert: none$1, onRemove: none$1, onTag: none$1 };
64704
65051
  return { root: root2, ...wire, style: some({ height: some("100%"), maxHeight: none$1, openDepth: none$1, toolbar: some(true) }) };
64705
65052
  }, [type, decoded, editable, write]);
@@ -64733,18 +65080,21 @@ const DatasetPreview = memo(function DatasetPreview2({
64733
65080
  /* @__PURE__ */ jsx$1(Text, { fontSize: "lg", color: "fg.warning", fontWeight: "bold", children: "Value too large to display" }),
64734
65081
  /* @__PURE__ */ jsxs(Text, { color: "fg.muted", fontSize: "sm", children: [
64735
65082
  "The data is ",
64736
- formatSize(sizeBytes),
65083
+ formatSize(sizeBytes, words),
64737
65084
  ", which exceeds the ",
64738
- formatSize(sizeLimit),
65085
+ formatSize(sizeLimit, words),
64739
65086
  " display limit."
64740
65087
  ] }),
64741
65088
  /* @__PURE__ */ jsx$1(DownloadButton, { onClick: download, label: "Download value" })
64742
65089
  ] });
64743
65090
  }
65091
+ if (valueQuery.error) {
65092
+ const { message, details } = formatApiError(valueQuery.error);
65093
+ return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Load failed", message, details: details ?? formatError(valueQuery.error) });
65094
+ }
64744
65095
  if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading..." });
64745
- if (valueQuery.error) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
64746
65096
  const count = type === void 0 || decoded === void 0 ? null : type.type === "Array" ? decoded.length : type.type === "Set" || type.type === "Dict" ? decoded.size : null;
64747
- const countText = count === null ? "" : `${count.toLocaleString()} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
65097
+ const countText = count === null ? "" : `${words.number(count)} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
64748
65098
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64749
65099
  /* @__PURE__ */ jsxs(Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64750
65100
  keyType !== null && inlineKeys !== null && /* @__PURE__ */ jsx$1(
@@ -64760,7 +65110,7 @@ const DatasetPreview = memo(function DatasetPreview2({
64760
65110
  /* @__PURE__ */ jsxs(Flex, { flex: 1, justify: "flex-end", align: "center", gap: 2, children: [
64761
65111
  /* @__PURE__ */ jsxs(Text, { fontSize: "xs", color: "fg.muted", children: [
64762
65112
  countText,
64763
- formatSize(sizeBytes)
65113
+ formatSize(sizeBytes, words)
64764
65114
  ] }),
64765
65115
  /* @__PURE__ */ jsx$1(DownloadButton, { onClick: download })
64766
65116
  ] })
@@ -64813,19 +65163,15 @@ const UITaskPreview = memo(function UITaskPreview2({
64813
65163
  const workspace = (config2 == null ? void 0 : config2.workspace) ?? (e3 == null ? void 0 : e3.workspace) ?? null;
64814
65164
  const token2 = (config2 == null ? void 0 : config2.token) ?? (e3 == null ? void 0 : e3.token) ?? null;
64815
65165
  const requestOptions = { token: token2 };
65166
+ const bound3 = cache3 == null ? void 0 : cache3.getConfig().workspace;
65167
+ const foreign = (config2 == null ? void 0 : config2.workspace) !== void 0 && bound3 !== void 0 && config2.workspace !== bound3;
64816
65168
  const detailsQuery = useTaskDetails(apiUrl ?? "", repo, workspace, task, { requestOptions });
64817
65169
  const details = detailsQuery.data;
64818
- const kind = details ? getTaskKind(details) : null;
64819
- const isUI = kind === "ui";
64820
- const manifest = useMemo(() => {
64821
- if (!details || !isUI) return null;
64822
- const meta3 = getTaskMetadata(details);
64823
- return meta3 ? decodeManifest(meta3) : { paths: [], functions: [], records: [], pages: [] };
64824
- }, [details, isUI]);
64825
- const outputPath = details ? treePathToString$1(details.output) : null;
65170
+ const manifest = useMemo(() => (details == null ? void 0 : details.role.type) === "ui" ? details.role.value : null, [details]);
65171
+ const outputPath = details ? treePathToString$1(details.output.path) : null;
64826
65172
  const preloads = useMemo(
64827
- () => manifest && workspace ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
64828
- [manifest, workspace]
65173
+ () => manifest && workspace && !foreign ? manifest.paths.map((path2) => ({ workspace, path: path2 })) : [],
65174
+ [manifest, workspace, foreign]
64829
65175
  );
64830
65176
  const { loading: preloading, error: preloadError } = usePreloadReactiveDatasets(preloads);
64831
65177
  const scopedPlatforms = useMemo(
@@ -64851,7 +65197,7 @@ const UITaskPreview = memo(function UITaskPreview2({
64851
65197
  [manifest]
64852
65198
  );
64853
65199
  useEffect(() => {
64854
- if (!cache3 || !manifest || !workspace) return;
65200
+ if (!cache3 || !manifest || !workspace || foreign) return;
64855
65201
  for (const path2 of manifest.paths) {
64856
65202
  cache3.setRefetchInterval(workspace, path2, pollInterval);
64857
65203
  }
@@ -64860,7 +65206,7 @@ const UITaskPreview = memo(function UITaskPreview2({
64860
65206
  cache3.clearRefetchInterval(workspace, path2);
64861
65207
  }
64862
65208
  };
64863
- }, [cache3, manifest, workspace, pollInterval]);
65209
+ }, [cache3, manifest, workspace, pollInterval, foreign]);
64864
65210
  const statusQuery = useDatasetStatus(apiUrl ?? "", repo, workspace, outputPath, { requestOptions });
64865
65211
  const valueQuery = useDatasetValue(apiUrl ?? "", repo, workspace, outputPath, {
64866
65212
  requestOptions,
@@ -64870,16 +65216,20 @@ const UITaskPreview = memo(function UITaskPreview2({
64870
65216
  });
64871
65217
  const store = useMemo(() => createUIStore(), [task]);
64872
65218
  if (!apiUrl) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Configuration error", message: "No apiUrl available — render UITaskPreview inside an <E3Provider> or pass a config prop." });
65219
+ if (foreign) {
65220
+ return /* @__PURE__ */ jsx$1(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.` });
65221
+ }
64873
65222
  if (detailsQuery.isLoading) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading task..." });
64874
65223
  if (detailsQuery.error) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message });
64875
65224
  if (!details) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "info", title: "No task", message: `Task "${task}" not found` });
64876
- if (!isUI) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Not a UI task", message: `Task "${task}" has kind "${kind ?? "(none)"}"` });
65225
+ if (!manifest) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Not a UI task", message: `Task "${task}" is a ${details.role.type} task` });
64877
65226
  if (preloadError) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Preload failed", message: preloadError.message });
64878
65227
  if (preloading) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading datasets..." });
65228
+ if (statusQuery.error) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Error", message: statusQuery.error.message });
64879
65229
  if (statusQuery.isLoading || !statusQuery.data) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading..." });
64880
65230
  if (statusQuery.data.refType !== "value") return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "info", title: "No output yet", message: "Task has not produced a value" });
65231
+ if (valueQuery.error) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Load failed", message: valueQuery.error.message });
64881
65232
  if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading..." });
64882
- if (valueQuery.error) return /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
64883
65233
  return /* @__PURE__ */ jsx$1(UIStoreProvider, { store, children: /* @__PURE__ */ jsx$1(ErrorBoundary, { children: /* @__PURE__ */ jsx$1(Box, { height: "100%", overflow: bare ? void 0 : "auto", p: bare ? void 0 : "4", minH: bare ? 0 : void 0, children: /* @__PURE__ */ jsx$1(
64884
65234
  EastChakraComponent,
64885
65235
  {
@@ -65336,7 +65686,7 @@ const DataTaskPreview = memo(function DataTaskPreview2({
65336
65686
  const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
65337
65687
  ...requestOptions != null && { requestOptions }
65338
65688
  });
65339
- const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output) : null;
65689
+ const outputPath = detailsQuery.data ? treePathToString(detailsQuery.data.output.path) : null;
65340
65690
  return /* @__PURE__ */ jsxs(Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
65341
65691
  /* @__PURE__ */ jsx$1(
65342
65692
  Flex,
@@ -65400,13 +65750,14 @@ const TaskPreview = memo(function TaskPreview2({
65400
65750
  requestOptions,
65401
65751
  bare = false
65402
65752
  }) {
65753
+ var _a2;
65403
65754
  const detailsQuery = useTaskDetails(apiUrl, repo, workspace, task, {
65404
65755
  ...requestOptions != null && { requestOptions }
65405
65756
  });
65406
- const kind = detailsQuery.data ? getTaskKind(detailsQuery.data) : null;
65757
+ const isUI = ((_a2 = detailsQuery.data) == null ? void 0 : _a2.role.type) === "ui";
65407
65758
  return /* @__PURE__ */ jsxs(Box, { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden", children: [
65408
65759
  !bare && /* @__PURE__ */ jsx$1(Flex, { px: 4, py: 2, borderBottom: "1px solid", borderColor: "border.subtle", bg: "bg.surface", align: "center", flexShrink: 0, children: /* @__PURE__ */ jsx$1(Text, { fontSize: "sm", fontWeight: "medium", color: "fg", children: task }) }),
65409
- /* @__PURE__ */ jsx$1(Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) : kind === "ui" ? /* @__PURE__ */ jsx$1(
65760
+ /* @__PURE__ */ jsx$1(Box, { flex: 1, overflow: "hidden", minHeight: 0, children: detailsQuery.isLoading ? /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "loading", title: "Loading task..." }) : detailsQuery.error ? /* @__PURE__ */ jsx$1(StatusDisplay, { variant: "error", title: "Error", message: detailsQuery.error.message }) : isUI ? /* @__PURE__ */ jsx$1(
65410
65761
  UITaskPreview,
65411
65762
  {
65412
65763
  task,
@@ -65424,33 +65775,16 @@ const TaskPreview = memo(function TaskPreview2({
65424
65775
  }
65425
65776
  ) })
65426
65777
  ] });
65427
- }, (prev2, next2) => prev2.task === next2.task && prev2.repo === next2.repo && prev2.workspace === next2.workspace && prev2.bare === next2.bare);
65778
+ }, (prev2, next2) => {
65779
+ var _a2, _b2;
65780
+ 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);
65781
+ });
65428
65782
  function formatPrimitive(type, value) {
65429
- switch (type.type) {
65430
- case "Null":
65431
- return "null";
65432
- case "Boolean":
65433
- return value ? "true" : "false";
65434
- case "Integer":
65435
- return String(value);
65436
- case "Float": {
65437
- const num2 = value;
65438
- if (Number.isNaN(num2)) return "NaN";
65439
- if (!Number.isFinite(num2)) return num2 > 0 ? "Infinity" : "-Infinity";
65440
- return String(num2);
65441
- }
65442
- case "String":
65443
- return `"${value}"`;
65444
- case "DateTime":
65445
- return value.toISOString();
65446
- case "Blob":
65447
- return `Blob[${value.length} bytes]`;
65448
- default:
65449
- return String(value);
65450
- }
65783
+ if (type.type === "Blob") return `Blob[${value.length} bytes]`;
65784
+ return printFor(type)(value);
65451
65785
  }
65452
65786
  function isPrimitive(type) {
65453
- return ["Null", "Boolean", "Integer", "Float", "String", "DateTime", "Blob", "Never"].includes(type.type);
65787
+ return ["Null", "Boolean", "Integer", "Float", "String", "DateTime", "Blob"].includes(type.type);
65454
65788
  }
65455
65789
  function getTypeLabel(type, value) {
65456
65790
  switch (type.type) {
@@ -65572,8 +65906,8 @@ function ValueNode({
65572
65906
  }
65573
65907
  if (type.type === "Variant") {
65574
65908
  const cases = type.value;
65575
- const variant2 = value;
65576
- const activeCase = cases.find((c2) => c2.name === variant2.type);
65909
+ const chosen = value;
65910
+ const activeCase = cases.find((c2) => c2.name === chosen.type);
65577
65911
  if (!activeCase) {
65578
65912
  return /* @__PURE__ */ jsxs(Box, { py: 0.5, display: "flex", alignItems: "center", gap: 2, children: [
65579
65913
  label && /* @__PURE__ */ jsxs(Text, { as: "span", color: "accent.purple", fontWeight: "medium", children: [
@@ -65582,7 +65916,7 @@ function ValueNode({
65582
65916
  ] }),
65583
65917
  /* @__PURE__ */ jsxs(Text, { as: "span", color: "fg.danger", children: [
65584
65918
  "Unknown variant: ",
65585
- variant2.type
65919
+ chosen.type
65586
65920
  ] })
65587
65921
  ] });
65588
65922
  }
@@ -65594,7 +65928,7 @@ function ValueNode({
65594
65928
  ] }),
65595
65929
  /* @__PURE__ */ jsxs(Badge, { size: "xs", colorPalette: "brand", variant: "subtle", children: [
65596
65930
  ".",
65597
- variant2.type
65931
+ chosen.type
65598
65932
  ] })
65599
65933
  ] });
65600
65934
  }
@@ -65606,14 +65940,14 @@ function ValueNode({
65606
65940
  ] }),
65607
65941
  /* @__PURE__ */ jsxs(Badge, { size: "xs", colorPalette: "brand", variant: "subtle", children: [
65608
65942
  ".",
65609
- variant2.type
65943
+ chosen.type
65610
65944
  ] })
65611
65945
  ] }),
65612
65946
  /* @__PURE__ */ jsx$1(Box, { pl: 4, children: /* @__PURE__ */ jsx$1(
65613
65947
  ValueNode,
65614
65948
  {
65615
65949
  type: activeCase.type,
65616
- value: variant2.value,
65950
+ value: chosen.value,
65617
65951
  depth: depth + 1
65618
65952
  }
65619
65953
  ) })
@@ -65819,8 +66153,6 @@ export {
65819
66153
  getBindingTypes,
65820
66154
  getReactiveDatasetCache,
65821
66155
  getStagedStore,
65822
- getTaskKind,
65823
- getTaskMetadata,
65824
66156
  initializeFunctionApi,
65825
66157
  initializePagedApi,
65826
66158
  initializeReactiveDatasetCache,
@@ -65828,6 +66160,7 @@ export {
65828
66160
  initializeStagedStore,
65829
66161
  isBindingTracking,
65830
66162
  onWriteError,
66163
+ pagedRevisionKey,
65831
66164
  pagedTotalKey,
65832
66165
  pagedWindowKey,
65833
66166
  preloadReactiveDatasetList,