@elaraai/e3-ui-components 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,11 +18,11 @@ 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, EastError, ConflictError, equalFor, SortedMap, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, compareFor, walkPatch, pathToString, pathDisplay, printFor, isVariant, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, none as none$1, some, fromEastTypeValue, OptionType, StringType } from "@elaraai/east";
21
+ import { variant, EastError, ConflictError, equalFor, SortedMap, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, compareFor, none as none$1, some, walkPatch, pathToString, pathDisplay, printFor, isVariant, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, fromEastTypeValue, OptionType, StringType } from "@elaraai/east";
22
22
  import { EastTypeType } from "@elaraai/east/internal";
23
- import { bindPlatformFn, funcBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionQueue, DecisionJournal, decodeManifest } from "@elaraai/e3-ui/internal";
23
+ import { bindPlatformFn, funcBindPlatformFn, recordBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionQueue, DecisionJournal, decodeManifest } from "@elaraai/e3-ui/internal";
24
24
  import { registerReactiveTracker, registerPlatformImplementation } from "@elaraai/east-ui-components/platform";
25
- import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, workspaceFunctionCall, workspaceFunctionList, ApiError, 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";
25
+ import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, workspaceFunctionCall, workspaceFunctionList, workspaceRecordHistory, workspaceRecordMutate, workspaceRecordDescribe, ApiError, 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";
26
26
  import { jsx as jsx$1, Fragment, jsxs } from "react/jsx-runtime";
27
27
  import * as React from "react";
28
28
  import React__default, { useId, useMemo, useContext, createContext, useRef, useEffect, useState, useCallback, useSyncExternalStore, memo, forwardRef, useLayoutEffect, Fragment as Fragment$1, Component } from "react";
@@ -2088,9 +2088,6 @@ function datasetCacheKey(workspace, path2) {
2088
2088
  const pathStr = datasetPathToString(path2);
2089
2089
  return pathStr ? `${workspace}.${pathStr}` : workspace;
2090
2090
  }
2091
- function toTreePath(path2) {
2092
- return path2;
2093
- }
2094
2091
  class ReactiveDatasetCache {
2095
2092
  constructor(config2, api3, clock2 = realClock) {
2096
2093
  __publicField(this, "destroyed", false);
@@ -2212,7 +2209,7 @@ class ReactiveDatasetCache {
2212
2209
  const prevTail = this.writeChains.get(key) ?? Promise.resolve();
2213
2210
  const run = prevTail.then(async () => {
2214
2211
  try {
2215
- await this.api.set(workspace, toTreePath(path2), value);
2212
+ await this.api.set(workspace, path2, value);
2216
2213
  if (this.destroyed) return;
2217
2214
  this.writeBaselines.set(key, {
2218
2215
  value,
@@ -2267,6 +2264,14 @@ class ReactiveDatasetCache {
2267
2264
  if (this.destroyed) return;
2268
2265
  await this.api.launchDataflow(workspace);
2269
2266
  }
2267
+ /**
2268
+ * Launch a workspace dataflow run without writing first. The standalone
2269
+ * half of {@link writeAndStart}; see the interface doc for semantics.
2270
+ */
2271
+ async launchDataflow(workspace) {
2272
+ if (this.destroyed) return;
2273
+ await this.api.launchDataflow(workspace);
2274
+ }
2270
2275
  /**
2271
2276
  * Preload a dataset into cache. Concurrent preloads of the same key
2272
2277
  * share one fetch (query-core dedup); a write racing the fetch cancels
@@ -2282,7 +2287,7 @@ class ReactiveDatasetCache {
2282
2287
  await this.client.fetchQuery({
2283
2288
  queryKey,
2284
2289
  queryFn: async () => {
2285
- const result = await this.fetchDataset(workspace, path2);
2290
+ const result = await this.api.get(workspace, path2);
2286
2291
  fetchedHash = result.hash;
2287
2292
  return result.data;
2288
2293
  }
@@ -2298,14 +2303,11 @@ class ReactiveDatasetCache {
2298
2303
  this.knownHashes.set(key, fetchedHash);
2299
2304
  this.notifyChange(key);
2300
2305
  }
2301
- fetchDataset(workspace, path2) {
2302
- return this.api.get(workspace, toTreePath(path2));
2303
- }
2304
2306
  /**
2305
2307
  * List fields at a path. Empty path lists workspace root.
2306
2308
  */
2307
2309
  list(workspace, path2) {
2308
- return path2.length === 0 ? this.api.listRoot(workspace) : this.api.listAt(workspace, toTreePath(path2));
2310
+ return path2.length === 0 ? this.api.listRoot(workspace) : this.api.listAt(workspace, path2);
2309
2311
  }
2310
2312
  /**
2311
2313
  * Set refetch interval for a dataset (polling).
@@ -2361,6 +2363,11 @@ class ReactiveDatasetCache {
2361
2363
  this.workspacePollers.delete(workspace);
2362
2364
  }
2363
2365
  }
2366
+ refresh(workspace) {
2367
+ if (this.destroyed) return Promise.resolve();
2368
+ if (!this.workspacePollers.has(workspace)) return Promise.resolve();
2369
+ return this.pollWorkspaceStatus(workspace);
2370
+ }
2364
2371
  /**
2365
2372
  * Poll workspace status and reconcile the cache with the server.
2366
2373
  * Concurrent calls for the same workspace dedupe to a single fetch.
@@ -2425,7 +2432,7 @@ class ReactiveDatasetCache {
2425
2432
  if (pending.length === 0) return;
2426
2433
  const fetched = await Promise.allSettled(
2427
2434
  pending.map(
2428
- (p2) => p2.kind === "fetch" ? this.fetchDataset(workspace, p2.path).then((result) => ({ p: p2, result })) : Promise.resolve({ p: p2, result: null })
2435
+ (p2) => p2.kind === "fetch" ? this.api.get(workspace, p2.path).then((result) => ({ p: p2, result })) : Promise.resolve({ p: p2, result: null })
2429
2436
  )
2430
2437
  );
2431
2438
  if (this.destroyed) return;
@@ -2608,6 +2615,9 @@ class IndexedDBStagedAdapter {
2608
2615
  req.onerror = () => reject2(req.error);
2609
2616
  req.onblocked = () => reject2(new Error("IndexedDB open blocked"));
2610
2617
  });
2618
+ void this.dbPromise.catch(() => {
2619
+ this.dbPromise = null;
2620
+ });
2611
2621
  }
2612
2622
  return this.dbPromise;
2613
2623
  }
@@ -2683,6 +2693,11 @@ class StagedStore {
2683
2693
  __publicField(this, "adapter");
2684
2694
  __publicField(this, "hydrated");
2685
2695
  __publicField(this, "inFlight", /* @__PURE__ */ new Set());
2696
+ // Per-key persistence chain — serializes save/remove for a key so they
2697
+ // land in IndexedDB in call order (last write wins) instead of racing.
2698
+ __publicField(this, "saveChains", /* @__PURE__ */ new Map());
2699
+ // Listeners notified when a persistence op fails durably.
2700
+ __publicField(this, "errorListeners", /* @__PURE__ */ new Set());
2686
2701
  this.adapter = adapter;
2687
2702
  this.hydrated = this.hydrate();
2688
2703
  }
@@ -2719,14 +2734,14 @@ class StagedStore {
2719
2734
  snapshot: next2.snapshot,
2720
2735
  buffered: next2.buffered
2721
2736
  };
2722
- this.track(this.adapter.save(key, persisted));
2737
+ this.persist(key, () => this.adapter.save(key, persisted));
2723
2738
  }
2724
2739
  discard(workspace, path2) {
2725
2740
  const key = datasetCacheKey(workspace, path2);
2726
2741
  if (!this.entries.has(key)) return false;
2727
2742
  this.entries.delete(key);
2728
2743
  this.notify(key);
2729
- this.track(this.adapter.remove(key));
2744
+ this.persist(key, () => this.adapter.remove(key));
2730
2745
  return true;
2731
2746
  }
2732
2747
  listKeys() {
@@ -2747,11 +2762,18 @@ class StagedStore {
2747
2762
  getKeyVersion(key) {
2748
2763
  return this.versions.get(key) ?? 0;
2749
2764
  }
2765
+ onPersistError(cb) {
2766
+ this.errorListeners.add(cb);
2767
+ return () => {
2768
+ this.errorListeners.delete(cb);
2769
+ };
2770
+ }
2750
2771
  /** Test-only: clear in-memory + persisted state. */
2751
2772
  async clear() {
2752
2773
  const keys = [...this.entries.keys()];
2753
2774
  this.entries.clear();
2754
2775
  for (const key of keys) this.notify(key);
2776
+ await this.flushPending();
2755
2777
  await this.adapter.clear();
2756
2778
  }
2757
2779
  // ----- internals -------------------------------------------------------
@@ -2760,20 +2782,45 @@ class StagedStore {
2760
2782
  const subs = this.subscribers.get(key);
2761
2783
  if (subs) for (const cb of subs) cb();
2762
2784
  }
2763
- track(p2) {
2764
- const wrapped = p2.catch((err) => {
2765
- console.warn("[StagedStore] persistence operation failed:", err);
2766
- }).then(() => {
2767
- this.inFlight.delete(wrapped);
2785
+ /**
2786
+ * Serialize a persistence op behind any prior op for the same key, so they
2787
+ * land in IndexedDB in call order (last write wins) rather than racing.
2788
+ * Tracks the op for {@link flushPending} and routes failures to
2789
+ * {@link onPersistError} listeners (falling back to a console warning).
2790
+ */
2791
+ persist(key, op) {
2792
+ const prev2 = this.saveChains.get(key) ?? Promise.resolve();
2793
+ const settled = prev2.then(op, op).then(
2794
+ () => void 0,
2795
+ (err) => {
2796
+ this.emitPersistError(key, err);
2797
+ }
2798
+ ).then(() => {
2799
+ if (this.saveChains.get(key) === settled) this.saveChains.delete(key);
2800
+ this.inFlight.delete(settled);
2768
2801
  });
2769
- this.inFlight.add(wrapped);
2802
+ this.saveChains.set(key, settled);
2803
+ this.inFlight.add(settled);
2804
+ }
2805
+ emitPersistError(key, err) {
2806
+ if (this.errorListeners.size === 0) {
2807
+ console.warn("[StagedStore] persistence operation failed:", err);
2808
+ return;
2809
+ }
2810
+ for (const cb of [...this.errorListeners]) {
2811
+ try {
2812
+ cb(key, err);
2813
+ } catch (listenerErr) {
2814
+ console.warn("[StagedStore] persist-error listener threw:", listenerErr);
2815
+ }
2816
+ }
2770
2817
  }
2771
2818
  async hydrate() {
2772
2819
  let persisted;
2773
2820
  try {
2774
2821
  persisted = await this.adapter.loadAll();
2775
2822
  } catch (err) {
2776
- console.warn("[StagedStore] hydrate failed:", err);
2823
+ this.emitPersistError("", err);
2777
2824
  return;
2778
2825
  }
2779
2826
  for (const [key, p2] of persisted) {
@@ -2809,7 +2856,7 @@ function clearStagedStoreSingleton() {
2809
2856
  }
2810
2857
  const UNCHANGED_VARIANT = variant("unchanged", null);
2811
2858
  const blobEqual = equalFor(BlobType);
2812
- const eastTypeEqual$1 = equalFor(EastTypeType);
2859
+ const eastTypeEqual$2 = equalFor(EastTypeType);
2813
2860
  const helpersCache = new SortedMap(
2814
2861
  void 0,
2815
2862
  compareFor(EastTypeType)
@@ -2862,6 +2909,7 @@ class BindRuntime {
2862
2909
  // List cache helpers.
2863
2910
  __publicField(this, "listCache", /* @__PURE__ */ new Map());
2864
2911
  this.staged = staged;
2912
+ this.staged.onPersistError((_key, err) => this.emitWriteError(err));
2865
2913
  }
2866
2914
  // ----- cache singleton ------------------------------------------------
2867
2915
  /** Read the active cache. Throws if none has been initialized. */
@@ -2899,6 +2947,16 @@ class BindRuntime {
2899
2947
  this.writeErrorListeners.delete(cb);
2900
2948
  };
2901
2949
  }
2950
+ /** Fan a write/persistence error out to every registered listener. */
2951
+ emitWriteError(error3) {
2952
+ for (const cb of this.writeErrorListeners) {
2953
+ try {
2954
+ cb(error3);
2955
+ } catch (cbErr) {
2956
+ console.error("Data.bind write-error listener threw:", cbErr);
2957
+ }
2958
+ }
2959
+ }
2902
2960
  /** Resolve once every currently-queued write has finished
2903
2961
  * (success or failure). Errors are NOT thrown — subscribe via
2904
2962
  * {@link onWriteError} for those. */
@@ -2936,13 +2994,7 @@ class BindRuntime {
2936
2994
  await writeFn();
2937
2995
  } catch (error3) {
2938
2996
  console.error("Data.bind write failed:", error3);
2939
- for (const cb of this.writeErrorListeners) {
2940
- try {
2941
- cb(error3);
2942
- } catch (cbErr) {
2943
- console.error("Data.bind write-error listener threw:", cbErr);
2944
- }
2945
- }
2997
+ this.emitWriteError(error3);
2946
2998
  }
2947
2999
  }
2948
3000
  this.isProcessingWrites = false;
@@ -2999,7 +3051,7 @@ class BindRuntime {
2999
3051
  return;
3000
3052
  }
3001
3053
  const shapeChanged = existing.mode !== info.mode || existing.hasPatchDataset !== info.hasPatchDataset;
3002
- const typeChanged = !eastTypeEqual$1(existing.sourceType, info.sourceType);
3054
+ const typeChanged = !eastTypeEqual$2(existing.sourceType, info.sourceType);
3003
3055
  if (!shapeChanged && !typeChanged) {
3004
3056
  this.bindingRegistry.set(key, info);
3005
3057
  return;
@@ -3102,6 +3154,10 @@ class BindRuntime {
3102
3154
  }
3103
3155
  return null;
3104
3156
  };
3157
+ const startLaunch = () => {
3158
+ this.queueWrite(() => cache3.launchDataflow(ws));
3159
+ return null;
3160
+ };
3105
3161
  if (mode === "direct" && !patchPath) {
3106
3162
  return {
3107
3163
  read: () => {
@@ -3116,6 +3172,7 @@ class BindRuntime {
3116
3172
  this.queueWrite(() => cache3.writeAndStart(ws, sourcePath, encodeT(value)));
3117
3173
  return null;
3118
3174
  },
3175
+ start: startLaunch,
3119
3176
  source: () => {
3120
3177
  this.trackPath(ws, sourcePath);
3121
3178
  return readSourceValue();
@@ -3149,6 +3206,7 @@ class BindRuntime {
3149
3206
  this.queueWrite(() => cache3.writeAndStart(ws, pPath2, encodePatch(next2)));
3150
3207
  return null;
3151
3208
  },
3209
+ start: startLaunch,
3152
3210
  source: () => {
3153
3211
  this.trackPath(ws, sourcePath);
3154
3212
  return readSourceValue();
@@ -3162,12 +3220,8 @@ class BindRuntime {
3162
3220
  const patchVal = readPatchValue();
3163
3221
  this.queueWrite(async () => {
3164
3222
  const next2 = apply2(sourceVal, patchVal);
3165
- const writes = [];
3166
- cache3.batch(() => {
3167
- writes.push(cache3.write(ws, sourcePath, encodeT(next2)));
3168
- writes.push(cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT)));
3169
- });
3170
- await Promise.all(writes);
3223
+ await cache3.write(ws, sourcePath, encodeT(next2));
3224
+ await cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT));
3171
3225
  });
3172
3226
  return null;
3173
3227
  },
@@ -3193,6 +3247,7 @@ class BindRuntime {
3193
3247
  },
3194
3248
  write: writeStaged,
3195
3249
  writeAndStart: writeStaged,
3250
+ start: startLaunch,
3196
3251
  source: () => {
3197
3252
  this.trackPath(ws, sourcePath);
3198
3253
  return readSourceValue();
@@ -3237,6 +3292,7 @@ class BindRuntime {
3237
3292
  },
3238
3293
  write: writeStaged,
3239
3294
  writeAndStart: writeStaged,
3295
+ start: startLaunch,
3240
3296
  source: () => {
3241
3297
  this.trackPath(ws, sourcePath);
3242
3298
  return readSourceValue();
@@ -3385,6 +3441,110 @@ registerReactiveTracker({
3385
3441
  }
3386
3442
  });
3387
3443
  registerPlatformImplementation(BindPlatform);
3444
+ class TrackedChannelStore {
3445
+ constructor() {
3446
+ __publicField(this, "entries", /* @__PURE__ */ new Map());
3447
+ // Per-key reactive subscription shim.
3448
+ __publicField(this, "keySubscribers", /* @__PURE__ */ new Map());
3449
+ __publicField(this, "keyVersions", /* @__PURE__ */ new Map());
3450
+ // Reactive tracking — read closures push their channel keys here while a
3451
+ // Reactive.Root render is being tracked.
3452
+ __publicField(this, "trackingContext", null);
3453
+ }
3454
+ // ----- reactive tracking ----------------------------------------------
3455
+ enableTracking() {
3456
+ this.trackingContext = /* @__PURE__ */ new Set();
3457
+ return this.trackingContext;
3458
+ }
3459
+ disableTracking() {
3460
+ const keys = this.trackingContext ? [...this.trackingContext] : [];
3461
+ this.trackingContext = null;
3462
+ return keys;
3463
+ }
3464
+ isTracking() {
3465
+ return this.trackingContext !== null;
3466
+ }
3467
+ /** Record a channel-key dependency for the current render (no-op when not
3468
+ * tracking). */
3469
+ track(key) {
3470
+ var _a2;
3471
+ (_a2 = this.trackingContext) == null ? void 0 : _a2.add(key);
3472
+ }
3473
+ // ----- subscription shim ------------------------------------------------
3474
+ subscribe(key, callback) {
3475
+ let subs = this.keySubscribers.get(key);
3476
+ if (!subs) {
3477
+ subs = /* @__PURE__ */ new Set();
3478
+ this.keySubscribers.set(key, subs);
3479
+ }
3480
+ subs.add(callback);
3481
+ return () => {
3482
+ subs.delete(callback);
3483
+ if (subs.size === 0 && this.keySubscribers.get(key) === subs) {
3484
+ this.keySubscribers.delete(key);
3485
+ }
3486
+ };
3487
+ }
3488
+ getKeyVersion(key) {
3489
+ return this.keyVersions.get(key) ?? 0;
3490
+ }
3491
+ notify(key) {
3492
+ this.keyVersions.set(key, (this.keyVersions.get(key) ?? 0) + 1);
3493
+ const subs = this.keySubscribers.get(key);
3494
+ if (subs) for (const cb of [...subs]) cb();
3495
+ }
3496
+ // ----- channel registry + latest-wins latch ----------------------------
3497
+ entry(key) {
3498
+ let entry = this.entries.get(key);
3499
+ if (!entry) {
3500
+ entry = this.createEntry();
3501
+ this.entries.set(key, entry);
3502
+ }
3503
+ return entry;
3504
+ }
3505
+ /**
3506
+ * Open a latest-wins launch on a channel: bump the seq, mark the entry
3507
+ * `running`, optionally `reset` it (e.g. clear a prior error), notify, and
3508
+ * return a `settle` that applies a terminal mutation only if this launch is
3509
+ * still the current one (a superseded / cancelled launch settles to a
3510
+ * no-op).
3511
+ */
3512
+ beginLaunch(key, reset2) {
3513
+ const entry = this.entry(key);
3514
+ entry.launchSeq += 1;
3515
+ const mySeq = entry.launchSeq;
3516
+ entry.status = "running";
3517
+ reset2 == null ? void 0 : reset2(entry);
3518
+ this.notify(key);
3519
+ const settle = (mutate) => {
3520
+ const current = this.entries.get(key);
3521
+ if (!current || current.launchSeq !== mySeq) return;
3522
+ mutate(current);
3523
+ this.notify(key);
3524
+ };
3525
+ return { entry, settle };
3526
+ }
3527
+ /**
3528
+ * Client-side cancel: if the channel is running, orphan the in-flight wait
3529
+ * (bump the seq) and mark it `cancelled`. `after` runs before the notify,
3530
+ * for subclass-specific cleanup (e.g. dropping an in-flight handle).
3531
+ */
3532
+ cancelChannel(key, after) {
3533
+ const entry = this.entries.get(key);
3534
+ if (entry && entry.status === "running") {
3535
+ entry.launchSeq += 1;
3536
+ entry.status = "cancelled";
3537
+ after == null ? void 0 : after(entry);
3538
+ this.notify(key);
3539
+ }
3540
+ }
3541
+ /** Drop all channel state (entries + subscriptions + versions). */
3542
+ clearChannels() {
3543
+ this.entries.clear();
3544
+ this.keySubscribers.clear();
3545
+ this.keyVersions.clear();
3546
+ }
3547
+ }
3388
3548
  function createDefaultFunctionApi(apiUrl, repo, getToken) {
3389
3549
  const opts = () => ({ token: getToken() });
3390
3550
  return {
@@ -3400,8 +3560,8 @@ function createDefaultFunctionApi(apiUrl, repo, getToken) {
3400
3560
  }
3401
3561
  };
3402
3562
  }
3403
- const eastTypeEqual = equalFor(EastTypeType);
3404
- function signatureOfHandleType(handleType) {
3563
+ const eastTypeEqual$1 = equalFor(EastTypeType);
3564
+ function signatureOfFuncHandleType(handleType) {
3405
3565
  var _a2, _b2;
3406
3566
  if (handleType.type !== "Struct") {
3407
3567
  throw new Error(`Func.bind: handle type must be a Struct; got ${handleType.type}`);
@@ -3460,10 +3620,10 @@ function errorOfExecuteResult(result) {
3460
3620
  stderr: result.stderr
3461
3621
  };
3462
3622
  default:
3463
- return transportError(`unexpected outcome "${outcome.type}"`);
3623
+ return transportError$1(`unexpected outcome "${outcome.type}"`);
3464
3624
  }
3465
3625
  }
3466
- function transportError(message) {
3626
+ function transportError$1(message) {
3467
3627
  return {
3468
3628
  kind: variant("transport", { message }),
3469
3629
  message,
@@ -3474,20 +3634,16 @@ function transportError(message) {
3474
3634
  function funcChannelKey(workspace, name) {
3475
3635
  return `func:${workspace}:${name}`;
3476
3636
  }
3477
- class FuncRuntime {
3637
+ class FuncRuntime extends TrackedChannelStore {
3478
3638
  constructor() {
3639
+ super(...arguments);
3479
3640
  __publicField(this, "api", null);
3480
3641
  __publicField(this, "workspace", null);
3481
- __publicField(this, "entries", /* @__PURE__ */ new Map());
3482
3642
  // Signature lists are fetched once per workspace and cached.
3483
3643
  __publicField(this, "signatureLists", /* @__PURE__ */ new Map());
3484
- // Subscription shim — same contract (and the same stale-disposer
3485
- // guard) as the dataset cache's.
3486
- __publicField(this, "keySubscribers", /* @__PURE__ */ new Map());
3487
- __publicField(this, "keyVersions", /* @__PURE__ */ new Map());
3488
- // Reactive tracking — read closures push their channel keys here while
3489
- // a Reactive.Root render is being tracked.
3490
- __publicField(this, "trackingContext", null);
3644
+ }
3645
+ createEntry() {
3646
+ return { status: "idle", launchSeq: 0 };
3491
3647
  }
3492
3648
  // ----- wiring ----------------------------------------------------------
3493
3649
  /** Install the API adapter + workspace — called by the React provider
@@ -3500,59 +3656,8 @@ class FuncRuntime {
3500
3656
  clear() {
3501
3657
  this.api = null;
3502
3658
  this.workspace = null;
3503
- this.entries.clear();
3659
+ this.clearChannels();
3504
3660
  this.signatureLists.clear();
3505
- this.keySubscribers.clear();
3506
- this.keyVersions.clear();
3507
- }
3508
- // ----- reactive tracking ----------------------------------------------
3509
- enableTracking() {
3510
- this.trackingContext = /* @__PURE__ */ new Set();
3511
- return this.trackingContext;
3512
- }
3513
- disableTracking() {
3514
- const keys = this.trackingContext ? [...this.trackingContext] : [];
3515
- this.trackingContext = null;
3516
- return keys;
3517
- }
3518
- isTracking() {
3519
- return this.trackingContext !== null;
3520
- }
3521
- track(key) {
3522
- var _a2;
3523
- (_a2 = this.trackingContext) == null ? void 0 : _a2.add(key);
3524
- }
3525
- // ----- subscription shim ------------------------------------------------
3526
- subscribe(key, callback) {
3527
- let subs = this.keySubscribers.get(key);
3528
- if (!subs) {
3529
- subs = /* @__PURE__ */ new Set();
3530
- this.keySubscribers.set(key, subs);
3531
- }
3532
- subs.add(callback);
3533
- return () => {
3534
- subs.delete(callback);
3535
- if (subs.size === 0 && this.keySubscribers.get(key) === subs) {
3536
- this.keySubscribers.delete(key);
3537
- }
3538
- };
3539
- }
3540
- getKeyVersion(key) {
3541
- return this.keyVersions.get(key) ?? 0;
3542
- }
3543
- notify(key) {
3544
- this.keyVersions.set(key, (this.keyVersions.get(key) ?? 0) + 1);
3545
- const subs = this.keySubscribers.get(key);
3546
- if (subs) for (const cb of [...subs]) cb();
3547
- }
3548
- // ----- registry ----------------------------------------------------------
3549
- entry(key) {
3550
- let entry = this.entries.get(key);
3551
- if (!entry) {
3552
- entry = { status: "idle", launchSeq: 0 };
3553
- this.entries.set(key, entry);
3554
- }
3555
- return entry;
3556
3661
  }
3557
3662
  /** The deployed signature list for a workspace (fetched once). */
3558
3663
  signatures(workspace) {
@@ -3575,7 +3680,7 @@ class FuncRuntime {
3575
3680
  try {
3576
3681
  list = await this.signatures(workspace);
3577
3682
  } catch (err) {
3578
- return transportError(`failed to list workspace functions: ${err instanceof Error ? err.message : String(err)}`);
3683
+ return transportError$1(`failed to list workspace functions: ${err instanceof Error ? err.message : String(err)}`);
3579
3684
  }
3580
3685
  const deployed = list.find((f2) => f2.name === name);
3581
3686
  if (!deployed) {
@@ -3586,8 +3691,8 @@ class FuncRuntime {
3586
3691
  stderr: ""
3587
3692
  };
3588
3693
  }
3589
- const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual(t4, sig.inputs[i]));
3590
- const outputMatches = eastTypeEqual(deployed.outputType, sig.output);
3694
+ const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual$1(t4, sig.inputs[i]));
3695
+ const outputMatches = eastTypeEqual$1(deployed.outputType, sig.output);
3591
3696
  if (!inputsMatch || !outputMatches) {
3592
3697
  const message = `signature mismatch for "${name}": bound (${sig.inputs.length} inputs) disagrees with the deployed package`;
3593
3698
  return {
@@ -3602,17 +3707,7 @@ class FuncRuntime {
3602
3707
  // ----- closure semantics -------------------------------------------------
3603
3708
  launch(workspace, name, sig, args) {
3604
3709
  const key = funcChannelKey(workspace, name);
3605
- const entry = this.entry(key);
3606
- entry.launchSeq += 1;
3607
- const mySeq = entry.launchSeq;
3608
- entry.status = "running";
3609
- this.notify(key);
3610
- const settle = (mutate) => {
3611
- const current = this.entries.get(key);
3612
- if (!current || current.launchSeq !== mySeq) return;
3613
- mutate(current);
3614
- this.notify(key);
3615
- };
3710
+ const { settle } = this.beginLaunch(key);
3616
3711
  void (async () => {
3617
3712
  const invalid = await this.validate(workspace, name, sig);
3618
3713
  if (invalid) {
@@ -3626,7 +3721,7 @@ class FuncRuntime {
3626
3721
  if (!api3) {
3627
3722
  settle((e3) => {
3628
3723
  e3.status = "failed";
3629
- e3.error = transportError("no FunctionApi installed");
3724
+ e3.error = transportError$1("no FunctionApi installed");
3630
3725
  });
3631
3726
  return;
3632
3727
  }
@@ -3637,7 +3732,7 @@ class FuncRuntime {
3637
3732
  } catch (err) {
3638
3733
  settle((e3) => {
3639
3734
  e3.status = "failed";
3640
- e3.error = transportError(err instanceof Error ? err.message : String(err));
3735
+ e3.error = transportError$1(err instanceof Error ? err.message : String(err));
3641
3736
  });
3642
3737
  return;
3643
3738
  }
@@ -3648,7 +3743,7 @@ class FuncRuntime {
3648
3743
  } catch (err) {
3649
3744
  settle((e3) => {
3650
3745
  e3.status = "failed";
3651
- e3.error = transportError(`failed to decode result: ${err instanceof Error ? err.message : String(err)}`);
3746
+ e3.error = transportError$1(`failed to decode result: ${err instanceof Error ? err.message : String(err)}`);
3652
3747
  });
3653
3748
  return;
3654
3749
  }
@@ -3668,7 +3763,7 @@ class FuncRuntime {
3668
3763
  }
3669
3764
  /** Build the handle value for one `Func.bind` platform evaluation. */
3670
3765
  buildHandle(handleType, name) {
3671
- const sig = signatureOfHandleType(handleType);
3766
+ const sig = signatureOfFuncHandleType(handleType);
3672
3767
  const runtime = this;
3673
3768
  const resolveWorkspace = () => {
3674
3769
  if (!runtime.workspace) {
@@ -3706,12 +3801,8 @@ class FuncRuntime {
3706
3801
  return entry.status === "running";
3707
3802
  },
3708
3803
  cancel: () => {
3709
- const { key, entry } = channel();
3710
- if (entry.status === "running") {
3711
- entry.launchSeq += 1;
3712
- entry.status = "cancelled";
3713
- runtime.notify(key);
3714
- }
3804
+ const { key } = channel();
3805
+ runtime.cancelChannel(key);
3715
3806
  return null;
3716
3807
  },
3717
3808
  binding: { name }
@@ -7451,6 +7542,430 @@ var faCircleInfo = {
7451
7542
  iconName: "circle-info",
7452
7543
  icon: [512, 512, ["info-circle"], "f05a", "M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512zM224 160a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm-8 64l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24z"]
7453
7544
  };
7545
+ function createDefaultRecordApi(apiUrl, repo, getToken) {
7546
+ const opts = () => ({ token: getToken() });
7547
+ return {
7548
+ async describe(workspace, record) {
7549
+ return workspaceRecordDescribe(apiUrl, repo, workspace, record, opts());
7550
+ },
7551
+ async mutate(workspace, record, mutation, req) {
7552
+ return workspaceRecordMutate(apiUrl, repo, workspace, record, mutation, {
7553
+ args: req.args,
7554
+ actor: none$1,
7555
+ limits: none$1
7556
+ }, opts());
7557
+ },
7558
+ async history(workspace, record, limit2, from2) {
7559
+ return workspaceRecordHistory(apiUrl, repo, workspace, record, limit2, opts(), from2);
7560
+ }
7561
+ };
7562
+ }
7563
+ const eastTypeEqual = equalFor(EastTypeType);
7564
+ const RESERVED_MUTATE_FIELDS = /* @__PURE__ */ new Set(["pending", "status", "error", "cancel"]);
7565
+ function signatureOfRecordHandleType(handleType) {
7566
+ var _a2, _b2;
7567
+ if (handleType.type !== "Struct") {
7568
+ throw new Error(`Record.bind: handle type must be a Struct; got ${handleType.type}`);
7569
+ }
7570
+ const fields = handleType.value;
7571
+ const read = (_a2 = fields.find((f2) => f2.name === "read")) == null ? void 0 : _a2.type;
7572
+ const mutate = (_b2 = fields.find((f2) => f2.name === "mutate")) == null ? void 0 : _b2.type;
7573
+ if ((read == null ? void 0 : read.type) !== "Function") {
7574
+ throw new Error("Record.bind: handle type is missing its read closure");
7575
+ }
7576
+ if ((mutate == null ? void 0 : mutate.type) !== "Struct") {
7577
+ throw new Error("Record.bind: handle type is missing its mutate struct");
7578
+ }
7579
+ const stateType = read.value.output;
7580
+ const mutations = /* @__PURE__ */ new Map();
7581
+ for (const field of mutate.value) {
7582
+ if (RESERVED_MUTATE_FIELDS.has(field.name)) continue;
7583
+ if (field.type.type !== "Function") {
7584
+ throw new Error(`Record.bind: mutate field "${field.name}" must be a closure`);
7585
+ }
7586
+ mutations.set(field.name, field.type.value.inputs);
7587
+ }
7588
+ return { stateType, mutations };
7589
+ }
7590
+ function errorOfMutationResult(result) {
7591
+ const outcome = result.outcome;
7592
+ switch (outcome.type) {
7593
+ case "invalid":
7594
+ return { kind: variant("invalid", { message: outcome.value.message }), message: outcome.value.message, stderr: "" };
7595
+ case "failed":
7596
+ return {
7597
+ kind: variant("failed", { exitCode: outcome.value.exitCode }),
7598
+ message: `reducer exited with code ${outcome.value.exitCode}`,
7599
+ stderr: outcome.value.stderr
7600
+ };
7601
+ case "too_large":
7602
+ return {
7603
+ kind: variant("too_large", { bytes: outcome.value.bytes, limit: outcome.value.limit }),
7604
+ message: `new state too large (${outcome.value.bytes} bytes, limit ${outcome.value.limit})`,
7605
+ stderr: outcome.value.stderr
7606
+ };
7607
+ case "timed_out":
7608
+ return {
7609
+ kind: variant("timed_out", { ms: outcome.value.ms }),
7610
+ message: `mutation timed out after ${outcome.value.ms}ms (server deadline)`,
7611
+ stderr: outcome.value.stderr
7612
+ };
7613
+ case "conflict":
7614
+ return {
7615
+ kind: variant("conflict", { attempts: outcome.value.attempts }),
7616
+ message: `compare-and-swap conflicted after ${outcome.value.attempts} attempts; try again`,
7617
+ stderr: ""
7618
+ };
7619
+ default:
7620
+ return transportError(`unexpected outcome "${outcome.type}"`);
7621
+ }
7622
+ }
7623
+ function transportError(message) {
7624
+ return { kind: variant("transport", { message }), message, stderr: "" };
7625
+ }
7626
+ function recordPath(name) {
7627
+ return [variant("field", "records"), variant("field", name)];
7628
+ }
7629
+ function recordChannelKey(workspace, name) {
7630
+ return `record:${workspace}:${name}`;
7631
+ }
7632
+ class RecordRuntime extends TrackedChannelStore {
7633
+ constructor() {
7634
+ super(...arguments);
7635
+ __publicField(this, "api", null);
7636
+ __publicField(this, "cache", null);
7637
+ __publicField(this, "workspace", null);
7638
+ // Describe signatures, fetched once per (workspace, record), failures evicted.
7639
+ __publicField(this, "signatureCache", /* @__PURE__ */ new Map());
7640
+ // History per channel, fetched once + refreshed on commit.
7641
+ __publicField(this, "histories", /* @__PURE__ */ new Map());
7642
+ __publicField(this, "historyInFlight", /* @__PURE__ */ new Set());
7643
+ // Channels whose last history fetch failed. Guards `history()` from
7644
+ // re-issuing the request on every render (a failing endpoint would
7645
+ // otherwise spin); cleared on the next commit / on clear() so a later
7646
+ // mutation retries.
7647
+ __publicField(this, "historyFailed", /* @__PURE__ */ new Set());
7648
+ }
7649
+ createEntry() {
7650
+ return { status: "idle", launchSeq: 0 };
7651
+ }
7652
+ // ----- wiring ----------------------------------------------------------
7653
+ /** Install the API adapter + dataset cache + workspace — called by the
7654
+ * React provider (or a test/showcase harness) before any handle is used. */
7655
+ initialize(api3, cache3, workspace) {
7656
+ this.api = api3;
7657
+ this.cache = cache3;
7658
+ this.workspace = workspace;
7659
+ }
7660
+ /** Tear down the adapter and all record state. */
7661
+ clear() {
7662
+ this.api = null;
7663
+ this.cache = null;
7664
+ this.workspace = null;
7665
+ this.clearChannels();
7666
+ this.signatureCache.clear();
7667
+ this.histories.clear();
7668
+ this.historyInFlight.clear();
7669
+ this.historyFailed.clear();
7670
+ }
7671
+ resolveWorkspace() {
7672
+ if (!this.workspace) {
7673
+ throw new Error("Record.bind: no workspace configured — mount a provider (or call initializeRecordApi) first");
7674
+ }
7675
+ return this.workspace;
7676
+ }
7677
+ /** The deployed describe signature for a record (fetched once, evicted on failure). */
7678
+ signature(workspace, record) {
7679
+ const sigKey = `${workspace}:${record}`;
7680
+ let promise4 = this.signatureCache.get(sigKey);
7681
+ if (!promise4) {
7682
+ const api3 = this.api;
7683
+ if (!api3) return Promise.reject(new Error("Record.bind: no RecordApi installed"));
7684
+ promise4 = api3.describe(workspace, record).catch((err) => {
7685
+ this.signatureCache.delete(sigKey);
7686
+ throw err;
7687
+ });
7688
+ this.signatureCache.set(sigKey, promise4);
7689
+ }
7690
+ return promise4;
7691
+ }
7692
+ /** Validate a mutation's declared arg types against the deployed signature. */
7693
+ async validate(workspace, record, mutation, argTypes) {
7694
+ let sig;
7695
+ try {
7696
+ sig = await this.signature(workspace, record);
7697
+ } catch (err) {
7698
+ return transportError(`failed to describe record "${record}": ${err instanceof Error ? err.message : String(err)}`);
7699
+ }
7700
+ const deployed = sig.mutations.find((m2) => m2.name === mutation);
7701
+ if (!deployed) {
7702
+ const message = `no mutation "${mutation}" on record "${record}" in the deployed package`;
7703
+ return { kind: variant("invalid", { message }), message, stderr: "" };
7704
+ }
7705
+ const argsMatch = deployed.argTypes.length === argTypes.length && deployed.argTypes.every((t4, i) => eastTypeEqual(t4, argTypes[i]));
7706
+ if (!argsMatch) {
7707
+ const message = `signature mismatch for mutation "${mutation}": bound (${argTypes.length} args) disagrees with the deployed package`;
7708
+ return { kind: variant("invalid", { message }), message, stderr: "" };
7709
+ }
7710
+ return null;
7711
+ }
7712
+ /** Fetch a record's history once per channel (deduped); notify on settle. */
7713
+ fetchHistory(workspace, record, key) {
7714
+ if (this.historyInFlight.has(key)) return;
7715
+ this.historyInFlight.add(key);
7716
+ void (async () => {
7717
+ const api3 = this.api;
7718
+ try {
7719
+ if (!api3) throw new Error("no RecordApi installed");
7720
+ const result = await api3.history(workspace, record, void 0);
7721
+ this.histories.set(key, result.commits);
7722
+ this.historyFailed.delete(key);
7723
+ } catch {
7724
+ this.historyFailed.add(key);
7725
+ } finally {
7726
+ this.historyInFlight.delete(key);
7727
+ this.notify(key);
7728
+ }
7729
+ })();
7730
+ }
7731
+ // ----- closure semantics -------------------------------------------------
7732
+ launchMutation(workspace, record, mutation, argTypes, args) {
7733
+ const key = recordChannelKey(workspace, record);
7734
+ const { entry, settle } = this.beginLaunch(key, (e3) => {
7735
+ delete e3.error;
7736
+ });
7737
+ const run = (async () => {
7738
+ var _a2;
7739
+ const invalid = await this.validate(workspace, record, mutation, argTypes);
7740
+ if (invalid) {
7741
+ settle((e3) => {
7742
+ e3.status = "failed";
7743
+ e3.error = invalid;
7744
+ });
7745
+ return;
7746
+ }
7747
+ const api3 = this.api;
7748
+ if (!api3) {
7749
+ settle((e3) => {
7750
+ e3.status = "failed";
7751
+ e3.error = transportError("no RecordApi installed");
7752
+ });
7753
+ return;
7754
+ }
7755
+ let result;
7756
+ try {
7757
+ const encoded = args.map((arg, i) => encodeBeast2For(argTypes[i])(arg));
7758
+ result = await api3.mutate(workspace, record, mutation, { args: encoded });
7759
+ } catch (err) {
7760
+ settle((e3) => {
7761
+ e3.status = "failed";
7762
+ e3.error = transportError(err instanceof Error ? err.message : String(err));
7763
+ });
7764
+ return;
7765
+ }
7766
+ if (result.outcome.type === "committed") {
7767
+ settle((e3) => {
7768
+ e3.status = "committed";
7769
+ delete e3.error;
7770
+ });
7771
+ void ((_a2 = this.cache) == null ? void 0 : _a2.refresh(workspace));
7772
+ this.histories.delete(key);
7773
+ this.historyFailed.delete(key);
7774
+ this.notify(key);
7775
+ } else {
7776
+ settle((e3) => {
7777
+ e3.status = "failed";
7778
+ e3.error = errorOfMutationResult(result);
7779
+ });
7780
+ }
7781
+ })();
7782
+ entry.inflight = run;
7783
+ void run.finally(() => {
7784
+ const current = this.entries.get(key);
7785
+ if (current && current.inflight === run) delete current.inflight;
7786
+ });
7787
+ }
7788
+ /** Build the handle value for one `Record.bind` platform evaluation. */
7789
+ buildHandle(handleType, name) {
7790
+ const sig = signatureOfRecordHandleType(handleType);
7791
+ const decodeState = decodeBeast2For(sig.stateType);
7792
+ const runtime = this;
7793
+ const path2 = recordPath(name);
7794
+ const requireCache = () => {
7795
+ if (!runtime.cache) {
7796
+ throw new Error("Record.bind: no dataset cache configured — mount a provider first");
7797
+ }
7798
+ return runtime.cache;
7799
+ };
7800
+ const mutate = {
7801
+ pending: () => {
7802
+ const ws = runtime.resolveWorkspace();
7803
+ const key = recordChannelKey(ws, name);
7804
+ runtime.track(key);
7805
+ return runtime.entry(key).status === "running";
7806
+ },
7807
+ status: () => {
7808
+ const ws = runtime.resolveWorkspace();
7809
+ const key = recordChannelKey(ws, name);
7810
+ runtime.track(key);
7811
+ return variant(runtime.entry(key).status, null);
7812
+ },
7813
+ error: () => {
7814
+ const ws = runtime.resolveWorkspace();
7815
+ const key = recordChannelKey(ws, name);
7816
+ runtime.track(key);
7817
+ const entry = runtime.entry(key);
7818
+ return entry.status === "failed" && entry.error !== void 0 ? some(entry.error) : none$1;
7819
+ },
7820
+ cancel: () => {
7821
+ const ws = runtime.resolveWorkspace();
7822
+ runtime.cancelChannel(recordChannelKey(ws, name), (e3) => {
7823
+ delete e3.error;
7824
+ delete e3.inflight;
7825
+ });
7826
+ return null;
7827
+ }
7828
+ };
7829
+ for (const [mutationName, argTypes] of sig.mutations) {
7830
+ mutate[mutationName] = (...args) => {
7831
+ runtime.launchMutation(runtime.resolveWorkspace(), name, mutationName, argTypes, args);
7832
+ return null;
7833
+ };
7834
+ }
7835
+ return {
7836
+ read: () => {
7837
+ const ws = runtime.resolveWorkspace();
7838
+ trackDatasetPath(ws, path2);
7839
+ const bytes = requireCache().read(ws, path2);
7840
+ if (bytes === void 0) {
7841
+ throw new Error(`Record.bind: record state not loaded: ${datasetCacheKey(ws, path2)} (it should be preloaded via the UI task manifest)`);
7842
+ }
7843
+ return decodeState(bytes);
7844
+ },
7845
+ status: () => {
7846
+ const ws = runtime.resolveWorkspace();
7847
+ trackDatasetPath(ws, path2);
7848
+ return requireCache().getStatus(ws, path2);
7849
+ },
7850
+ history: () => {
7851
+ const ws = runtime.resolveWorkspace();
7852
+ const key = recordChannelKey(ws, name);
7853
+ runtime.track(key);
7854
+ const cached = runtime.histories.get(key);
7855
+ if (cached === void 0) {
7856
+ if (!runtime.historyFailed.has(key)) runtime.fetchHistory(ws, name, key);
7857
+ return none$1;
7858
+ }
7859
+ return some(cached);
7860
+ },
7861
+ mutate,
7862
+ start: () => {
7863
+ const ws = runtime.resolveWorkspace();
7864
+ const cache3 = requireCache();
7865
+ const inflight = runtime.entry(recordChannelKey(ws, name)).inflight;
7866
+ void Promise.resolve(inflight).catch(() => void 0).then(() => cache3.launchDataflow(ws)).catch(() => void 0);
7867
+ return null;
7868
+ },
7869
+ binding: { name, mutations: [...sig.mutations.keys()] }
7870
+ };
7871
+ }
7872
+ // ----- platform building -------------------------------------------------
7873
+ /** Build a `Record.bind` PlatformFunction bound to this runtime. Pass
7874
+ * `allowed=null` for an unscoped impl; pass a Set of record names for
7875
+ * manifest scoping. */
7876
+ buildPlatform(allowed) {
7877
+ return recordBindPlatformFn.implement(
7878
+ (handleType) => (nameArg) => {
7879
+ const name = nameArg;
7880
+ if (allowed && !allowed.has(name)) {
7881
+ throw new Error(
7882
+ `Record.bind: record "${name}" is not in this UI task's manifest — bind it in the task body so derivation records it`
7883
+ );
7884
+ }
7885
+ return this.buildHandle(handleType, name);
7886
+ }
7887
+ );
7888
+ }
7889
+ }
7890
+ const defaultRecordRuntime = new RecordRuntime();
7891
+ function initializeRecordApi(api3, cache3, workspace) {
7892
+ defaultRecordRuntime.initialize(api3, cache3, workspace);
7893
+ }
7894
+ function clearRecordApi() {
7895
+ defaultRecordRuntime.clear();
7896
+ }
7897
+ const RecordPlatform = [defaultRecordRuntime.buildPlatform(null)];
7898
+ function createScopedRecordPlatform(records) {
7899
+ return [defaultRecordRuntime.buildPlatform(new Set(records))];
7900
+ }
7901
+ function createInMemoryRecordApi(cache3, workspace, defs2) {
7902
+ const compiled = /* @__PURE__ */ new Map();
7903
+ for (const def of defs2) {
7904
+ const stateType = toEastTypeValue(def.stateType);
7905
+ const mutations = /* @__PURE__ */ new Map();
7906
+ for (const m2 of def.mutations) {
7907
+ mutations.set(m2.name, { argTypes: m2.argTypes.map((t4) => toEastTypeValue(t4)), reduce: m2.reduce });
7908
+ }
7909
+ const genesis = {
7910
+ hash: `${def.name}-0`.padEnd(64, "0"),
7911
+ parent: none$1,
7912
+ state: `${def.name}-state-0`.padEnd(64, "0"),
7913
+ mutation: "$init",
7914
+ actor: "memory",
7915
+ at: /* @__PURE__ */ new Date(0)
7916
+ };
7917
+ compiled.set(def.name, { stateType, mutations, commits: [genesis], seq: 0 });
7918
+ void cache3.write(workspace, recordPath(def.name), encodeBeast2For(stateType)(def.initial));
7919
+ }
7920
+ return {
7921
+ async describe(_ws, record) {
7922
+ const c2 = compiled.get(record);
7923
+ if (!c2) throw new Error(`no in-memory record "${record}"`);
7924
+ return {
7925
+ name: record,
7926
+ mutations: [...c2.mutations].map(([name, m2]) => ({ name, argTypes: m2.argTypes }))
7927
+ };
7928
+ },
7929
+ async mutate(ws, record, mutation, req) {
7930
+ const c2 = compiled.get(record);
7931
+ if (!c2) throw new Error(`no in-memory record "${record}"`);
7932
+ const m2 = c2.mutations.get(mutation);
7933
+ if (!m2) return { outcome: variant("invalid", { message: `no mutation "${mutation}"` }) };
7934
+ const current = cache3.read(ws, recordPath(record));
7935
+ const state = current !== void 0 ? decodeBeast2For(c2.stateType)(current) : void 0;
7936
+ const args = req.args.map((bytes, i) => decodeBeast2For(m2.argTypes[i])(bytes));
7937
+ const next2 = m2.reduce(state, ...args);
7938
+ await cache3.write(ws, recordPath(record), encodeBeast2For(c2.stateType)(next2));
7939
+ c2.seq += 1;
7940
+ const hash2 = `${record}-${c2.seq}`.padEnd(64, "0");
7941
+ c2.commits.unshift({
7942
+ hash: hash2,
7943
+ parent: some(c2.commits[0].hash),
7944
+ state: `${record}-state-${c2.seq}`.padEnd(64, "0"),
7945
+ mutation,
7946
+ actor: "memory",
7947
+ at: /* @__PURE__ */ new Date(0)
7948
+ });
7949
+ return { outcome: variant("committed", { commitHash: hash2, stateHash: `${record}-state-${c2.seq}`.padEnd(64, "0") }) };
7950
+ },
7951
+ async history(_ws, record, limit2) {
7952
+ const c2 = compiled.get(record);
7953
+ if (!c2) throw new Error(`no in-memory record "${record}"`);
7954
+ const commits = limit2 !== void 0 ? c2.commits.slice(0, limit2) : c2.commits;
7955
+ return { commits };
7956
+ }
7957
+ };
7958
+ }
7959
+ registerReactiveTracker({
7960
+ id: "record-bind",
7961
+ enableTracking: () => defaultRecordRuntime.enableTracking(),
7962
+ disableTracking: () => defaultRecordRuntime.disableTracking(),
7963
+ getStore: () => ({
7964
+ subscribe: (key, cb) => defaultRecordRuntime.subscribe(key, cb),
7965
+ getKeyVersion: (key) => defaultRecordRuntime.getKeyVersion(key)
7966
+ })
7967
+ });
7968
+ registerPlatformImplementation(RecordPlatform);
7454
7969
  const E3ConfigContext = createContext(null);
7455
7970
  function E3Provider({ children: children2, config: config2, queryClient: externalClient }) {
7456
7971
  const client = useMemo(
@@ -7501,11 +8016,21 @@ function ReactiveDatasetProvider({
7501
8016
  );
7502
8017
  }
7503
8018
  }, [e3.apiUrl, e3.repo, e3.workspace]);
8019
+ useMemo(() => {
8020
+ if (e3.workspace !== void 0) {
8021
+ initializeRecordApi(
8022
+ createDefaultRecordApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8023
+ cache3,
8024
+ e3.workspace
8025
+ );
8026
+ }
8027
+ }, [e3.apiUrl, e3.repo, e3.workspace, cache3]);
7504
8028
  useEffect(() => {
7505
8029
  return () => {
7506
8030
  const ws = cache3.getConfig().workspace;
7507
8031
  clearReactiveDatasetCache();
7508
8032
  clearFunctionApi();
8033
+ clearRecordApi();
7509
8034
  cache3.destroy();
7510
8035
  if (ws) clearBindingRegistry(ws);
7511
8036
  else clearBindingRegistry();
@@ -60713,30 +61238,37 @@ function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
60713
61238
  ] });
60714
61239
  }
60715
61240
  function ValidatePanel({ vm, barList }) {
60716
- const treatedPct = Math.round(vm.primary.treatedShare * 100);
61241
+ const stat = useSlotRecipe({ key: "stat" })({ size: "lg" });
61242
+ const meter = useSlotRecipe({ key: "segmentedMeter" })({});
60717
61243
  return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
60718
61244
  /* @__PURE__ */ jsx$1(Cap, { help: "tab_validate", children: vm.headline }),
60719
61245
  /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
60720
- /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
60721
- /* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsx$1(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
60722
- 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, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: "brand.solid", children: vm.primary.nTotal.toLocaleString() })
61246
+ /* @__PURE__ */ jsxs(Box, { css: stat.root, children: [
61247
+ /* @__PURE__ */ jsx$1(Text, { css: stat.label, children: /* @__PURE__ */ jsx$1(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
61248
+ 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() })
60723
61249
  ] }),
60724
- /* @__PURE__ */ jsxs(Box, { flex: "1", minW: "220px", children: [
60725
- /* @__PURE__ */ jsx$1(Cap, { help: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }),
60726
- /* @__PURE__ */ jsxs(Box, { display: "flex", h: "10px", borderRadius: "full", overflow: "hidden", borderWidth: "1px", borderColor: "border.subtle", children: [
60727
- /* @__PURE__ */ jsx$1(Box, { bg: "brand.solid", width: `${treatedPct}%` }),
60728
- /* @__PURE__ */ jsx$1(Box, { bg: "bg.emphasized", flex: "1" })
61250
+ /* @__PURE__ */ jsxs(Box, { css: meter.root, flex: "1", minW: "220px", children: [
61251
+ /* @__PURE__ */ jsx$1(Text, { css: meter.label, children: /* @__PURE__ */ jsx$1(Help, { id: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }) }),
61252
+ /* @__PURE__ */ jsxs(Box, { css: meter.track, children: [
61253
+ /* @__PURE__ */ jsx$1(Box, { css: meter.segment, flex: vm.primary.treatedShare, bg: "brand.solid" }),
61254
+ /* @__PURE__ */ jsx$1(Box, { css: meter.segment, flex: 1 - vm.primary.treatedShare, bg: "bg.emphasized" })
60729
61255
  ] }),
60730
- /* @__PURE__ */ jsxs(Box, { display: "flex", justifyContent: "space-between", mt: "1.5", children: [
60731
- /* @__PURE__ */ jsxs(Text, { textStyle: "caption", children: [
60732
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.fg", fontWeight: "semibold", children: vm.primary.nTreated.toLocaleString() }),
60733
- " ",
60734
- vm.holdback ? "treated" : "get it"
61256
+ /* @__PURE__ */ jsxs(Box, { css: meter.keyRow, justifyContent: "space-between", children: [
61257
+ /* @__PURE__ */ jsxs(Box, { css: meter.keyItem, children: [
61258
+ /* @__PURE__ */ jsx$1(Box, { css: meter.keyDot, bg: "brand.solid" }),
61259
+ /* @__PURE__ */ jsxs("span", { children: [
61260
+ /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: vm.primary.nTreated.toLocaleString() }),
61261
+ " ",
61262
+ vm.holdback ? "treated" : "get it"
61263
+ ] })
60735
61264
  ] }),
60736
- /* @__PURE__ */ jsxs(Text, { textStyle: "caption", children: [
60737
- /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: vm.primary.nControl.toLocaleString() }),
60738
- " ",
60739
- vm.holdback ? "held back" : "left alone"
61265
+ /* @__PURE__ */ jsxs(Box, { css: meter.keyItem, children: [
61266
+ /* @__PURE__ */ jsx$1(Box, { css: meter.keyDot, bg: "bg.emphasized" }),
61267
+ /* @__PURE__ */ jsxs("span", { children: [
61268
+ /* @__PURE__ */ jsx$1(Text, { as: "span", css: meter.valueText, children: vm.primary.nControl.toLocaleString() }),
61269
+ " ",
61270
+ vm.holdback ? "held back" : "left alone"
61271
+ ] })
60740
61272
  ] })
60741
61273
  ] })
60742
61274
  ] })
@@ -63028,7 +63560,7 @@ const UITaskPreview = memo(function UITaskPreview2({
63028
63560
  const manifest = useMemo(() => {
63029
63561
  if (!details || !isUI) return null;
63030
63562
  const meta3 = getTaskMetadata(details);
63031
- return meta3 ? decodeManifest(meta3) : { paths: [], functions: [] };
63563
+ return meta3 ? decodeManifest(meta3) : { paths: [], functions: [], records: [] };
63032
63564
  }, [details, isUI]);
63033
63565
  const outputPath = details ? treePathToString$1(details.output) : null;
63034
63566
  const preloads = useMemo(
@@ -63041,6 +63573,7 @@ const UITaskPreview = memo(function UITaskPreview2({
63041
63573
  ...StateImpl,
63042
63574
  ...createScopedBindPlatform(manifest),
63043
63575
  ...createScopedFuncPlatform(manifest.functions),
63576
+ ...createScopedRecordPlatform(manifest.records),
63044
63577
  ...OverlayImpl
63045
63578
  ] : void 0,
63046
63579
  [manifest]
@@ -63639,6 +64172,8 @@ export {
63639
64172
  ReactiveDatasetCache,
63640
64173
  ReactiveDatasetLoader,
63641
64174
  ReactiveDatasetProvider,
64175
+ RecordPlatform,
64176
+ RecordRuntime,
63642
64177
  StagedStore,
63643
64178
  StatusDisplay,
63644
64179
  TaskPreview,
@@ -63650,17 +64185,22 @@ export {
63650
64185
  clearPendingWrites,
63651
64186
  clearReactiveDatasetCache,
63652
64187
  clearReactiveDatasetListCache,
64188
+ clearRecordApi,
63653
64189
  clearStagedStoreSingleton,
63654
64190
  createDefaultDatasetApi,
63655
64191
  createDefaultFunctionApi,
64192
+ createDefaultRecordApi,
63656
64193
  createInMemoryFunctionApi,
64194
+ createInMemoryRecordApi,
63657
64195
  createReactiveDatasetCache,
63658
64196
  createScopedBindPlatform,
63659
64197
  createScopedFuncPlatform,
64198
+ createScopedRecordPlatform,
63660
64199
  datasetCacheKey,
63661
64200
  datasetPathToString,
63662
64201
  defaultBindRuntime,
63663
64202
  defaultFuncRuntime,
64203
+ defaultRecordRuntime,
63664
64204
  disableBindingTracking,
63665
64205
  enableBindingTracking,
63666
64206
  formatApiError,
@@ -63673,12 +64213,14 @@ export {
63673
64213
  getTaskMetadata,
63674
64214
  initializeFunctionApi,
63675
64215
  initializeReactiveDatasetCache,
64216
+ initializeRecordApi,
63676
64217
  initializeStagedStore,
63677
64218
  isBindingTracking,
63678
64219
  onWriteError,
63679
64220
  preloadReactiveDatasetList,
63680
64221
  realClock,
63681
- signatureOfHandleType,
64222
+ recordChannelKey,
64223
+ signatureOfFuncHandleType,
63682
64224
  trackDatasetPath,
63683
64225
  useDataflowCancel,
63684
64226
  useDataflowExecute,