@elaraai/e3-ui-components 1.0.12 → 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/components/UITaskPreview.d.ts.map +1 -1
- package/dist/experiment/charts.d.ts +31 -2
- package/dist/experiment/charts.d.ts.map +1 -1
- package/dist/experiment/derive.d.ts +176 -74
- package/dist/experiment/derive.d.ts.map +1 -1
- package/dist/experiment/help-ui.d.ts +29 -0
- package/dist/experiment/help-ui.d.ts.map +1 -0
- package/dist/experiment/help.d.ts +319 -0
- package/dist/experiment/help.d.ts.map +1 -0
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/run-runtime.d.ts +9 -0
- package/dist/experiment/run-runtime.d.ts.map +1 -1
- package/dist/index.cjs +2001 -518
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2005 -522
- package/dist/index.js.map +1 -1
- package/dist/platform/bind-runtime.d.ts +3 -0
- package/dist/platform/bind-runtime.d.ts.map +1 -1
- package/dist/platform/dataset-hooks.d.ts.map +1 -1
- package/dist/platform/dataset-store.d.ts +24 -1
- package/dist/platform/dataset-store.d.ts.map +1 -1
- package/dist/platform/func-runtime.d.ts +20 -16
- package/dist/platform/func-runtime.d.ts.map +1 -1
- package/dist/platform/index.d.ts +2 -1
- package/dist/platform/index.d.ts.map +1 -1
- package/dist/platform/record-runtime.d.ts +127 -0
- package/dist/platform/record-runtime.d.ts.map +1 -0
- package/dist/platform/staged-store.d.ts +17 -1
- package/dist/platform/staged-store.d.ts.map +1 -1
- package/dist/platform/tracked-channel.d.ts +71 -0
- package/dist/platform/tracked-channel.d.ts.map +1 -0
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -18,15 +18,15 @@ 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,
|
|
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";
|
|
29
|
-
import { Box, Flex, Text, HStack, Input, useSlotRecipe, useRecipe, VStack, useToken, Drawer, Portal, Field, NativeSelect, Textarea, chakra, Tooltip, SegmentGroup, Menu, useChakraContext, Spinner, Code, Clipboard, IconButton, Badge, Button, Tabs, useTabs } from "@chakra-ui/react";
|
|
29
|
+
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, Badge, Button, Tabs, useTabs } from "@chakra-ui/react";
|
|
30
30
|
import { implementUIComponent, SliceEditPopover, SlicePredicateBuilder, formatPredicate, getSomeorUndefined, StateRuntime, buildSliceHandle, DEFAULT_SLICE_STATE, registerPlatformImplementation as registerPlatformImplementation$1, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, OverlayImpl, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
|
|
31
31
|
import { createPortal } from "react-dom";
|
|
32
32
|
import { sliceMatches } from "@elaraai/east-ui/internal";
|
|
@@ -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,
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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.
|
|
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
|
-
|
|
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$
|
|
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
|
-
|
|
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$
|
|
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
|
-
|
|
3166
|
-
cache3.
|
|
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
|
|
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
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
3710
|
-
|
|
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 }
|
|
@@ -7446,6 +7537,435 @@ var faArrowDown = {
|
|
|
7446
7537
|
iconName: "arrow-down",
|
|
7447
7538
|
icon: [384, 512, [8595], "f063", "M169.4 502.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 402.7 224 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 370.7-105.4-105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"]
|
|
7448
7539
|
};
|
|
7540
|
+
var faCircleInfo = {
|
|
7541
|
+
prefix: "fas",
|
|
7542
|
+
iconName: "circle-info",
|
|
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"]
|
|
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);
|
|
7449
7969
|
const E3ConfigContext = createContext(null);
|
|
7450
7970
|
function E3Provider({ children: children2, config: config2, queryClient: externalClient }) {
|
|
7451
7971
|
const client = useMemo(
|
|
@@ -7496,11 +8016,21 @@ function ReactiveDatasetProvider({
|
|
|
7496
8016
|
);
|
|
7497
8017
|
}
|
|
7498
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]);
|
|
7499
8028
|
useEffect(() => {
|
|
7500
8029
|
return () => {
|
|
7501
8030
|
const ws = cache3.getConfig().workspace;
|
|
7502
8031
|
clearReactiveDatasetCache();
|
|
7503
8032
|
clearFunctionApi();
|
|
8033
|
+
clearRecordApi();
|
|
7504
8034
|
cache3.destroy();
|
|
7505
8035
|
if (ws) clearBindingRegistry(ws);
|
|
7506
8036
|
else clearBindingRegistry();
|
|
@@ -21861,13 +22391,13 @@ var hashIterableInts = function hashIterableInts2(iterator) {
|
|
|
21861
22391
|
}
|
|
21862
22392
|
return hash2;
|
|
21863
22393
|
};
|
|
21864
|
-
var hashInt = function hashInt2(
|
|
22394
|
+
var hashInt = function hashInt2(num2) {
|
|
21865
22395
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED;
|
|
21866
|
-
return seed * K +
|
|
22396
|
+
return seed * K + num2 | 0;
|
|
21867
22397
|
};
|
|
21868
|
-
var hashIntAlt = function hashIntAlt2(
|
|
22398
|
+
var hashIntAlt = function hashIntAlt2(num2) {
|
|
21869
22399
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED_ALT;
|
|
21870
|
-
return (seed << 5) + seed +
|
|
22400
|
+
return (seed << 5) + seed + num2 | 0;
|
|
21871
22401
|
};
|
|
21872
22402
|
var combineHashes = function combineHashes2(hash1, hash2) {
|
|
21873
22403
|
return hash1 * 2097152 + hash2;
|
|
@@ -23454,7 +23984,7 @@ var max = function max2(arr2) {
|
|
|
23454
23984
|
}
|
|
23455
23985
|
return max5;
|
|
23456
23986
|
};
|
|
23457
|
-
var mean
|
|
23987
|
+
var mean = function mean2(arr2) {
|
|
23458
23988
|
var begin3 = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0;
|
|
23459
23989
|
var end3 = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : arr2.length;
|
|
23460
23990
|
var total = 0;
|
|
@@ -25767,7 +26297,7 @@ var getPreference = function getPreference2(S2, preference) {
|
|
|
25767
26297
|
if (preference === "median") {
|
|
25768
26298
|
p2 = median(S2);
|
|
25769
26299
|
} else if (preference === "mean") {
|
|
25770
|
-
p2 = mean
|
|
26300
|
+
p2 = mean(S2);
|
|
25771
26301
|
} else if (preference === "min") {
|
|
25772
26302
|
p2 = min(S2);
|
|
25773
26303
|
} else if (preference === "max") {
|
|
@@ -56740,7 +57270,7 @@ function useBindingValue(binding) {
|
|
|
56740
57270
|
return { value, mode, pending, mutate, commit, discard };
|
|
56741
57271
|
}
|
|
56742
57272
|
const IDLE = { call: () => {
|
|
56743
|
-
}, result: null, status: "idle", pending: false };
|
|
57273
|
+
}, result: null, status: "idle", pending: false, error: null };
|
|
56744
57274
|
function useFuncCall(name, inputs, output2) {
|
|
56745
57275
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
56746
57276
|
const ready4 = !!name && !!inputs && workspace !== "";
|
|
@@ -56763,15 +57293,22 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56763
57293
|
useCallback(() => channelKey ? defaultFuncRuntime.getKeyVersion(channelKey) : 0, [channelKey])
|
|
56764
57294
|
);
|
|
56765
57295
|
return useMemo(() => {
|
|
57296
|
+
var _a2;
|
|
56766
57297
|
if (!handle) return IDLE;
|
|
56767
57298
|
let result = null;
|
|
56768
57299
|
let status = "idle";
|
|
56769
57300
|
let pending = false;
|
|
57301
|
+
let error3 = null;
|
|
56770
57302
|
try {
|
|
56771
57303
|
const read = handle.read();
|
|
56772
57304
|
result = read.type === "some" ? read.value : null;
|
|
56773
57305
|
status = handle.status().type;
|
|
56774
57306
|
pending = handle.pending();
|
|
57307
|
+
const err = handle.error();
|
|
57308
|
+
if (err.type === "some") {
|
|
57309
|
+
const v3 = err.value;
|
|
57310
|
+
error3 = { kind: ((_a2 = v3.kind) == null ? void 0 : _a2.type) ?? "failed", message: v3.message ?? "", stdout: v3.stdout ?? "", stderr: v3.stderr ?? "" };
|
|
57311
|
+
}
|
|
56775
57312
|
} catch {
|
|
56776
57313
|
}
|
|
56777
57314
|
return { call: (...args) => {
|
|
@@ -56779,7 +57316,7 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56779
57316
|
handle.call(...args);
|
|
56780
57317
|
} catch {
|
|
56781
57318
|
}
|
|
56782
|
-
}, result, status, pending };
|
|
57319
|
+
}, result, status, pending, error: error3 };
|
|
56783
57320
|
}, [handle, version2]);
|
|
56784
57321
|
}
|
|
56785
57322
|
var propTypes = { exports: {} };
|
|
@@ -59023,6 +59560,370 @@ function Circle(_ref2) {
|
|
|
59023
59560
|
className: cx("visx-circle", className)
|
|
59024
59561
|
}, restProps));
|
|
59025
59562
|
}
|
|
59563
|
+
function fillHelp(entry, vars) {
|
|
59564
|
+
const sub = (s2) => s2.replace(/\{(\w+)\}/g, (m2, k2) => k2 in vars ? vars[k2] : m2);
|
|
59565
|
+
return {
|
|
59566
|
+
term: sub(entry.term),
|
|
59567
|
+
gist: sub(entry.gist),
|
|
59568
|
+
detail: sub(entry.detail),
|
|
59569
|
+
...entry.jargon !== void 0 ? { jargon: sub(entry.jargon) } : {}
|
|
59570
|
+
};
|
|
59571
|
+
}
|
|
59572
|
+
const HELP = {
|
|
59573
|
+
// ── Guidance mode (the toggle itself) ───────────────────────────────
|
|
59574
|
+
guidance: {
|
|
59575
|
+
term: "Guidance",
|
|
59576
|
+
gist: "Plain-language explainers for everything on this surface.",
|
|
59577
|
+
detail: "While this is on, hover any heading, value or chart label to see what it means — in business terms, with the statistical name underneath. Click to turn it off."
|
|
59578
|
+
},
|
|
59579
|
+
// ── Header ──────────────────────────────────────────────────────────
|
|
59580
|
+
header: {
|
|
59581
|
+
term: "The question",
|
|
59582
|
+
gist: "You’re asking whether {treatment} actually moved {outcome}.",
|
|
59583
|
+
detail: "Pick the lever you changed and the result you cared about, then hit Run — the surface answers whether the change is real and trustworthy.",
|
|
59584
|
+
jargon: "a binary-treatment causal effect"
|
|
59585
|
+
},
|
|
59586
|
+
// ── Set-up rail ─────────────────────────────────────────────────────
|
|
59587
|
+
step_treatment: {
|
|
59588
|
+
term: "What did you change?",
|
|
59589
|
+
gist: "The lever you’re testing — a yes/no flag on each {subject}.",
|
|
59590
|
+
detail: "It splits {subjects} into a “did” group and a “didn’t” group; the whole experiment compares those two.",
|
|
59591
|
+
jargon: "the treatment (binary)"
|
|
59592
|
+
},
|
|
59593
|
+
step_outcome: {
|
|
59594
|
+
term: "What did you want it to improve?",
|
|
59595
|
+
gist: "The result you’re hoping {treatment} moved.",
|
|
59596
|
+
detail: "A number measured on each {subject} (revenue, a duration, a score, …). The effect is the change in this we can credit to the lever.",
|
|
59597
|
+
jargon: "the outcome"
|
|
59598
|
+
},
|
|
59599
|
+
step_confounders: {
|
|
59600
|
+
term: "What else was different?",
|
|
59601
|
+
gist: "Things about the “did” group that could also explain the change.",
|
|
59602
|
+
detail: "If the {subjects} you treated already differed on something that matters — bigger, older, higher-value — that, not the lever, could be moving the result. We level the playing field on each of these so the comparison is fair. Add the ones you suspect.",
|
|
59603
|
+
jargon: "confounders / the backdoor adjustment set"
|
|
59604
|
+
},
|
|
59605
|
+
step_population: {
|
|
59606
|
+
term: "Which {subjects}?",
|
|
59607
|
+
gist: "Narrow the experiment to a slice of the {subjects}.",
|
|
59608
|
+
detail: "Filter to a subset you care about — a region, a period, a category — before measuring, so the answer is about the {subjects} that matter.",
|
|
59609
|
+
jargon: "population / cohort filter"
|
|
59610
|
+
},
|
|
59611
|
+
adv_method: {
|
|
59612
|
+
term: "How to compare",
|
|
59613
|
+
gist: "The maths used to make the like-for-like comparison.",
|
|
59614
|
+
detail: "“Regression” fits a line through the confounders; “re-weighting” up-weights rare look-alikes. They usually agree — if they don’t, treat the result with care.",
|
|
59615
|
+
jargon: "estimator: linear regression vs propensity-score weighting"
|
|
59616
|
+
},
|
|
59617
|
+
adv_estimand: {
|
|
59618
|
+
term: "Answer for",
|
|
59619
|
+
gist: "Who the effect is reported for.",
|
|
59620
|
+
detail: "“All” averages over everyone; “only treated” asks what the lever did for the {subjects} you actually treated — useful when you’d only ever apply it to them.",
|
|
59621
|
+
jargon: "estimand: ATE vs ATT"
|
|
59622
|
+
},
|
|
59623
|
+
confounder_imbalance: {
|
|
59624
|
+
term: "How lopsided this one was",
|
|
59625
|
+
gist: "How different the two groups were on this factor, before adjusting.",
|
|
59626
|
+
detail: "A long bar means the treated and untreated {subjects} started far apart here — exactly the kind of gap the adjustment has to close.",
|
|
59627
|
+
jargon: "standardised mean difference (SMD)"
|
|
59628
|
+
},
|
|
59629
|
+
// ── Tabs ────────────────────────────────────────────────────────────
|
|
59630
|
+
tab_answer: {
|
|
59631
|
+
term: "Answer",
|
|
59632
|
+
gist: "The headline: did {treatment} move {outcome}, and by how much?",
|
|
59633
|
+
detail: "Shows the raw gap, the fair like-for-like estimate, and an honest verdict."
|
|
59634
|
+
},
|
|
59635
|
+
tab_trust: {
|
|
59636
|
+
term: "Can we trust it?",
|
|
59637
|
+
gist: "The stress tests we ran to try to break the answer.",
|
|
59638
|
+
detail: "Each check probes a way the result could be a fluke; green means it held up."
|
|
59639
|
+
},
|
|
59640
|
+
tab_dose: {
|
|
59641
|
+
term: "How much?",
|
|
59642
|
+
gist: "How the result changes as a factor goes up or down.",
|
|
59643
|
+
detail: "Not just yes/no — the shape of the response, so you can find the sweet spot."
|
|
59644
|
+
},
|
|
59645
|
+
// ── Answer tab ──────────────────────────────────────────────────────
|
|
59646
|
+
answer_effect: {
|
|
59647
|
+
term: "Like-for-like effect",
|
|
59648
|
+
gist: "The change in {outcome} we can credit to {treatment}.",
|
|
59649
|
+
detail: "It compares treated vs untreated {subjects} who were otherwise similar — so it strips out “who you targeted” and leaves the lever’s real impact.",
|
|
59650
|
+
jargon: "the backdoor-adjusted average treatment effect"
|
|
59651
|
+
},
|
|
59652
|
+
answer_ci: {
|
|
59653
|
+
term: "95% range",
|
|
59654
|
+
gist: "Where the true effect most likely sits.",
|
|
59655
|
+
detail: "If this range doesn’t cross 0, the effect is real, not noise. The wider it is, the less certain — usually because of fewer {subjects}.",
|
|
59656
|
+
jargon: "95% confidence interval"
|
|
59657
|
+
},
|
|
59658
|
+
answer_flip: {
|
|
59659
|
+
term: "Raw and like-for-like disagree",
|
|
59660
|
+
gist: "The plain average points one way; the fair comparison points the other.",
|
|
59661
|
+
detail: "This happens when the treated group differed sharply on a confounder. Trust the like-for-like number — the raw gap is misleading here.",
|
|
59662
|
+
jargon: "confounding / Simpson’s-paradox sign flip"
|
|
59663
|
+
},
|
|
59664
|
+
answer_cautious: {
|
|
59665
|
+
term: "Treat this as provisional",
|
|
59666
|
+
gist: "We got a number, but a trust check failed.",
|
|
59667
|
+
detail: "The estimate may still be driven by something we didn’t adjust for. See “Can we trust it?” before acting on it.",
|
|
59668
|
+
jargon: "adjustment_insufficient verdict"
|
|
59669
|
+
},
|
|
59670
|
+
forest_plot: {
|
|
59671
|
+
term: "Raw average vs. like-for-like",
|
|
59672
|
+
gist: "The misleading number next to the fair one.",
|
|
59673
|
+
detail: "Each bar is an estimate with its uncertainty range; the dashed line is “no effect”. A bar clear of that line is a real change.",
|
|
59674
|
+
jargon: "a forest plot of naive vs adjusted estimates"
|
|
59675
|
+
},
|
|
59676
|
+
forest_naive: {
|
|
59677
|
+
term: "Raw average",
|
|
59678
|
+
gist: "The plain difference, with no adjusting.",
|
|
59679
|
+
detail: "Just treated-minus-untreated. It mixes in who you targeted, so it can mislead — it’s here only as the “before” picture.",
|
|
59680
|
+
jargon: "the unadjusted mean difference"
|
|
59681
|
+
},
|
|
59682
|
+
forest_adjusted: {
|
|
59683
|
+
term: "Like-for-like",
|
|
59684
|
+
gist: "The fair comparison after levelling the confounders.",
|
|
59685
|
+
detail: "This is the number to act on — what {treatment} did among otherwise-similar {subjects}.",
|
|
59686
|
+
jargon: "the adjusted effect"
|
|
59687
|
+
},
|
|
59688
|
+
balance: {
|
|
59689
|
+
term: "How unbalanced each one was",
|
|
59690
|
+
gist: "How far apart the two groups started on each factor.",
|
|
59691
|
+
detail: "Big bars are the factors the adjustment had to work hardest to correct — and the ones most worth double-checking.",
|
|
59692
|
+
jargon: "pre-adjustment covariate balance (SMD)"
|
|
59693
|
+
},
|
|
59694
|
+
counts: {
|
|
59695
|
+
term: "The {subject} counts",
|
|
59696
|
+
gist: "How many {subjects} went into the comparison.",
|
|
59697
|
+
detail: "“Compared like-for-like” are the ones with a fair match on the other side; “no fair match” were set aside because nobody comparable existed.",
|
|
59698
|
+
jargon: "n total / on-support / off-support"
|
|
59699
|
+
},
|
|
59700
|
+
// ── Refusal zones + overlap ─────────────────────────────────────────
|
|
59701
|
+
overlap_histogram: {
|
|
59702
|
+
term: "Do the two groups overlap?",
|
|
59703
|
+
gist: "Whether treated and untreated {subjects} actually look alike anywhere.",
|
|
59704
|
+
detail: "Each bar is how many {subjects} sit at a given “likelihood of being treated”. Where the two arms both have bars, a fair comparison exists; if they barely meet, there’s nothing to compare.",
|
|
59705
|
+
jargon: "propensity-score overlap / common support"
|
|
59706
|
+
},
|
|
59707
|
+
refusal_positivity: {
|
|
59708
|
+
term: "No like-for-like comparison exists",
|
|
59709
|
+
gist: "The treated and untreated {subjects} are too different to compare.",
|
|
59710
|
+
detail: "For almost every treated {subject} there’s no similar untreated one (or vice-versa), so there’s no fair “other side”. We refuse rather than extrapolate into thin air. Try fewer or different confounders.",
|
|
59711
|
+
jargon: "positivity / overlap violation"
|
|
59712
|
+
},
|
|
59713
|
+
refusal_not_estimable: {
|
|
59714
|
+
term: "Can’t be estimated",
|
|
59715
|
+
gist: "Almost every {subject} is on one side of {treatment}, so there’s no comparison group.",
|
|
59716
|
+
detail: "Measuring an effect needs a decent number of {subjects} who got the lever AND who didn’t. When one side is just a handful, we refuse to guess rather than read too much into a few people.",
|
|
59717
|
+
jargon: "no treatment variation"
|
|
59718
|
+
},
|
|
59719
|
+
// ── Trust tab ───────────────────────────────────────────────────────
|
|
59720
|
+
trust_intro: {
|
|
59721
|
+
term: "Can we trust it?",
|
|
59722
|
+
gist: "Before believing the answer, we tried to break it.",
|
|
59723
|
+
detail: "Each row is a different way the result could be a fluke; colour shows whether it held up (pass) or wobbled (caution)."
|
|
59724
|
+
},
|
|
59725
|
+
check_shuffle: {
|
|
59726
|
+
term: "Shuffle test",
|
|
59727
|
+
gist: "We randomly re-label who got {treatment} and re-measure.",
|
|
59728
|
+
detail: "On fake labels a genuine effect should collapse to nothing. If a “fake” effect still appears, the real one isn’t trustworthy.",
|
|
59729
|
+
jargon: "placebo / permutation refuter"
|
|
59730
|
+
},
|
|
59731
|
+
check_dropsome: {
|
|
59732
|
+
term: "Drop-some test",
|
|
59733
|
+
gist: "We re-run on random slices of the {subjects}.",
|
|
59734
|
+
detail: "A trustworthy effect barely moves when you drop some data; a jumpy one is resting on a few {subjects}.",
|
|
59735
|
+
jargon: "data-subset refuter"
|
|
59736
|
+
},
|
|
59737
|
+
check_decoy: {
|
|
59738
|
+
term: "Decoy factor",
|
|
59739
|
+
gist: "We add a totally random extra factor and re-measure.",
|
|
59740
|
+
detail: "A made-up factor shouldn’t change the answer. If it does, the model is over-reacting to noise.",
|
|
59741
|
+
jargon: "random-common-cause refuter"
|
|
59742
|
+
},
|
|
59743
|
+
check_hidden: {
|
|
59744
|
+
term: "Hidden cause",
|
|
59745
|
+
gist: "How strong an unrecorded cause would have to be to overturn the result.",
|
|
59746
|
+
detail: "Something you didn’t measure could drive both who got {treatment} and {outcome}. “Holds throughout” means only an implausibly strong hidden cause could flip it.",
|
|
59747
|
+
jargon: "unobserved-confounding sensitivity / E-value"
|
|
59748
|
+
},
|
|
59749
|
+
sensitivity: {
|
|
59750
|
+
term: "Effect as a hidden cause gets stronger",
|
|
59751
|
+
gist: "What happens to the effect if an unrecorded cause is dialled up.",
|
|
59752
|
+
detail: "The line is the estimate as a simulated hidden cause grows. The point where it crosses 0 is how much hidden bias it would take to erase the result.",
|
|
59753
|
+
jargon: "sensitivity (tipping-point) curve"
|
|
59754
|
+
},
|
|
59755
|
+
// ── Dose tab ────────────────────────────────────────────────────────
|
|
59756
|
+
dose_curve: {
|
|
59757
|
+
term: "{outcome} gained vs. {feature}",
|
|
59758
|
+
gist: "How {outcome} responds as {feature} goes up.",
|
|
59759
|
+
detail: "The line is the average change in {outcome} at each level of {feature}, with its uncertainty band — the shape tells you where more pays off and where it flattens.",
|
|
59760
|
+
jargon: "accumulated local effects (ALE) dose-response"
|
|
59761
|
+
},
|
|
59762
|
+
dose_here: {
|
|
59763
|
+
term: "You are here",
|
|
59764
|
+
gist: "Where most of your {subjects} currently sit on {feature}.",
|
|
59765
|
+
detail: "The busiest level today — your starting point for deciding whether to push higher or lower.",
|
|
59766
|
+
jargon: "the modal bin"
|
|
59767
|
+
},
|
|
59768
|
+
dose_sweet: {
|
|
59769
|
+
term: "Sweet spot",
|
|
59770
|
+
gist: "Where the gains are solid and start to flatten.",
|
|
59771
|
+
detail: "Past here, pushing {feature} higher buys little extra {outcome} — a sensible target.",
|
|
59772
|
+
jargon: "the diminishing-returns knee"
|
|
59773
|
+
},
|
|
59774
|
+
dose_reco: {
|
|
59775
|
+
term: "Recommended",
|
|
59776
|
+
gist: "The level of {feature} we’d aim for.",
|
|
59777
|
+
detail: "The sweet-spot level and the {outcome} you’d expect there, with its range — and the trade-off of going one step further.",
|
|
59778
|
+
jargon: "the recommended operating point"
|
|
59779
|
+
},
|
|
59780
|
+
dose_marginal: {
|
|
59781
|
+
term: "Extra {outcome} per step",
|
|
59782
|
+
gist: "What each additional step of {feature} buys you.",
|
|
59783
|
+
detail: "Tall early bars then short ones is the classic diminishing-returns shape — spend effort where the bars are tall.",
|
|
59784
|
+
jargon: "marginal / incremental effect per bin"
|
|
59785
|
+
},
|
|
59786
|
+
// ── Verdicts ────────────────────────────────────────────────────────
|
|
59787
|
+
verdict_causal: {
|
|
59788
|
+
term: "Causal",
|
|
59789
|
+
gist: "A real change you can credit to {treatment} — and it held up.",
|
|
59790
|
+
detail: "The like-for-like effect is clear, sizeable, and survived the trust checks. Safe to act on.",
|
|
59791
|
+
jargon: "a robust, identified effect"
|
|
59792
|
+
},
|
|
59793
|
+
verdict_modest: {
|
|
59794
|
+
term: "Modest / unclear",
|
|
59795
|
+
gist: "There may be an effect, but it’s small or fuzzy.",
|
|
59796
|
+
detail: "The fair comparison can’t cleanly separate it from no-effect (the range straddles 0, or it’s tiny). Don’t bet much on it.",
|
|
59797
|
+
jargon: "effect CI spans zero / below materiality"
|
|
59798
|
+
},
|
|
59799
|
+
verdict_adjustment_insufficient: {
|
|
59800
|
+
term: "Not trustworthy yet",
|
|
59801
|
+
gist: "We got a number, but a trust check failed.",
|
|
59802
|
+
detail: "Likely something we didn’t adjust for is still in play. Treat as provisional and look at “Can we trust it?”.",
|
|
59803
|
+
jargon: "placebo / robustness failure"
|
|
59804
|
+
},
|
|
59805
|
+
verdict_non_identifiable_positivity: {
|
|
59806
|
+
term: "No fair comparison",
|
|
59807
|
+
gist: "Treated and untreated {subjects} don’t overlap enough to compare.",
|
|
59808
|
+
detail: "There’s no like-for-like “other side”, so any number would be extrapolation. See the overlap chart.",
|
|
59809
|
+
jargon: "positivity / overlap violation"
|
|
59810
|
+
},
|
|
59811
|
+
verdict_not_estimable: {
|
|
59812
|
+
term: "Can’t be estimated",
|
|
59813
|
+
gist: "Almost everyone is on one side of {treatment} — no comparison group.",
|
|
59814
|
+
detail: "One arm is just a handful of {subjects}, so we won’t guess an effect from it.",
|
|
59815
|
+
jargon: "no treatment variation"
|
|
59816
|
+
},
|
|
59817
|
+
// ── Validate tab ────────────────────────────────────────────────────
|
|
59818
|
+
tab_validate: {
|
|
59819
|
+
term: "Validate",
|
|
59820
|
+
gist: "The real experiment you’d run to prove this — for sure.",
|
|
59821
|
+
detail: "This analysis adjusted for what it could measure. A controlled trial — assigning {treatment} at random — removes any leftover doubt. This tab sizes that trial."
|
|
59822
|
+
},
|
|
59823
|
+
validate_size: {
|
|
59824
|
+
term: "How many to run",
|
|
59825
|
+
gist: "The number of {subjects} the trial needs.",
|
|
59826
|
+
detail: "Enough to spot a change this size if it’s real. A bigger or cleaner effect needs fewer; a small or noisy one needs many more.",
|
|
59827
|
+
jargon: "required sample size at the target power"
|
|
59828
|
+
},
|
|
59829
|
+
validate_split: {
|
|
59830
|
+
term: "How to split them",
|
|
59831
|
+
gist: "How many get {treatment} versus are left alone.",
|
|
59832
|
+
detail: "Assign at random — that’s what makes the two groups comparable with no adjusting. An even split is the most efficient; treating fewer needs a larger total.",
|
|
59833
|
+
jargon: "randomised allocation between arms"
|
|
59834
|
+
},
|
|
59835
|
+
validate_match: {
|
|
59836
|
+
term: "Match the groups on",
|
|
59837
|
+
gist: "Keep both groups balanced on these — they were lopsided last time.",
|
|
59838
|
+
detail: "Give each group the same mix of these factors when you split, so neither one starts ahead. These are the factors that muddied the original comparison most.",
|
|
59839
|
+
jargon: "stratified / blocked randomisation on the high-imbalance covariates"
|
|
59840
|
+
},
|
|
59841
|
+
validate_power: {
|
|
59842
|
+
term: "Chance of detecting it",
|
|
59843
|
+
gist: "How likely the trial is to catch the effect, by size.",
|
|
59844
|
+
detail: "More {subjects} means a better chance of a clear result. The marker shows where today’s data sits — usually well short of a confident answer.",
|
|
59845
|
+
jargon: "statistical power vs sample size"
|
|
59846
|
+
},
|
|
59847
|
+
validate_holdback: {
|
|
59848
|
+
term: "Hold back a control",
|
|
59849
|
+
gist: "Keep a random group untreated to compare against.",
|
|
59850
|
+
detail: "Right now almost everything got {treatment}, so there’s nothing to compare to. Next time, leave a random sample untreated — that group becomes the fair baseline.",
|
|
59851
|
+
jargon: "a randomised control arm"
|
|
59852
|
+
},
|
|
59853
|
+
// ── Journal ─────────────────────────────────────────────────────────
|
|
59854
|
+
journal: {
|
|
59855
|
+
term: "Committed experiments",
|
|
59856
|
+
gist: "A log of the questions you’ve saved.",
|
|
59857
|
+
detail: "Each row is a framing you committed — its verdict and headline effect — so the team can see what’s been tried and what held up.",
|
|
59858
|
+
jargon: "the experiment journal"
|
|
59859
|
+
}
|
|
59860
|
+
};
|
|
59861
|
+
const GuidanceContext = createContext({ on: true, vars: {} });
|
|
59862
|
+
function GuidanceProvider({ on: on3, vars, children: children2 }) {
|
|
59863
|
+
return /* @__PURE__ */ jsx$1(GuidanceContext.Provider, { value: { on: on3, vars }, children: children2 });
|
|
59864
|
+
}
|
|
59865
|
+
function HelpCard({ id: id3, extraVars }) {
|
|
59866
|
+
const { vars } = useContext(GuidanceContext);
|
|
59867
|
+
const e3 = fillHelp(HELP[id3], { ...vars, ...extraVars });
|
|
59868
|
+
const slot = useSlotRecipe({ key: "hoverCard" })();
|
|
59869
|
+
return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "1.5", children: [
|
|
59870
|
+
/* @__PURE__ */ jsx$1(Box, { css: slot.title, mb: "0", children: e3.term }),
|
|
59871
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", children: e3.gist }),
|
|
59872
|
+
/* @__PURE__ */ jsx$1(Box, { css: slot.description, lineHeight: "1.5", children: e3.detail }),
|
|
59873
|
+
e3.jargon && /* @__PURE__ */ jsxs(Text, { textStyle: "caption", color: "fg.subtle", mt: "0.5", pt: "1.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59874
|
+
"Technically: ",
|
|
59875
|
+
e3.jargon
|
|
59876
|
+
] })
|
|
59877
|
+
] });
|
|
59878
|
+
}
|
|
59879
|
+
function Hover({ id: id3, extraVars, trigger }) {
|
|
59880
|
+
return /* @__PURE__ */ jsxs(HoverCard.Root, { openDelay: 150, closeDelay: 80, size: "sm", positioning: { placement: "top" }, children: [
|
|
59881
|
+
/* @__PURE__ */ jsx$1(HoverCard.Trigger, { asChild: true, children: trigger }),
|
|
59882
|
+
/* @__PURE__ */ jsx$1(Portal, { children: /* @__PURE__ */ jsx$1(HoverCard.Positioner, { children: /* @__PURE__ */ jsxs(HoverCard.Content, { children: [
|
|
59883
|
+
/* @__PURE__ */ jsx$1(HoverCard.Arrow, { children: /* @__PURE__ */ jsx$1(HoverCard.ArrowTip, {}) }),
|
|
59884
|
+
/* @__PURE__ */ jsx$1(HelpCard, { id: id3, extraVars })
|
|
59885
|
+
] }) }) })
|
|
59886
|
+
] });
|
|
59887
|
+
}
|
|
59888
|
+
function Help({ id: id3, extraVars, children: children2, display = "inline", gap }) {
|
|
59889
|
+
const { on: on3 } = useContext(GuidanceContext);
|
|
59890
|
+
if (!on3) return /* @__PURE__ */ jsx$1(Fragment, { children: children2 });
|
|
59891
|
+
return /* @__PURE__ */ jsx$1(Hover, { id: id3, extraVars, trigger: (
|
|
59892
|
+
// `tabIndex={0}` makes the term keyboard-focusable — the HoverCard's zag
|
|
59893
|
+
// machine opens on focus, so the glossary is reachable without a pointer.
|
|
59894
|
+
/* @__PURE__ */ jsx$1(
|
|
59895
|
+
Box,
|
|
59896
|
+
{
|
|
59897
|
+
as: "span",
|
|
59898
|
+
cursor: "help",
|
|
59899
|
+
tabIndex: 0,
|
|
59900
|
+
display,
|
|
59901
|
+
...gap ? { alignItems: "center", gap } : {},
|
|
59902
|
+
_focusVisible: { outline: "1px dotted", outlineColor: "brand.fg", outlineOffset: "2px", borderRadius: "2px" },
|
|
59903
|
+
children: children2
|
|
59904
|
+
}
|
|
59905
|
+
)
|
|
59906
|
+
) });
|
|
59907
|
+
}
|
|
59908
|
+
function GuidanceToggle({ on: on3, onToggle }) {
|
|
59909
|
+
const iconButton = useRecipe({ key: "iconButton" });
|
|
59910
|
+
return /* @__PURE__ */ jsx$1(Hover, { id: "guidance", trigger: /* @__PURE__ */ jsx$1(
|
|
59911
|
+
Box,
|
|
59912
|
+
{
|
|
59913
|
+
as: "button",
|
|
59914
|
+
css: iconButton({ variant: on3 ? "subtle" : "ghost", size: "sm" }),
|
|
59915
|
+
onClick: onToggle,
|
|
59916
|
+
"aria-pressed": on3,
|
|
59917
|
+
"aria-label": "Toggle guidance",
|
|
59918
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
59919
|
+
children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faCircleInfo })
|
|
59920
|
+
}
|
|
59921
|
+
) });
|
|
59922
|
+
}
|
|
59923
|
+
function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
|
|
59924
|
+
const span = /* @__PURE__ */ jsx$1("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
|
|
59925
|
+
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 }) });
|
|
59926
|
+
}
|
|
59026
59927
|
function svgText(size3, fill, family, weight8) {
|
|
59027
59928
|
return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
|
|
59028
59929
|
}
|
|
@@ -59078,7 +59979,7 @@ const niceCeil = (v3) => {
|
|
|
59078
59979
|
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;
|
|
59079
59980
|
return step3 * mag;
|
|
59080
59981
|
};
|
|
59081
|
-
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }) {
|
|
59982
|
+
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
|
|
59082
59983
|
const t4 = useChartTheme();
|
|
59083
59984
|
const [ref, w2] = useMeasuredWidth();
|
|
59084
59985
|
const n2 = rows.length;
|
|
@@ -59088,11 +59989,14 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59088
59989
|
const innerH = hPx - padT - padB;
|
|
59089
59990
|
const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
|
|
59090
59991
|
const rowH = innerH / Math.max(1, n2);
|
|
59091
|
-
const zeroX = x2(0);
|
|
59092
|
-
const
|
|
59992
|
+
const zeroX = Math.max(0, Math.min(innerW, x2(0)));
|
|
59993
|
+
const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
|
|
59994
|
+
const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
|
|
59995
|
+
const zf = frac(0);
|
|
59996
|
+
const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
|
|
59093
59997
|
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: [
|
|
59094
59998
|
/* @__PURE__ */ jsx$1(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
|
|
59095
|
-
/* @__PURE__ */ jsx$1("text", { x: zeroX, y: -6, textAnchor:
|
|
59999
|
+
/* @__PURE__ */ jsx$1("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
|
|
59096
60000
|
rows.map((r2, i) => {
|
|
59097
60001
|
const cy = rowH * i + rowH / 2;
|
|
59098
60002
|
const col = t4.tone(r2.tone);
|
|
@@ -59101,20 +60005,20 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59101
60005
|
/* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59102
60006
|
/* @__PURE__ */ jsx$1(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59103
60007
|
/* @__PURE__ */ jsx$1(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
|
|
59104
|
-
/* @__PURE__ */ jsx$1(
|
|
60008
|
+
/* @__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 }),
|
|
59105
60009
|
r2.note && /* @__PURE__ */ jsx$1("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
|
|
59106
60010
|
/* @__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) })
|
|
59107
60011
|
] }, i);
|
|
59108
60012
|
}),
|
|
59109
60013
|
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59110
|
-
ticks2.map((tk, i) => /* @__PURE__ */ jsxs(Group, { left: x2(tk), children: [
|
|
60014
|
+
ticks2.map((tk, i) => /* @__PURE__ */ jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
|
|
59111
60015
|
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
59112
|
-
/* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 14, textAnchor:
|
|
60016
|
+
/* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed$1(tk) })
|
|
59113
60017
|
] }, `t${i}`)),
|
|
59114
60018
|
unit2 && /* @__PURE__ */ jsx$1("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
|
|
59115
60019
|
] }) }) });
|
|
59116
60020
|
}
|
|
59117
|
-
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2 }) {
|
|
60021
|
+
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
|
|
59118
60022
|
const t4 = useChartTheme();
|
|
59119
60023
|
const [ref, w2] = useMeasuredWidth();
|
|
59120
60024
|
const hPx = height2 ?? 100;
|
|
@@ -59126,13 +60030,18 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59126
60030
|
const innerW = Math.max(0, w2 - padL - padR);
|
|
59127
60031
|
const innerH = hPx - padT - padB;
|
|
59128
60032
|
const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
|
|
59129
|
-
const
|
|
59130
|
-
const
|
|
60033
|
+
const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
|
|
60034
|
+
const data4 = mid.map((m2, i) => {
|
|
60035
|
+
const mm = fin(m2, 0);
|
|
60036
|
+
return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
|
|
60037
|
+
});
|
|
60038
|
+
const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
|
|
60039
|
+
const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
|
|
59131
60040
|
const yHiN = niceCeil(Math.max(dataMax, 0));
|
|
59132
60041
|
const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
|
|
59133
60042
|
const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
|
|
59134
|
-
const
|
|
59135
|
-
const
|
|
60043
|
+
const yfmt = (v3) => yFormat === "percent" ? `${Math.round(v3)}%` : signed$1(v3);
|
|
60044
|
+
const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
|
|
59136
60045
|
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: [
|
|
59137
60046
|
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59138
60047
|
yTicks.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -59145,11 +60054,17 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59145
60054
|
/* @__PURE__ */ jsx$1(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
|
|
59146
60055
|
marks.map((m2, i) => {
|
|
59147
60056
|
const mc = t4.tone(m2.tone);
|
|
59148
|
-
const dotY = yScale(mid[m2.at]
|
|
60057
|
+
const dotY = yScale(fin(mid[m2.at], 0));
|
|
60058
|
+
const labY = Math.max(9, dotY - 9);
|
|
60059
|
+
const atEnd = m2.at >= mid.length - 2;
|
|
60060
|
+
const atStart = m2.at <= 1;
|
|
60061
|
+
const align = atEnd ? "right" : atStart ? "left" : "center";
|
|
60062
|
+
const labW = m2.label.length * 6 + 6;
|
|
60063
|
+
const labX = atEnd ? -labW : atStart ? 0 : -labW / 2;
|
|
59149
60064
|
return /* @__PURE__ */ jsxs(Group, { left: xScale(m2.at), children: [
|
|
59150
60065
|
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
|
|
59151
60066
|
/* @__PURE__ */ jsx$1(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
|
|
59152
|
-
/* @__PURE__ */ jsx$1(
|
|
60067
|
+
/* @__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 })
|
|
59153
60068
|
] }, `mk${i}`);
|
|
59154
60069
|
}),
|
|
59155
60070
|
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
@@ -59159,17 +60074,54 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59159
60074
|
})
|
|
59160
60075
|
] }) }) });
|
|
59161
60076
|
}
|
|
60077
|
+
function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
|
|
60078
|
+
const t4 = useChartTheme();
|
|
60079
|
+
const [ref, w2] = useMeasuredWidth();
|
|
60080
|
+
const hPx = height2 ?? 150;
|
|
60081
|
+
const padL = 8, padR = 8, padT = 16, padB = 26;
|
|
60082
|
+
const innerW = Math.max(0, w2 - padL - padR);
|
|
60083
|
+
const innerH = hPx - padT - padB;
|
|
60084
|
+
const centerY = innerH / 2;
|
|
60085
|
+
const halfH = centerY - 4;
|
|
60086
|
+
const bins = Math.max(treated.length, control.length);
|
|
60087
|
+
const x2 = createLinearScale({ domain, range: [0, innerW] });
|
|
60088
|
+
const span = domain[1] - domain[0];
|
|
60089
|
+
const binW = bins > 0 ? innerW / bins : innerW;
|
|
60090
|
+
const barW = Math.max(1, binW * 0.78);
|
|
60091
|
+
const maxVal = Math.max(1e-9, ...treated, ...control);
|
|
60092
|
+
const tCol = t4.tone("pos");
|
|
60093
|
+
const cCol = t4.tone("muted");
|
|
60094
|
+
const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
|
|
60095
|
+
const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
|
|
60096
|
+
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: [
|
|
60097
|
+
/* @__PURE__ */ jsx$1("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
|
|
60098
|
+
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 }),
|
|
60099
|
+
Array.from({ length: bins }, (_2, i) => {
|
|
60100
|
+
const ht = Number(treated[i] ?? 0) / maxVal * halfH;
|
|
60101
|
+
const hc = Number(control[i] ?? 0) / maxVal * halfH;
|
|
60102
|
+
const left = cx2(i) - barW / 2;
|
|
60103
|
+
return /* @__PURE__ */ jsxs(Group, { children: [
|
|
60104
|
+
ht > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
|
|
60105
|
+
hc > 0 && /* @__PURE__ */ jsx$1("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
|
|
60106
|
+
] }, i);
|
|
60107
|
+
}),
|
|
60108
|
+
/* @__PURE__ */ jsx$1(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
60109
|
+
/* @__PURE__ */ jsx$1("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
|
|
60110
|
+
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}`))
|
|
60111
|
+
] }) }) });
|
|
60112
|
+
}
|
|
59162
60113
|
const arr = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
60114
|
+
const arrI = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
59163
60115
|
const getOpt = (o2) => o2 && o2.type === "some" ? o2.value : void 0;
|
|
59164
|
-
const fmt$1 = (x2) =>
|
|
59165
|
-
const
|
|
59166
|
-
|
|
59167
|
-
|
|
59168
|
-
const
|
|
59169
|
-
|
|
59170
|
-
|
|
59171
|
-
return Math.sqrt(xs.reduce((a2, b2) => a2 + (b2 - m2) * (b2 - m2), 0) / xs.length);
|
|
60116
|
+
const fmt$1 = (x2) => {
|
|
60117
|
+
const r2 = Number.isInteger(x2) ? String(x2) : x2.toFixed(1);
|
|
60118
|
+
return r2 === "-0.0" ? "0.0" : r2;
|
|
60119
|
+
};
|
|
60120
|
+
const signed = (x2) => {
|
|
60121
|
+
const f2 = fmt$1(x2);
|
|
60122
|
+
return f2 === "0" || f2 === "0.0" || f2.startsWith("-") ? f2 : `+${f2}`;
|
|
59172
60123
|
};
|
|
60124
|
+
const clamp01 = (x2) => x2 < 0 ? 0 : x2 > 1 ? 1 : x2;
|
|
59173
60125
|
function band(absStd) {
|
|
59174
60126
|
if (absStd >= 0.66) return { level: "large gap", tone: "neg" };
|
|
59175
60127
|
if (absStd >= 0.33) return { level: "some", tone: "warn" };
|
|
@@ -59184,11 +60136,22 @@ const kindOf = (cols, name) => {
|
|
|
59184
60136
|
var _a2;
|
|
59185
60137
|
return (_a2 = cols.find((c2) => c2.name === name)) == null ? void 0 : _a2.kind;
|
|
59186
60138
|
};
|
|
59187
|
-
|
|
59188
|
-
|
|
59189
|
-
|
|
59190
|
-
|
|
59191
|
-
|
|
60139
|
+
const VERDICT_TONE = {
|
|
60140
|
+
causal: "pos",
|
|
60141
|
+
modest: "warn",
|
|
60142
|
+
adjustment_insufficient: "warn",
|
|
60143
|
+
non_identifiable_positivity: "neg",
|
|
60144
|
+
not_estimable: "muted"
|
|
60145
|
+
};
|
|
60146
|
+
const VERDICT_LABEL = {
|
|
60147
|
+
causal: "Causal effect",
|
|
60148
|
+
modest: "Modest / unclear",
|
|
60149
|
+
adjustment_insufficient: "Not trustworthy yet",
|
|
60150
|
+
non_identifiable_positivity: "No fair comparison",
|
|
60151
|
+
not_estimable: "Can’t be estimated"
|
|
60152
|
+
};
|
|
60153
|
+
function deriveVerdict(v3) {
|
|
60154
|
+
return { tag: v3.type, tone: VERDICT_TONE[v3.type], label: VERDICT_LABEL[v3.type] };
|
|
59192
60155
|
}
|
|
59193
60156
|
function treatmentKind(cols, treatment) {
|
|
59194
60157
|
switch (kindOf(cols, treatment)) {
|
|
@@ -59204,124 +60167,133 @@ function treatmentKind(cols, treatment) {
|
|
|
59204
60167
|
return "value";
|
|
59205
60168
|
}
|
|
59206
60169
|
}
|
|
59207
|
-
function deriveSpec(
|
|
59208
|
-
const tKind = kindOf(cols,
|
|
59209
|
-
const outUnit = unitOf(meta3,
|
|
59210
|
-
const balByCol =
|
|
59211
|
-
const
|
|
60170
|
+
function deriveSpec(config2, cols, result, meta3, dataLen) {
|
|
60171
|
+
const tKind = kindOf(cols, config2.treatment);
|
|
60172
|
+
const outUnit = unitOf(meta3, config2.outcome);
|
|
60173
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
60174
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
60175
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
60176
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
60177
|
+
}
|
|
60178
|
+
const confounders = config2.common_causes.map((col) => {
|
|
59212
60179
|
const b2 = balByCol.get(col);
|
|
59213
|
-
const absStd = b2 ? Math.abs(b2.
|
|
60180
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
59214
60181
|
const { level, tone } = band(absStd);
|
|
59215
|
-
const dir = b2 ? b2.
|
|
59216
|
-
const reason = b2 ? `treated
|
|
60182
|
+
const dir = b2 ? b2.treated_mean < b2.control_mean ? "lower" : "higher" : "";
|
|
60183
|
+
const reason = b2 ? `the treated group ran ${dir} on ${col}` : "measured once you Run";
|
|
59217
60184
|
return { col, reason, imbalance: clamp01(absStd), level, tone };
|
|
59218
60185
|
});
|
|
59219
|
-
const inPlay = /* @__PURE__ */ new Set([
|
|
60186
|
+
const inPlay = /* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes]);
|
|
59220
60187
|
const suggest = cols.find((c2) => !inPlay.has(c2.name));
|
|
59221
60188
|
const suggestion = suggest ? `add ${suggest.name}` : "";
|
|
59222
|
-
const methodV = getOpt(
|
|
60189
|
+
const methodV = getOpt(config2.method);
|
|
59223
60190
|
const method = (methodV == null ? void 0 : methodV.type) === "propensity_score_weighting" ? "reweighting" : "regression";
|
|
59224
|
-
const targetV = getOpt(
|
|
60191
|
+
const targetV = getOpt(config2.estimand);
|
|
59225
60192
|
const target = targetV && (targetV.type === "att" || targetV.type === "atc") ? "treated" : "all";
|
|
59226
|
-
const trim2 = getOpt(spec.trim) !== void 0;
|
|
59227
60193
|
return {
|
|
59228
|
-
treatment:
|
|
59229
|
-
treatmentKind: treatmentKind(cols,
|
|
59230
|
-
outcome:
|
|
60194
|
+
treatment: config2.treatment,
|
|
60195
|
+
treatmentKind: treatmentKind(cols, config2.treatment),
|
|
60196
|
+
outcome: config2.outcome,
|
|
59231
60197
|
outcomeKind: outUnit ? `number · ${outUnit}` : "number",
|
|
59232
60198
|
comparison: tKind === "boolean" ? "yes · vs no" : "high · vs low",
|
|
59233
60199
|
confounders,
|
|
59234
60200
|
suggestion,
|
|
59235
60201
|
method,
|
|
59236
60202
|
target,
|
|
59237
|
-
|
|
59238
|
-
dataLabel: `${result ? Number(result.nTotal) : dataLen} rows`
|
|
60203
|
+
dataLabel: `${result ? Number(result.n_total) : dataLen} rows`
|
|
59239
60204
|
};
|
|
59240
60205
|
}
|
|
59241
60206
|
function balanceDisplay(b2, categorical) {
|
|
59242
|
-
const isProp = categorical.has(b2.
|
|
59243
|
-
return isProp ? `${Math.round(b2.
|
|
59244
|
-
}
|
|
59245
|
-
function deriveAnswer(
|
|
59246
|
-
const ci = getOpt(
|
|
59247
|
-
const nci = getOpt(result.
|
|
59248
|
-
const categorical = new Set(
|
|
59249
|
-
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.
|
|
60207
|
+
const isProp = categorical.has(b2.base_column) && b2.treated_mean >= 0 && b2.treated_mean <= 1 && b2.control_mean >= 0 && b2.control_mean <= 1;
|
|
60208
|
+
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)}`;
|
|
60209
|
+
}
|
|
60210
|
+
function deriveAnswer(config2, result, adj, meta3) {
|
|
60211
|
+
const ci = getOpt(adj.ci);
|
|
60212
|
+
const nci = getOpt(result.naive_ci);
|
|
60213
|
+
const categorical = new Set(getOpt(config2.categorical) ?? []);
|
|
60214
|
+
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
|
|
59250
60215
|
col: b2.column,
|
|
59251
|
-
treated: b2.
|
|
59252
|
-
control: b2.
|
|
60216
|
+
treated: b2.treated_mean,
|
|
60217
|
+
control: b2.control_mean,
|
|
59253
60218
|
display: balanceDisplay(b2, categorical),
|
|
59254
|
-
frac: clamp01(Math.abs(b2.
|
|
59255
|
-
tone: band(Math.abs(b2.
|
|
60219
|
+
frac: clamp01(Math.abs(b2.std_diff)),
|
|
60220
|
+
tone: band(Math.abs(b2.std_diff)).tone
|
|
59256
60221
|
}));
|
|
59257
|
-
const
|
|
59258
|
-
const
|
|
60222
|
+
const lo2 = (ci == null ? void 0 : ci.lower) ?? adj.effect;
|
|
60223
|
+
const hi = (ci == null ? void 0 : ci.upper) ?? adj.effect;
|
|
60224
|
+
const clear = result.verdict.type === "causal" || (ci ? lo2 > 0 || hi < 0 : false);
|
|
60225
|
+
const flip = Math.sign(result.naive) !== Math.sign(adj.effect) && result.naive !== 0;
|
|
60226
|
+
const nDropped = Number(result.n_dropped);
|
|
60227
|
+
const nTotal = Number(result.n_total);
|
|
59259
60228
|
return {
|
|
59260
|
-
treatment:
|
|
59261
|
-
outcome:
|
|
59262
|
-
unit: unitOf(meta3,
|
|
60229
|
+
treatment: config2.treatment,
|
|
60230
|
+
outcome: config2.outcome,
|
|
60231
|
+
unit: unitOf(meta3, config2.outcome),
|
|
59263
60232
|
naive: result.naive,
|
|
59264
60233
|
naiveLo: (nci == null ? void 0 : nci.lower) ?? result.naive,
|
|
59265
60234
|
naiveHi: (nci == null ? void 0 : nci.upper) ?? result.naive,
|
|
59266
|
-
effect:
|
|
59267
|
-
lo:
|
|
59268
|
-
hi
|
|
59269
|
-
|
|
59270
|
-
|
|
59271
|
-
|
|
60235
|
+
effect: adj.effect,
|
|
60236
|
+
lo: lo2,
|
|
60237
|
+
hi,
|
|
60238
|
+
clear,
|
|
60239
|
+
flip,
|
|
60240
|
+
cautious: result.verdict.type === "adjustment_insufficient",
|
|
60241
|
+
nTotal: result.n_total,
|
|
60242
|
+
nTreated: result.n_treated,
|
|
60243
|
+
nControl: result.n_control,
|
|
59272
60244
|
nCompared: BigInt(Math.max(0, nTotal - nDropped)),
|
|
59273
|
-
nDropped: result.
|
|
60245
|
+
nDropped: result.n_dropped,
|
|
59274
60246
|
balance
|
|
59275
60247
|
};
|
|
59276
60248
|
}
|
|
59277
|
-
function
|
|
59278
|
-
const
|
|
59279
|
-
|
|
59280
|
-
|
|
59281
|
-
|
|
59282
|
-
|
|
59283
|
-
|
|
59284
|
-
|
|
59285
|
-
|
|
59286
|
-
|
|
59287
|
-
value:
|
|
59288
|
-
|
|
59289
|
-
|
|
59290
|
-
|
|
59291
|
-
|
|
59292
|
-
|
|
59293
|
-
|
|
59294
|
-
|
|
59295
|
-
|
|
59296
|
-
|
|
59297
|
-
|
|
59298
|
-
|
|
59299
|
-
|
|
59300
|
-
|
|
59301
|
-
|
|
59302
|
-
|
|
59303
|
-
|
|
59304
|
-
return {
|
|
59305
|
-
name: "Decoy cause",
|
|
59306
|
-
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
59307
|
-
value: `${fmt$1(est)} → ${fmt$1(m2)}`,
|
|
59308
|
-
passed,
|
|
59309
|
-
tip: { type: "none", value: null }
|
|
59310
|
-
};
|
|
59311
|
-
}
|
|
59312
|
-
default: {
|
|
59313
|
-
const strengths = arr(getOpt(c2.strengths));
|
|
59314
|
-
const tip = zeroCrossing(strengths, effects);
|
|
59315
|
-
const passed = tip === null;
|
|
59316
|
-
return {
|
|
59317
|
-
name: "Hidden cause",
|
|
59318
|
-
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
59319
|
-
value: tip === null ? "holds throughout" : `tips at ${fmt$1(tip)}`,
|
|
59320
|
-
passed,
|
|
59321
|
-
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." }
|
|
59322
|
-
};
|
|
59323
|
-
}
|
|
60249
|
+
function deriveRefusal(config2, result) {
|
|
60250
|
+
const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
|
|
60251
|
+
if (result.verdict.type === "not_estimable") {
|
|
60252
|
+
const reason = result.verdict.value || "The treatment barely varies, so no comparison can be formed.";
|
|
60253
|
+
return {
|
|
60254
|
+
kind: "not_estimable",
|
|
60255
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
60256
|
+
body: reason,
|
|
60257
|
+
evidence: [
|
|
60258
|
+
{ label: "treated", value: String(nT) },
|
|
60259
|
+
{ label: "untreated", value: String(nC) },
|
|
60260
|
+
{ label: "rows", value: String(nTot) }
|
|
60261
|
+
]
|
|
60262
|
+
};
|
|
60263
|
+
}
|
|
60264
|
+
if (result.verdict.type === "non_identifiable_positivity") {
|
|
60265
|
+
const pct = Math.round(result.overlap.common_support_frac * 100);
|
|
60266
|
+
return {
|
|
60267
|
+
kind: "positivity",
|
|
60268
|
+
title: "No like-for-like comparison exists",
|
|
60269
|
+
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.`,
|
|
60270
|
+
evidence: [
|
|
60271
|
+
{ label: "common support", value: `${pct}%` },
|
|
60272
|
+
{ label: "treated", value: String(nT) },
|
|
60273
|
+
{ label: "untreated", value: String(nC) }
|
|
60274
|
+
]
|
|
60275
|
+
};
|
|
59324
60276
|
}
|
|
60277
|
+
return {
|
|
60278
|
+
kind: "not_estimable",
|
|
60279
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
60280
|
+
body: "The engine could not produce a like-for-like estimate for this configuration.",
|
|
60281
|
+
evidence: [
|
|
60282
|
+
{ label: "treated", value: String(nT) },
|
|
60283
|
+
{ label: "untreated", value: String(nC) },
|
|
60284
|
+
{ label: "rows", value: String(nTot) }
|
|
60285
|
+
]
|
|
60286
|
+
};
|
|
60287
|
+
}
|
|
60288
|
+
function deriveOverlap(o2) {
|
|
60289
|
+
const pct = Math.round(o2.common_support_frac * 100);
|
|
60290
|
+
return {
|
|
60291
|
+
treated: arr(o2.treated_propensity),
|
|
60292
|
+
control: arr(o2.control_propensity),
|
|
60293
|
+
commonSupportFrac: o2.common_support_frac,
|
|
60294
|
+
positivityOk: o2.positivity_ok,
|
|
60295
|
+
supportLabel: `${pct}% common support`
|
|
60296
|
+
};
|
|
59325
60297
|
}
|
|
59326
60298
|
function zeroCrossing(xs, ys) {
|
|
59327
60299
|
for (let i = 1; i < ys.length && i < xs.length; i++) {
|
|
@@ -59333,27 +60305,80 @@ function zeroCrossing(xs, ys) {
|
|
|
59333
60305
|
}
|
|
59334
60306
|
return null;
|
|
59335
60307
|
}
|
|
59336
|
-
function deriveRefute(
|
|
59337
|
-
const checks =
|
|
59338
|
-
const
|
|
59339
|
-
const
|
|
59340
|
-
const
|
|
59341
|
-
|
|
59342
|
-
|
|
59343
|
-
|
|
59344
|
-
|
|
59345
|
-
|
|
59346
|
-
|
|
59347
|
-
|
|
59348
|
-
|
|
59349
|
-
}
|
|
59350
|
-
|
|
59351
|
-
|
|
60308
|
+
function deriveRefute(r2, adj) {
|
|
60309
|
+
const checks = [];
|
|
60310
|
+
const est = (adj == null ? void 0 : adj.effect) ?? 0;
|
|
60311
|
+
const placeboEffect = getOpt(r2.placebo_effect);
|
|
60312
|
+
const placeboPasses = getOpt(r2.placebo_passes);
|
|
60313
|
+
if (placeboEffect !== void 0 || placeboPasses !== void 0) {
|
|
60314
|
+
checks.push({
|
|
60315
|
+
name: "Shuffle test",
|
|
60316
|
+
desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
|
|
60317
|
+
value: placeboEffect !== void 0 ? `→ ${signed(placeboEffect)}` : placeboPasses ? "passed" : "failed",
|
|
60318
|
+
passed: placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15)),
|
|
60319
|
+
tip: { type: "some", value: "Randomly re-label which rows were treated. A genuine effect should vanish." },
|
|
60320
|
+
help: "check_shuffle"
|
|
60321
|
+
});
|
|
60322
|
+
}
|
|
60323
|
+
const ds = getOpt(r2.data_subset_effect);
|
|
60324
|
+
if (ds !== void 0) {
|
|
60325
|
+
const dstd = getOpt(r2.data_subset_std) ?? 0;
|
|
60326
|
+
checks.push({
|
|
60327
|
+
name: "Drop-some test",
|
|
60328
|
+
desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
|
|
60329
|
+
value: `${fmt$1(ds)} ± ${fmt$1(dstd)}`,
|
|
60330
|
+
passed: Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2),
|
|
60331
|
+
tip: { type: "none", value: null },
|
|
60332
|
+
help: "check_dropsome"
|
|
60333
|
+
});
|
|
60334
|
+
}
|
|
60335
|
+
const rcc = getOpt(r2.random_cc_within_ci);
|
|
60336
|
+
if (rcc !== void 0) {
|
|
60337
|
+
checks.push({
|
|
60338
|
+
name: "Decoy cause",
|
|
60339
|
+
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
60340
|
+
value: rcc ? "within range" : "moved",
|
|
60341
|
+
passed: rcc,
|
|
60342
|
+
tip: { type: "none", value: null },
|
|
60343
|
+
help: "check_decoy"
|
|
60344
|
+
});
|
|
60345
|
+
}
|
|
60346
|
+
const sens = getOpt(r2.sensitivity);
|
|
60347
|
+
const evalue = getOpt(r2.robustness_value);
|
|
60348
|
+
if (sens !== void 0 || evalue !== void 0) {
|
|
60349
|
+
const strengths = sens ? arr(sens.strengths) : [];
|
|
60350
|
+
const effects = sens ? arr(sens.effects) : [];
|
|
60351
|
+
const tip = sens ? zeroCrossing(strengths, effects) : null;
|
|
60352
|
+
const value = tip !== null ? `tips at ${fmt$1(tip)}` : evalue !== void 0 ? `E-value ${fmt$1(evalue)}` : "holds throughout";
|
|
60353
|
+
checks.push({
|
|
60354
|
+
name: "Hidden cause",
|
|
60355
|
+
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
60356
|
+
value,
|
|
60357
|
+
passed: tip === null,
|
|
60358
|
+
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." },
|
|
60359
|
+
help: "check_hidden"
|
|
60360
|
+
});
|
|
60361
|
+
}
|
|
60362
|
+
let sensVM = null;
|
|
60363
|
+
if (sens !== void 0) {
|
|
60364
|
+
const mid = arr(sens.effects);
|
|
60365
|
+
if (mid.length) {
|
|
60366
|
+
const ci = getOpt(adj == null ? void 0 : adj.ci);
|
|
60367
|
+
const half = ci ? Math.abs(ci.upper - ci.lower) / 2 : 0;
|
|
60368
|
+
const lo2 = mid.map((m2) => m2 - half);
|
|
60369
|
+
const hi = mid.map((m2) => m2 + half);
|
|
60370
|
+
const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
|
|
60371
|
+
sensVM = { lo: lo2, mid, hi, xTicks: ["none", "weaker", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)] };
|
|
60372
|
+
}
|
|
60373
|
+
}
|
|
60374
|
+
return { checks, sens: sensVM };
|
|
60375
|
+
}
|
|
60376
|
+
function deriveDose(dose, config2, meta3) {
|
|
59352
60377
|
const grid = arr(dose.grid);
|
|
59353
60378
|
const mid = arr(dose.effect);
|
|
59354
60379
|
const lo2 = getOpt(dose.lower) ? arr(getOpt(dose.lower)) : mid.slice();
|
|
59355
60380
|
const hi = getOpt(dose.upper) ? arr(getOpt(dose.upper)) : mid.slice();
|
|
59356
|
-
const sizes =
|
|
60381
|
+
const sizes = arrI(dose.size);
|
|
59357
60382
|
const featureUnit = unitOf(meta3, dose.feature);
|
|
59358
60383
|
const here = sizes.length ? sizes.indexOf(Math.max(...sizes)) : 0;
|
|
59359
60384
|
const marg = mid.map((m2, i) => i === 0 ? 0 : m2 - mid[i - 1]);
|
|
@@ -59373,31 +60398,28 @@ function deriveDose(dose, spec, meta3) {
|
|
|
59373
60398
|
let lastStep = margSteps.length - 1;
|
|
59374
60399
|
while (lastStep > 0 && Math.abs(margSteps[lastStep]) < 0.12 * maxMarg) lastStep--;
|
|
59375
60400
|
const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: `${fmt$1(grid[i + 1])}`, value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
|
|
59376
|
-
const segLabels = ((_a2 = getOpt(dose.segments)) == null ? void 0 : _a2.map((s2) => s2.label)) ?? [];
|
|
59377
|
-
const segments = segLabels.length ? ["all", ...segLabels] : ["all"];
|
|
59378
60401
|
const stepFrom = here < sweet ? here : Math.max(0, sweet - 1);
|
|
59379
60402
|
const gainToSweet = (mid[sweet] ?? 0) - (mid[stepFrom] ?? 0);
|
|
59380
60403
|
const nextGain = sweet + 1 < mid.length ? mid[sweet + 1] - mid[sweet] : 0;
|
|
59381
60404
|
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)}.`;
|
|
60405
|
+
const marks = [];
|
|
60406
|
+
if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
|
|
60407
|
+
marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
|
|
59382
60408
|
return {
|
|
59383
60409
|
feature: dose.feature,
|
|
59384
|
-
outcome:
|
|
60410
|
+
outcome: config2.outcome,
|
|
59385
60411
|
lo: lo2,
|
|
59386
60412
|
mid,
|
|
59387
60413
|
hi,
|
|
59388
60414
|
xTicks,
|
|
59389
60415
|
yTicks,
|
|
59390
|
-
marks
|
|
59391
|
-
{ at: BigInt(here), label: "you are here", tone: "muted" },
|
|
59392
|
-
{ at: BigInt(sweet), label: "sweet spot", tone: "pos" }
|
|
59393
|
-
],
|
|
60416
|
+
marks,
|
|
59394
60417
|
recoLabel: `≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
|
|
59395
60418
|
recoEffect: mid[sweet] ?? 0,
|
|
59396
60419
|
recoLo: lo2[sweet] ?? 0,
|
|
59397
60420
|
recoHi: hi[sweet] ?? 0,
|
|
59398
60421
|
tradeoff,
|
|
59399
|
-
marginal
|
|
59400
|
-
segments
|
|
60422
|
+
marginal
|
|
59401
60423
|
};
|
|
59402
60424
|
}
|
|
59403
60425
|
function relTime(d2, now2) {
|
|
@@ -59407,30 +60429,126 @@ function relTime(d2, now2) {
|
|
|
59407
60429
|
if (days < 7) return d2.toLocaleDateString(void 0, { weekday: "short" });
|
|
59408
60430
|
return "last wk";
|
|
59409
60431
|
}
|
|
60432
|
+
const VERDICT_WORD = {
|
|
60433
|
+
causal: "causal",
|
|
60434
|
+
modest: "modest",
|
|
60435
|
+
adjustment_insufficient: "not robust",
|
|
60436
|
+
non_identifiable_positivity: "no overlap",
|
|
60437
|
+
not_estimable: "n/a"
|
|
60438
|
+
};
|
|
59410
60439
|
function deriveJournalRow(row, now2) {
|
|
59411
|
-
const
|
|
59412
|
-
const clear = ci ? ci.lower > 0 || ci.upper < 0 : true;
|
|
59413
|
-
const dirUp = row.effect > 0;
|
|
60440
|
+
const adj = getOpt(row.adjusted);
|
|
59414
60441
|
return {
|
|
59415
|
-
treatment: row.
|
|
59416
|
-
outcome: row.
|
|
59417
|
-
confounders: row.
|
|
59418
|
-
effect: signed(row.
|
|
59419
|
-
verdict:
|
|
59420
|
-
verdictTone:
|
|
59421
|
-
who: row.
|
|
59422
|
-
when: relTime(row.
|
|
59423
|
-
};
|
|
59424
|
-
}
|
|
59425
|
-
function deriveView(
|
|
60442
|
+
treatment: row.config.treatment,
|
|
60443
|
+
outcome: row.config.outcome,
|
|
60444
|
+
confounders: row.config.common_causes.join(", "),
|
|
60445
|
+
effect: adj !== void 0 ? signed(adj) : signed(row.naive),
|
|
60446
|
+
verdict: VERDICT_WORD[row.verdict.type],
|
|
60447
|
+
verdictTone: VERDICT_TONE[row.verdict.type],
|
|
60448
|
+
who: row.committed_by,
|
|
60449
|
+
when: relTime(row.committed_at, now2)
|
|
60450
|
+
};
|
|
60451
|
+
}
|
|
60452
|
+
function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2) {
|
|
60453
|
+
const adj = result ? getOpt(result.adjusted) : void 0;
|
|
60454
|
+
const refutation = result ? getOpt(result.refutation) : void 0;
|
|
60455
|
+
const doseR = result ? getOpt(result.dose_response) : void 0;
|
|
59426
60456
|
return {
|
|
59427
|
-
|
|
59428
|
-
|
|
59429
|
-
|
|
59430
|
-
|
|
60457
|
+
// The set-up rail reflects the LIVE config (the editor); the result deck
|
|
60458
|
+
// reflects RANCONFIG — the config that produced `result` — so the result
|
|
60459
|
+
// strings never drift ahead of the numbers on a live picker edit.
|
|
60460
|
+
spec: deriveSpec(config2, cols, result, meta3, dataLen),
|
|
60461
|
+
verdict: result ? deriveVerdict(result.verdict) : null,
|
|
60462
|
+
answer: result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null,
|
|
60463
|
+
refusal: result && !adj ? deriveRefusal(ranConfig, result) : null,
|
|
60464
|
+
overlap: result ? deriveOverlap(result.overlap) : null,
|
|
60465
|
+
refute: refutation ? deriveRefute(refutation, adj) : null,
|
|
60466
|
+
dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
|
|
59431
60467
|
journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2)) : null
|
|
59432
60468
|
};
|
|
59433
60469
|
}
|
|
60470
|
+
const DESIGN_HEADLINE = {
|
|
60471
|
+
detect_observed: "Confirm it with a controlled trial",
|
|
60472
|
+
de_bias: "Make it trustworthy with a controlled trial",
|
|
60473
|
+
resolve_vs_null: "Settle it with a bigger trial",
|
|
60474
|
+
restrict_to_overlap: "You can’t compare yet",
|
|
60475
|
+
create_control: "Hold some back next time"
|
|
60476
|
+
};
|
|
60477
|
+
function deriveDesignOption(o2) {
|
|
60478
|
+
return {
|
|
60479
|
+
label: o2.label,
|
|
60480
|
+
nTotal: Number(o2.n_total),
|
|
60481
|
+
nTreated: Number(o2.n_treated),
|
|
60482
|
+
nControl: Number(o2.n_control),
|
|
60483
|
+
treatedShare: o2.treated_share
|
|
60484
|
+
};
|
|
60485
|
+
}
|
|
60486
|
+
function deriveDesign(d2, result, ranConfig, meta3) {
|
|
60487
|
+
const basis = d2.basis.type;
|
|
60488
|
+
const outcome = ranConfig.outcome;
|
|
60489
|
+
const unit2 = unitOf(meta3, outcome);
|
|
60490
|
+
const options2 = d2.options.map(deriveDesignOption);
|
|
60491
|
+
const primary = options2[0] ?? { label: "Even split", nTotal: 0, nTreated: 0, nControl: 0, treatedShare: 0.5 };
|
|
60492
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
60493
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
60494
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
60495
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
60496
|
+
}
|
|
60497
|
+
const matchOn = d2.match_on.map((col) => {
|
|
60498
|
+
var _a2;
|
|
60499
|
+
const b2 = balByCol.get(col);
|
|
60500
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
60501
|
+
const { tone } = band(absStd);
|
|
60502
|
+
return { col, frac: clamp01(absStd), tone, display: getOpt((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.label) ?? col };
|
|
60503
|
+
});
|
|
60504
|
+
const ns = arrI(d2.power_curve.n);
|
|
60505
|
+
const powers = arr(d2.power_curve.power);
|
|
60506
|
+
const len = Math.min(ns.length, powers.length);
|
|
60507
|
+
const nearest = (target) => {
|
|
60508
|
+
let bi = 0, bd = Infinity;
|
|
60509
|
+
for (let i = 0; i < len; i++) {
|
|
60510
|
+
const dd = Math.abs(ns[i] - target);
|
|
60511
|
+
if (dd < bd) {
|
|
60512
|
+
bd = dd;
|
|
60513
|
+
bi = i;
|
|
60514
|
+
}
|
|
60515
|
+
}
|
|
60516
|
+
return bi;
|
|
60517
|
+
};
|
|
60518
|
+
const xTicks = len ? [String(ns[0]), String(ns[Math.floor((len - 1) / 2)]), String(ns[len - 1])] : [];
|
|
60519
|
+
const marks = [];
|
|
60520
|
+
if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${(d2.target_power * 100).toFixed(0)}%`, tone: "pos", help: "validate_power" });
|
|
60521
|
+
const cp = getOpt(d2.current_power);
|
|
60522
|
+
if (cp !== void 0 && len) {
|
|
60523
|
+
const curN = result ? Number(result.n_total) : 0;
|
|
60524
|
+
if (curN > 0) {
|
|
60525
|
+
const offScale = curN >= (ns[len - 1] ?? 0);
|
|
60526
|
+
marks.push({
|
|
60527
|
+
at: offScale ? len - 1 : nearest(curN),
|
|
60528
|
+
label: offScale ? `you’re here · ${(cp * 100).toFixed(0)}% (off-scale)` : `you’re here ${(cp * 100).toFixed(0)}%`,
|
|
60529
|
+
tone: "muted",
|
|
60530
|
+
help: "validate_power"
|
|
60531
|
+
});
|
|
60532
|
+
}
|
|
60533
|
+
}
|
|
60534
|
+
const faint = d2.outcome_sd > 0 && Math.abs(d2.target_effect) / d2.outcome_sd <= 0.011;
|
|
60535
|
+
return {
|
|
60536
|
+
headline: DESIGN_HEADLINE[basis] ?? "Validate this",
|
|
60537
|
+
rationale: d2.rationale,
|
|
60538
|
+
primary,
|
|
60539
|
+
alternates: options2.slice(1),
|
|
60540
|
+
matchOn,
|
|
60541
|
+
curve: { mid: powers.slice(0, len).map((p2) => p2 * 100), xTicks, marks },
|
|
60542
|
+
targetLabel: `${signed(d2.target_effect)}${unit2 ? ` ${unit2}` : ""}`,
|
|
60543
|
+
targetPctLabel: `${(d2.target_power * 100).toFixed(0)}%`,
|
|
60544
|
+
holdback: basis === "create_control",
|
|
60545
|
+
showOverlap: basis === "restrict_to_overlap",
|
|
60546
|
+
faint
|
|
60547
|
+
};
|
|
60548
|
+
}
|
|
60549
|
+
const SUBJECT_ONE = "record";
|
|
60550
|
+
const SUBJECT_MANY = "records";
|
|
60551
|
+
const AUTORUN_MAX_ROWS = 5e4;
|
|
59434
60552
|
const TONE_TOKEN = {
|
|
59435
60553
|
neg: "fg.danger",
|
|
59436
60554
|
pos: "fg.success",
|
|
@@ -59468,13 +60586,73 @@ function useColumns(workspace, source) {
|
|
|
59468
60586
|
};
|
|
59469
60587
|
}, [types == null ? void 0 : types.sourceType]);
|
|
59470
60588
|
}
|
|
59471
|
-
|
|
59472
|
-
|
|
59473
|
-
|
|
60589
|
+
function rowMatchesAll(row, preds) {
|
|
60590
|
+
return preds.every((p2) => {
|
|
60591
|
+
const { fieldId, op } = p2.value;
|
|
60592
|
+
const v3 = row[fieldId];
|
|
60593
|
+
const ov = op.value;
|
|
60594
|
+
switch (op.type) {
|
|
60595
|
+
case "eq":
|
|
60596
|
+
case "is":
|
|
60597
|
+
return v3 === ov;
|
|
60598
|
+
case "neq":
|
|
60599
|
+
return v3 !== ov;
|
|
60600
|
+
case "lt":
|
|
60601
|
+
return num(v3) != null && num(ov) != null ? num(v3) < num(ov) : true;
|
|
60602
|
+
case "lte":
|
|
60603
|
+
return num(v3) != null && num(ov) != null ? num(v3) <= num(ov) : true;
|
|
60604
|
+
case "gt":
|
|
60605
|
+
return num(v3) != null && num(ov) != null ? num(v3) > num(ov) : true;
|
|
60606
|
+
case "gte":
|
|
60607
|
+
return num(v3) != null && num(ov) != null ? num(v3) >= num(ov) : true;
|
|
60608
|
+
case "in":
|
|
60609
|
+
return ov instanceof Set ? ov.has(v3) : true;
|
|
60610
|
+
case "notIn":
|
|
60611
|
+
return ov instanceof Set ? !ov.has(v3) : true;
|
|
60612
|
+
default:
|
|
60613
|
+
return true;
|
|
60614
|
+
}
|
|
60615
|
+
});
|
|
60616
|
+
}
|
|
60617
|
+
const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
|
|
60618
|
+
function Cap({ children: children2, help }) {
|
|
60619
|
+
return /* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: children2 }) : children2 });
|
|
59474
60620
|
}
|
|
59475
60621
|
function Card({ children: children2, mt = "3" }) {
|
|
59476
60622
|
return /* @__PURE__ */ jsx$1(Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", mt, children: children2 });
|
|
59477
60623
|
}
|
|
60624
|
+
function RunError({ error: error3 }) {
|
|
60625
|
+
const tail = (error3.stderr || error3.stdout || "").trim().split("\n").slice(-12).join("\n");
|
|
60626
|
+
return /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", flexDirection: "column", gap: "2", mt: "3", children: [
|
|
60627
|
+
/* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "flex-start", gap: "2", color: "fg.danger", children: [
|
|
60628
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", mt: "0.5", fontSize: "12px", flexShrink: "0", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60629
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
60630
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "Could not run the experiment." }),
|
|
60631
|
+
" ",
|
|
60632
|
+
error3.message
|
|
60633
|
+
] })
|
|
60634
|
+
] }),
|
|
60635
|
+
tail && /* @__PURE__ */ jsx$1(
|
|
60636
|
+
Box,
|
|
60637
|
+
{
|
|
60638
|
+
as: "pre",
|
|
60639
|
+
m: "0",
|
|
60640
|
+
p: "2",
|
|
60641
|
+
maxH: "160px",
|
|
60642
|
+
overflow: "auto",
|
|
60643
|
+
bg: "bg.canvas",
|
|
60644
|
+
borderRadius: "md",
|
|
60645
|
+
borderWidth: "1px",
|
|
60646
|
+
borderColor: "border.subtle",
|
|
60647
|
+
fontFamily: "mono",
|
|
60648
|
+
fontSize: "11px",
|
|
60649
|
+
color: "fg.muted",
|
|
60650
|
+
whiteSpace: "pre-wrap",
|
|
60651
|
+
children: tail
|
|
60652
|
+
}
|
|
60653
|
+
)
|
|
60654
|
+
] });
|
|
60655
|
+
}
|
|
59478
60656
|
function ActionButton({ button, variant: variant2, label, onClick, disabled: disabled2, pulse = false }) {
|
|
59479
60657
|
return /* @__PURE__ */ jsx$1(
|
|
59480
60658
|
Box,
|
|
@@ -59490,8 +60668,46 @@ function ActionButton({ button, variant: variant2, label, onClick, disabled: dis
|
|
|
59490
60668
|
}
|
|
59491
60669
|
);
|
|
59492
60670
|
}
|
|
60671
|
+
function DeckSkeleton({ tab }) {
|
|
60672
|
+
const sk = useRecipe({ key: "skeleton" });
|
|
60673
|
+
const bar = (w2, h2) => /* @__PURE__ */ jsx$1(Box, { css: sk({ variant: "line" }), width: w2, height: h2 });
|
|
60674
|
+
const block = (h2) => /* @__PURE__ */ jsx$1(Box, { css: sk({ variant: "block" }), width: "100%", minHeight: h2 });
|
|
60675
|
+
return /* @__PURE__ */ jsxs(Box, { p: "4.5", display: "flex", flexDirection: "column", gap: "4", children: [
|
|
60676
|
+
tab === "answer" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60677
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", gap: "4.5", alignItems: "flex-end", children: [
|
|
60678
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "2", children: [
|
|
60679
|
+
bar("64px", "10px"),
|
|
60680
|
+
bar("120px", "32px")
|
|
60681
|
+
] }),
|
|
60682
|
+
bar("170px", "24px")
|
|
60683
|
+
] }),
|
|
60684
|
+
block("120px"),
|
|
60685
|
+
block("120px")
|
|
60686
|
+
] }),
|
|
60687
|
+
tab === "trust" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60688
|
+
bar("70%", "14px"),
|
|
60689
|
+
block("160px")
|
|
60690
|
+
] }),
|
|
60691
|
+
tab === "dose" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60692
|
+
block("240px"),
|
|
60693
|
+
/* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", children: [
|
|
60694
|
+
block("90px"),
|
|
60695
|
+
block("90px")
|
|
60696
|
+
] })
|
|
60697
|
+
] }),
|
|
60698
|
+
tab === "validate" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
60699
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", gap: "5", alignItems: "flex-end", children: [
|
|
60700
|
+
bar("120px", "32px"),
|
|
60701
|
+
bar("200px", "10px")
|
|
60702
|
+
] }),
|
|
60703
|
+
block("110px"),
|
|
60704
|
+
block("150px")
|
|
60705
|
+
] })
|
|
60706
|
+
] });
|
|
60707
|
+
}
|
|
60708
|
+
const experimentValueEqual = equalFor(Experiment.Component.schema);
|
|
59493
60709
|
const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
59494
|
-
var _a2, _b2, _c2;
|
|
60710
|
+
var _a2, _b2, _c2, _d2;
|
|
59495
60711
|
const v3 = value;
|
|
59496
60712
|
const button = useRecipe({ key: "button" });
|
|
59497
60713
|
const chip = useRecipe({ key: "chip" });
|
|
@@ -59501,12 +60717,19 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
|
59501
60717
|
const es = useSlotRecipe({ key: "eyebrowRow" })({});
|
|
59502
60718
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
59503
60719
|
const data4 = useBindingValue(v3.data);
|
|
59504
|
-
const
|
|
60720
|
+
const configBind = useBindingValue(v3.config);
|
|
59505
60721
|
const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
|
|
60722
|
+
const populationBind = useBindingValue(v3.population.type === "some" ? v3.population.value : null);
|
|
59506
60723
|
const meta3 = getOpt(v3.columnMeta);
|
|
59507
60724
|
const readonly = getOpt(v3.readonly) ?? false;
|
|
59508
60725
|
const { columns, rowArrayType } = useColumns(workspace, v3.data.source);
|
|
59509
|
-
const
|
|
60726
|
+
const config2 = configBind.value;
|
|
60727
|
+
const [localPop, setLocalPop] = useState([]);
|
|
60728
|
+
const population = populationBind.value ?? localPop;
|
|
60729
|
+
const filteredRows = useMemo(() => {
|
|
60730
|
+
if (!data4.value) return null;
|
|
60731
|
+
return population.length ? data4.value.filter((r2) => rowMatchesAll(r2, population)) : data4.value;
|
|
60732
|
+
}, [data4.value, population]);
|
|
59510
60733
|
const fields = useMemo(
|
|
59511
60734
|
() => columns.filter((c2) => c2.kind !== "other").map((c2) => {
|
|
59512
60735
|
var _a3;
|
|
@@ -59518,132 +60741,176 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
|
59518
60741
|
}),
|
|
59519
60742
|
[columns, meta3]
|
|
59520
60743
|
);
|
|
59521
|
-
const estInputs = useMemo(() => rowArrayType ? [rowArrayType, Experiment.Types.
|
|
59522
|
-
const
|
|
59523
|
-
const
|
|
59524
|
-
const
|
|
59525
|
-
|
|
59526
|
-
|
|
59527
|
-
|
|
59528
|
-
|
|
59529
|
-
|
|
59530
|
-
|
|
60744
|
+
const estInputs = useMemo(() => rowArrayType ? [rowArrayType, Experiment.Types.Config] : null, [rowArrayType]);
|
|
60745
|
+
const experiment = useFuncCall(v3.experiment.name, estInputs, Experiment.Types.Result);
|
|
60746
|
+
const hasDesign = v3.design.type === "some";
|
|
60747
|
+
const designInputs = useMemo(
|
|
60748
|
+
() => rowArrayType ? [rowArrayType, Experiment.Types.Config, Experiment.Types.Result, Experiment.Types.DesignConfig] : null,
|
|
60749
|
+
[rowArrayType]
|
|
60750
|
+
);
|
|
60751
|
+
const design = useFuncCall(v3.design.type === "some" ? v3.design.value.name : null, designInputs, Experiment.Types.Design);
|
|
60752
|
+
const pendingConfigRef = useRef(null);
|
|
60753
|
+
const [ranConfig, setRanConfig] = useState(null);
|
|
59531
60754
|
const runAll = useCallback(() => {
|
|
59532
|
-
if (!
|
|
59533
|
-
|
|
59534
|
-
|
|
59535
|
-
|
|
59536
|
-
}, [data4.value, spec, estimate, refute, dose, doseFeature, v3.refute.type, v3.dose.type]);
|
|
60755
|
+
if (!filteredRows || !config2) return;
|
|
60756
|
+
pendingConfigRef.current = config2;
|
|
60757
|
+
experiment.call(filteredRows, config2);
|
|
60758
|
+
}, [filteredRows, config2, experiment]);
|
|
59537
60759
|
const autoRan = useRef(false);
|
|
59538
60760
|
useEffect(() => {
|
|
59539
60761
|
if (autoRan.current) return;
|
|
59540
|
-
if (!
|
|
59541
|
-
if (
|
|
60762
|
+
if (!filteredRows || !config2 || !rowArrayType) return;
|
|
60763
|
+
if (experiment.result !== null || experiment.status === "running") {
|
|
59542
60764
|
autoRan.current = true;
|
|
59543
60765
|
return;
|
|
59544
60766
|
}
|
|
59545
|
-
if (
|
|
60767
|
+
if (experiment.status !== "idle") return;
|
|
59546
60768
|
autoRan.current = true;
|
|
60769
|
+
if (filteredRows.length > AUTORUN_MAX_ROWS) return;
|
|
59547
60770
|
runAll();
|
|
59548
|
-
}, [
|
|
60771
|
+
}, [filteredRows, config2, rowArrayType, experiment.result, experiment.status, runAll]);
|
|
60772
|
+
useEffect(() => {
|
|
60773
|
+
if (experiment.result !== null) setRanConfig(pendingConfigRef.current);
|
|
60774
|
+
}, [experiment.result]);
|
|
59549
60775
|
const [stale, setStale] = useState(false);
|
|
59550
|
-
const
|
|
60776
|
+
const editConfig = useCallback((next2) => {
|
|
60777
|
+
if (readonly) return;
|
|
60778
|
+
setStale(true);
|
|
60779
|
+
queueMicrotask(() => configBind.mutate(next2));
|
|
60780
|
+
}, [readonly, configBind]);
|
|
60781
|
+
const editPopulation = useCallback((next2) => {
|
|
60782
|
+
if (readonly) return;
|
|
59551
60783
|
setStale(true);
|
|
59552
|
-
queueMicrotask(() =>
|
|
59553
|
-
|
|
60784
|
+
if (v3.population.type === "some") queueMicrotask(() => populationBind.mutate(next2));
|
|
60785
|
+
else setLocalPop(next2);
|
|
60786
|
+
}, [readonly, v3.population.type, populationBind]);
|
|
59554
60787
|
const onRun = useCallback(() => {
|
|
59555
60788
|
runAll();
|
|
59556
60789
|
setStale(false);
|
|
59557
60790
|
}, [runAll]);
|
|
59558
60791
|
const onCommit = useCallback(async () => {
|
|
59559
|
-
if (!
|
|
59560
|
-
const
|
|
60792
|
+
if (!config2 || !experiment.result) return;
|
|
60793
|
+
const r2 = experiment.result;
|
|
60794
|
+
const row = {
|
|
60795
|
+
config: config2,
|
|
60796
|
+
verdict: r2.verdict,
|
|
60797
|
+
naive: r2.naive,
|
|
60798
|
+
adjusted: r2.adjusted.type === "some" ? some(r2.adjusted.value.effect) : none$1,
|
|
60799
|
+
committed_at: /* @__PURE__ */ new Date(),
|
|
60800
|
+
committed_by: "you"
|
|
60801
|
+
};
|
|
59561
60802
|
journalBind.mutate([row, ...journalBind.value ?? []]);
|
|
59562
60803
|
try {
|
|
59563
60804
|
await journalBind.commit();
|
|
59564
|
-
await
|
|
60805
|
+
await configBind.commit();
|
|
60806
|
+
if (v3.population.type === "some") await populationBind.commit();
|
|
59565
60807
|
setStale(false);
|
|
59566
60808
|
} catch {
|
|
59567
60809
|
}
|
|
59568
|
-
}, [
|
|
60810
|
+
}, [config2, experiment.result, journalBind, configBind, populationBind, v3.population.type]);
|
|
59569
60811
|
const [tab, setTab] = useState(((_a2 = getOpt(v3.defaultTab)) == null ? void 0 : _a2.type) ?? "answer");
|
|
60812
|
+
const [guidance, setGuidance] = useState(true);
|
|
59570
60813
|
const now2 = useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
59571
|
-
const nRows = (
|
|
60814
|
+
const nRows = (filteredRows == null ? void 0 : filteredRows.length) ?? 0;
|
|
60815
|
+
const helpVars = useMemo(() => {
|
|
60816
|
+
const rc = ranConfig ?? config2;
|
|
60817
|
+
const labelOf = (col) => {
|
|
60818
|
+
var _a3;
|
|
60819
|
+
return col ? getOpt((_a3 = meta3 == null ? void 0 : meta3.get(col)) == null ? void 0 : _a3.label) ?? col : "";
|
|
60820
|
+
};
|
|
60821
|
+
return { treatment: labelOf(rc == null ? void 0 : rc.treatment), outcome: labelOf(rc == null ? void 0 : rc.outcome), subject: SUBJECT_ONE, subjects: SUBJECT_MANY };
|
|
60822
|
+
}, [ranConfig, config2, meta3]);
|
|
59572
60823
|
const view = useMemo(() => {
|
|
59573
|
-
if (!
|
|
59574
|
-
return deriveView(
|
|
59575
|
-
}, [
|
|
59576
|
-
|
|
59577
|
-
|
|
59578
|
-
|
|
59579
|
-
|
|
59580
|
-
|
|
59581
|
-
|
|
59582
|
-
const
|
|
60824
|
+
if (!config2) return null;
|
|
60825
|
+
return deriveView(config2, ranConfig ?? config2, columns, experiment.result, journalBind.value, meta3, nRows, now2);
|
|
60826
|
+
}, [config2, ranConfig, columns, experiment.result, journalBind.value, meta3, nRows, now2]);
|
|
60827
|
+
const designConfigValue = useMemo(() => ({
|
|
60828
|
+
alpha: none$1,
|
|
60829
|
+
target_power: none$1,
|
|
60830
|
+
materiality: none$1,
|
|
60831
|
+
treated_shares: some([0.5, 0.3])
|
|
60832
|
+
}), []);
|
|
60833
|
+
const designSnapRef = useRef(null);
|
|
60834
|
+
useEffect(() => {
|
|
60835
|
+
var _a3;
|
|
60836
|
+
if (tab !== "validate" || !hasDesign || design.pending) return;
|
|
60837
|
+
if (!filteredRows || !config2 || !experiment.result) return;
|
|
60838
|
+
if (((_a3 = designSnapRef.current) == null ? void 0 : _a3.result) === experiment.result) return;
|
|
60839
|
+
designSnapRef.current = { result: experiment.result, config: ranConfig ?? config2 };
|
|
60840
|
+
design.call(filteredRows, ranConfig ?? config2, experiment.result, designConfigValue);
|
|
60841
|
+
}, [tab, hasDesign, filteredRows, config2, ranConfig, experiment.result, design, designConfigValue]);
|
|
60842
|
+
const vmDesign = useMemo(
|
|
60843
|
+
() => design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3) : null,
|
|
60844
|
+
[design.result, meta3]
|
|
60845
|
+
);
|
|
60846
|
+
const designFresh = design.result !== null && ((_b2 = designSnapRef.current) == null ? void 0 : _b2.result) === experiment.result && !design.pending;
|
|
60847
|
+
if (!config2 || !view) {
|
|
60848
|
+
const failed2 = experiment.status === "failed";
|
|
60849
|
+
return /* @__PURE__ */ jsx$1(Box, { layerStyle: "frame", p: "6", children: failed2 && experiment.error ? /* @__PURE__ */ jsx$1(RunError, { error: experiment.error }) : /* @__PURE__ */ jsx$1(Text, { className: failed2 ? void 0 : "elara-skeleton", textStyle: "body.sm", color: "fg.muted", children: failed2 ? "Could not run the experiment." : "Loading experiment…" }) });
|
|
60850
|
+
}
|
|
60851
|
+
const { spec: vs, answer: a2, refusal: ref, overlap: ov, refute: vr, dose: vd, journal, verdict } = view;
|
|
59583
60852
|
const higherBetter = getOpt((_c2 = meta3 == null ? void 0 : meta3.get(vs.outcome)) == null ? void 0 : _c2.higherIsBetter);
|
|
59584
|
-
const dirUp = ans.effect > 0;
|
|
59585
|
-
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
59586
|
-
const flip = Math.sign(ans.naive) !== Math.sign(ans.effect) && ans.naive !== 0;
|
|
59587
|
-
const top = ans.balance[0] ?? { col: "", display: "" };
|
|
59588
|
-
const lowerWord = ans.naive < 0 ? "lower" : "higher";
|
|
59589
|
-
const population = getOpt(spec.population) ?? [];
|
|
59590
60853
|
const dataStatus = statusR({ status: "success", size: "sm" });
|
|
59591
60854
|
const barList = (rows) => /* @__PURE__ */ jsx$1(Box, { css: bs.root, children: rows.map((r2, i) => /* @__PURE__ */ jsxs(Box, { css: bs.row, children: [
|
|
59592
60855
|
/* @__PURE__ */ jsx$1(Text, { css: bs.label, textStyle: "mono.sm", color: "fg.default", truncate: true, children: r2.label }),
|
|
59593
60856
|
/* @__PURE__ */ jsx$1(Box, { css: bs.track, children: /* @__PURE__ */ jsx$1(Box, { css: bs.fill, width: `${Math.round(r2.frac * 100)}%`, bg: toneToken(r2.tone) }) }),
|
|
59594
60857
|
/* @__PURE__ */ jsx$1(Text, { css: bs.value, children: r2.value })
|
|
59595
60858
|
] }, i)) });
|
|
59596
|
-
const specStaged =
|
|
60859
|
+
const specStaged = configBind.mode === "staged";
|
|
59597
60860
|
const hasJournal = v3.journal.type === "some";
|
|
59598
60861
|
const canRun = !readonly;
|
|
59599
60862
|
const canCommit = !readonly && specStaged && hasJournal;
|
|
59600
|
-
const runDisabled = !data4.value ||
|
|
59601
|
-
const commitDisabled =
|
|
59602
|
-
|
|
60863
|
+
const runDisabled = !data4.value || experiment.pending;
|
|
60864
|
+
const commitDisabled = experiment.status !== "succeeded" || experiment.pending || stale;
|
|
60865
|
+
const failed = experiment.status === "failed" && experiment.error;
|
|
60866
|
+
const showResult = experiment.result !== null && !experiment.pending && !failed;
|
|
60867
|
+
const tabKeys = [...["answer", "trust", "dose"], ...hasDesign ? ["validate"] : []];
|
|
60868
|
+
return /* @__PURE__ */ jsx$1(GuidanceProvider, { on: guidance, vars: helpVars, children: /* @__PURE__ */ jsxs(Box, { layerStyle: "frame", overflow: "visible", children: [
|
|
59603
60869
|
/* @__PURE__ */ jsxs(Box, { layerStyle: "header.bar", display: "flex", alignItems: "center", gap: "3.5", children: [
|
|
59604
|
-
/* @__PURE__ */
|
|
59605
|
-
"Does
|
|
59606
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.
|
|
59607
|
-
"
|
|
59608
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.
|
|
60870
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "title.card", color: "fg.muted", children: /* @__PURE__ */ jsxs(Help, { id: "header", children: [
|
|
60871
|
+
"Does ",
|
|
60872
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
|
|
60873
|
+
" change ",
|
|
60874
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
|
|
59609
60875
|
"?"
|
|
59610
|
-
] }),
|
|
60876
|
+
] }) }),
|
|
59611
60877
|
/* @__PURE__ */ jsx$1(Box, { flex: "1" }),
|
|
59612
60878
|
/* @__PURE__ */ jsxs(Box, { as: "span", css: dataStatus.root, children: [
|
|
59613
60879
|
/* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.indicator }),
|
|
59614
|
-
/* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.label, children: vs.dataLabel })
|
|
60880
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", css: dataStatus.label, children: stale ? `${nRows} rows` : vs.dataLabel })
|
|
59615
60881
|
] }),
|
|
60882
|
+
/* @__PURE__ */ jsx$1(GuidanceToggle, { on: guidance, onToggle: () => setGuidance((g2) => !g2) }),
|
|
59616
60883
|
canRun && /* @__PURE__ */ jsx$1(ActionButton, { button, variant: "solid", label: "Run", onClick: onRun, disabled: runDisabled, pulse: stale }),
|
|
59617
60884
|
canCommit && /* @__PURE__ */ jsx$1(ActionButton, { button, variant: "ghost", label: "Commit", onClick: onCommit, disabled: commitDisabled })
|
|
59618
60885
|
] }),
|
|
59619
60886
|
/* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "304px minmax(0,1fr)", alignItems: "start", children: [
|
|
59620
60887
|
/* @__PURE__ */ jsxs(Box, { borderRightWidth: "1px", borderColor: "border.subtle", children: [
|
|
59621
|
-
/* @__PURE__ */ jsxs(Step, { n: 1, title: "What did you change?", children: [
|
|
59622
|
-
/* @__PURE__ */ jsx$1(ColumnPick, { column: vs.treatment, kind: vs.treatmentKind, badge, button, choices: columns.filter((c2) => c2.kind === "boolean" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) =>
|
|
60888
|
+
/* @__PURE__ */ jsxs(Step, { n: 1, title: "What did you change?", help: "step_treatment", children: [
|
|
60889
|
+
/* @__PURE__ */ jsx$1(ColumnPick, { column: vs.treatment, kind: vs.treatmentKind, badge, button, choices: columns.filter((c2) => c2.kind === "boolean" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, treatment: c2 }) }),
|
|
59623
60890
|
/* @__PURE__ */ jsxs(Text, { textStyle: "caption", mt: "1.5", children: [
|
|
59624
60891
|
"Treated = ",
|
|
59625
60892
|
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: vs.comparison })
|
|
59626
60893
|
] })
|
|
59627
60894
|
] }),
|
|
59628
|
-
/* @__PURE__ */ jsx$1(Step, { n: 2, title: "What did you want it to improve?", children: /* @__PURE__ */ jsx$1(ColumnPick, { column: vs.outcome, kind: vs.outcomeKind, badge, button, choices: columns.filter((c2) => c2.kind === "float" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) =>
|
|
59629
|
-
/* @__PURE__ */ jsx$1(Step, { n: 3, title: "What else was different
|
|
59630
|
-
vs.confounders.map((c2, i) => /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 78px 16px", gap: "2.5", alignItems: "center", px: "2.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
60895
|
+
/* @__PURE__ */ jsx$1(Step, { n: 2, title: "What did you want it to improve?", help: "step_outcome", children: /* @__PURE__ */ jsx$1(ColumnPick, { column: vs.outcome, kind: vs.outcomeKind, badge, button, choices: columns.filter((c2) => c2.kind === "float" || c2.kind === "integer").map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, outcome: c2 }) }) }),
|
|
60896
|
+
/* @__PURE__ */ jsx$1(Step, { n: 3, title: "What else was different?", help: "step_confounders", children: /* @__PURE__ */ jsxs(Box, { layerStyle: "frame.flat", children: [
|
|
60897
|
+
/* @__PURE__ */ jsx$1(Box, { maxH: "216px", overflowY: "auto", children: vs.confounders.map((c2, i) => /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 78px 16px", gap: "2.5", alignItems: "center", px: "2.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
59631
60898
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
59632
60899
|
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", fontWeight: "semibold", color: "fg.default", children: c2.col }),
|
|
59633
60900
|
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption", lineHeight: "1.35", mt: "px", children: c2.reason })
|
|
59634
60901
|
] }),
|
|
59635
60902
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
59636
60903
|
/* @__PURE__ */ jsx$1(Box, { css: bs.track, children: /* @__PURE__ */ jsx$1(Box, { css: bs.fill, width: `${Math.round(c2.imbalance * 100)}%`, bg: toneToken(c2.tone) }) }),
|
|
59637
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: c2.level })
|
|
60904
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: /* @__PURE__ */ jsx$1(Help, { id: "confounder_imbalance", children: c2.level }) })
|
|
59638
60905
|
] }),
|
|
59639
|
-
/* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "ghost", size: "xs" }), px: "0", minW: "16px", display: "inline-flex", alignItems: "center", justifyContent: "center", "aria-label": `Remove ${c2.col}`, onClick: () =>
|
|
59640
|
-
] }, i)),
|
|
59641
|
-
vs.suggestion && /* @__PURE__ */ jsx$1(ColumnMenu, { choices: columns.filter((c2) => !(/* @__PURE__ */ new Set([
|
|
60906
|
+
/* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "ghost", size: "xs" }), px: "0", minW: "16px", display: "inline-flex", alignItems: "center", justifyContent: "center", "aria-label": `Remove ${c2.col}`, onClick: () => editConfig({ ...config2, common_causes: config2.common_causes.filter((x2) => x2 !== c2.col), categorical: config2.categorical.type === "some" ? some(config2.categorical.value.filter((x2) => x2 !== c2.col)) : none$1 }), children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faXmark, style: { fontSize: "11px" } }) })
|
|
60907
|
+
] }, i)) }),
|
|
60908
|
+
vs.suggestion && /* @__PURE__ */ jsx$1(ColumnMenu, { choices: columns.filter((c2) => !(/* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes])).has(c2.name)).map((c2) => c2.name), onPick: (c2) => editConfig({ ...config2, common_causes: [...config2.common_causes, c2] }), children: /* @__PURE__ */ jsxs(Box, { as: "button", css: button({ variant: "ghost", size: "sm" }), justifyContent: "flex-start", width: "100%", color: "brand.fg", fontFamily: "mono", display: "inline-flex", alignItems: "center", gap: "2", children: [
|
|
59642
60909
|
/* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faPlus, style: { fontSize: "9px" } }),
|
|
59643
60910
|
"add another"
|
|
59644
60911
|
] }) })
|
|
59645
60912
|
] }) }),
|
|
59646
|
-
/* @__PURE__ */ jsx$1(Step, { n: 4, title:
|
|
60913
|
+
/* @__PURE__ */ jsx$1(Step, { n: 4, title: `Which ${SUBJECT_MANY}?`, help: "step_population", children: /* @__PURE__ */ jsx$1(FilterRail, { fields, population, onChange: editPopulation, chip, button }) }),
|
|
59647
60914
|
/* @__PURE__ */ jsxs(Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59648
60915
|
/* @__PURE__ */ jsxs(
|
|
59649
60916
|
Box,
|
|
@@ -59676,173 +60943,168 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
|
59676
60943
|
}
|
|
59677
60944
|
),
|
|
59678
60945
|
/* @__PURE__ */ jsxs(Box, { px: "4.5", pb: "3.5", pt: "0.5", children: [
|
|
59679
|
-
/* @__PURE__ */ jsx$1(Segmented, { label: "How to compare", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) =>
|
|
59680
|
-
/* @__PURE__ */ jsx$1(Segmented, { label: "Answer for", left: "all
|
|
59681
|
-
/* @__PURE__ */ jsx$1(Segmented, { label: "Drop un-matchable", left: "on", right: "off", active: vs.trim ? "left" : "right", onPick: (s2) => editSpec({ ...spec, trim: s2 === "left" ? some(variant("overlap", null)) : none$1 }), last: true })
|
|
60946
|
+
/* @__PURE__ */ jsx$1(Segmented, { label: "How to compare", help: "adv_method", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, method: some(s2 === "right" ? variant("propensity_score_weighting", { weighting_scheme: none$1 }) : variant("linear_regression", null)) }) }),
|
|
60947
|
+
/* @__PURE__ */ jsx$1(Segmented, { label: "Answer for", help: "adv_estimand", left: "all", right: "only treated", active: vs.target === "treated" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, estimand: some(variant(s2 === "right" ? "att" : "ate", null)) }), last: true })
|
|
59682
60948
|
] })
|
|
59683
60949
|
] })
|
|
59684
60950
|
] }),
|
|
59685
60951
|
/* @__PURE__ */ jsxs(Box, { minW: "0", children: [
|
|
59686
|
-
/* @__PURE__ */
|
|
59687
|
-
|
|
59688
|
-
|
|
59689
|
-
|
|
59690
|
-
|
|
59691
|
-
|
|
59692
|
-
|
|
59693
|
-
|
|
59694
|
-
|
|
59695
|
-
|
|
59696
|
-
|
|
59697
|
-
|
|
59698
|
-
|
|
59699
|
-
|
|
59700
|
-
|
|
59701
|
-
|
|
59702
|
-
|
|
59703
|
-
|
|
60952
|
+
/* @__PURE__ */ jsxs(
|
|
60953
|
+
Box,
|
|
60954
|
+
{
|
|
60955
|
+
display: "flex",
|
|
60956
|
+
alignItems: "center",
|
|
60957
|
+
px: "4",
|
|
60958
|
+
borderBottomWidth: "1px",
|
|
60959
|
+
borderColor: "border.subtle",
|
|
60960
|
+
role: "tablist",
|
|
60961
|
+
"aria-label": "Result views",
|
|
60962
|
+
onKeyDown: (e3) => {
|
|
60963
|
+
var _a3;
|
|
60964
|
+
const i = tabKeys.indexOf(tab);
|
|
60965
|
+
let j2 = i;
|
|
60966
|
+
if (e3.key === "ArrowRight") j2 = (i + 1) % tabKeys.length;
|
|
60967
|
+
else if (e3.key === "ArrowLeft") j2 = (i - 1 + tabKeys.length) % tabKeys.length;
|
|
60968
|
+
else if (e3.key === "Home") j2 = 0;
|
|
60969
|
+
else if (e3.key === "End") j2 = tabKeys.length - 1;
|
|
60970
|
+
else return;
|
|
60971
|
+
e3.preventDefault();
|
|
60972
|
+
setTab(tabKeys[j2]);
|
|
60973
|
+
const btns = e3.currentTarget.querySelectorAll('[role="tab"]');
|
|
60974
|
+
(_a3 = btns[j2]) == null ? void 0 : _a3.focus();
|
|
59704
60975
|
},
|
|
59705
|
-
|
|
59706
|
-
|
|
59707
|
-
|
|
59708
|
-
|
|
59709
|
-
|
|
59710
|
-
|
|
59711
|
-
|
|
59712
|
-
|
|
59713
|
-
|
|
59714
|
-
|
|
59715
|
-
|
|
59716
|
-
|
|
59717
|
-
|
|
59718
|
-
|
|
59719
|
-
|
|
60976
|
+
children: [
|
|
60977
|
+
tabKeys.map((tk) => {
|
|
60978
|
+
const on3 = tab === tk;
|
|
60979
|
+
const tabHelp = tk === "answer" ? "tab_answer" : tk === "trust" ? "tab_trust" : tk === "dose" ? "tab_dose" : "tab_validate";
|
|
60980
|
+
const tabLabel = tk === "answer" ? "Answer" : tk === "trust" ? "Trust" : tk === "dose" ? "Dose" : "Validate";
|
|
60981
|
+
return /* @__PURE__ */ jsx$1(
|
|
60982
|
+
Box,
|
|
60983
|
+
{
|
|
60984
|
+
as: "button",
|
|
60985
|
+
role: "tab",
|
|
60986
|
+
"aria-selected": on3,
|
|
60987
|
+
tabIndex: on3 ? 0 : -1,
|
|
60988
|
+
onClick: () => setTab(tk),
|
|
60989
|
+
cursor: "pointer",
|
|
60990
|
+
fontSize: "xs",
|
|
60991
|
+
fontWeight: "semibold",
|
|
60992
|
+
px: "3.5",
|
|
60993
|
+
py: "3",
|
|
60994
|
+
mb: "-1px",
|
|
60995
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
60996
|
+
borderBottomWidth: "2px",
|
|
60997
|
+
borderColor: on3 ? "brand.solid" : "transparent",
|
|
60998
|
+
_hover: { color: on3 ? "brand.fg" : "fg.default" },
|
|
60999
|
+
_focusVisible: { outline: "2px solid", outlineColor: "brand.solid", outlineOffset: "-2px" },
|
|
61000
|
+
children: /* @__PURE__ */ jsx$1(Help, { id: tabHelp, children: tabLabel })
|
|
61001
|
+
},
|
|
61002
|
+
tk
|
|
61003
|
+
);
|
|
61004
|
+
}),
|
|
61005
|
+
experiment.pending && /* @__PURE__ */ jsxs(Box, { ml: "auto", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", pr: "1", children: [
|
|
61006
|
+
/* @__PURE__ */ jsx$1(Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
61007
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", children: "Running…" })
|
|
59720
61008
|
] })
|
|
59721
|
-
]
|
|
59722
|
-
|
|
59723
|
-
|
|
59724
|
-
|
|
59725
|
-
|
|
59726
|
-
|
|
59727
|
-
flip && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
59728
|
-
/* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61009
|
+
]
|
|
61010
|
+
}
|
|
61011
|
+
),
|
|
61012
|
+
failed ? /* @__PURE__ */ jsx$1(Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsx$1(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsx$1(DeckSkeleton, { tab }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
61013
|
+
stale && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "center", gap: "2", mx: "4.5", mt: "4.5", children: [
|
|
61014
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
59729
61015
|
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
59730
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "
|
|
59731
|
-
"
|
|
59732
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: ans.treatment }),
|
|
59733
|
-
" batches sit ",
|
|
59734
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
59735
|
-
" on ",
|
|
59736
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: ans.outcome }),
|
|
59737
|
-
" (",
|
|
59738
|
-
signed(ans.naive),
|
|
59739
|
-
") — but they also differ most on ",
|
|
59740
|
-
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
59741
|
-
" (",
|
|
59742
|
-
top.display,
|
|
59743
|
-
"). Adjusting for it reverses the result."
|
|
61016
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "Showing the previous setup." }),
|
|
61017
|
+
" Hit Run to update these results for your edits."
|
|
59744
61018
|
] })
|
|
59745
61019
|
] }),
|
|
59746
|
-
/* @__PURE__ */
|
|
59747
|
-
|
|
59748
|
-
|
|
59749
|
-
|
|
59750
|
-
|
|
59751
|
-
|
|
59752
|
-
|
|
59753
|
-
|
|
59754
|
-
|
|
59755
|
-
|
|
59756
|
-
|
|
59757
|
-
|
|
59758
|
-
|
|
59759
|
-
|
|
59760
|
-
|
|
59761
|
-
|
|
59762
|
-
|
|
59763
|
-
|
|
59764
|
-
/* @__PURE__ */
|
|
59765
|
-
|
|
59766
|
-
|
|
59767
|
-
|
|
59768
|
-
" ",
|
|
59769
|
-
label
|
|
59770
|
-
] }, i)) })
|
|
59771
|
-
] }),
|
|
59772
|
-
tab === "trust" && vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
59773
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }),
|
|
59774
|
-
/* @__PURE__ */ jsx$1(Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
59775
|
-
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
59776
|
-
return /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "auto 1fr minmax(96px,auto)", gap: "2.5", alignItems: "start", px: "3.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
59777
|
-
/* @__PURE__ */ jsx$1(Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsx$1(Box, { as: "span", css: cs.indicator }) }),
|
|
59778
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
59779
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: c2.name }),
|
|
59780
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
59781
|
-
] }),
|
|
59782
|
-
/* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
59783
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
59784
|
-
/* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
59785
|
-
] })
|
|
59786
|
-
] }, i);
|
|
59787
|
-
}) }),
|
|
59788
|
-
/* @__PURE__ */ jsxs(Card, { children: [
|
|
59789
|
-
/* @__PURE__ */ jsx$1(Cap, { children: "Effect as a hidden cause is made stronger" }),
|
|
59790
|
-
/* @__PURE__ */ jsx$1(AreaRange, { lo: vr.sensLo, mid: vr.sensMid, hi: vr.sensHi, zero: 0, tone: "brand", xTicks: vr.sensXTicks, yTicks: vr.sensYTicks, height: 132 })
|
|
59791
|
-
] })
|
|
59792
|
-
] }),
|
|
59793
|
-
tab === "dose" && vd && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
59794
|
-
vd.segments.length > 1 && /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "2.5", mb: "3.5", children: [
|
|
59795
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", children: "Response for" }),
|
|
59796
|
-
/* @__PURE__ */ jsx$1(SegmentSelect, { options: vd.segments, active: 0, onPick: () => {
|
|
59797
|
-
} })
|
|
61020
|
+
tab === "answer" && a2 && /* @__PURE__ */ jsx$1(AnswerNumeric, { a: a2, verdict, higherBetter, badge, barList }),
|
|
61021
|
+
tab === "answer" && !a2 && ref && /* @__PURE__ */ jsx$1(RefusalZone, { refusal: ref, overlap: ov, naiveValue: ((_d2 = experiment.result) == null ? void 0 : _d2.naive) ?? 0, outcome: vs.outcome }),
|
|
61022
|
+
tab === "trust" && vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61023
|
+
/* @__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." }) }),
|
|
61024
|
+
/* @__PURE__ */ jsx$1(Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
61025
|
+
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
61026
|
+
return /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "auto 1fr minmax(96px,auto)", gap: "2.5", alignItems: "start", px: "3.5", py: "2.5", borderTopWidth: i ? "1px" : "0", borderColor: "border.subtle", children: [
|
|
61027
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsx$1(Box, { as: "span", css: cs.indicator }) }),
|
|
61028
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
61029
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: /* @__PURE__ */ jsx$1(Help, { id: c2.help, children: c2.name }) }),
|
|
61030
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
61031
|
+
] }),
|
|
61032
|
+
/* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
61033
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
61034
|
+
/* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
61035
|
+
] })
|
|
61036
|
+
] }, i);
|
|
61037
|
+
}) }),
|
|
61038
|
+
vr.sens && /* @__PURE__ */ jsxs(Card, { children: [
|
|
61039
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "sensitivity", children: "Effect as a hidden cause is made stronger" }),
|
|
61040
|
+
/* @__PURE__ */ jsx$1(AreaRange, { lo: vr.sens.lo, mid: vr.sens.mid, hi: vr.sens.hi, zero: 0, tone: "brand", xTicks: vr.sens.xTicks, yTicks: vr.sens.yTicks, height: 132 })
|
|
61041
|
+
] })
|
|
59798
61042
|
] }),
|
|
59799
|
-
/* @__PURE__ */ jsxs(
|
|
59800
|
-
/* @__PURE__ */ jsxs(
|
|
59801
|
-
|
|
59802
|
-
"
|
|
59803
|
-
vd.feature
|
|
61043
|
+
tab === "trust" && !vr && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61044
|
+
/* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
|
|
61045
|
+
/* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
|
|
61046
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Nothing to stress-test" })
|
|
59804
61047
|
] }),
|
|
59805
|
-
/* @__PURE__ */
|
|
61048
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: [
|
|
61049
|
+
"There’s no adjusted estimate to try to break — the experiment couldn’t produce one (see the ",
|
|
61050
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
|
|
61051
|
+
" tab for why)."
|
|
61052
|
+
] })
|
|
59806
61053
|
] }),
|
|
59807
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
59808
|
-
/* @__PURE__ */ jsxs(
|
|
59809
|
-
/* @__PURE__ */
|
|
59810
|
-
|
|
59811
|
-
|
|
59812
|
-
|
|
59813
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
59814
|
-
/* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
59815
|
-
vd.outcome,
|
|
59816
|
-
" · ",
|
|
59817
|
-
signed(vd.recoLo),
|
|
59818
|
-
" … ",
|
|
59819
|
-
signed(vd.recoHi)
|
|
59820
|
-
] })
|
|
59821
|
-
] })
|
|
61054
|
+
tab === "dose" && vd && /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61055
|
+
/* @__PURE__ */ jsxs(Card, { mt: "0", children: [
|
|
61056
|
+
/* @__PURE__ */ jsxs(Cap, { help: "dose_curve", children: [
|
|
61057
|
+
vd.outcome,
|
|
61058
|
+
" gained vs. ",
|
|
61059
|
+
vd.feature
|
|
59822
61060
|
] }),
|
|
59823
|
-
/* @__PURE__ */ jsx$1(
|
|
61061
|
+
/* @__PURE__ */ jsx$1(AreaRange, { lo: vd.lo, mid: vd.mid, hi: vd.hi, tone: "pos", xTicks: vd.xTicks, yTicks: vd.yTicks, marks: vd.marks.map((m2) => ({ at: m2.at, label: m2.label, tone: m2.tone, help: m2.help })), height: 256 })
|
|
59824
61062
|
] }),
|
|
59825
|
-
/* @__PURE__ */ jsxs(Box, {
|
|
59826
|
-
/* @__PURE__ */ jsxs(
|
|
59827
|
-
"
|
|
59828
|
-
|
|
59829
|
-
|
|
61063
|
+
/* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", mt: "3", children: [
|
|
61064
|
+
/* @__PURE__ */ jsxs(Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
61065
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "dose_reco", children: "Recommended" }),
|
|
61066
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
61067
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", children: vd.recoLabel }),
|
|
61068
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2", children: [
|
|
61069
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
61070
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
61071
|
+
vd.outcome,
|
|
61072
|
+
" · ",
|
|
61073
|
+
signed(vd.recoLo),
|
|
61074
|
+
" … ",
|
|
61075
|
+
signed(vd.recoHi)
|
|
61076
|
+
] })
|
|
61077
|
+
] })
|
|
61078
|
+
] }),
|
|
61079
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption", mt: "2.5", pt: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: vd.tradeoff })
|
|
59830
61080
|
] }),
|
|
59831
|
-
/* @__PURE__ */
|
|
61081
|
+
/* @__PURE__ */ jsxs(Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
61082
|
+
/* @__PURE__ */ jsxs(Cap, { help: "dose_marginal", children: [
|
|
61083
|
+
"Extra ",
|
|
61084
|
+
vd.outcome,
|
|
61085
|
+
" per step"
|
|
61086
|
+
] }),
|
|
61087
|
+
/* @__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) }))) })
|
|
61088
|
+
] })
|
|
59832
61089
|
] })
|
|
59833
|
-
] })
|
|
61090
|
+
] }),
|
|
61091
|
+
tab === "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 }) : /* @__PURE__ */ jsxs(Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
|
|
61092
|
+
/* @__PURE__ */ jsx$1(Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
61093
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", children: "Sizing the trial that would confirm this…" })
|
|
61094
|
+
] }))
|
|
59834
61095
|
] })
|
|
59835
61096
|
] })
|
|
59836
61097
|
] }),
|
|
59837
61098
|
journal && journal.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
59838
61099
|
/* @__PURE__ */ jsxs(Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59839
|
-
/* @__PURE__ */ jsx$1(Box, { css: es.lbl, children: "Committed experiments" }),
|
|
61100
|
+
/* @__PURE__ */ jsx$1(Box, { css: es.lbl, children: /* @__PURE__ */ jsx$1(Help, { id: "journal", children: "Committed experiments" }) }),
|
|
59840
61101
|
/* @__PURE__ */ jsxs(Box, { css: es.meta, children: [
|
|
59841
61102
|
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
|
|
59842
|
-
" on record"
|
|
61103
|
+
" on record",
|
|
61104
|
+
journal.length > 50 ? " · showing newest 50" : ""
|
|
59843
61105
|
] })
|
|
59844
61106
|
] }),
|
|
59845
|
-
journal.map((r2, i) => /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
61107
|
+
journal.slice(0, 50).map((r2, i) => /* @__PURE__ */ jsxs(Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59846
61108
|
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", children: [
|
|
59847
61109
|
/* @__PURE__ */ jsxs(Text, { as: "span", fontWeight: "bold", children: [
|
|
59848
61110
|
r2.treatment,
|
|
@@ -59856,7 +61118,7 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
|
59856
61118
|
] })
|
|
59857
61119
|
] }),
|
|
59858
61120
|
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", fontWeight: "semibold", textAlign: "right", fontVariantNumeric: "tabular-nums", color: r2.verdictTone === "pos" ? "fg.success" : "fg.default", children: r2.effect }),
|
|
59859
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", textAlign: "right", color: r2.verdictTone
|
|
61121
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", textAlign: "right", color: toneToken(r2.verdictTone), children: r2.verdict }),
|
|
59860
61122
|
/* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", textAlign: "right", color: "fg.muted", children: [
|
|
59861
61123
|
r2.who,
|
|
59862
61124
|
" · ",
|
|
@@ -59864,8 +61126,187 @@ const EastChakraExperiment = memo(function EastChakraExperiment2({ value }) {
|
|
|
59864
61126
|
] })
|
|
59865
61127
|
] }, i))
|
|
59866
61128
|
] })
|
|
61129
|
+
] }) });
|
|
61130
|
+
}, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
|
|
61131
|
+
function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
|
|
61132
|
+
const dirUp = a2.effect > 0;
|
|
61133
|
+
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
61134
|
+
const lowerWord = a2.naive < 0 ? "lower" : "higher";
|
|
61135
|
+
const top = a2.balance[0] ?? { col: "", display: "" };
|
|
61136
|
+
const kpiColor = a2.clear && !a2.cautious ? "fg.success" : "fg.warning";
|
|
61137
|
+
const badgeOk = a2.clear && !a2.cautious;
|
|
61138
|
+
const badgeText = a2.cautious ? (verdict == null ? void 0 : verdict.label) ?? "Not trustworthy yet" : a2.clear ? `${statusWord} with ${a2.treatment}` : "No clear effect";
|
|
61139
|
+
return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61140
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gap: "4.5", flexWrap: "wrap", children: [
|
|
61141
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
61142
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
|
|
61143
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
|
|
61144
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsx$1(Help, { id: "answer_effect", children: signed(a2.effect) }) }),
|
|
61145
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxs(Help, { id: "answer_ci", children: [
|
|
61146
|
+
"95% CI ",
|
|
61147
|
+
signed(a2.lo),
|
|
61148
|
+
" … ",
|
|
61149
|
+
signed(a2.hi)
|
|
61150
|
+
] }) })
|
|
61151
|
+
] })
|
|
61152
|
+
] }),
|
|
61153
|
+
/* @__PURE__ */ jsxs(Box, { as: "span", css: badge({ variant: badgeOk ? "ok" : "warn", size: "md" }), alignSelf: "flex-end", mb: "1", display: "inline-flex", alignItems: "center", gap: "1", children: [
|
|
61154
|
+
a2.clear && !a2.cautious && /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: dirUp ? faArrowUp : faArrowDown, style: { fontSize: "10px" } }),
|
|
61155
|
+
/* @__PURE__ */ jsx$1(Help, { id: `verdict_${(verdict == null ? void 0 : verdict.tag) ?? "modest"}`, children: badgeText })
|
|
61156
|
+
] })
|
|
61157
|
+
] }),
|
|
61158
|
+
a2.cautious && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
61159
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61160
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
61161
|
+
/* @__PURE__ */ jsx$1(Help, { id: "answer_cautious", children: /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "Treat this as provisional." }) }),
|
|
61162
|
+
" We adjusted and got a number, but a robustness check failed — the estimate may still be driven by something we didn’t adjust for. See ",
|
|
61163
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: "Can we trust it?" })
|
|
61164
|
+
] })
|
|
61165
|
+
] }),
|
|
61166
|
+
a2.flip && /* @__PURE__ */ jsxs(Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
61167
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61168
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
61169
|
+
/* @__PURE__ */ jsx$1(Help, { id: "answer_flip", children: /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "bold", children: "Raw and like-for-like disagree." }) }),
|
|
61170
|
+
" In the plain average, the ",
|
|
61171
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: a2.treatment }),
|
|
61172
|
+
" group sits ",
|
|
61173
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
61174
|
+
" on ",
|
|
61175
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: a2.outcome }),
|
|
61176
|
+
" (",
|
|
61177
|
+
signed(a2.naive),
|
|
61178
|
+
") — but they also differ most on ",
|
|
61179
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
61180
|
+
" (",
|
|
61181
|
+
top.display,
|
|
61182
|
+
"). Adjusting for it reverses the result."
|
|
61183
|
+
] })
|
|
61184
|
+
] }),
|
|
61185
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
61186
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "forest_plot", children: "Raw average vs. like-for-like" }),
|
|
61187
|
+
/* @__PURE__ */ jsx$1(
|
|
61188
|
+
ForestPlot,
|
|
61189
|
+
{
|
|
61190
|
+
rows: [
|
|
61191
|
+
{ label: "Raw average", note: "unadjusted", est: a2.naive, lo: a2.naiveLo, hi: a2.naiveHi, tone: a2.naive < 0 ? "neg" : "pos" },
|
|
61192
|
+
{ label: "Like-for-like", note: "adjusted", est: a2.effect, lo: a2.lo, hi: a2.hi, tone: a2.clear && !a2.cautious ? "pos" : "warn" }
|
|
61193
|
+
],
|
|
61194
|
+
min: Math.floor(Math.min(0, a2.naiveLo, a2.lo) - 2),
|
|
61195
|
+
max: Math.ceil(Math.max(0, a2.naiveHi, a2.hi) + 2),
|
|
61196
|
+
unit: `change in ${a2.outcome}${a2.unit ? ` (${a2.unit})` : ""}`,
|
|
61197
|
+
height: 150,
|
|
61198
|
+
rowHelp: ["forest_naive", "forest_adjusted"]
|
|
61199
|
+
}
|
|
61200
|
+
)
|
|
61201
|
+
] }),
|
|
61202
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
61203
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "balance", children: "How unbalanced each one was — before adjusting" }),
|
|
61204
|
+
/* @__PURE__ */ jsx$1(Box, { py: "0.5", maxH: "208px", overflowY: "auto", children: barList(a2.balance.map((b2) => ({ label: b2.col, frac: b2.frac, tone: b2.tone, value: b2.display }))) })
|
|
61205
|
+
] }),
|
|
61206
|
+
/* @__PURE__ */ jsx$1(Box, { mt: "3.5", children: /* @__PURE__ */ jsx$1(Help, { id: "counts", display: "inline-flex", gap: "4", children: [[a2.nTotal, SUBJECT_MANY], [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: [
|
|
61207
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
|
|
61208
|
+
" ",
|
|
61209
|
+
label
|
|
61210
|
+
] }, i)) }) })
|
|
59867
61211
|
] });
|
|
59868
|
-
}
|
|
61212
|
+
}
|
|
61213
|
+
function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
|
|
61214
|
+
return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61215
|
+
/* @__PURE__ */ jsxs(Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.warning", children: [
|
|
61216
|
+
/* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "14px" } }),
|
|
61217
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "title.card", color: "fg.default", children: /* @__PURE__ */ jsx$1(Help, { id: refusal.kind === "positivity" ? "refusal_positivity" : "refusal_not_estimable", children: refusal.title }) })
|
|
61218
|
+
] }),
|
|
61219
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.muted", mb: "3", lineHeight: "1.5", children: refusal.body }),
|
|
61220
|
+
refusal.kind === "positivity" && overlap && /* @__PURE__ */ jsxs(Card, { mt: "1", children: [
|
|
61221
|
+
/* @__PURE__ */ jsxs(Cap, { help: "overlap_histogram", children: [
|
|
61222
|
+
"Propensity overlap — ",
|
|
61223
|
+
overlap.supportLabel
|
|
61224
|
+
] }),
|
|
61225
|
+
/* @__PURE__ */ jsx$1(OverlapHistogram, { treated: overlap.treated, control: overlap.control, supportLabel: overlap.supportLabel, positivityOk: overlap.positivityOk, height: 170 })
|
|
61226
|
+
] }),
|
|
61227
|
+
/* @__PURE__ */ jsx$1(Box, { display: "flex", gap: "4", mt: "3.5", children: refusal.evidence.map((e3, i) => /* @__PURE__ */ jsxs(Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
61228
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: e3.value }),
|
|
61229
|
+
" ",
|
|
61230
|
+
e3.label
|
|
61231
|
+
] }, i)) }),
|
|
61232
|
+
/* @__PURE__ */ jsxs(Text, { textStyle: "caption", color: "fg.subtle", mt: "3.5", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
61233
|
+
"Raw average difference in ",
|
|
61234
|
+
outcome,
|
|
61235
|
+
" (context only, not an answer): ",
|
|
61236
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue) })
|
|
61237
|
+
] })
|
|
61238
|
+
] });
|
|
61239
|
+
}
|
|
61240
|
+
function ValidatePanel({ vm, barList }) {
|
|
61241
|
+
const stat = useSlotRecipe({ key: "stat" })({ size: "lg" });
|
|
61242
|
+
const meter = useSlotRecipe({ key: "segmentedMeter" })({});
|
|
61243
|
+
return /* @__PURE__ */ jsxs(Box, { p: "4.5", children: [
|
|
61244
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "tab_validate", children: vm.headline }),
|
|
61245
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
|
|
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() })
|
|
61249
|
+
] }),
|
|
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" })
|
|
61255
|
+
] }),
|
|
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
|
+
] })
|
|
61264
|
+
] }),
|
|
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
|
+
] })
|
|
61272
|
+
] })
|
|
61273
|
+
] })
|
|
61274
|
+
] })
|
|
61275
|
+
] }),
|
|
61276
|
+
vm.matchOn.length > 0 && /* @__PURE__ */ jsxs(Card, { children: [
|
|
61277
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "validate_match", children: "Match both groups on" }),
|
|
61278
|
+
/* @__PURE__ */ jsx$1(Box, { py: "0.5", children: barList(vm.matchOn.map((m2) => ({ label: m2.display, frac: m2.frac, tone: m2.tone, value: "" }))) })
|
|
61279
|
+
] }),
|
|
61280
|
+
vm.curve.mid.length > 0 && /* @__PURE__ */ jsxs(Card, { children: [
|
|
61281
|
+
/* @__PURE__ */ jsx$1(Cap, { help: "validate_power", children: "Chance of detecting it" }),
|
|
61282
|
+
/* @__PURE__ */ jsx$1(
|
|
61283
|
+
AreaRange,
|
|
61284
|
+
{
|
|
61285
|
+
lo: vm.curve.mid,
|
|
61286
|
+
mid: vm.curve.mid,
|
|
61287
|
+
hi: vm.curve.mid,
|
|
61288
|
+
tone: "brand",
|
|
61289
|
+
xTicks: vm.curve.xTicks,
|
|
61290
|
+
yTicks: ["100", "50", "0"],
|
|
61291
|
+
yFormat: "percent",
|
|
61292
|
+
marks: vm.curve.marks.map((m2) => ({ at: m2.at, label: m2.label, tone: m2.tone, help: m2.help })),
|
|
61293
|
+
height: 170
|
|
61294
|
+
}
|
|
61295
|
+
)
|
|
61296
|
+
] }),
|
|
61297
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", mt: "3.5", children: vm.rationale }),
|
|
61298
|
+
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: [
|
|
61299
|
+
/* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: o2.label }),
|
|
61300
|
+
" · ",
|
|
61301
|
+
o2.nTotal.toLocaleString(),
|
|
61302
|
+
" total (",
|
|
61303
|
+
o2.nTreated.toLocaleString(),
|
|
61304
|
+
" / ",
|
|
61305
|
+
o2.nControl.toLocaleString(),
|
|
61306
|
+
")"
|
|
61307
|
+
] }, i)) })
|
|
61308
|
+
] });
|
|
61309
|
+
}
|
|
59869
61310
|
function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
59870
61311
|
const [open, setOpen] = useState(null);
|
|
59871
61312
|
const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
|
|
@@ -59916,11 +61357,11 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
|
59916
61357
|
)
|
|
59917
61358
|
] });
|
|
59918
61359
|
}
|
|
59919
|
-
function Step({ n: n2, title, children: children2 }) {
|
|
61360
|
+
function Step({ n: n2, title, help, children: children2 }) {
|
|
59920
61361
|
return /* @__PURE__ */ jsxs(Box, { px: "4.5", py: "3", borderBottomWidth: "1px", borderColor: "border.subtle", children: [
|
|
59921
|
-
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2", mb: "2", children: [
|
|
59922
|
-
/* @__PURE__ */ jsx$1(Box, { as: "span", fontFamily: "mono", fontSize: "
|
|
59923
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "title.row", children: title })
|
|
61362
|
+
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "baseline", gap: "2.5", mb: "2", children: [
|
|
61363
|
+
/* @__PURE__ */ jsx$1(Box, { as: "span", fontFamily: "mono", fontSize: "11px", fontWeight: "bold", color: "brand.fg", flex: "0 0 auto", lineHeight: "1", children: n2 }),
|
|
61364
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "title.row", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: title }) : title })
|
|
59924
61365
|
] }),
|
|
59925
61366
|
children2
|
|
59926
61367
|
] });
|
|
@@ -59938,13 +61379,45 @@ function ColumnPick({ column: column2, kind, choices, onPick, badge, button }) {
|
|
|
59938
61379
|
/* @__PURE__ */ jsx$1(Box, { as: "span", display: "inline-flex", color: "fg.muted", fontSize: "10px", children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faChevronDown }) })
|
|
59939
61380
|
] }) });
|
|
59940
61381
|
}
|
|
59941
|
-
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "
|
|
61382
|
+
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "xs" }) {
|
|
59942
61383
|
const s2 = useSlotRecipe({ key: "segmentGroup" })({ size: size3 });
|
|
59943
|
-
return /* @__PURE__ */ jsx$1(
|
|
61384
|
+
return /* @__PURE__ */ jsx$1(
|
|
61385
|
+
Box,
|
|
61386
|
+
{
|
|
61387
|
+
css: s2.root,
|
|
61388
|
+
...fill ? { width: "100%" } : {},
|
|
61389
|
+
role: "radiogroup",
|
|
61390
|
+
onKeyDown: (e3) => {
|
|
61391
|
+
var _a2;
|
|
61392
|
+
const d2 = e3.key === "ArrowRight" || e3.key === "ArrowDown" ? 1 : e3.key === "ArrowLeft" || e3.key === "ArrowUp" ? -1 : 0;
|
|
61393
|
+
if (!d2) return;
|
|
61394
|
+
e3.preventDefault();
|
|
61395
|
+
const next2 = (active + d2 + options2.length) % options2.length;
|
|
61396
|
+
onPick(next2);
|
|
61397
|
+
const btns = e3.currentTarget.querySelectorAll('[role="radio"]');
|
|
61398
|
+
(_a2 = btns[next2]) == null ? void 0 : _a2.focus();
|
|
61399
|
+
},
|
|
61400
|
+
children: options2.map((o2, i) => /* @__PURE__ */ jsx$1(
|
|
61401
|
+
Box,
|
|
61402
|
+
{
|
|
61403
|
+
as: "button",
|
|
61404
|
+
role: "radio",
|
|
61405
|
+
"aria-checked": active === i,
|
|
61406
|
+
tabIndex: active === i ? 0 : -1,
|
|
61407
|
+
css: s2.item,
|
|
61408
|
+
...fill ? { flex: "1" } : {},
|
|
61409
|
+
...active === i ? { "data-state": "checked" } : {},
|
|
61410
|
+
onClick: () => onPick(i),
|
|
61411
|
+
children: /* @__PURE__ */ jsx$1(Box, { as: "span", css: s2.itemText, children: o2 })
|
|
61412
|
+
},
|
|
61413
|
+
i
|
|
61414
|
+
))
|
|
61415
|
+
}
|
|
61416
|
+
);
|
|
59944
61417
|
}
|
|
59945
|
-
function Segmented({ label, left, right, active, onPick, last: last2 }) {
|
|
61418
|
+
function Segmented({ label, left, right, active, onPick, last: last2, help }) {
|
|
59946
61419
|
return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", alignItems: "stretch", gap: "1.5", py: "2", borderBottomWidth: last2 ? "0" : "1px", borderColor: "border.subtle", children: [
|
|
59947
|
-
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: label }),
|
|
61420
|
+
/* @__PURE__ */ jsx$1(Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: help ? /* @__PURE__ */ jsx$1(Help, { id: help, children: label }) : label }),
|
|
59948
61421
|
/* @__PURE__ */ jsx$1(SegmentSelect, { options: [left, right], active: active === "left" ? 0 : 1, onPick: (i) => onPick(i === 0 ? "left" : "right"), fill: true, size: "xs" })
|
|
59949
61422
|
] });
|
|
59950
61423
|
}
|
|
@@ -61646,10 +63119,10 @@ function formatPrimitive(type, value) {
|
|
|
61646
63119
|
case "Integer":
|
|
61647
63120
|
return String(value);
|
|
61648
63121
|
case "Float": {
|
|
61649
|
-
const
|
|
61650
|
-
if (Number.isNaN(
|
|
61651
|
-
if (!Number.isFinite(
|
|
61652
|
-
return String(
|
|
63122
|
+
const num2 = value;
|
|
63123
|
+
if (Number.isNaN(num2)) return "NaN";
|
|
63124
|
+
if (!Number.isFinite(num2)) return num2 > 0 ? "Infinity" : "-Infinity";
|
|
63125
|
+
return String(num2);
|
|
61653
63126
|
}
|
|
61654
63127
|
case "String":
|
|
61655
63128
|
return `"${value}"`;
|
|
@@ -62087,7 +63560,7 @@ const UITaskPreview = memo(function UITaskPreview2({
|
|
|
62087
63560
|
const manifest = useMemo(() => {
|
|
62088
63561
|
if (!details || !isUI) return null;
|
|
62089
63562
|
const meta3 = getTaskMetadata(details);
|
|
62090
|
-
return meta3 ? decodeManifest(meta3) : { paths: [], functions: [] };
|
|
63563
|
+
return meta3 ? decodeManifest(meta3) : { paths: [], functions: [], records: [] };
|
|
62091
63564
|
}, [details, isUI]);
|
|
62092
63565
|
const outputPath = details ? treePathToString$1(details.output) : null;
|
|
62093
63566
|
const preloads = useMemo(
|
|
@@ -62100,6 +63573,7 @@ const UITaskPreview = memo(function UITaskPreview2({
|
|
|
62100
63573
|
...StateImpl,
|
|
62101
63574
|
...createScopedBindPlatform(manifest),
|
|
62102
63575
|
...createScopedFuncPlatform(manifest.functions),
|
|
63576
|
+
...createScopedRecordPlatform(manifest.records),
|
|
62103
63577
|
...OverlayImpl
|
|
62104
63578
|
] : void 0,
|
|
62105
63579
|
[manifest]
|
|
@@ -62698,6 +64172,8 @@ export {
|
|
|
62698
64172
|
ReactiveDatasetCache,
|
|
62699
64173
|
ReactiveDatasetLoader,
|
|
62700
64174
|
ReactiveDatasetProvider,
|
|
64175
|
+
RecordPlatform,
|
|
64176
|
+
RecordRuntime,
|
|
62701
64177
|
StagedStore,
|
|
62702
64178
|
StatusDisplay,
|
|
62703
64179
|
TaskPreview,
|
|
@@ -62709,17 +64185,22 @@ export {
|
|
|
62709
64185
|
clearPendingWrites,
|
|
62710
64186
|
clearReactiveDatasetCache,
|
|
62711
64187
|
clearReactiveDatasetListCache,
|
|
64188
|
+
clearRecordApi,
|
|
62712
64189
|
clearStagedStoreSingleton,
|
|
62713
64190
|
createDefaultDatasetApi,
|
|
62714
64191
|
createDefaultFunctionApi,
|
|
64192
|
+
createDefaultRecordApi,
|
|
62715
64193
|
createInMemoryFunctionApi,
|
|
64194
|
+
createInMemoryRecordApi,
|
|
62716
64195
|
createReactiveDatasetCache,
|
|
62717
64196
|
createScopedBindPlatform,
|
|
62718
64197
|
createScopedFuncPlatform,
|
|
64198
|
+
createScopedRecordPlatform,
|
|
62719
64199
|
datasetCacheKey,
|
|
62720
64200
|
datasetPathToString,
|
|
62721
64201
|
defaultBindRuntime,
|
|
62722
64202
|
defaultFuncRuntime,
|
|
64203
|
+
defaultRecordRuntime,
|
|
62723
64204
|
disableBindingTracking,
|
|
62724
64205
|
enableBindingTracking,
|
|
62725
64206
|
formatApiError,
|
|
@@ -62732,12 +64213,14 @@ export {
|
|
|
62732
64213
|
getTaskMetadata,
|
|
62733
64214
|
initializeFunctionApi,
|
|
62734
64215
|
initializeReactiveDatasetCache,
|
|
64216
|
+
initializeRecordApi,
|
|
62735
64217
|
initializeStagedStore,
|
|
62736
64218
|
isBindingTracking,
|
|
62737
64219
|
onWriteError,
|
|
62738
64220
|
preloadReactiveDatasetList,
|
|
62739
64221
|
realClock,
|
|
62740
|
-
|
|
64222
|
+
recordChannelKey,
|
|
64223
|
+
signatureOfFuncHandleType,
|
|
62741
64224
|
trackDatasetPath,
|
|
62742
64225
|
useDataflowCancel,
|
|
62743
64226
|
useDataflowExecute,
|