@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.cjs
CHANGED
|
@@ -2106,9 +2106,6 @@ function datasetCacheKey(workspace, path2) {
|
|
|
2106
2106
|
const pathStr = datasetPathToString(path2);
|
|
2107
2107
|
return pathStr ? `${workspace}.${pathStr}` : workspace;
|
|
2108
2108
|
}
|
|
2109
|
-
function toTreePath(path2) {
|
|
2110
|
-
return path2;
|
|
2111
|
-
}
|
|
2112
2109
|
class ReactiveDatasetCache {
|
|
2113
2110
|
constructor(config2, api3, clock2 = realClock) {
|
|
2114
2111
|
__publicField(this, "destroyed", false);
|
|
@@ -2230,7 +2227,7 @@ class ReactiveDatasetCache {
|
|
|
2230
2227
|
const prevTail = this.writeChains.get(key) ?? Promise.resolve();
|
|
2231
2228
|
const run = prevTail.then(async () => {
|
|
2232
2229
|
try {
|
|
2233
|
-
await this.api.set(workspace,
|
|
2230
|
+
await this.api.set(workspace, path2, value);
|
|
2234
2231
|
if (this.destroyed) return;
|
|
2235
2232
|
this.writeBaselines.set(key, {
|
|
2236
2233
|
value,
|
|
@@ -2285,6 +2282,14 @@ class ReactiveDatasetCache {
|
|
|
2285
2282
|
if (this.destroyed) return;
|
|
2286
2283
|
await this.api.launchDataflow(workspace);
|
|
2287
2284
|
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Launch a workspace dataflow run without writing first. The standalone
|
|
2287
|
+
* half of {@link writeAndStart}; see the interface doc for semantics.
|
|
2288
|
+
*/
|
|
2289
|
+
async launchDataflow(workspace) {
|
|
2290
|
+
if (this.destroyed) return;
|
|
2291
|
+
await this.api.launchDataflow(workspace);
|
|
2292
|
+
}
|
|
2288
2293
|
/**
|
|
2289
2294
|
* Preload a dataset into cache. Concurrent preloads of the same key
|
|
2290
2295
|
* share one fetch (query-core dedup); a write racing the fetch cancels
|
|
@@ -2300,7 +2305,7 @@ class ReactiveDatasetCache {
|
|
|
2300
2305
|
await this.client.fetchQuery({
|
|
2301
2306
|
queryKey,
|
|
2302
2307
|
queryFn: async () => {
|
|
2303
|
-
const result = await this.
|
|
2308
|
+
const result = await this.api.get(workspace, path2);
|
|
2304
2309
|
fetchedHash = result.hash;
|
|
2305
2310
|
return result.data;
|
|
2306
2311
|
}
|
|
@@ -2316,14 +2321,11 @@ class ReactiveDatasetCache {
|
|
|
2316
2321
|
this.knownHashes.set(key, fetchedHash);
|
|
2317
2322
|
this.notifyChange(key);
|
|
2318
2323
|
}
|
|
2319
|
-
fetchDataset(workspace, path2) {
|
|
2320
|
-
return this.api.get(workspace, toTreePath(path2));
|
|
2321
|
-
}
|
|
2322
2324
|
/**
|
|
2323
2325
|
* List fields at a path. Empty path lists workspace root.
|
|
2324
2326
|
*/
|
|
2325
2327
|
list(workspace, path2) {
|
|
2326
|
-
return path2.length === 0 ? this.api.listRoot(workspace) : this.api.listAt(workspace,
|
|
2328
|
+
return path2.length === 0 ? this.api.listRoot(workspace) : this.api.listAt(workspace, path2);
|
|
2327
2329
|
}
|
|
2328
2330
|
/**
|
|
2329
2331
|
* Set refetch interval for a dataset (polling).
|
|
@@ -2379,6 +2381,11 @@ class ReactiveDatasetCache {
|
|
|
2379
2381
|
this.workspacePollers.delete(workspace);
|
|
2380
2382
|
}
|
|
2381
2383
|
}
|
|
2384
|
+
refresh(workspace) {
|
|
2385
|
+
if (this.destroyed) return Promise.resolve();
|
|
2386
|
+
if (!this.workspacePollers.has(workspace)) return Promise.resolve();
|
|
2387
|
+
return this.pollWorkspaceStatus(workspace);
|
|
2388
|
+
}
|
|
2382
2389
|
/**
|
|
2383
2390
|
* Poll workspace status and reconcile the cache with the server.
|
|
2384
2391
|
* Concurrent calls for the same workspace dedupe to a single fetch.
|
|
@@ -2443,7 +2450,7 @@ class ReactiveDatasetCache {
|
|
|
2443
2450
|
if (pending.length === 0) return;
|
|
2444
2451
|
const fetched = await Promise.allSettled(
|
|
2445
2452
|
pending.map(
|
|
2446
|
-
(p2) => p2.kind === "fetch" ? this.
|
|
2453
|
+
(p2) => p2.kind === "fetch" ? this.api.get(workspace, p2.path).then((result) => ({ p: p2, result })) : Promise.resolve({ p: p2, result: null })
|
|
2447
2454
|
)
|
|
2448
2455
|
);
|
|
2449
2456
|
if (this.destroyed) return;
|
|
@@ -2626,6 +2633,9 @@ class IndexedDBStagedAdapter {
|
|
|
2626
2633
|
req.onerror = () => reject2(req.error);
|
|
2627
2634
|
req.onblocked = () => reject2(new Error("IndexedDB open blocked"));
|
|
2628
2635
|
});
|
|
2636
|
+
void this.dbPromise.catch(() => {
|
|
2637
|
+
this.dbPromise = null;
|
|
2638
|
+
});
|
|
2629
2639
|
}
|
|
2630
2640
|
return this.dbPromise;
|
|
2631
2641
|
}
|
|
@@ -2701,6 +2711,11 @@ class StagedStore {
|
|
|
2701
2711
|
__publicField(this, "adapter");
|
|
2702
2712
|
__publicField(this, "hydrated");
|
|
2703
2713
|
__publicField(this, "inFlight", /* @__PURE__ */ new Set());
|
|
2714
|
+
// Per-key persistence chain — serializes save/remove for a key so they
|
|
2715
|
+
// land in IndexedDB in call order (last write wins) instead of racing.
|
|
2716
|
+
__publicField(this, "saveChains", /* @__PURE__ */ new Map());
|
|
2717
|
+
// Listeners notified when a persistence op fails durably.
|
|
2718
|
+
__publicField(this, "errorListeners", /* @__PURE__ */ new Set());
|
|
2704
2719
|
this.adapter = adapter;
|
|
2705
2720
|
this.hydrated = this.hydrate();
|
|
2706
2721
|
}
|
|
@@ -2737,14 +2752,14 @@ class StagedStore {
|
|
|
2737
2752
|
snapshot: next2.snapshot,
|
|
2738
2753
|
buffered: next2.buffered
|
|
2739
2754
|
};
|
|
2740
|
-
this.
|
|
2755
|
+
this.persist(key, () => this.adapter.save(key, persisted));
|
|
2741
2756
|
}
|
|
2742
2757
|
discard(workspace, path2) {
|
|
2743
2758
|
const key = datasetCacheKey(workspace, path2);
|
|
2744
2759
|
if (!this.entries.has(key)) return false;
|
|
2745
2760
|
this.entries.delete(key);
|
|
2746
2761
|
this.notify(key);
|
|
2747
|
-
this.
|
|
2762
|
+
this.persist(key, () => this.adapter.remove(key));
|
|
2748
2763
|
return true;
|
|
2749
2764
|
}
|
|
2750
2765
|
listKeys() {
|
|
@@ -2765,11 +2780,18 @@ class StagedStore {
|
|
|
2765
2780
|
getKeyVersion(key) {
|
|
2766
2781
|
return this.versions.get(key) ?? 0;
|
|
2767
2782
|
}
|
|
2783
|
+
onPersistError(cb) {
|
|
2784
|
+
this.errorListeners.add(cb);
|
|
2785
|
+
return () => {
|
|
2786
|
+
this.errorListeners.delete(cb);
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2768
2789
|
/** Test-only: clear in-memory + persisted state. */
|
|
2769
2790
|
async clear() {
|
|
2770
2791
|
const keys = [...this.entries.keys()];
|
|
2771
2792
|
this.entries.clear();
|
|
2772
2793
|
for (const key of keys) this.notify(key);
|
|
2794
|
+
await this.flushPending();
|
|
2773
2795
|
await this.adapter.clear();
|
|
2774
2796
|
}
|
|
2775
2797
|
// ----- internals -------------------------------------------------------
|
|
@@ -2778,20 +2800,45 @@ class StagedStore {
|
|
|
2778
2800
|
const subs = this.subscribers.get(key);
|
|
2779
2801
|
if (subs) for (const cb of subs) cb();
|
|
2780
2802
|
}
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2803
|
+
/**
|
|
2804
|
+
* Serialize a persistence op behind any prior op for the same key, so they
|
|
2805
|
+
* land in IndexedDB in call order (last write wins) rather than racing.
|
|
2806
|
+
* Tracks the op for {@link flushPending} and routes failures to
|
|
2807
|
+
* {@link onPersistError} listeners (falling back to a console warning).
|
|
2808
|
+
*/
|
|
2809
|
+
persist(key, op) {
|
|
2810
|
+
const prev2 = this.saveChains.get(key) ?? Promise.resolve();
|
|
2811
|
+
const settled = prev2.then(op, op).then(
|
|
2812
|
+
() => void 0,
|
|
2813
|
+
(err) => {
|
|
2814
|
+
this.emitPersistError(key, err);
|
|
2815
|
+
}
|
|
2816
|
+
).then(() => {
|
|
2817
|
+
if (this.saveChains.get(key) === settled) this.saveChains.delete(key);
|
|
2818
|
+
this.inFlight.delete(settled);
|
|
2786
2819
|
});
|
|
2787
|
-
this.
|
|
2820
|
+
this.saveChains.set(key, settled);
|
|
2821
|
+
this.inFlight.add(settled);
|
|
2822
|
+
}
|
|
2823
|
+
emitPersistError(key, err) {
|
|
2824
|
+
if (this.errorListeners.size === 0) {
|
|
2825
|
+
console.warn("[StagedStore] persistence operation failed:", err);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
for (const cb of [...this.errorListeners]) {
|
|
2829
|
+
try {
|
|
2830
|
+
cb(key, err);
|
|
2831
|
+
} catch (listenerErr) {
|
|
2832
|
+
console.warn("[StagedStore] persist-error listener threw:", listenerErr);
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2788
2835
|
}
|
|
2789
2836
|
async hydrate() {
|
|
2790
2837
|
let persisted;
|
|
2791
2838
|
try {
|
|
2792
2839
|
persisted = await this.adapter.loadAll();
|
|
2793
2840
|
} catch (err) {
|
|
2794
|
-
|
|
2841
|
+
this.emitPersistError("", err);
|
|
2795
2842
|
return;
|
|
2796
2843
|
}
|
|
2797
2844
|
for (const [key, p2] of persisted) {
|
|
@@ -2827,7 +2874,7 @@ function clearStagedStoreSingleton() {
|
|
|
2827
2874
|
}
|
|
2828
2875
|
const UNCHANGED_VARIANT = east.variant("unchanged", null);
|
|
2829
2876
|
const blobEqual = east.equalFor(east.BlobType);
|
|
2830
|
-
const eastTypeEqual$
|
|
2877
|
+
const eastTypeEqual$2 = east.equalFor(internal$1.EastTypeType);
|
|
2831
2878
|
const helpersCache = new east.SortedMap(
|
|
2832
2879
|
void 0,
|
|
2833
2880
|
east.compareFor(internal$1.EastTypeType)
|
|
@@ -2880,6 +2927,7 @@ class BindRuntime {
|
|
|
2880
2927
|
// List cache helpers.
|
|
2881
2928
|
__publicField(this, "listCache", /* @__PURE__ */ new Map());
|
|
2882
2929
|
this.staged = staged;
|
|
2930
|
+
this.staged.onPersistError((_key, err) => this.emitWriteError(err));
|
|
2883
2931
|
}
|
|
2884
2932
|
// ----- cache singleton ------------------------------------------------
|
|
2885
2933
|
/** Read the active cache. Throws if none has been initialized. */
|
|
@@ -2917,6 +2965,16 @@ class BindRuntime {
|
|
|
2917
2965
|
this.writeErrorListeners.delete(cb);
|
|
2918
2966
|
};
|
|
2919
2967
|
}
|
|
2968
|
+
/** Fan a write/persistence error out to every registered listener. */
|
|
2969
|
+
emitWriteError(error3) {
|
|
2970
|
+
for (const cb of this.writeErrorListeners) {
|
|
2971
|
+
try {
|
|
2972
|
+
cb(error3);
|
|
2973
|
+
} catch (cbErr) {
|
|
2974
|
+
console.error("Data.bind write-error listener threw:", cbErr);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2920
2978
|
/** Resolve once every currently-queued write has finished
|
|
2921
2979
|
* (success or failure). Errors are NOT thrown — subscribe via
|
|
2922
2980
|
* {@link onWriteError} for those. */
|
|
@@ -2954,13 +3012,7 @@ class BindRuntime {
|
|
|
2954
3012
|
await writeFn();
|
|
2955
3013
|
} catch (error3) {
|
|
2956
3014
|
console.error("Data.bind write failed:", error3);
|
|
2957
|
-
|
|
2958
|
-
try {
|
|
2959
|
-
cb(error3);
|
|
2960
|
-
} catch (cbErr) {
|
|
2961
|
-
console.error("Data.bind write-error listener threw:", cbErr);
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
3015
|
+
this.emitWriteError(error3);
|
|
2964
3016
|
}
|
|
2965
3017
|
}
|
|
2966
3018
|
this.isProcessingWrites = false;
|
|
@@ -3017,7 +3069,7 @@ class BindRuntime {
|
|
|
3017
3069
|
return;
|
|
3018
3070
|
}
|
|
3019
3071
|
const shapeChanged = existing.mode !== info.mode || existing.hasPatchDataset !== info.hasPatchDataset;
|
|
3020
|
-
const typeChanged = !eastTypeEqual$
|
|
3072
|
+
const typeChanged = !eastTypeEqual$2(existing.sourceType, info.sourceType);
|
|
3021
3073
|
if (!shapeChanged && !typeChanged) {
|
|
3022
3074
|
this.bindingRegistry.set(key, info);
|
|
3023
3075
|
return;
|
|
@@ -3120,6 +3172,10 @@ class BindRuntime {
|
|
|
3120
3172
|
}
|
|
3121
3173
|
return null;
|
|
3122
3174
|
};
|
|
3175
|
+
const startLaunch = () => {
|
|
3176
|
+
this.queueWrite(() => cache3.launchDataflow(ws));
|
|
3177
|
+
return null;
|
|
3178
|
+
};
|
|
3123
3179
|
if (mode === "direct" && !patchPath) {
|
|
3124
3180
|
return {
|
|
3125
3181
|
read: () => {
|
|
@@ -3134,6 +3190,7 @@ class BindRuntime {
|
|
|
3134
3190
|
this.queueWrite(() => cache3.writeAndStart(ws, sourcePath, encodeT(value)));
|
|
3135
3191
|
return null;
|
|
3136
3192
|
},
|
|
3193
|
+
start: startLaunch,
|
|
3137
3194
|
source: () => {
|
|
3138
3195
|
this.trackPath(ws, sourcePath);
|
|
3139
3196
|
return readSourceValue();
|
|
@@ -3167,6 +3224,7 @@ class BindRuntime {
|
|
|
3167
3224
|
this.queueWrite(() => cache3.writeAndStart(ws, pPath2, encodePatch(next2)));
|
|
3168
3225
|
return null;
|
|
3169
3226
|
},
|
|
3227
|
+
start: startLaunch,
|
|
3170
3228
|
source: () => {
|
|
3171
3229
|
this.trackPath(ws, sourcePath);
|
|
3172
3230
|
return readSourceValue();
|
|
@@ -3180,12 +3238,8 @@ class BindRuntime {
|
|
|
3180
3238
|
const patchVal = readPatchValue();
|
|
3181
3239
|
this.queueWrite(async () => {
|
|
3182
3240
|
const next2 = apply2(sourceVal, patchVal);
|
|
3183
|
-
|
|
3184
|
-
cache3.
|
|
3185
|
-
writes.push(cache3.write(ws, sourcePath, encodeT(next2)));
|
|
3186
|
-
writes.push(cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT)));
|
|
3187
|
-
});
|
|
3188
|
-
await Promise.all(writes);
|
|
3241
|
+
await cache3.write(ws, sourcePath, encodeT(next2));
|
|
3242
|
+
await cache3.write(ws, pPath2, encodePatch(UNCHANGED_VARIANT));
|
|
3189
3243
|
});
|
|
3190
3244
|
return null;
|
|
3191
3245
|
},
|
|
@@ -3211,6 +3265,7 @@ class BindRuntime {
|
|
|
3211
3265
|
},
|
|
3212
3266
|
write: writeStaged,
|
|
3213
3267
|
writeAndStart: writeStaged,
|
|
3268
|
+
start: startLaunch,
|
|
3214
3269
|
source: () => {
|
|
3215
3270
|
this.trackPath(ws, sourcePath);
|
|
3216
3271
|
return readSourceValue();
|
|
@@ -3255,6 +3310,7 @@ class BindRuntime {
|
|
|
3255
3310
|
},
|
|
3256
3311
|
write: writeStaged,
|
|
3257
3312
|
writeAndStart: writeStaged,
|
|
3313
|
+
start: startLaunch,
|
|
3258
3314
|
source: () => {
|
|
3259
3315
|
this.trackPath(ws, sourcePath);
|
|
3260
3316
|
return readSourceValue();
|
|
@@ -3403,6 +3459,110 @@ platform.registerReactiveTracker({
|
|
|
3403
3459
|
}
|
|
3404
3460
|
});
|
|
3405
3461
|
platform.registerPlatformImplementation(BindPlatform);
|
|
3462
|
+
class TrackedChannelStore {
|
|
3463
|
+
constructor() {
|
|
3464
|
+
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
3465
|
+
// Per-key reactive subscription shim.
|
|
3466
|
+
__publicField(this, "keySubscribers", /* @__PURE__ */ new Map());
|
|
3467
|
+
__publicField(this, "keyVersions", /* @__PURE__ */ new Map());
|
|
3468
|
+
// Reactive tracking — read closures push their channel keys here while a
|
|
3469
|
+
// Reactive.Root render is being tracked.
|
|
3470
|
+
__publicField(this, "trackingContext", null);
|
|
3471
|
+
}
|
|
3472
|
+
// ----- reactive tracking ----------------------------------------------
|
|
3473
|
+
enableTracking() {
|
|
3474
|
+
this.trackingContext = /* @__PURE__ */ new Set();
|
|
3475
|
+
return this.trackingContext;
|
|
3476
|
+
}
|
|
3477
|
+
disableTracking() {
|
|
3478
|
+
const keys = this.trackingContext ? [...this.trackingContext] : [];
|
|
3479
|
+
this.trackingContext = null;
|
|
3480
|
+
return keys;
|
|
3481
|
+
}
|
|
3482
|
+
isTracking() {
|
|
3483
|
+
return this.trackingContext !== null;
|
|
3484
|
+
}
|
|
3485
|
+
/** Record a channel-key dependency for the current render (no-op when not
|
|
3486
|
+
* tracking). */
|
|
3487
|
+
track(key) {
|
|
3488
|
+
var _a2;
|
|
3489
|
+
(_a2 = this.trackingContext) == null ? void 0 : _a2.add(key);
|
|
3490
|
+
}
|
|
3491
|
+
// ----- subscription shim ------------------------------------------------
|
|
3492
|
+
subscribe(key, callback) {
|
|
3493
|
+
let subs = this.keySubscribers.get(key);
|
|
3494
|
+
if (!subs) {
|
|
3495
|
+
subs = /* @__PURE__ */ new Set();
|
|
3496
|
+
this.keySubscribers.set(key, subs);
|
|
3497
|
+
}
|
|
3498
|
+
subs.add(callback);
|
|
3499
|
+
return () => {
|
|
3500
|
+
subs.delete(callback);
|
|
3501
|
+
if (subs.size === 0 && this.keySubscribers.get(key) === subs) {
|
|
3502
|
+
this.keySubscribers.delete(key);
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
getKeyVersion(key) {
|
|
3507
|
+
return this.keyVersions.get(key) ?? 0;
|
|
3508
|
+
}
|
|
3509
|
+
notify(key) {
|
|
3510
|
+
this.keyVersions.set(key, (this.keyVersions.get(key) ?? 0) + 1);
|
|
3511
|
+
const subs = this.keySubscribers.get(key);
|
|
3512
|
+
if (subs) for (const cb of [...subs]) cb();
|
|
3513
|
+
}
|
|
3514
|
+
// ----- channel registry + latest-wins latch ----------------------------
|
|
3515
|
+
entry(key) {
|
|
3516
|
+
let entry = this.entries.get(key);
|
|
3517
|
+
if (!entry) {
|
|
3518
|
+
entry = this.createEntry();
|
|
3519
|
+
this.entries.set(key, entry);
|
|
3520
|
+
}
|
|
3521
|
+
return entry;
|
|
3522
|
+
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Open a latest-wins launch on a channel: bump the seq, mark the entry
|
|
3525
|
+
* `running`, optionally `reset` it (e.g. clear a prior error), notify, and
|
|
3526
|
+
* return a `settle` that applies a terminal mutation only if this launch is
|
|
3527
|
+
* still the current one (a superseded / cancelled launch settles to a
|
|
3528
|
+
* no-op).
|
|
3529
|
+
*/
|
|
3530
|
+
beginLaunch(key, reset2) {
|
|
3531
|
+
const entry = this.entry(key);
|
|
3532
|
+
entry.launchSeq += 1;
|
|
3533
|
+
const mySeq = entry.launchSeq;
|
|
3534
|
+
entry.status = "running";
|
|
3535
|
+
reset2 == null ? void 0 : reset2(entry);
|
|
3536
|
+
this.notify(key);
|
|
3537
|
+
const settle = (mutate) => {
|
|
3538
|
+
const current = this.entries.get(key);
|
|
3539
|
+
if (!current || current.launchSeq !== mySeq) return;
|
|
3540
|
+
mutate(current);
|
|
3541
|
+
this.notify(key);
|
|
3542
|
+
};
|
|
3543
|
+
return { entry, settle };
|
|
3544
|
+
}
|
|
3545
|
+
/**
|
|
3546
|
+
* Client-side cancel: if the channel is running, orphan the in-flight wait
|
|
3547
|
+
* (bump the seq) and mark it `cancelled`. `after` runs before the notify,
|
|
3548
|
+
* for subclass-specific cleanup (e.g. dropping an in-flight handle).
|
|
3549
|
+
*/
|
|
3550
|
+
cancelChannel(key, after) {
|
|
3551
|
+
const entry = this.entries.get(key);
|
|
3552
|
+
if (entry && entry.status === "running") {
|
|
3553
|
+
entry.launchSeq += 1;
|
|
3554
|
+
entry.status = "cancelled";
|
|
3555
|
+
after == null ? void 0 : after(entry);
|
|
3556
|
+
this.notify(key);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
/** Drop all channel state (entries + subscriptions + versions). */
|
|
3560
|
+
clearChannels() {
|
|
3561
|
+
this.entries.clear();
|
|
3562
|
+
this.keySubscribers.clear();
|
|
3563
|
+
this.keyVersions.clear();
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3406
3566
|
function createDefaultFunctionApi(apiUrl, repo, getToken) {
|
|
3407
3567
|
const opts = () => ({ token: getToken() });
|
|
3408
3568
|
return {
|
|
@@ -3418,8 +3578,8 @@ function createDefaultFunctionApi(apiUrl, repo, getToken) {
|
|
|
3418
3578
|
}
|
|
3419
3579
|
};
|
|
3420
3580
|
}
|
|
3421
|
-
const eastTypeEqual = east.equalFor(internal$1.EastTypeType);
|
|
3422
|
-
function
|
|
3581
|
+
const eastTypeEqual$1 = east.equalFor(internal$1.EastTypeType);
|
|
3582
|
+
function signatureOfFuncHandleType(handleType) {
|
|
3423
3583
|
var _a2, _b2;
|
|
3424
3584
|
if (handleType.type !== "Struct") {
|
|
3425
3585
|
throw new Error(`Func.bind: handle type must be a Struct; got ${handleType.type}`);
|
|
@@ -3478,10 +3638,10 @@ function errorOfExecuteResult(result) {
|
|
|
3478
3638
|
stderr: result.stderr
|
|
3479
3639
|
};
|
|
3480
3640
|
default:
|
|
3481
|
-
return transportError(`unexpected outcome "${outcome.type}"`);
|
|
3641
|
+
return transportError$1(`unexpected outcome "${outcome.type}"`);
|
|
3482
3642
|
}
|
|
3483
3643
|
}
|
|
3484
|
-
function transportError(message) {
|
|
3644
|
+
function transportError$1(message) {
|
|
3485
3645
|
return {
|
|
3486
3646
|
kind: east.variant("transport", { message }),
|
|
3487
3647
|
message,
|
|
@@ -3492,20 +3652,16 @@ function transportError(message) {
|
|
|
3492
3652
|
function funcChannelKey(workspace, name) {
|
|
3493
3653
|
return `func:${workspace}:${name}`;
|
|
3494
3654
|
}
|
|
3495
|
-
class FuncRuntime {
|
|
3655
|
+
class FuncRuntime extends TrackedChannelStore {
|
|
3496
3656
|
constructor() {
|
|
3657
|
+
super(...arguments);
|
|
3497
3658
|
__publicField(this, "api", null);
|
|
3498
3659
|
__publicField(this, "workspace", null);
|
|
3499
|
-
__publicField(this, "entries", /* @__PURE__ */ new Map());
|
|
3500
3660
|
// Signature lists are fetched once per workspace and cached.
|
|
3501
3661
|
__publicField(this, "signatureLists", /* @__PURE__ */ new Map());
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
__publicField(this, "keyVersions", /* @__PURE__ */ new Map());
|
|
3506
|
-
// Reactive tracking — read closures push their channel keys here while
|
|
3507
|
-
// a Reactive.Root render is being tracked.
|
|
3508
|
-
__publicField(this, "trackingContext", null);
|
|
3662
|
+
}
|
|
3663
|
+
createEntry() {
|
|
3664
|
+
return { status: "idle", launchSeq: 0 };
|
|
3509
3665
|
}
|
|
3510
3666
|
// ----- wiring ----------------------------------------------------------
|
|
3511
3667
|
/** Install the API adapter + workspace — called by the React provider
|
|
@@ -3518,59 +3674,8 @@ class FuncRuntime {
|
|
|
3518
3674
|
clear() {
|
|
3519
3675
|
this.api = null;
|
|
3520
3676
|
this.workspace = null;
|
|
3521
|
-
this.
|
|
3677
|
+
this.clearChannels();
|
|
3522
3678
|
this.signatureLists.clear();
|
|
3523
|
-
this.keySubscribers.clear();
|
|
3524
|
-
this.keyVersions.clear();
|
|
3525
|
-
}
|
|
3526
|
-
// ----- reactive tracking ----------------------------------------------
|
|
3527
|
-
enableTracking() {
|
|
3528
|
-
this.trackingContext = /* @__PURE__ */ new Set();
|
|
3529
|
-
return this.trackingContext;
|
|
3530
|
-
}
|
|
3531
|
-
disableTracking() {
|
|
3532
|
-
const keys = this.trackingContext ? [...this.trackingContext] : [];
|
|
3533
|
-
this.trackingContext = null;
|
|
3534
|
-
return keys;
|
|
3535
|
-
}
|
|
3536
|
-
isTracking() {
|
|
3537
|
-
return this.trackingContext !== null;
|
|
3538
|
-
}
|
|
3539
|
-
track(key) {
|
|
3540
|
-
var _a2;
|
|
3541
|
-
(_a2 = this.trackingContext) == null ? void 0 : _a2.add(key);
|
|
3542
|
-
}
|
|
3543
|
-
// ----- subscription shim ------------------------------------------------
|
|
3544
|
-
subscribe(key, callback) {
|
|
3545
|
-
let subs = this.keySubscribers.get(key);
|
|
3546
|
-
if (!subs) {
|
|
3547
|
-
subs = /* @__PURE__ */ new Set();
|
|
3548
|
-
this.keySubscribers.set(key, subs);
|
|
3549
|
-
}
|
|
3550
|
-
subs.add(callback);
|
|
3551
|
-
return () => {
|
|
3552
|
-
subs.delete(callback);
|
|
3553
|
-
if (subs.size === 0 && this.keySubscribers.get(key) === subs) {
|
|
3554
|
-
this.keySubscribers.delete(key);
|
|
3555
|
-
}
|
|
3556
|
-
};
|
|
3557
|
-
}
|
|
3558
|
-
getKeyVersion(key) {
|
|
3559
|
-
return this.keyVersions.get(key) ?? 0;
|
|
3560
|
-
}
|
|
3561
|
-
notify(key) {
|
|
3562
|
-
this.keyVersions.set(key, (this.keyVersions.get(key) ?? 0) + 1);
|
|
3563
|
-
const subs = this.keySubscribers.get(key);
|
|
3564
|
-
if (subs) for (const cb of [...subs]) cb();
|
|
3565
|
-
}
|
|
3566
|
-
// ----- registry ----------------------------------------------------------
|
|
3567
|
-
entry(key) {
|
|
3568
|
-
let entry = this.entries.get(key);
|
|
3569
|
-
if (!entry) {
|
|
3570
|
-
entry = { status: "idle", launchSeq: 0 };
|
|
3571
|
-
this.entries.set(key, entry);
|
|
3572
|
-
}
|
|
3573
|
-
return entry;
|
|
3574
3679
|
}
|
|
3575
3680
|
/** The deployed signature list for a workspace (fetched once). */
|
|
3576
3681
|
signatures(workspace) {
|
|
@@ -3593,7 +3698,7 @@ class FuncRuntime {
|
|
|
3593
3698
|
try {
|
|
3594
3699
|
list = await this.signatures(workspace);
|
|
3595
3700
|
} catch (err) {
|
|
3596
|
-
return transportError(`failed to list workspace functions: ${err instanceof Error ? err.message : String(err)}`);
|
|
3701
|
+
return transportError$1(`failed to list workspace functions: ${err instanceof Error ? err.message : String(err)}`);
|
|
3597
3702
|
}
|
|
3598
3703
|
const deployed = list.find((f2) => f2.name === name);
|
|
3599
3704
|
if (!deployed) {
|
|
@@ -3604,8 +3709,8 @@ class FuncRuntime {
|
|
|
3604
3709
|
stderr: ""
|
|
3605
3710
|
};
|
|
3606
3711
|
}
|
|
3607
|
-
const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual(t4, sig.inputs[i]));
|
|
3608
|
-
const outputMatches = eastTypeEqual(deployed.outputType, sig.output);
|
|
3712
|
+
const inputsMatch = deployed.inputTypes.length === sig.inputs.length && deployed.inputTypes.every((t4, i) => eastTypeEqual$1(t4, sig.inputs[i]));
|
|
3713
|
+
const outputMatches = eastTypeEqual$1(deployed.outputType, sig.output);
|
|
3609
3714
|
if (!inputsMatch || !outputMatches) {
|
|
3610
3715
|
const message = `signature mismatch for "${name}": bound (${sig.inputs.length} inputs) disagrees with the deployed package`;
|
|
3611
3716
|
return {
|
|
@@ -3620,17 +3725,7 @@ class FuncRuntime {
|
|
|
3620
3725
|
// ----- closure semantics -------------------------------------------------
|
|
3621
3726
|
launch(workspace, name, sig, args) {
|
|
3622
3727
|
const key = funcChannelKey(workspace, name);
|
|
3623
|
-
const
|
|
3624
|
-
entry.launchSeq += 1;
|
|
3625
|
-
const mySeq = entry.launchSeq;
|
|
3626
|
-
entry.status = "running";
|
|
3627
|
-
this.notify(key);
|
|
3628
|
-
const settle = (mutate) => {
|
|
3629
|
-
const current = this.entries.get(key);
|
|
3630
|
-
if (!current || current.launchSeq !== mySeq) return;
|
|
3631
|
-
mutate(current);
|
|
3632
|
-
this.notify(key);
|
|
3633
|
-
};
|
|
3728
|
+
const { settle } = this.beginLaunch(key);
|
|
3634
3729
|
void (async () => {
|
|
3635
3730
|
const invalid = await this.validate(workspace, name, sig);
|
|
3636
3731
|
if (invalid) {
|
|
@@ -3644,7 +3739,7 @@ class FuncRuntime {
|
|
|
3644
3739
|
if (!api3) {
|
|
3645
3740
|
settle((e3) => {
|
|
3646
3741
|
e3.status = "failed";
|
|
3647
|
-
e3.error = transportError("no FunctionApi installed");
|
|
3742
|
+
e3.error = transportError$1("no FunctionApi installed");
|
|
3648
3743
|
});
|
|
3649
3744
|
return;
|
|
3650
3745
|
}
|
|
@@ -3655,7 +3750,7 @@ class FuncRuntime {
|
|
|
3655
3750
|
} catch (err) {
|
|
3656
3751
|
settle((e3) => {
|
|
3657
3752
|
e3.status = "failed";
|
|
3658
|
-
e3.error = transportError(err instanceof Error ? err.message : String(err));
|
|
3753
|
+
e3.error = transportError$1(err instanceof Error ? err.message : String(err));
|
|
3659
3754
|
});
|
|
3660
3755
|
return;
|
|
3661
3756
|
}
|
|
@@ -3666,7 +3761,7 @@ class FuncRuntime {
|
|
|
3666
3761
|
} catch (err) {
|
|
3667
3762
|
settle((e3) => {
|
|
3668
3763
|
e3.status = "failed";
|
|
3669
|
-
e3.error = transportError(`failed to decode result: ${err instanceof Error ? err.message : String(err)}`);
|
|
3764
|
+
e3.error = transportError$1(`failed to decode result: ${err instanceof Error ? err.message : String(err)}`);
|
|
3670
3765
|
});
|
|
3671
3766
|
return;
|
|
3672
3767
|
}
|
|
@@ -3686,7 +3781,7 @@ class FuncRuntime {
|
|
|
3686
3781
|
}
|
|
3687
3782
|
/** Build the handle value for one `Func.bind` platform evaluation. */
|
|
3688
3783
|
buildHandle(handleType, name) {
|
|
3689
|
-
const sig =
|
|
3784
|
+
const sig = signatureOfFuncHandleType(handleType);
|
|
3690
3785
|
const runtime = this;
|
|
3691
3786
|
const resolveWorkspace = () => {
|
|
3692
3787
|
if (!runtime.workspace) {
|
|
@@ -3724,12 +3819,8 @@ class FuncRuntime {
|
|
|
3724
3819
|
return entry.status === "running";
|
|
3725
3820
|
},
|
|
3726
3821
|
cancel: () => {
|
|
3727
|
-
const { key
|
|
3728
|
-
|
|
3729
|
-
entry.launchSeq += 1;
|
|
3730
|
-
entry.status = "cancelled";
|
|
3731
|
-
runtime.notify(key);
|
|
3732
|
-
}
|
|
3822
|
+
const { key } = channel();
|
|
3823
|
+
runtime.cancelChannel(key);
|
|
3733
3824
|
return null;
|
|
3734
3825
|
},
|
|
3735
3826
|
binding: { name }
|
|
@@ -7464,6 +7555,435 @@ var faArrowDown = {
|
|
|
7464
7555
|
iconName: "arrow-down",
|
|
7465
7556
|
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"]
|
|
7466
7557
|
};
|
|
7558
|
+
var faCircleInfo = {
|
|
7559
|
+
prefix: "fas",
|
|
7560
|
+
iconName: "circle-info",
|
|
7561
|
+
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"]
|
|
7562
|
+
};
|
|
7563
|
+
function createDefaultRecordApi(apiUrl, repo, getToken) {
|
|
7564
|
+
const opts = () => ({ token: getToken() });
|
|
7565
|
+
return {
|
|
7566
|
+
async describe(workspace, record) {
|
|
7567
|
+
return e3ApiClient.workspaceRecordDescribe(apiUrl, repo, workspace, record, opts());
|
|
7568
|
+
},
|
|
7569
|
+
async mutate(workspace, record, mutation, req) {
|
|
7570
|
+
return e3ApiClient.workspaceRecordMutate(apiUrl, repo, workspace, record, mutation, {
|
|
7571
|
+
args: req.args,
|
|
7572
|
+
actor: east.none,
|
|
7573
|
+
limits: east.none
|
|
7574
|
+
}, opts());
|
|
7575
|
+
},
|
|
7576
|
+
async history(workspace, record, limit2, from2) {
|
|
7577
|
+
return e3ApiClient.workspaceRecordHistory(apiUrl, repo, workspace, record, limit2, opts(), from2);
|
|
7578
|
+
}
|
|
7579
|
+
};
|
|
7580
|
+
}
|
|
7581
|
+
const eastTypeEqual = east.equalFor(internal$1.EastTypeType);
|
|
7582
|
+
const RESERVED_MUTATE_FIELDS = /* @__PURE__ */ new Set(["pending", "status", "error", "cancel"]);
|
|
7583
|
+
function signatureOfRecordHandleType(handleType) {
|
|
7584
|
+
var _a2, _b2;
|
|
7585
|
+
if (handleType.type !== "Struct") {
|
|
7586
|
+
throw new Error(`Record.bind: handle type must be a Struct; got ${handleType.type}`);
|
|
7587
|
+
}
|
|
7588
|
+
const fields = handleType.value;
|
|
7589
|
+
const read = (_a2 = fields.find((f2) => f2.name === "read")) == null ? void 0 : _a2.type;
|
|
7590
|
+
const mutate = (_b2 = fields.find((f2) => f2.name === "mutate")) == null ? void 0 : _b2.type;
|
|
7591
|
+
if ((read == null ? void 0 : read.type) !== "Function") {
|
|
7592
|
+
throw new Error("Record.bind: handle type is missing its read closure");
|
|
7593
|
+
}
|
|
7594
|
+
if ((mutate == null ? void 0 : mutate.type) !== "Struct") {
|
|
7595
|
+
throw new Error("Record.bind: handle type is missing its mutate struct");
|
|
7596
|
+
}
|
|
7597
|
+
const stateType = read.value.output;
|
|
7598
|
+
const mutations = /* @__PURE__ */ new Map();
|
|
7599
|
+
for (const field of mutate.value) {
|
|
7600
|
+
if (RESERVED_MUTATE_FIELDS.has(field.name)) continue;
|
|
7601
|
+
if (field.type.type !== "Function") {
|
|
7602
|
+
throw new Error(`Record.bind: mutate field "${field.name}" must be a closure`);
|
|
7603
|
+
}
|
|
7604
|
+
mutations.set(field.name, field.type.value.inputs);
|
|
7605
|
+
}
|
|
7606
|
+
return { stateType, mutations };
|
|
7607
|
+
}
|
|
7608
|
+
function errorOfMutationResult(result) {
|
|
7609
|
+
const outcome = result.outcome;
|
|
7610
|
+
switch (outcome.type) {
|
|
7611
|
+
case "invalid":
|
|
7612
|
+
return { kind: east.variant("invalid", { message: outcome.value.message }), message: outcome.value.message, stderr: "" };
|
|
7613
|
+
case "failed":
|
|
7614
|
+
return {
|
|
7615
|
+
kind: east.variant("failed", { exitCode: outcome.value.exitCode }),
|
|
7616
|
+
message: `reducer exited with code ${outcome.value.exitCode}`,
|
|
7617
|
+
stderr: outcome.value.stderr
|
|
7618
|
+
};
|
|
7619
|
+
case "too_large":
|
|
7620
|
+
return {
|
|
7621
|
+
kind: east.variant("too_large", { bytes: outcome.value.bytes, limit: outcome.value.limit }),
|
|
7622
|
+
message: `new state too large (${outcome.value.bytes} bytes, limit ${outcome.value.limit})`,
|
|
7623
|
+
stderr: outcome.value.stderr
|
|
7624
|
+
};
|
|
7625
|
+
case "timed_out":
|
|
7626
|
+
return {
|
|
7627
|
+
kind: east.variant("timed_out", { ms: outcome.value.ms }),
|
|
7628
|
+
message: `mutation timed out after ${outcome.value.ms}ms (server deadline)`,
|
|
7629
|
+
stderr: outcome.value.stderr
|
|
7630
|
+
};
|
|
7631
|
+
case "conflict":
|
|
7632
|
+
return {
|
|
7633
|
+
kind: east.variant("conflict", { attempts: outcome.value.attempts }),
|
|
7634
|
+
message: `compare-and-swap conflicted after ${outcome.value.attempts} attempts; try again`,
|
|
7635
|
+
stderr: ""
|
|
7636
|
+
};
|
|
7637
|
+
default:
|
|
7638
|
+
return transportError(`unexpected outcome "${outcome.type}"`);
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
function transportError(message) {
|
|
7642
|
+
return { kind: east.variant("transport", { message }), message, stderr: "" };
|
|
7643
|
+
}
|
|
7644
|
+
function recordPath(name) {
|
|
7645
|
+
return [east.variant("field", "records"), east.variant("field", name)];
|
|
7646
|
+
}
|
|
7647
|
+
function recordChannelKey(workspace, name) {
|
|
7648
|
+
return `record:${workspace}:${name}`;
|
|
7649
|
+
}
|
|
7650
|
+
class RecordRuntime extends TrackedChannelStore {
|
|
7651
|
+
constructor() {
|
|
7652
|
+
super(...arguments);
|
|
7653
|
+
__publicField(this, "api", null);
|
|
7654
|
+
__publicField(this, "cache", null);
|
|
7655
|
+
__publicField(this, "workspace", null);
|
|
7656
|
+
// Describe signatures, fetched once per (workspace, record), failures evicted.
|
|
7657
|
+
__publicField(this, "signatureCache", /* @__PURE__ */ new Map());
|
|
7658
|
+
// History per channel, fetched once + refreshed on commit.
|
|
7659
|
+
__publicField(this, "histories", /* @__PURE__ */ new Map());
|
|
7660
|
+
__publicField(this, "historyInFlight", /* @__PURE__ */ new Set());
|
|
7661
|
+
// Channels whose last history fetch failed. Guards `history()` from
|
|
7662
|
+
// re-issuing the request on every render (a failing endpoint would
|
|
7663
|
+
// otherwise spin); cleared on the next commit / on clear() so a later
|
|
7664
|
+
// mutation retries.
|
|
7665
|
+
__publicField(this, "historyFailed", /* @__PURE__ */ new Set());
|
|
7666
|
+
}
|
|
7667
|
+
createEntry() {
|
|
7668
|
+
return { status: "idle", launchSeq: 0 };
|
|
7669
|
+
}
|
|
7670
|
+
// ----- wiring ----------------------------------------------------------
|
|
7671
|
+
/** Install the API adapter + dataset cache + workspace — called by the
|
|
7672
|
+
* React provider (or a test/showcase harness) before any handle is used. */
|
|
7673
|
+
initialize(api3, cache3, workspace) {
|
|
7674
|
+
this.api = api3;
|
|
7675
|
+
this.cache = cache3;
|
|
7676
|
+
this.workspace = workspace;
|
|
7677
|
+
}
|
|
7678
|
+
/** Tear down the adapter and all record state. */
|
|
7679
|
+
clear() {
|
|
7680
|
+
this.api = null;
|
|
7681
|
+
this.cache = null;
|
|
7682
|
+
this.workspace = null;
|
|
7683
|
+
this.clearChannels();
|
|
7684
|
+
this.signatureCache.clear();
|
|
7685
|
+
this.histories.clear();
|
|
7686
|
+
this.historyInFlight.clear();
|
|
7687
|
+
this.historyFailed.clear();
|
|
7688
|
+
}
|
|
7689
|
+
resolveWorkspace() {
|
|
7690
|
+
if (!this.workspace) {
|
|
7691
|
+
throw new Error("Record.bind: no workspace configured — mount a provider (or call initializeRecordApi) first");
|
|
7692
|
+
}
|
|
7693
|
+
return this.workspace;
|
|
7694
|
+
}
|
|
7695
|
+
/** The deployed describe signature for a record (fetched once, evicted on failure). */
|
|
7696
|
+
signature(workspace, record) {
|
|
7697
|
+
const sigKey = `${workspace}:${record}`;
|
|
7698
|
+
let promise4 = this.signatureCache.get(sigKey);
|
|
7699
|
+
if (!promise4) {
|
|
7700
|
+
const api3 = this.api;
|
|
7701
|
+
if (!api3) return Promise.reject(new Error("Record.bind: no RecordApi installed"));
|
|
7702
|
+
promise4 = api3.describe(workspace, record).catch((err) => {
|
|
7703
|
+
this.signatureCache.delete(sigKey);
|
|
7704
|
+
throw err;
|
|
7705
|
+
});
|
|
7706
|
+
this.signatureCache.set(sigKey, promise4);
|
|
7707
|
+
}
|
|
7708
|
+
return promise4;
|
|
7709
|
+
}
|
|
7710
|
+
/** Validate a mutation's declared arg types against the deployed signature. */
|
|
7711
|
+
async validate(workspace, record, mutation, argTypes) {
|
|
7712
|
+
let sig;
|
|
7713
|
+
try {
|
|
7714
|
+
sig = await this.signature(workspace, record);
|
|
7715
|
+
} catch (err) {
|
|
7716
|
+
return transportError(`failed to describe record "${record}": ${err instanceof Error ? err.message : String(err)}`);
|
|
7717
|
+
}
|
|
7718
|
+
const deployed = sig.mutations.find((m2) => m2.name === mutation);
|
|
7719
|
+
if (!deployed) {
|
|
7720
|
+
const message = `no mutation "${mutation}" on record "${record}" in the deployed package`;
|
|
7721
|
+
return { kind: east.variant("invalid", { message }), message, stderr: "" };
|
|
7722
|
+
}
|
|
7723
|
+
const argsMatch = deployed.argTypes.length === argTypes.length && deployed.argTypes.every((t4, i) => eastTypeEqual(t4, argTypes[i]));
|
|
7724
|
+
if (!argsMatch) {
|
|
7725
|
+
const message = `signature mismatch for mutation "${mutation}": bound (${argTypes.length} args) disagrees with the deployed package`;
|
|
7726
|
+
return { kind: east.variant("invalid", { message }), message, stderr: "" };
|
|
7727
|
+
}
|
|
7728
|
+
return null;
|
|
7729
|
+
}
|
|
7730
|
+
/** Fetch a record's history once per channel (deduped); notify on settle. */
|
|
7731
|
+
fetchHistory(workspace, record, key) {
|
|
7732
|
+
if (this.historyInFlight.has(key)) return;
|
|
7733
|
+
this.historyInFlight.add(key);
|
|
7734
|
+
void (async () => {
|
|
7735
|
+
const api3 = this.api;
|
|
7736
|
+
try {
|
|
7737
|
+
if (!api3) throw new Error("no RecordApi installed");
|
|
7738
|
+
const result = await api3.history(workspace, record, void 0);
|
|
7739
|
+
this.histories.set(key, result.commits);
|
|
7740
|
+
this.historyFailed.delete(key);
|
|
7741
|
+
} catch {
|
|
7742
|
+
this.historyFailed.add(key);
|
|
7743
|
+
} finally {
|
|
7744
|
+
this.historyInFlight.delete(key);
|
|
7745
|
+
this.notify(key);
|
|
7746
|
+
}
|
|
7747
|
+
})();
|
|
7748
|
+
}
|
|
7749
|
+
// ----- closure semantics -------------------------------------------------
|
|
7750
|
+
launchMutation(workspace, record, mutation, argTypes, args) {
|
|
7751
|
+
const key = recordChannelKey(workspace, record);
|
|
7752
|
+
const { entry, settle } = this.beginLaunch(key, (e3) => {
|
|
7753
|
+
delete e3.error;
|
|
7754
|
+
});
|
|
7755
|
+
const run = (async () => {
|
|
7756
|
+
var _a2;
|
|
7757
|
+
const invalid = await this.validate(workspace, record, mutation, argTypes);
|
|
7758
|
+
if (invalid) {
|
|
7759
|
+
settle((e3) => {
|
|
7760
|
+
e3.status = "failed";
|
|
7761
|
+
e3.error = invalid;
|
|
7762
|
+
});
|
|
7763
|
+
return;
|
|
7764
|
+
}
|
|
7765
|
+
const api3 = this.api;
|
|
7766
|
+
if (!api3) {
|
|
7767
|
+
settle((e3) => {
|
|
7768
|
+
e3.status = "failed";
|
|
7769
|
+
e3.error = transportError("no RecordApi installed");
|
|
7770
|
+
});
|
|
7771
|
+
return;
|
|
7772
|
+
}
|
|
7773
|
+
let result;
|
|
7774
|
+
try {
|
|
7775
|
+
const encoded = args.map((arg, i) => east.encodeBeast2For(argTypes[i])(arg));
|
|
7776
|
+
result = await api3.mutate(workspace, record, mutation, { args: encoded });
|
|
7777
|
+
} catch (err) {
|
|
7778
|
+
settle((e3) => {
|
|
7779
|
+
e3.status = "failed";
|
|
7780
|
+
e3.error = transportError(err instanceof Error ? err.message : String(err));
|
|
7781
|
+
});
|
|
7782
|
+
return;
|
|
7783
|
+
}
|
|
7784
|
+
if (result.outcome.type === "committed") {
|
|
7785
|
+
settle((e3) => {
|
|
7786
|
+
e3.status = "committed";
|
|
7787
|
+
delete e3.error;
|
|
7788
|
+
});
|
|
7789
|
+
void ((_a2 = this.cache) == null ? void 0 : _a2.refresh(workspace));
|
|
7790
|
+
this.histories.delete(key);
|
|
7791
|
+
this.historyFailed.delete(key);
|
|
7792
|
+
this.notify(key);
|
|
7793
|
+
} else {
|
|
7794
|
+
settle((e3) => {
|
|
7795
|
+
e3.status = "failed";
|
|
7796
|
+
e3.error = errorOfMutationResult(result);
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7799
|
+
})();
|
|
7800
|
+
entry.inflight = run;
|
|
7801
|
+
void run.finally(() => {
|
|
7802
|
+
const current = this.entries.get(key);
|
|
7803
|
+
if (current && current.inflight === run) delete current.inflight;
|
|
7804
|
+
});
|
|
7805
|
+
}
|
|
7806
|
+
/** Build the handle value for one `Record.bind` platform evaluation. */
|
|
7807
|
+
buildHandle(handleType, name) {
|
|
7808
|
+
const sig = signatureOfRecordHandleType(handleType);
|
|
7809
|
+
const decodeState = east.decodeBeast2For(sig.stateType);
|
|
7810
|
+
const runtime = this;
|
|
7811
|
+
const path2 = recordPath(name);
|
|
7812
|
+
const requireCache = () => {
|
|
7813
|
+
if (!runtime.cache) {
|
|
7814
|
+
throw new Error("Record.bind: no dataset cache configured — mount a provider first");
|
|
7815
|
+
}
|
|
7816
|
+
return runtime.cache;
|
|
7817
|
+
};
|
|
7818
|
+
const mutate = {
|
|
7819
|
+
pending: () => {
|
|
7820
|
+
const ws = runtime.resolveWorkspace();
|
|
7821
|
+
const key = recordChannelKey(ws, name);
|
|
7822
|
+
runtime.track(key);
|
|
7823
|
+
return runtime.entry(key).status === "running";
|
|
7824
|
+
},
|
|
7825
|
+
status: () => {
|
|
7826
|
+
const ws = runtime.resolveWorkspace();
|
|
7827
|
+
const key = recordChannelKey(ws, name);
|
|
7828
|
+
runtime.track(key);
|
|
7829
|
+
return east.variant(runtime.entry(key).status, null);
|
|
7830
|
+
},
|
|
7831
|
+
error: () => {
|
|
7832
|
+
const ws = runtime.resolveWorkspace();
|
|
7833
|
+
const key = recordChannelKey(ws, name);
|
|
7834
|
+
runtime.track(key);
|
|
7835
|
+
const entry = runtime.entry(key);
|
|
7836
|
+
return entry.status === "failed" && entry.error !== void 0 ? east.some(entry.error) : east.none;
|
|
7837
|
+
},
|
|
7838
|
+
cancel: () => {
|
|
7839
|
+
const ws = runtime.resolveWorkspace();
|
|
7840
|
+
runtime.cancelChannel(recordChannelKey(ws, name), (e3) => {
|
|
7841
|
+
delete e3.error;
|
|
7842
|
+
delete e3.inflight;
|
|
7843
|
+
});
|
|
7844
|
+
return null;
|
|
7845
|
+
}
|
|
7846
|
+
};
|
|
7847
|
+
for (const [mutationName, argTypes] of sig.mutations) {
|
|
7848
|
+
mutate[mutationName] = (...args) => {
|
|
7849
|
+
runtime.launchMutation(runtime.resolveWorkspace(), name, mutationName, argTypes, args);
|
|
7850
|
+
return null;
|
|
7851
|
+
};
|
|
7852
|
+
}
|
|
7853
|
+
return {
|
|
7854
|
+
read: () => {
|
|
7855
|
+
const ws = runtime.resolveWorkspace();
|
|
7856
|
+
trackDatasetPath(ws, path2);
|
|
7857
|
+
const bytes = requireCache().read(ws, path2);
|
|
7858
|
+
if (bytes === void 0) {
|
|
7859
|
+
throw new Error(`Record.bind: record state not loaded: ${datasetCacheKey(ws, path2)} (it should be preloaded via the UI task manifest)`);
|
|
7860
|
+
}
|
|
7861
|
+
return decodeState(bytes);
|
|
7862
|
+
},
|
|
7863
|
+
status: () => {
|
|
7864
|
+
const ws = runtime.resolveWorkspace();
|
|
7865
|
+
trackDatasetPath(ws, path2);
|
|
7866
|
+
return requireCache().getStatus(ws, path2);
|
|
7867
|
+
},
|
|
7868
|
+
history: () => {
|
|
7869
|
+
const ws = runtime.resolveWorkspace();
|
|
7870
|
+
const key = recordChannelKey(ws, name);
|
|
7871
|
+
runtime.track(key);
|
|
7872
|
+
const cached = runtime.histories.get(key);
|
|
7873
|
+
if (cached === void 0) {
|
|
7874
|
+
if (!runtime.historyFailed.has(key)) runtime.fetchHistory(ws, name, key);
|
|
7875
|
+
return east.none;
|
|
7876
|
+
}
|
|
7877
|
+
return east.some(cached);
|
|
7878
|
+
},
|
|
7879
|
+
mutate,
|
|
7880
|
+
start: () => {
|
|
7881
|
+
const ws = runtime.resolveWorkspace();
|
|
7882
|
+
const cache3 = requireCache();
|
|
7883
|
+
const inflight = runtime.entry(recordChannelKey(ws, name)).inflight;
|
|
7884
|
+
void Promise.resolve(inflight).catch(() => void 0).then(() => cache3.launchDataflow(ws)).catch(() => void 0);
|
|
7885
|
+
return null;
|
|
7886
|
+
},
|
|
7887
|
+
binding: { name, mutations: [...sig.mutations.keys()] }
|
|
7888
|
+
};
|
|
7889
|
+
}
|
|
7890
|
+
// ----- platform building -------------------------------------------------
|
|
7891
|
+
/** Build a `Record.bind` PlatformFunction bound to this runtime. Pass
|
|
7892
|
+
* `allowed=null` for an unscoped impl; pass a Set of record names for
|
|
7893
|
+
* manifest scoping. */
|
|
7894
|
+
buildPlatform(allowed) {
|
|
7895
|
+
return internal.recordBindPlatformFn.implement(
|
|
7896
|
+
(handleType) => (nameArg) => {
|
|
7897
|
+
const name = nameArg;
|
|
7898
|
+
if (allowed && !allowed.has(name)) {
|
|
7899
|
+
throw new Error(
|
|
7900
|
+
`Record.bind: record "${name}" is not in this UI task's manifest — bind it in the task body so derivation records it`
|
|
7901
|
+
);
|
|
7902
|
+
}
|
|
7903
|
+
return this.buildHandle(handleType, name);
|
|
7904
|
+
}
|
|
7905
|
+
);
|
|
7906
|
+
}
|
|
7907
|
+
}
|
|
7908
|
+
const defaultRecordRuntime = new RecordRuntime();
|
|
7909
|
+
function initializeRecordApi(api3, cache3, workspace) {
|
|
7910
|
+
defaultRecordRuntime.initialize(api3, cache3, workspace);
|
|
7911
|
+
}
|
|
7912
|
+
function clearRecordApi() {
|
|
7913
|
+
defaultRecordRuntime.clear();
|
|
7914
|
+
}
|
|
7915
|
+
const RecordPlatform = [defaultRecordRuntime.buildPlatform(null)];
|
|
7916
|
+
function createScopedRecordPlatform(records) {
|
|
7917
|
+
return [defaultRecordRuntime.buildPlatform(new Set(records))];
|
|
7918
|
+
}
|
|
7919
|
+
function createInMemoryRecordApi(cache3, workspace, defs2) {
|
|
7920
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
7921
|
+
for (const def of defs2) {
|
|
7922
|
+
const stateType = east.toEastTypeValue(def.stateType);
|
|
7923
|
+
const mutations = /* @__PURE__ */ new Map();
|
|
7924
|
+
for (const m2 of def.mutations) {
|
|
7925
|
+
mutations.set(m2.name, { argTypes: m2.argTypes.map((t4) => east.toEastTypeValue(t4)), reduce: m2.reduce });
|
|
7926
|
+
}
|
|
7927
|
+
const genesis = {
|
|
7928
|
+
hash: `${def.name}-0`.padEnd(64, "0"),
|
|
7929
|
+
parent: east.none,
|
|
7930
|
+
state: `${def.name}-state-0`.padEnd(64, "0"),
|
|
7931
|
+
mutation: "$init",
|
|
7932
|
+
actor: "memory",
|
|
7933
|
+
at: /* @__PURE__ */ new Date(0)
|
|
7934
|
+
};
|
|
7935
|
+
compiled.set(def.name, { stateType, mutations, commits: [genesis], seq: 0 });
|
|
7936
|
+
void cache3.write(workspace, recordPath(def.name), east.encodeBeast2For(stateType)(def.initial));
|
|
7937
|
+
}
|
|
7938
|
+
return {
|
|
7939
|
+
async describe(_ws, record) {
|
|
7940
|
+
const c2 = compiled.get(record);
|
|
7941
|
+
if (!c2) throw new Error(`no in-memory record "${record}"`);
|
|
7942
|
+
return {
|
|
7943
|
+
name: record,
|
|
7944
|
+
mutations: [...c2.mutations].map(([name, m2]) => ({ name, argTypes: m2.argTypes }))
|
|
7945
|
+
};
|
|
7946
|
+
},
|
|
7947
|
+
async mutate(ws, record, mutation, req) {
|
|
7948
|
+
const c2 = compiled.get(record);
|
|
7949
|
+
if (!c2) throw new Error(`no in-memory record "${record}"`);
|
|
7950
|
+
const m2 = c2.mutations.get(mutation);
|
|
7951
|
+
if (!m2) return { outcome: east.variant("invalid", { message: `no mutation "${mutation}"` }) };
|
|
7952
|
+
const current = cache3.read(ws, recordPath(record));
|
|
7953
|
+
const state = current !== void 0 ? east.decodeBeast2For(c2.stateType)(current) : void 0;
|
|
7954
|
+
const args = req.args.map((bytes, i) => east.decodeBeast2For(m2.argTypes[i])(bytes));
|
|
7955
|
+
const next2 = m2.reduce(state, ...args);
|
|
7956
|
+
await cache3.write(ws, recordPath(record), east.encodeBeast2For(c2.stateType)(next2));
|
|
7957
|
+
c2.seq += 1;
|
|
7958
|
+
const hash2 = `${record}-${c2.seq}`.padEnd(64, "0");
|
|
7959
|
+
c2.commits.unshift({
|
|
7960
|
+
hash: hash2,
|
|
7961
|
+
parent: east.some(c2.commits[0].hash),
|
|
7962
|
+
state: `${record}-state-${c2.seq}`.padEnd(64, "0"),
|
|
7963
|
+
mutation,
|
|
7964
|
+
actor: "memory",
|
|
7965
|
+
at: /* @__PURE__ */ new Date(0)
|
|
7966
|
+
});
|
|
7967
|
+
return { outcome: east.variant("committed", { commitHash: hash2, stateHash: `${record}-state-${c2.seq}`.padEnd(64, "0") }) };
|
|
7968
|
+
},
|
|
7969
|
+
async history(_ws, record, limit2) {
|
|
7970
|
+
const c2 = compiled.get(record);
|
|
7971
|
+
if (!c2) throw new Error(`no in-memory record "${record}"`);
|
|
7972
|
+
const commits = limit2 !== void 0 ? c2.commits.slice(0, limit2) : c2.commits;
|
|
7973
|
+
return { commits };
|
|
7974
|
+
}
|
|
7975
|
+
};
|
|
7976
|
+
}
|
|
7977
|
+
platform.registerReactiveTracker({
|
|
7978
|
+
id: "record-bind",
|
|
7979
|
+
enableTracking: () => defaultRecordRuntime.enableTracking(),
|
|
7980
|
+
disableTracking: () => defaultRecordRuntime.disableTracking(),
|
|
7981
|
+
getStore: () => ({
|
|
7982
|
+
subscribe: (key, cb) => defaultRecordRuntime.subscribe(key, cb),
|
|
7983
|
+
getKeyVersion: (key) => defaultRecordRuntime.getKeyVersion(key)
|
|
7984
|
+
})
|
|
7985
|
+
});
|
|
7986
|
+
platform.registerPlatformImplementation(RecordPlatform);
|
|
7467
7987
|
const E3ConfigContext = React.createContext(null);
|
|
7468
7988
|
function E3Provider({ children: children2, config: config2, queryClient: externalClient }) {
|
|
7469
7989
|
const client = React.useMemo(
|
|
@@ -7514,11 +8034,21 @@ function ReactiveDatasetProvider({
|
|
|
7514
8034
|
);
|
|
7515
8035
|
}
|
|
7516
8036
|
}, [e3.apiUrl, e3.repo, e3.workspace]);
|
|
8037
|
+
React.useMemo(() => {
|
|
8038
|
+
if (e3.workspace !== void 0) {
|
|
8039
|
+
initializeRecordApi(
|
|
8040
|
+
createDefaultRecordApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
|
|
8041
|
+
cache3,
|
|
8042
|
+
e3.workspace
|
|
8043
|
+
);
|
|
8044
|
+
}
|
|
8045
|
+
}, [e3.apiUrl, e3.repo, e3.workspace, cache3]);
|
|
7517
8046
|
React.useEffect(() => {
|
|
7518
8047
|
return () => {
|
|
7519
8048
|
const ws = cache3.getConfig().workspace;
|
|
7520
8049
|
clearReactiveDatasetCache();
|
|
7521
8050
|
clearFunctionApi();
|
|
8051
|
+
clearRecordApi();
|
|
7522
8052
|
cache3.destroy();
|
|
7523
8053
|
if (ws) clearBindingRegistry(ws);
|
|
7524
8054
|
else clearBindingRegistry();
|
|
@@ -21879,13 +22409,13 @@ var hashIterableInts = function hashIterableInts2(iterator) {
|
|
|
21879
22409
|
}
|
|
21880
22410
|
return hash2;
|
|
21881
22411
|
};
|
|
21882
|
-
var hashInt = function hashInt2(
|
|
22412
|
+
var hashInt = function hashInt2(num2) {
|
|
21883
22413
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED;
|
|
21884
|
-
return seed * K +
|
|
22414
|
+
return seed * K + num2 | 0;
|
|
21885
22415
|
};
|
|
21886
|
-
var hashIntAlt = function hashIntAlt2(
|
|
22416
|
+
var hashIntAlt = function hashIntAlt2(num2) {
|
|
21887
22417
|
var seed = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : DEFAULT_HASH_SEED_ALT;
|
|
21888
|
-
return (seed << 5) + seed +
|
|
22418
|
+
return (seed << 5) + seed + num2 | 0;
|
|
21889
22419
|
};
|
|
21890
22420
|
var combineHashes = function combineHashes2(hash1, hash2) {
|
|
21891
22421
|
return hash1 * 2097152 + hash2;
|
|
@@ -23472,7 +24002,7 @@ var max = function max2(arr2) {
|
|
|
23472
24002
|
}
|
|
23473
24003
|
return max5;
|
|
23474
24004
|
};
|
|
23475
|
-
var mean
|
|
24005
|
+
var mean = function mean2(arr2) {
|
|
23476
24006
|
var begin3 = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 0;
|
|
23477
24007
|
var end3 = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : arr2.length;
|
|
23478
24008
|
var total = 0;
|
|
@@ -25785,7 +26315,7 @@ var getPreference = function getPreference2(S2, preference) {
|
|
|
25785
26315
|
if (preference === "median") {
|
|
25786
26316
|
p2 = median(S2);
|
|
25787
26317
|
} else if (preference === "mean") {
|
|
25788
|
-
p2 = mean
|
|
26318
|
+
p2 = mean(S2);
|
|
25789
26319
|
} else if (preference === "min") {
|
|
25790
26320
|
p2 = min(S2);
|
|
25791
26321
|
} else if (preference === "max") {
|
|
@@ -56758,7 +57288,7 @@ function useBindingValue(binding) {
|
|
|
56758
57288
|
return { value, mode, pending, mutate, commit, discard };
|
|
56759
57289
|
}
|
|
56760
57290
|
const IDLE = { call: () => {
|
|
56761
|
-
}, result: null, status: "idle", pending: false };
|
|
57291
|
+
}, result: null, status: "idle", pending: false, error: null };
|
|
56762
57292
|
function useFuncCall(name, inputs, output2) {
|
|
56763
57293
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
56764
57294
|
const ready4 = !!name && !!inputs && workspace !== "";
|
|
@@ -56781,15 +57311,22 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56781
57311
|
React.useCallback(() => channelKey ? defaultFuncRuntime.getKeyVersion(channelKey) : 0, [channelKey])
|
|
56782
57312
|
);
|
|
56783
57313
|
return React.useMemo(() => {
|
|
57314
|
+
var _a2;
|
|
56784
57315
|
if (!handle) return IDLE;
|
|
56785
57316
|
let result = null;
|
|
56786
57317
|
let status = "idle";
|
|
56787
57318
|
let pending = false;
|
|
57319
|
+
let error3 = null;
|
|
56788
57320
|
try {
|
|
56789
57321
|
const read = handle.read();
|
|
56790
57322
|
result = read.type === "some" ? read.value : null;
|
|
56791
57323
|
status = handle.status().type;
|
|
56792
57324
|
pending = handle.pending();
|
|
57325
|
+
const err = handle.error();
|
|
57326
|
+
if (err.type === "some") {
|
|
57327
|
+
const v3 = err.value;
|
|
57328
|
+
error3 = { kind: ((_a2 = v3.kind) == null ? void 0 : _a2.type) ?? "failed", message: v3.message ?? "", stdout: v3.stdout ?? "", stderr: v3.stderr ?? "" };
|
|
57329
|
+
}
|
|
56793
57330
|
} catch {
|
|
56794
57331
|
}
|
|
56795
57332
|
return { call: (...args) => {
|
|
@@ -56797,7 +57334,7 @@ function useFuncCall(name, inputs, output2) {
|
|
|
56797
57334
|
handle.call(...args);
|
|
56798
57335
|
} catch {
|
|
56799
57336
|
}
|
|
56800
|
-
}, result, status, pending };
|
|
57337
|
+
}, result, status, pending, error: error3 };
|
|
56801
57338
|
}, [handle, version2]);
|
|
56802
57339
|
}
|
|
56803
57340
|
var propTypes = { exports: {} };
|
|
@@ -59041,6 +59578,370 @@ function Circle(_ref2) {
|
|
|
59041
59578
|
className: cx("visx-circle", className)
|
|
59042
59579
|
}, restProps));
|
|
59043
59580
|
}
|
|
59581
|
+
function fillHelp(entry, vars) {
|
|
59582
|
+
const sub = (s2) => s2.replace(/\{(\w+)\}/g, (m2, k2) => k2 in vars ? vars[k2] : m2);
|
|
59583
|
+
return {
|
|
59584
|
+
term: sub(entry.term),
|
|
59585
|
+
gist: sub(entry.gist),
|
|
59586
|
+
detail: sub(entry.detail),
|
|
59587
|
+
...entry.jargon !== void 0 ? { jargon: sub(entry.jargon) } : {}
|
|
59588
|
+
};
|
|
59589
|
+
}
|
|
59590
|
+
const HELP = {
|
|
59591
|
+
// ── Guidance mode (the toggle itself) ───────────────────────────────
|
|
59592
|
+
guidance: {
|
|
59593
|
+
term: "Guidance",
|
|
59594
|
+
gist: "Plain-language explainers for everything on this surface.",
|
|
59595
|
+
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."
|
|
59596
|
+
},
|
|
59597
|
+
// ── Header ──────────────────────────────────────────────────────────
|
|
59598
|
+
header: {
|
|
59599
|
+
term: "The question",
|
|
59600
|
+
gist: "You’re asking whether {treatment} actually moved {outcome}.",
|
|
59601
|
+
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.",
|
|
59602
|
+
jargon: "a binary-treatment causal effect"
|
|
59603
|
+
},
|
|
59604
|
+
// ── Set-up rail ─────────────────────────────────────────────────────
|
|
59605
|
+
step_treatment: {
|
|
59606
|
+
term: "What did you change?",
|
|
59607
|
+
gist: "The lever you’re testing — a yes/no flag on each {subject}.",
|
|
59608
|
+
detail: "It splits {subjects} into a “did” group and a “didn’t” group; the whole experiment compares those two.",
|
|
59609
|
+
jargon: "the treatment (binary)"
|
|
59610
|
+
},
|
|
59611
|
+
step_outcome: {
|
|
59612
|
+
term: "What did you want it to improve?",
|
|
59613
|
+
gist: "The result you’re hoping {treatment} moved.",
|
|
59614
|
+
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.",
|
|
59615
|
+
jargon: "the outcome"
|
|
59616
|
+
},
|
|
59617
|
+
step_confounders: {
|
|
59618
|
+
term: "What else was different?",
|
|
59619
|
+
gist: "Things about the “did” group that could also explain the change.",
|
|
59620
|
+
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.",
|
|
59621
|
+
jargon: "confounders / the backdoor adjustment set"
|
|
59622
|
+
},
|
|
59623
|
+
step_population: {
|
|
59624
|
+
term: "Which {subjects}?",
|
|
59625
|
+
gist: "Narrow the experiment to a slice of the {subjects}.",
|
|
59626
|
+
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.",
|
|
59627
|
+
jargon: "population / cohort filter"
|
|
59628
|
+
},
|
|
59629
|
+
adv_method: {
|
|
59630
|
+
term: "How to compare",
|
|
59631
|
+
gist: "The maths used to make the like-for-like comparison.",
|
|
59632
|
+
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.",
|
|
59633
|
+
jargon: "estimator: linear regression vs propensity-score weighting"
|
|
59634
|
+
},
|
|
59635
|
+
adv_estimand: {
|
|
59636
|
+
term: "Answer for",
|
|
59637
|
+
gist: "Who the effect is reported for.",
|
|
59638
|
+
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.",
|
|
59639
|
+
jargon: "estimand: ATE vs ATT"
|
|
59640
|
+
},
|
|
59641
|
+
confounder_imbalance: {
|
|
59642
|
+
term: "How lopsided this one was",
|
|
59643
|
+
gist: "How different the two groups were on this factor, before adjusting.",
|
|
59644
|
+
detail: "A long bar means the treated and untreated {subjects} started far apart here — exactly the kind of gap the adjustment has to close.",
|
|
59645
|
+
jargon: "standardised mean difference (SMD)"
|
|
59646
|
+
},
|
|
59647
|
+
// ── Tabs ────────────────────────────────────────────────────────────
|
|
59648
|
+
tab_answer: {
|
|
59649
|
+
term: "Answer",
|
|
59650
|
+
gist: "The headline: did {treatment} move {outcome}, and by how much?",
|
|
59651
|
+
detail: "Shows the raw gap, the fair like-for-like estimate, and an honest verdict."
|
|
59652
|
+
},
|
|
59653
|
+
tab_trust: {
|
|
59654
|
+
term: "Can we trust it?",
|
|
59655
|
+
gist: "The stress tests we ran to try to break the answer.",
|
|
59656
|
+
detail: "Each check probes a way the result could be a fluke; green means it held up."
|
|
59657
|
+
},
|
|
59658
|
+
tab_dose: {
|
|
59659
|
+
term: "How much?",
|
|
59660
|
+
gist: "How the result changes as a factor goes up or down.",
|
|
59661
|
+
detail: "Not just yes/no — the shape of the response, so you can find the sweet spot."
|
|
59662
|
+
},
|
|
59663
|
+
// ── Answer tab ──────────────────────────────────────────────────────
|
|
59664
|
+
answer_effect: {
|
|
59665
|
+
term: "Like-for-like effect",
|
|
59666
|
+
gist: "The change in {outcome} we can credit to {treatment}.",
|
|
59667
|
+
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.",
|
|
59668
|
+
jargon: "the backdoor-adjusted average treatment effect"
|
|
59669
|
+
},
|
|
59670
|
+
answer_ci: {
|
|
59671
|
+
term: "95% range",
|
|
59672
|
+
gist: "Where the true effect most likely sits.",
|
|
59673
|
+
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}.",
|
|
59674
|
+
jargon: "95% confidence interval"
|
|
59675
|
+
},
|
|
59676
|
+
answer_flip: {
|
|
59677
|
+
term: "Raw and like-for-like disagree",
|
|
59678
|
+
gist: "The plain average points one way; the fair comparison points the other.",
|
|
59679
|
+
detail: "This happens when the treated group differed sharply on a confounder. Trust the like-for-like number — the raw gap is misleading here.",
|
|
59680
|
+
jargon: "confounding / Simpson’s-paradox sign flip"
|
|
59681
|
+
},
|
|
59682
|
+
answer_cautious: {
|
|
59683
|
+
term: "Treat this as provisional",
|
|
59684
|
+
gist: "We got a number, but a trust check failed.",
|
|
59685
|
+
detail: "The estimate may still be driven by something we didn’t adjust for. See “Can we trust it?” before acting on it.",
|
|
59686
|
+
jargon: "adjustment_insufficient verdict"
|
|
59687
|
+
},
|
|
59688
|
+
forest_plot: {
|
|
59689
|
+
term: "Raw average vs. like-for-like",
|
|
59690
|
+
gist: "The misleading number next to the fair one.",
|
|
59691
|
+
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.",
|
|
59692
|
+
jargon: "a forest plot of naive vs adjusted estimates"
|
|
59693
|
+
},
|
|
59694
|
+
forest_naive: {
|
|
59695
|
+
term: "Raw average",
|
|
59696
|
+
gist: "The plain difference, with no adjusting.",
|
|
59697
|
+
detail: "Just treated-minus-untreated. It mixes in who you targeted, so it can mislead — it’s here only as the “before” picture.",
|
|
59698
|
+
jargon: "the unadjusted mean difference"
|
|
59699
|
+
},
|
|
59700
|
+
forest_adjusted: {
|
|
59701
|
+
term: "Like-for-like",
|
|
59702
|
+
gist: "The fair comparison after levelling the confounders.",
|
|
59703
|
+
detail: "This is the number to act on — what {treatment} did among otherwise-similar {subjects}.",
|
|
59704
|
+
jargon: "the adjusted effect"
|
|
59705
|
+
},
|
|
59706
|
+
balance: {
|
|
59707
|
+
term: "How unbalanced each one was",
|
|
59708
|
+
gist: "How far apart the two groups started on each factor.",
|
|
59709
|
+
detail: "Big bars are the factors the adjustment had to work hardest to correct — and the ones most worth double-checking.",
|
|
59710
|
+
jargon: "pre-adjustment covariate balance (SMD)"
|
|
59711
|
+
},
|
|
59712
|
+
counts: {
|
|
59713
|
+
term: "The {subject} counts",
|
|
59714
|
+
gist: "How many {subjects} went into the comparison.",
|
|
59715
|
+
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.",
|
|
59716
|
+
jargon: "n total / on-support / off-support"
|
|
59717
|
+
},
|
|
59718
|
+
// ── Refusal zones + overlap ─────────────────────────────────────────
|
|
59719
|
+
overlap_histogram: {
|
|
59720
|
+
term: "Do the two groups overlap?",
|
|
59721
|
+
gist: "Whether treated and untreated {subjects} actually look alike anywhere.",
|
|
59722
|
+
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.",
|
|
59723
|
+
jargon: "propensity-score overlap / common support"
|
|
59724
|
+
},
|
|
59725
|
+
refusal_positivity: {
|
|
59726
|
+
term: "No like-for-like comparison exists",
|
|
59727
|
+
gist: "The treated and untreated {subjects} are too different to compare.",
|
|
59728
|
+
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.",
|
|
59729
|
+
jargon: "positivity / overlap violation"
|
|
59730
|
+
},
|
|
59731
|
+
refusal_not_estimable: {
|
|
59732
|
+
term: "Can’t be estimated",
|
|
59733
|
+
gist: "Almost every {subject} is on one side of {treatment}, so there’s no comparison group.",
|
|
59734
|
+
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.",
|
|
59735
|
+
jargon: "no treatment variation"
|
|
59736
|
+
},
|
|
59737
|
+
// ── Trust tab ───────────────────────────────────────────────────────
|
|
59738
|
+
trust_intro: {
|
|
59739
|
+
term: "Can we trust it?",
|
|
59740
|
+
gist: "Before believing the answer, we tried to break it.",
|
|
59741
|
+
detail: "Each row is a different way the result could be a fluke; colour shows whether it held up (pass) or wobbled (caution)."
|
|
59742
|
+
},
|
|
59743
|
+
check_shuffle: {
|
|
59744
|
+
term: "Shuffle test",
|
|
59745
|
+
gist: "We randomly re-label who got {treatment} and re-measure.",
|
|
59746
|
+
detail: "On fake labels a genuine effect should collapse to nothing. If a “fake” effect still appears, the real one isn’t trustworthy.",
|
|
59747
|
+
jargon: "placebo / permutation refuter"
|
|
59748
|
+
},
|
|
59749
|
+
check_dropsome: {
|
|
59750
|
+
term: "Drop-some test",
|
|
59751
|
+
gist: "We re-run on random slices of the {subjects}.",
|
|
59752
|
+
detail: "A trustworthy effect barely moves when you drop some data; a jumpy one is resting on a few {subjects}.",
|
|
59753
|
+
jargon: "data-subset refuter"
|
|
59754
|
+
},
|
|
59755
|
+
check_decoy: {
|
|
59756
|
+
term: "Decoy factor",
|
|
59757
|
+
gist: "We add a totally random extra factor and re-measure.",
|
|
59758
|
+
detail: "A made-up factor shouldn’t change the answer. If it does, the model is over-reacting to noise.",
|
|
59759
|
+
jargon: "random-common-cause refuter"
|
|
59760
|
+
},
|
|
59761
|
+
check_hidden: {
|
|
59762
|
+
term: "Hidden cause",
|
|
59763
|
+
gist: "How strong an unrecorded cause would have to be to overturn the result.",
|
|
59764
|
+
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.",
|
|
59765
|
+
jargon: "unobserved-confounding sensitivity / E-value"
|
|
59766
|
+
},
|
|
59767
|
+
sensitivity: {
|
|
59768
|
+
term: "Effect as a hidden cause gets stronger",
|
|
59769
|
+
gist: "What happens to the effect if an unrecorded cause is dialled up.",
|
|
59770
|
+
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.",
|
|
59771
|
+
jargon: "sensitivity (tipping-point) curve"
|
|
59772
|
+
},
|
|
59773
|
+
// ── Dose tab ────────────────────────────────────────────────────────
|
|
59774
|
+
dose_curve: {
|
|
59775
|
+
term: "{outcome} gained vs. {feature}",
|
|
59776
|
+
gist: "How {outcome} responds as {feature} goes up.",
|
|
59777
|
+
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.",
|
|
59778
|
+
jargon: "accumulated local effects (ALE) dose-response"
|
|
59779
|
+
},
|
|
59780
|
+
dose_here: {
|
|
59781
|
+
term: "You are here",
|
|
59782
|
+
gist: "Where most of your {subjects} currently sit on {feature}.",
|
|
59783
|
+
detail: "The busiest level today — your starting point for deciding whether to push higher or lower.",
|
|
59784
|
+
jargon: "the modal bin"
|
|
59785
|
+
},
|
|
59786
|
+
dose_sweet: {
|
|
59787
|
+
term: "Sweet spot",
|
|
59788
|
+
gist: "Where the gains are solid and start to flatten.",
|
|
59789
|
+
detail: "Past here, pushing {feature} higher buys little extra {outcome} — a sensible target.",
|
|
59790
|
+
jargon: "the diminishing-returns knee"
|
|
59791
|
+
},
|
|
59792
|
+
dose_reco: {
|
|
59793
|
+
term: "Recommended",
|
|
59794
|
+
gist: "The level of {feature} we’d aim for.",
|
|
59795
|
+
detail: "The sweet-spot level and the {outcome} you’d expect there, with its range — and the trade-off of going one step further.",
|
|
59796
|
+
jargon: "the recommended operating point"
|
|
59797
|
+
},
|
|
59798
|
+
dose_marginal: {
|
|
59799
|
+
term: "Extra {outcome} per step",
|
|
59800
|
+
gist: "What each additional step of {feature} buys you.",
|
|
59801
|
+
detail: "Tall early bars then short ones is the classic diminishing-returns shape — spend effort where the bars are tall.",
|
|
59802
|
+
jargon: "marginal / incremental effect per bin"
|
|
59803
|
+
},
|
|
59804
|
+
// ── Verdicts ────────────────────────────────────────────────────────
|
|
59805
|
+
verdict_causal: {
|
|
59806
|
+
term: "Causal",
|
|
59807
|
+
gist: "A real change you can credit to {treatment} — and it held up.",
|
|
59808
|
+
detail: "The like-for-like effect is clear, sizeable, and survived the trust checks. Safe to act on.",
|
|
59809
|
+
jargon: "a robust, identified effect"
|
|
59810
|
+
},
|
|
59811
|
+
verdict_modest: {
|
|
59812
|
+
term: "Modest / unclear",
|
|
59813
|
+
gist: "There may be an effect, but it’s small or fuzzy.",
|
|
59814
|
+
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.",
|
|
59815
|
+
jargon: "effect CI spans zero / below materiality"
|
|
59816
|
+
},
|
|
59817
|
+
verdict_adjustment_insufficient: {
|
|
59818
|
+
term: "Not trustworthy yet",
|
|
59819
|
+
gist: "We got a number, but a trust check failed.",
|
|
59820
|
+
detail: "Likely something we didn’t adjust for is still in play. Treat as provisional and look at “Can we trust it?”.",
|
|
59821
|
+
jargon: "placebo / robustness failure"
|
|
59822
|
+
},
|
|
59823
|
+
verdict_non_identifiable_positivity: {
|
|
59824
|
+
term: "No fair comparison",
|
|
59825
|
+
gist: "Treated and untreated {subjects} don’t overlap enough to compare.",
|
|
59826
|
+
detail: "There’s no like-for-like “other side”, so any number would be extrapolation. See the overlap chart.",
|
|
59827
|
+
jargon: "positivity / overlap violation"
|
|
59828
|
+
},
|
|
59829
|
+
verdict_not_estimable: {
|
|
59830
|
+
term: "Can’t be estimated",
|
|
59831
|
+
gist: "Almost everyone is on one side of {treatment} — no comparison group.",
|
|
59832
|
+
detail: "One arm is just a handful of {subjects}, so we won’t guess an effect from it.",
|
|
59833
|
+
jargon: "no treatment variation"
|
|
59834
|
+
},
|
|
59835
|
+
// ── Validate tab ────────────────────────────────────────────────────
|
|
59836
|
+
tab_validate: {
|
|
59837
|
+
term: "Validate",
|
|
59838
|
+
gist: "The real experiment you’d run to prove this — for sure.",
|
|
59839
|
+
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."
|
|
59840
|
+
},
|
|
59841
|
+
validate_size: {
|
|
59842
|
+
term: "How many to run",
|
|
59843
|
+
gist: "The number of {subjects} the trial needs.",
|
|
59844
|
+
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.",
|
|
59845
|
+
jargon: "required sample size at the target power"
|
|
59846
|
+
},
|
|
59847
|
+
validate_split: {
|
|
59848
|
+
term: "How to split them",
|
|
59849
|
+
gist: "How many get {treatment} versus are left alone.",
|
|
59850
|
+
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.",
|
|
59851
|
+
jargon: "randomised allocation between arms"
|
|
59852
|
+
},
|
|
59853
|
+
validate_match: {
|
|
59854
|
+
term: "Match the groups on",
|
|
59855
|
+
gist: "Keep both groups balanced on these — they were lopsided last time.",
|
|
59856
|
+
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.",
|
|
59857
|
+
jargon: "stratified / blocked randomisation on the high-imbalance covariates"
|
|
59858
|
+
},
|
|
59859
|
+
validate_power: {
|
|
59860
|
+
term: "Chance of detecting it",
|
|
59861
|
+
gist: "How likely the trial is to catch the effect, by size.",
|
|
59862
|
+
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.",
|
|
59863
|
+
jargon: "statistical power vs sample size"
|
|
59864
|
+
},
|
|
59865
|
+
validate_holdback: {
|
|
59866
|
+
term: "Hold back a control",
|
|
59867
|
+
gist: "Keep a random group untreated to compare against.",
|
|
59868
|
+
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.",
|
|
59869
|
+
jargon: "a randomised control arm"
|
|
59870
|
+
},
|
|
59871
|
+
// ── Journal ─────────────────────────────────────────────────────────
|
|
59872
|
+
journal: {
|
|
59873
|
+
term: "Committed experiments",
|
|
59874
|
+
gist: "A log of the questions you’ve saved.",
|
|
59875
|
+
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.",
|
|
59876
|
+
jargon: "the experiment journal"
|
|
59877
|
+
}
|
|
59878
|
+
};
|
|
59879
|
+
const GuidanceContext = React.createContext({ on: true, vars: {} });
|
|
59880
|
+
function GuidanceProvider({ on: on3, vars, children: children2 }) {
|
|
59881
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GuidanceContext.Provider, { value: { on: on3, vars }, children: children2 });
|
|
59882
|
+
}
|
|
59883
|
+
function HelpCard({ id: id3, extraVars }) {
|
|
59884
|
+
const { vars } = React.useContext(GuidanceContext);
|
|
59885
|
+
const e3 = fillHelp(HELP[id3], { ...vars, ...extraVars });
|
|
59886
|
+
const slot = react.useSlotRecipe({ key: "hoverCard" })();
|
|
59887
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "1.5", children: [
|
|
59888
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: slot.title, mb: "0", children: e3.term }),
|
|
59889
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", children: e3.gist }),
|
|
59890
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: slot.description, lineHeight: "1.5", children: e3.detail }),
|
|
59891
|
+
e3.jargon && /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.subtle", mt: "0.5", pt: "1.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59892
|
+
"Technically: ",
|
|
59893
|
+
e3.jargon
|
|
59894
|
+
] })
|
|
59895
|
+
] });
|
|
59896
|
+
}
|
|
59897
|
+
function Hover({ id: id3, extraVars, trigger }) {
|
|
59898
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.HoverCard.Root, { openDelay: 150, closeDelay: 80, size: "sm", positioning: { placement: "top" }, children: [
|
|
59899
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Trigger, { asChild: true, children: trigger }),
|
|
59900
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Positioner, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.HoverCard.Content, { children: [
|
|
59901
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.Arrow, { children: /* @__PURE__ */ jsxRuntime.jsx(react.HoverCard.ArrowTip, {}) }),
|
|
59902
|
+
/* @__PURE__ */ jsxRuntime.jsx(HelpCard, { id: id3, extraVars })
|
|
59903
|
+
] }) }) })
|
|
59904
|
+
] });
|
|
59905
|
+
}
|
|
59906
|
+
function Help({ id: id3, extraVars, children: children2, display = "inline", gap }) {
|
|
59907
|
+
const { on: on3 } = React.useContext(GuidanceContext);
|
|
59908
|
+
if (!on3) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children2 });
|
|
59909
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Hover, { id: id3, extraVars, trigger: (
|
|
59910
|
+
// `tabIndex={0}` makes the term keyboard-focusable — the HoverCard's zag
|
|
59911
|
+
// machine opens on focus, so the glossary is reachable without a pointer.
|
|
59912
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
59913
|
+
react.Box,
|
|
59914
|
+
{
|
|
59915
|
+
as: "span",
|
|
59916
|
+
cursor: "help",
|
|
59917
|
+
tabIndex: 0,
|
|
59918
|
+
display,
|
|
59919
|
+
...gap ? { alignItems: "center", gap } : {},
|
|
59920
|
+
_focusVisible: { outline: "1px dotted", outlineColor: "brand.fg", outlineOffset: "2px", borderRadius: "2px" },
|
|
59921
|
+
children: children2
|
|
59922
|
+
}
|
|
59923
|
+
)
|
|
59924
|
+
) });
|
|
59925
|
+
}
|
|
59926
|
+
function GuidanceToggle({ on: on3, onToggle }) {
|
|
59927
|
+
const iconButton = react.useRecipe({ key: "iconButton" });
|
|
59928
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Hover, { id: "guidance", trigger: /* @__PURE__ */ jsxRuntime.jsx(
|
|
59929
|
+
react.Box,
|
|
59930
|
+
{
|
|
59931
|
+
as: "button",
|
|
59932
|
+
css: iconButton({ variant: on3 ? "subtle" : "ghost", size: "sm" }),
|
|
59933
|
+
onClick: onToggle,
|
|
59934
|
+
"aria-pressed": on3,
|
|
59935
|
+
"aria-label": "Toggle guidance",
|
|
59936
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
59937
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faCircleInfo })
|
|
59938
|
+
}
|
|
59939
|
+
) });
|
|
59940
|
+
}
|
|
59941
|
+
function ChartLabel({ x: x2, y: y2, w: w2, align, size: size3, weight: weight8, color: color2, family, help, children: children2 }) {
|
|
59942
|
+
const span = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: `${size3}px`, fontFamily: family, fontWeight: weight8, color: color2, lineHeight: 1, whiteSpace: "nowrap" }, children: children2 });
|
|
59943
|
+
return /* @__PURE__ */ jsxRuntime.jsx("foreignObject", { x: x2, y: y2, width: w2, height: size3 + 6, style: { overflow: "visible" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { textAlign: align, fontSize: `${size3}px`, lineHeight: 1 }, children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: span }) : span }) });
|
|
59944
|
+
}
|
|
59044
59945
|
function svgText(size3, fill, family, weight8) {
|
|
59045
59946
|
return { fontSize: `${size3}px`, fill, fontFamily: family, ...weight8 !== void 0 ? { fontWeight: weight8 } : {} };
|
|
59046
59947
|
}
|
|
@@ -59096,7 +59997,7 @@ const niceCeil = (v3) => {
|
|
|
59096
59997
|
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;
|
|
59097
59998
|
return step3 * mag;
|
|
59098
59999
|
};
|
|
59099
|
-
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }) {
|
|
60000
|
+
function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2, rowHelp }) {
|
|
59100
60001
|
const t4 = useChartTheme();
|
|
59101
60002
|
const [ref, w2] = useMeasuredWidth();
|
|
59102
60003
|
const n2 = rows.length;
|
|
@@ -59106,11 +60007,14 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59106
60007
|
const innerH = hPx - padT - padB;
|
|
59107
60008
|
const x2 = createLinearScale({ domain: [min4, max5], range: [0, innerW] });
|
|
59108
60009
|
const rowH = innerH / Math.max(1, n2);
|
|
59109
|
-
const zeroX = x2(0);
|
|
59110
|
-
const
|
|
60010
|
+
const zeroX = Math.max(0, Math.min(innerW, x2(0)));
|
|
60011
|
+
const frac = (v3) => max5 > min4 ? (v3 - min4) / (max5 - min4) : 0.5;
|
|
60012
|
+
const anchorAt = (v3) => frac(v3) < 0.06 ? "start" : frac(v3) > 0.94 ? "end" : "middle";
|
|
60013
|
+
const zf = frac(0);
|
|
60014
|
+
const ticks2 = zf > 0.12 && zf < 0.88 ? [min4, 0, max5] : [min4, max5];
|
|
59111
60015
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
59112
60016
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: zeroX, y: -4 }, to: { x: zeroX, y: innerH }, stroke: t4.muted, strokeWidth: 1, strokeDasharray: "3 2" }),
|
|
59113
|
-
/* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor:
|
|
60017
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: zeroX, y: -6, textAnchor: anchorAt(0), style: svgText(t4.labelSize, t4.muted, t4.mono), children: "no effect" }),
|
|
59114
60018
|
rows.map((r2, i) => {
|
|
59115
60019
|
const cy = rowH * i + rowH / 2;
|
|
59116
60020
|
const col = t4.tone(r2.tone);
|
|
@@ -59119,20 +60023,20 @@ function ForestPlot({ rows, min: min4, max: max5, unit: unit2, height: height2 }
|
|
|
59119
60023
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.lo), y: -4 }, to: { x: x2(r2.lo), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59120
60024
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: x2(r2.hi), y: -4 }, to: { x: x2(r2.hi), y: 4 }, stroke: col, strokeWidth: 1.5 }),
|
|
59121
60025
|
/* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: x2(r2.est), cy: 0, r: t4.scatterRadius, fill: col, stroke: t4.surface, strokeWidth: 1.8 }),
|
|
59122
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
60026
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: -padL + 2, y: -13, w: padL - 6, align: "left", size: t4.labelSize, weight: 600, color: t4.ink, family: t4.body, ...(rowHelp == null ? void 0 : rowHelp[i]) ? { help: rowHelp[i] } : {}, children: r2.label }),
|
|
59123
60027
|
r2.note && /* @__PURE__ */ jsxRuntime.jsx("text", { x: -padL + 2, y: 9, style: svgText(9, t4.faint, t4.body), children: r2.note }),
|
|
59124
60028
|
/* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW + padR - 2, y: 4, textAnchor: "end", style: svgText(t4.titleSize, col, t4.mono, 700), children: signed$1(r2.est) })
|
|
59125
60029
|
] }, i);
|
|
59126
60030
|
}),
|
|
59127
60031
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59128
|
-
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: x2(tk), children: [
|
|
60032
|
+
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: Math.max(0, Math.min(innerW, x2(tk))), children: [
|
|
59129
60033
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: 0, y: innerH + 4 }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
59130
|
-
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor:
|
|
60034
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 14, textAnchor: anchorAt(tk), style: svgText(t4.labelSize, t4.muted, t4.mono), children: signed$1(tk) })
|
|
59131
60035
|
] }, `t${i}`)),
|
|
59132
60036
|
unit2 && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW / 2, y: innerH + 26, textAnchor: "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: unit2 })
|
|
59133
60037
|
] }) }) });
|
|
59134
60038
|
}
|
|
59135
|
-
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2 }) {
|
|
60039
|
+
function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, tone: toneName, marks = [], height: height2, yFormat = "signed" }) {
|
|
59136
60040
|
const t4 = useChartTheme();
|
|
59137
60041
|
const [ref, w2] = useMeasuredWidth();
|
|
59138
60042
|
const hPx = height2 ?? 100;
|
|
@@ -59144,13 +60048,18 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59144
60048
|
const innerW = Math.max(0, w2 - padL - padR);
|
|
59145
60049
|
const innerH = hPx - padT - padB;
|
|
59146
60050
|
const xScale = createLinearScale({ domain: [0, Math.max(1, mid.length - 1)], range: [0, innerW] });
|
|
59147
|
-
const
|
|
59148
|
-
const
|
|
60051
|
+
const fin = (x2, fallback) => typeof x2 === "number" && Number.isFinite(x2) ? x2 : fallback;
|
|
60052
|
+
const data4 = mid.map((m2, i) => {
|
|
60053
|
+
const mm = fin(m2, 0);
|
|
60054
|
+
return { i, lo: fin(lo2[i], mm), hi: fin(hi[i], mm), mid: mm };
|
|
60055
|
+
});
|
|
60056
|
+
const dataMin = Math.min(...data4.map((d2) => d2.lo), zero2 ?? Infinity);
|
|
60057
|
+
const dataMax = Math.max(...data4.map((d2) => d2.hi), zero2 ?? -Infinity);
|
|
59149
60058
|
const yHiN = niceCeil(Math.max(dataMax, 0));
|
|
59150
60059
|
const yLoN = dataMin < -0.08 * yHiN ? -niceCeil(-dataMin) : 0;
|
|
59151
60060
|
const yScale = createLinearScale({ domain: [yLoN, yHiN], range: [innerH, 0] });
|
|
59152
|
-
const
|
|
59153
|
-
const
|
|
60061
|
+
const yfmt = (v3) => yFormat === "percent" ? `${Math.round(v3)}%` : signed$1(v3);
|
|
60062
|
+
const yLabels = [yfmt(yHiN), yfmt((yHiN + yLoN) / 2), yfmt(yLoN)];
|
|
59154
60063
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
59155
60064
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: 0 }, to: { x: 0, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
59156
60065
|
yTicks.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -59163,11 +60072,17 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59163
60072
|
/* @__PURE__ */ jsxRuntime.jsx(LinePath, { data: data4, x: (d2) => xScale(d2.i), y: (d2) => yScale(d2.mid), curve: monotoneX, stroke: col, strokeWidth: t4.lineWidth }),
|
|
59164
60073
|
marks.map((m2, i) => {
|
|
59165
60074
|
const mc = t4.tone(m2.tone);
|
|
59166
|
-
const dotY = yScale(mid[m2.at]
|
|
60075
|
+
const dotY = yScale(fin(mid[m2.at], 0));
|
|
60076
|
+
const labY = Math.max(9, dotY - 9);
|
|
60077
|
+
const atEnd = m2.at >= mid.length - 2;
|
|
60078
|
+
const atStart = m2.at <= 1;
|
|
60079
|
+
const align = atEnd ? "right" : atStart ? "left" : "center";
|
|
60080
|
+
const labW = m2.label.length * 6 + 6;
|
|
60081
|
+
const labX = atEnd ? -labW : atStart ? 0 : -labW / 2;
|
|
59167
60082
|
return /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: xScale(m2.at), children: [
|
|
59168
60083
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: -2 }, to: { x: 0, y: innerH }, stroke: mc, strokeWidth: 1, strokeDasharray: "2 3" }),
|
|
59169
60084
|
/* @__PURE__ */ jsxRuntime.jsx(Circle, { cx: 0, cy: dotY, r: t4.dotRadius, fill: mc, stroke: t4.surface, strokeWidth: 1.5 }),
|
|
59170
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
60085
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChartLabel, { x: labX, y: labY - 11, w: labW, align, size: t4.labelSize, weight: 500, color: mc, family: t4.body, ...m2.help ? { help: m2.help } : {}, children: m2.label })
|
|
59171
60086
|
] }, `mk${i}`);
|
|
59172
60087
|
}),
|
|
59173
60088
|
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: innerH }, to: { x: innerW, y: innerH }, stroke: t4.rule, strokeWidth: 1 }),
|
|
@@ -59177,17 +60092,54 @@ function AreaRange({ lo: lo2, mid, hi, xTicks = [], yTicks = [], zero: zero2, to
|
|
|
59177
60092
|
})
|
|
59178
60093
|
] }) }) });
|
|
59179
60094
|
}
|
|
60095
|
+
function OverlapHistogram({ treated, control, domain = [0, 1], supportLabel, positivityOk, height: height2 }) {
|
|
60096
|
+
const t4 = useChartTheme();
|
|
60097
|
+
const [ref, w2] = useMeasuredWidth();
|
|
60098
|
+
const hPx = height2 ?? 150;
|
|
60099
|
+
const padL = 8, padR = 8, padT = 16, padB = 26;
|
|
60100
|
+
const innerW = Math.max(0, w2 - padL - padR);
|
|
60101
|
+
const innerH = hPx - padT - padB;
|
|
60102
|
+
const centerY = innerH / 2;
|
|
60103
|
+
const halfH = centerY - 4;
|
|
60104
|
+
const bins = Math.max(treated.length, control.length);
|
|
60105
|
+
const x2 = createLinearScale({ domain, range: [0, innerW] });
|
|
60106
|
+
const span = domain[1] - domain[0];
|
|
60107
|
+
const binW = bins > 0 ? innerW / bins : innerW;
|
|
60108
|
+
const barW = Math.max(1, binW * 0.78);
|
|
60109
|
+
const maxVal = Math.max(1e-9, ...treated, ...control);
|
|
60110
|
+
const tCol = t4.tone("pos");
|
|
60111
|
+
const cCol = t4.tone("muted");
|
|
60112
|
+
const ticks2 = [domain[0], (domain[0] + domain[1]) / 2, domain[1]];
|
|
60113
|
+
const cx2 = (i) => x2(domain[0] + (i + 0.5) / bins * span);
|
|
60114
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { ref, width: "100%", height: `${hPx}px`, children: w2 > 0 && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: w2, height: hPx, style: { display: "block" }, children: /* @__PURE__ */ jsxRuntime.jsxs(Group, { left: padL, top: padT, children: [
|
|
60115
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: -4, style: svgText(9, t4.faint, t4.body), children: "treated ↑" }),
|
|
60116
|
+
supportLabel && /* @__PURE__ */ jsxRuntime.jsx("text", { x: innerW, y: -4, textAnchor: "end", style: svgText(t4.labelSize, positivityOk ? t4.tone("pos") : t4.tone("warn"), t4.mono, 600), children: supportLabel }),
|
|
60117
|
+
Array.from({ length: bins }, (_2, i) => {
|
|
60118
|
+
const ht = Number(treated[i] ?? 0) / maxVal * halfH;
|
|
60119
|
+
const hc = Number(control[i] ?? 0) / maxVal * halfH;
|
|
60120
|
+
const left = cx2(i) - barW / 2;
|
|
60121
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
|
|
60122
|
+
ht > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY - ht, width: barW, height: ht, fill: tCol, fillOpacity: 0.85, rx: 0.5 }),
|
|
60123
|
+
hc > 0 && /* @__PURE__ */ jsxRuntime.jsx("rect", { x: left, y: centerY, width: barW, height: hc, fill: cCol, fillOpacity: 0.6, rx: 0.5 })
|
|
60124
|
+
] }, i);
|
|
60125
|
+
}),
|
|
60126
|
+
/* @__PURE__ */ jsxRuntime.jsx(Line, { from: { x: 0, y: centerY }, to: { x: innerW, y: centerY }, stroke: t4.ruleStrong, strokeWidth: 1 }),
|
|
60127
|
+
/* @__PURE__ */ jsxRuntime.jsx("text", { x: 0, y: innerH + 4, style: svgText(9, t4.faint, t4.body), children: "untreated ↓" }),
|
|
60128
|
+
ticks2.map((tk, i) => /* @__PURE__ */ jsxRuntime.jsx("text", { x: x2(tk), y: innerH + 16, textAnchor: i === 0 ? "start" : i === ticks2.length - 1 ? "end" : "middle", style: svgText(t4.labelSize, t4.muted, t4.mono), children: fmt$2(tk) }, `x${i}`))
|
|
60129
|
+
] }) }) });
|
|
60130
|
+
}
|
|
59180
60131
|
const arr = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
60132
|
+
const arrI = (v3) => v3 ? Array.from(v3, Number) : [];
|
|
59181
60133
|
const getOpt = (o2) => o2 && o2.type === "some" ? o2.value : void 0;
|
|
59182
|
-
const fmt$1 = (x2) =>
|
|
59183
|
-
const
|
|
59184
|
-
|
|
59185
|
-
|
|
59186
|
-
const
|
|
59187
|
-
|
|
59188
|
-
|
|
59189
|
-
return Math.sqrt(xs.reduce((a2, b2) => a2 + (b2 - m2) * (b2 - m2), 0) / xs.length);
|
|
60134
|
+
const fmt$1 = (x2) => {
|
|
60135
|
+
const r2 = Number.isInteger(x2) ? String(x2) : x2.toFixed(1);
|
|
60136
|
+
return r2 === "-0.0" ? "0.0" : r2;
|
|
60137
|
+
};
|
|
60138
|
+
const signed = (x2) => {
|
|
60139
|
+
const f2 = fmt$1(x2);
|
|
60140
|
+
return f2 === "0" || f2 === "0.0" || f2.startsWith("-") ? f2 : `+${f2}`;
|
|
59190
60141
|
};
|
|
60142
|
+
const clamp01 = (x2) => x2 < 0 ? 0 : x2 > 1 ? 1 : x2;
|
|
59191
60143
|
function band(absStd) {
|
|
59192
60144
|
if (absStd >= 0.66) return { level: "large gap", tone: "neg" };
|
|
59193
60145
|
if (absStd >= 0.33) return { level: "some", tone: "warn" };
|
|
@@ -59202,11 +60154,22 @@ const kindOf = (cols, name) => {
|
|
|
59202
60154
|
var _a2;
|
|
59203
60155
|
return (_a2 = cols.find((c2) => c2.name === name)) == null ? void 0 : _a2.kind;
|
|
59204
60156
|
};
|
|
59205
|
-
|
|
59206
|
-
|
|
59207
|
-
|
|
59208
|
-
|
|
59209
|
-
|
|
60157
|
+
const VERDICT_TONE = {
|
|
60158
|
+
causal: "pos",
|
|
60159
|
+
modest: "warn",
|
|
60160
|
+
adjustment_insufficient: "warn",
|
|
60161
|
+
non_identifiable_positivity: "neg",
|
|
60162
|
+
not_estimable: "muted"
|
|
60163
|
+
};
|
|
60164
|
+
const VERDICT_LABEL = {
|
|
60165
|
+
causal: "Causal effect",
|
|
60166
|
+
modest: "Modest / unclear",
|
|
60167
|
+
adjustment_insufficient: "Not trustworthy yet",
|
|
60168
|
+
non_identifiable_positivity: "No fair comparison",
|
|
60169
|
+
not_estimable: "Can’t be estimated"
|
|
60170
|
+
};
|
|
60171
|
+
function deriveVerdict(v3) {
|
|
60172
|
+
return { tag: v3.type, tone: VERDICT_TONE[v3.type], label: VERDICT_LABEL[v3.type] };
|
|
59210
60173
|
}
|
|
59211
60174
|
function treatmentKind(cols, treatment) {
|
|
59212
60175
|
switch (kindOf(cols, treatment)) {
|
|
@@ -59222,124 +60185,133 @@ function treatmentKind(cols, treatment) {
|
|
|
59222
60185
|
return "value";
|
|
59223
60186
|
}
|
|
59224
60187
|
}
|
|
59225
|
-
function deriveSpec(
|
|
59226
|
-
const tKind = kindOf(cols,
|
|
59227
|
-
const outUnit = unitOf(meta3,
|
|
59228
|
-
const balByCol =
|
|
59229
|
-
const
|
|
60188
|
+
function deriveSpec(config2, cols, result, meta3, dataLen) {
|
|
60189
|
+
const tKind = kindOf(cols, config2.treatment);
|
|
60190
|
+
const outUnit = unitOf(meta3, config2.outcome);
|
|
60191
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
60192
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
60193
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
60194
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
60195
|
+
}
|
|
60196
|
+
const confounders = config2.common_causes.map((col) => {
|
|
59230
60197
|
const b2 = balByCol.get(col);
|
|
59231
|
-
const absStd = b2 ? Math.abs(b2.
|
|
60198
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
59232
60199
|
const { level, tone } = band(absStd);
|
|
59233
|
-
const dir = b2 ? b2.
|
|
59234
|
-
const reason = b2 ? `treated
|
|
60200
|
+
const dir = b2 ? b2.treated_mean < b2.control_mean ? "lower" : "higher" : "";
|
|
60201
|
+
const reason = b2 ? `the treated group ran ${dir} on ${col}` : "measured once you Run";
|
|
59235
60202
|
return { col, reason, imbalance: clamp01(absStd), level, tone };
|
|
59236
60203
|
});
|
|
59237
|
-
const inPlay = /* @__PURE__ */ new Set([
|
|
60204
|
+
const inPlay = /* @__PURE__ */ new Set([config2.treatment, config2.outcome, ...config2.common_causes]);
|
|
59238
60205
|
const suggest = cols.find((c2) => !inPlay.has(c2.name));
|
|
59239
60206
|
const suggestion = suggest ? `add ${suggest.name}` : "";
|
|
59240
|
-
const methodV = getOpt(
|
|
60207
|
+
const methodV = getOpt(config2.method);
|
|
59241
60208
|
const method = (methodV == null ? void 0 : methodV.type) === "propensity_score_weighting" ? "reweighting" : "regression";
|
|
59242
|
-
const targetV = getOpt(
|
|
60209
|
+
const targetV = getOpt(config2.estimand);
|
|
59243
60210
|
const target = targetV && (targetV.type === "att" || targetV.type === "atc") ? "treated" : "all";
|
|
59244
|
-
const trim2 = getOpt(spec.trim) !== void 0;
|
|
59245
60211
|
return {
|
|
59246
|
-
treatment:
|
|
59247
|
-
treatmentKind: treatmentKind(cols,
|
|
59248
|
-
outcome:
|
|
60212
|
+
treatment: config2.treatment,
|
|
60213
|
+
treatmentKind: treatmentKind(cols, config2.treatment),
|
|
60214
|
+
outcome: config2.outcome,
|
|
59249
60215
|
outcomeKind: outUnit ? `number · ${outUnit}` : "number",
|
|
59250
60216
|
comparison: tKind === "boolean" ? "yes · vs no" : "high · vs low",
|
|
59251
60217
|
confounders,
|
|
59252
60218
|
suggestion,
|
|
59253
60219
|
method,
|
|
59254
60220
|
target,
|
|
59255
|
-
|
|
59256
|
-
dataLabel: `${result ? Number(result.nTotal) : dataLen} rows`
|
|
60221
|
+
dataLabel: `${result ? Number(result.n_total) : dataLen} rows`
|
|
59257
60222
|
};
|
|
59258
60223
|
}
|
|
59259
60224
|
function balanceDisplay(b2, categorical) {
|
|
59260
|
-
const isProp = categorical.has(b2.
|
|
59261
|
-
return isProp ? `${Math.round(b2.
|
|
59262
|
-
}
|
|
59263
|
-
function deriveAnswer(
|
|
59264
|
-
const ci = getOpt(
|
|
59265
|
-
const nci = getOpt(result.
|
|
59266
|
-
const categorical = new Set(
|
|
59267
|
-
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.
|
|
60225
|
+
const isProp = categorical.has(b2.base_column) && b2.treated_mean >= 0 && b2.treated_mean <= 1 && b2.control_mean >= 0 && b2.control_mean <= 1;
|
|
60226
|
+
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)}`;
|
|
60227
|
+
}
|
|
60228
|
+
function deriveAnswer(config2, result, adj, meta3) {
|
|
60229
|
+
const ci = getOpt(adj.ci);
|
|
60230
|
+
const nci = getOpt(result.naive_ci);
|
|
60231
|
+
const categorical = new Set(getOpt(config2.categorical) ?? []);
|
|
60232
|
+
const balance = [...result.balance].sort((a2, b2) => Math.abs(b2.std_diff) - Math.abs(a2.std_diff)).map((b2) => ({
|
|
59268
60233
|
col: b2.column,
|
|
59269
|
-
treated: b2.
|
|
59270
|
-
control: b2.
|
|
60234
|
+
treated: b2.treated_mean,
|
|
60235
|
+
control: b2.control_mean,
|
|
59271
60236
|
display: balanceDisplay(b2, categorical),
|
|
59272
|
-
frac: clamp01(Math.abs(b2.
|
|
59273
|
-
tone: band(Math.abs(b2.
|
|
60237
|
+
frac: clamp01(Math.abs(b2.std_diff)),
|
|
60238
|
+
tone: band(Math.abs(b2.std_diff)).tone
|
|
59274
60239
|
}));
|
|
59275
|
-
const
|
|
59276
|
-
const
|
|
60240
|
+
const lo2 = (ci == null ? void 0 : ci.lower) ?? adj.effect;
|
|
60241
|
+
const hi = (ci == null ? void 0 : ci.upper) ?? adj.effect;
|
|
60242
|
+
const clear = result.verdict.type === "causal" || (ci ? lo2 > 0 || hi < 0 : false);
|
|
60243
|
+
const flip = Math.sign(result.naive) !== Math.sign(adj.effect) && result.naive !== 0;
|
|
60244
|
+
const nDropped = Number(result.n_dropped);
|
|
60245
|
+
const nTotal = Number(result.n_total);
|
|
59277
60246
|
return {
|
|
59278
|
-
treatment:
|
|
59279
|
-
outcome:
|
|
59280
|
-
unit: unitOf(meta3,
|
|
60247
|
+
treatment: config2.treatment,
|
|
60248
|
+
outcome: config2.outcome,
|
|
60249
|
+
unit: unitOf(meta3, config2.outcome),
|
|
59281
60250
|
naive: result.naive,
|
|
59282
60251
|
naiveLo: (nci == null ? void 0 : nci.lower) ?? result.naive,
|
|
59283
60252
|
naiveHi: (nci == null ? void 0 : nci.upper) ?? result.naive,
|
|
59284
|
-
effect:
|
|
59285
|
-
lo:
|
|
59286
|
-
hi
|
|
59287
|
-
|
|
59288
|
-
|
|
59289
|
-
|
|
60253
|
+
effect: adj.effect,
|
|
60254
|
+
lo: lo2,
|
|
60255
|
+
hi,
|
|
60256
|
+
clear,
|
|
60257
|
+
flip,
|
|
60258
|
+
cautious: result.verdict.type === "adjustment_insufficient",
|
|
60259
|
+
nTotal: result.n_total,
|
|
60260
|
+
nTreated: result.n_treated,
|
|
60261
|
+
nControl: result.n_control,
|
|
59290
60262
|
nCompared: BigInt(Math.max(0, nTotal - nDropped)),
|
|
59291
|
-
nDropped: result.
|
|
60263
|
+
nDropped: result.n_dropped,
|
|
59292
60264
|
balance
|
|
59293
60265
|
};
|
|
59294
60266
|
}
|
|
59295
|
-
function
|
|
59296
|
-
const
|
|
59297
|
-
|
|
59298
|
-
|
|
59299
|
-
|
|
59300
|
-
|
|
59301
|
-
|
|
59302
|
-
|
|
59303
|
-
|
|
59304
|
-
|
|
59305
|
-
value:
|
|
59306
|
-
|
|
59307
|
-
|
|
59308
|
-
|
|
59309
|
-
|
|
59310
|
-
|
|
59311
|
-
|
|
59312
|
-
|
|
59313
|
-
|
|
59314
|
-
|
|
59315
|
-
|
|
59316
|
-
|
|
59317
|
-
|
|
59318
|
-
|
|
59319
|
-
|
|
59320
|
-
|
|
59321
|
-
|
|
59322
|
-
return {
|
|
59323
|
-
name: "Decoy cause",
|
|
59324
|
-
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
59325
|
-
value: `${fmt$1(est)} → ${fmt$1(m2)}`,
|
|
59326
|
-
passed,
|
|
59327
|
-
tip: { type: "none", value: null }
|
|
59328
|
-
};
|
|
59329
|
-
}
|
|
59330
|
-
default: {
|
|
59331
|
-
const strengths = arr(getOpt(c2.strengths));
|
|
59332
|
-
const tip = zeroCrossing(strengths, effects);
|
|
59333
|
-
const passed = tip === null;
|
|
59334
|
-
return {
|
|
59335
|
-
name: "Hidden cause",
|
|
59336
|
-
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
59337
|
-
value: tip === null ? "holds throughout" : `tips at ${fmt$1(tip)}`,
|
|
59338
|
-
passed,
|
|
59339
|
-
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." }
|
|
59340
|
-
};
|
|
59341
|
-
}
|
|
60267
|
+
function deriveRefusal(config2, result) {
|
|
60268
|
+
const nT = Number(result.n_treated), nC = Number(result.n_control), nTot = Number(result.n_total);
|
|
60269
|
+
if (result.verdict.type === "not_estimable") {
|
|
60270
|
+
const reason = result.verdict.value || "The treatment barely varies, so no comparison can be formed.";
|
|
60271
|
+
return {
|
|
60272
|
+
kind: "not_estimable",
|
|
60273
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
60274
|
+
body: reason,
|
|
60275
|
+
evidence: [
|
|
60276
|
+
{ label: "treated", value: String(nT) },
|
|
60277
|
+
{ label: "untreated", value: String(nC) },
|
|
60278
|
+
{ label: "rows", value: String(nTot) }
|
|
60279
|
+
]
|
|
60280
|
+
};
|
|
60281
|
+
}
|
|
60282
|
+
if (result.verdict.type === "non_identifiable_positivity") {
|
|
60283
|
+
const pct = Math.round(result.overlap.common_support_frac * 100);
|
|
60284
|
+
return {
|
|
60285
|
+
kind: "positivity",
|
|
60286
|
+
title: "No like-for-like comparison exists",
|
|
60287
|
+
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.`,
|
|
60288
|
+
evidence: [
|
|
60289
|
+
{ label: "common support", value: `${pct}%` },
|
|
60290
|
+
{ label: "treated", value: String(nT) },
|
|
60291
|
+
{ label: "untreated", value: String(nC) }
|
|
60292
|
+
]
|
|
60293
|
+
};
|
|
59342
60294
|
}
|
|
60295
|
+
return {
|
|
60296
|
+
kind: "not_estimable",
|
|
60297
|
+
title: `Can’t estimate the effect of ${config2.treatment}`,
|
|
60298
|
+
body: "The engine could not produce a like-for-like estimate for this configuration.",
|
|
60299
|
+
evidence: [
|
|
60300
|
+
{ label: "treated", value: String(nT) },
|
|
60301
|
+
{ label: "untreated", value: String(nC) },
|
|
60302
|
+
{ label: "rows", value: String(nTot) }
|
|
60303
|
+
]
|
|
60304
|
+
};
|
|
60305
|
+
}
|
|
60306
|
+
function deriveOverlap(o2) {
|
|
60307
|
+
const pct = Math.round(o2.common_support_frac * 100);
|
|
60308
|
+
return {
|
|
60309
|
+
treated: arr(o2.treated_propensity),
|
|
60310
|
+
control: arr(o2.control_propensity),
|
|
60311
|
+
commonSupportFrac: o2.common_support_frac,
|
|
60312
|
+
positivityOk: o2.positivity_ok,
|
|
60313
|
+
supportLabel: `${pct}% common support`
|
|
60314
|
+
};
|
|
59343
60315
|
}
|
|
59344
60316
|
function zeroCrossing(xs, ys) {
|
|
59345
60317
|
for (let i = 1; i < ys.length && i < xs.length; i++) {
|
|
@@ -59351,27 +60323,80 @@ function zeroCrossing(xs, ys) {
|
|
|
59351
60323
|
}
|
|
59352
60324
|
return null;
|
|
59353
60325
|
}
|
|
59354
|
-
function deriveRefute(
|
|
59355
|
-
const checks =
|
|
59356
|
-
const
|
|
59357
|
-
const
|
|
59358
|
-
const
|
|
59359
|
-
|
|
59360
|
-
|
|
59361
|
-
|
|
59362
|
-
|
|
59363
|
-
|
|
59364
|
-
|
|
59365
|
-
|
|
59366
|
-
|
|
59367
|
-
}
|
|
59368
|
-
|
|
59369
|
-
|
|
60326
|
+
function deriveRefute(r2, adj) {
|
|
60327
|
+
const checks = [];
|
|
60328
|
+
const est = (adj == null ? void 0 : adj.effect) ?? 0;
|
|
60329
|
+
const placeboEffect = getOpt(r2.placebo_effect);
|
|
60330
|
+
const placeboPasses = getOpt(r2.placebo_passes);
|
|
60331
|
+
if (placeboEffect !== void 0 || placeboPasses !== void 0) {
|
|
60332
|
+
checks.push({
|
|
60333
|
+
name: "Shuffle test",
|
|
60334
|
+
desc: "Shuffle which rows were treated; a real effect should collapse to zero.",
|
|
60335
|
+
value: placeboEffect !== void 0 ? `→ ${signed(placeboEffect)}` : placeboPasses ? "passed" : "failed",
|
|
60336
|
+
passed: placeboPasses ?? (placeboEffect !== void 0 && Math.abs(placeboEffect) < Math.max(0.5, Math.abs(est) * 0.15)),
|
|
60337
|
+
tip: { type: "some", value: "Randomly re-label which rows were treated. A genuine effect should vanish." },
|
|
60338
|
+
help: "check_shuffle"
|
|
60339
|
+
});
|
|
60340
|
+
}
|
|
60341
|
+
const ds = getOpt(r2.data_subset_effect);
|
|
60342
|
+
if (ds !== void 0) {
|
|
60343
|
+
const dstd = getOpt(r2.data_subset_std) ?? 0;
|
|
60344
|
+
checks.push({
|
|
60345
|
+
name: "Drop-some test",
|
|
60346
|
+
desc: "Re-estimate on random subsamples; a trustworthy effect stays put.",
|
|
60347
|
+
value: `${fmt$1(ds)} ± ${fmt$1(dstd)}`,
|
|
60348
|
+
passed: Math.abs(ds - est) < Math.max(0.5, Math.abs(est) * 0.2),
|
|
60349
|
+
tip: { type: "none", value: null },
|
|
60350
|
+
help: "check_dropsome"
|
|
60351
|
+
});
|
|
60352
|
+
}
|
|
60353
|
+
const rcc = getOpt(r2.random_cc_within_ci);
|
|
60354
|
+
if (rcc !== void 0) {
|
|
60355
|
+
checks.push({
|
|
60356
|
+
name: "Decoy cause",
|
|
60357
|
+
desc: "Add an irrelevant random factor; the answer should not move.",
|
|
60358
|
+
value: rcc ? "within range" : "moved",
|
|
60359
|
+
passed: rcc,
|
|
60360
|
+
tip: { type: "none", value: null },
|
|
60361
|
+
help: "check_decoy"
|
|
60362
|
+
});
|
|
60363
|
+
}
|
|
60364
|
+
const sens = getOpt(r2.sensitivity);
|
|
60365
|
+
const evalue = getOpt(r2.robustness_value);
|
|
60366
|
+
if (sens !== void 0 || evalue !== void 0) {
|
|
60367
|
+
const strengths = sens ? arr(sens.strengths) : [];
|
|
60368
|
+
const effects = sens ? arr(sens.effects) : [];
|
|
60369
|
+
const tip = sens ? zeroCrossing(strengths, effects) : null;
|
|
60370
|
+
const value = tip !== null ? `tips at ${fmt$1(tip)}` : evalue !== void 0 ? `E-value ${fmt$1(evalue)}` : "holds throughout";
|
|
60371
|
+
checks.push({
|
|
60372
|
+
name: "Hidden cause",
|
|
60373
|
+
desc: "How strong an unrecorded common cause would need to be to overturn the result.",
|
|
60374
|
+
value,
|
|
60375
|
+
passed: tip === null,
|
|
60376
|
+
tip: { type: "some", value: "Something unrecorded could drive both the treatment choice and the outcome." },
|
|
60377
|
+
help: "check_hidden"
|
|
60378
|
+
});
|
|
60379
|
+
}
|
|
60380
|
+
let sensVM = null;
|
|
60381
|
+
if (sens !== void 0) {
|
|
60382
|
+
const mid = arr(sens.effects);
|
|
60383
|
+
if (mid.length) {
|
|
60384
|
+
const ci = getOpt(adj == null ? void 0 : adj.ci);
|
|
60385
|
+
const half = ci ? Math.abs(ci.upper - ci.lower) / 2 : 0;
|
|
60386
|
+
const lo2 = mid.map((m2) => m2 - half);
|
|
60387
|
+
const hi = mid.map((m2) => m2 + half);
|
|
60388
|
+
const yLo = Math.min(0, ...lo2), yHi = Math.max(...hi, 0);
|
|
60389
|
+
sensVM = { lo: lo2, mid, hi, xTicks: ["none", "weaker", "stronger"], yTicks: [fmt$1(yLo), fmt$1((yLo + yHi) / 2), fmt$1(yHi)] };
|
|
60390
|
+
}
|
|
60391
|
+
}
|
|
60392
|
+
return { checks, sens: sensVM };
|
|
60393
|
+
}
|
|
60394
|
+
function deriveDose(dose, config2, meta3) {
|
|
59370
60395
|
const grid = arr(dose.grid);
|
|
59371
60396
|
const mid = arr(dose.effect);
|
|
59372
60397
|
const lo2 = getOpt(dose.lower) ? arr(getOpt(dose.lower)) : mid.slice();
|
|
59373
60398
|
const hi = getOpt(dose.upper) ? arr(getOpt(dose.upper)) : mid.slice();
|
|
59374
|
-
const sizes =
|
|
60399
|
+
const sizes = arrI(dose.size);
|
|
59375
60400
|
const featureUnit = unitOf(meta3, dose.feature);
|
|
59376
60401
|
const here = sizes.length ? sizes.indexOf(Math.max(...sizes)) : 0;
|
|
59377
60402
|
const marg = mid.map((m2, i) => i === 0 ? 0 : m2 - mid[i - 1]);
|
|
@@ -59391,31 +60416,28 @@ function deriveDose(dose, spec, meta3) {
|
|
|
59391
60416
|
let lastStep = margSteps.length - 1;
|
|
59392
60417
|
while (lastStep > 0 && Math.abs(margSteps[lastStep]) < 0.12 * maxMarg) lastStep--;
|
|
59393
60418
|
const marginal = margSteps.slice(0, lastStep + 1).map((v3, i) => ({ label: `${fmt$1(grid[i + 1])}`, value: v3, frac: clamp01(Math.abs(v3) / maxMarg) }));
|
|
59394
|
-
const segLabels = ((_a2 = getOpt(dose.segments)) == null ? void 0 : _a2.map((s2) => s2.label)) ?? [];
|
|
59395
|
-
const segments = segLabels.length ? ["all", ...segLabels] : ["all"];
|
|
59396
60419
|
const stepFrom = here < sweet ? here : Math.max(0, sweet - 1);
|
|
59397
60420
|
const gainToSweet = (mid[sweet] ?? 0) - (mid[stepFrom] ?? 0);
|
|
59398
60421
|
const nextGain = sweet + 1 < mid.length ? mid[sweet + 1] - mid[sweet] : 0;
|
|
59399
60422
|
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)}.`;
|
|
60423
|
+
const marks = [];
|
|
60424
|
+
if (sizes.length) marks.push({ at: here, label: "you are here", tone: "muted", help: "dose_here" });
|
|
60425
|
+
marks.push({ at: sweet, label: "sweet spot", tone: "pos", help: "dose_sweet" });
|
|
59400
60426
|
return {
|
|
59401
60427
|
feature: dose.feature,
|
|
59402
|
-
outcome:
|
|
60428
|
+
outcome: config2.outcome,
|
|
59403
60429
|
lo: lo2,
|
|
59404
60430
|
mid,
|
|
59405
60431
|
hi,
|
|
59406
60432
|
xTicks,
|
|
59407
60433
|
yTicks,
|
|
59408
|
-
marks
|
|
59409
|
-
{ at: BigInt(here), label: "you are here", tone: "muted" },
|
|
59410
|
-
{ at: BigInt(sweet), label: "sweet spot", tone: "pos" }
|
|
59411
|
-
],
|
|
60434
|
+
marks,
|
|
59412
60435
|
recoLabel: `≈ ${fmt$1(grid[sweet] ?? 0)}${featureUnit ? " " + featureUnit : ""}`,
|
|
59413
60436
|
recoEffect: mid[sweet] ?? 0,
|
|
59414
60437
|
recoLo: lo2[sweet] ?? 0,
|
|
59415
60438
|
recoHi: hi[sweet] ?? 0,
|
|
59416
60439
|
tradeoff,
|
|
59417
|
-
marginal
|
|
59418
|
-
segments
|
|
60440
|
+
marginal
|
|
59419
60441
|
};
|
|
59420
60442
|
}
|
|
59421
60443
|
function relTime(d2, now2) {
|
|
@@ -59425,30 +60447,126 @@ function relTime(d2, now2) {
|
|
|
59425
60447
|
if (days < 7) return d2.toLocaleDateString(void 0, { weekday: "short" });
|
|
59426
60448
|
return "last wk";
|
|
59427
60449
|
}
|
|
60450
|
+
const VERDICT_WORD = {
|
|
60451
|
+
causal: "causal",
|
|
60452
|
+
modest: "modest",
|
|
60453
|
+
adjustment_insufficient: "not robust",
|
|
60454
|
+
non_identifiable_positivity: "no overlap",
|
|
60455
|
+
not_estimable: "n/a"
|
|
60456
|
+
};
|
|
59428
60457
|
function deriveJournalRow(row, now2) {
|
|
59429
|
-
const
|
|
59430
|
-
const clear = ci ? ci.lower > 0 || ci.upper < 0 : true;
|
|
59431
|
-
const dirUp = row.effect > 0;
|
|
60458
|
+
const adj = getOpt(row.adjusted);
|
|
59432
60459
|
return {
|
|
59433
|
-
treatment: row.
|
|
59434
|
-
outcome: row.
|
|
59435
|
-
confounders: row.
|
|
59436
|
-
effect: signed(row.
|
|
59437
|
-
verdict:
|
|
59438
|
-
verdictTone:
|
|
59439
|
-
who: row.
|
|
59440
|
-
when: relTime(row.
|
|
59441
|
-
};
|
|
59442
|
-
}
|
|
59443
|
-
function deriveView(
|
|
60460
|
+
treatment: row.config.treatment,
|
|
60461
|
+
outcome: row.config.outcome,
|
|
60462
|
+
confounders: row.config.common_causes.join(", "),
|
|
60463
|
+
effect: adj !== void 0 ? signed(adj) : signed(row.naive),
|
|
60464
|
+
verdict: VERDICT_WORD[row.verdict.type],
|
|
60465
|
+
verdictTone: VERDICT_TONE[row.verdict.type],
|
|
60466
|
+
who: row.committed_by,
|
|
60467
|
+
when: relTime(row.committed_at, now2)
|
|
60468
|
+
};
|
|
60469
|
+
}
|
|
60470
|
+
function deriveView(config2, ranConfig, cols, result, journal, meta3, dataLen, now2) {
|
|
60471
|
+
const adj = result ? getOpt(result.adjusted) : void 0;
|
|
60472
|
+
const refutation = result ? getOpt(result.refutation) : void 0;
|
|
60473
|
+
const doseR = result ? getOpt(result.dose_response) : void 0;
|
|
59444
60474
|
return {
|
|
59445
|
-
|
|
59446
|
-
|
|
59447
|
-
|
|
59448
|
-
|
|
60475
|
+
// The set-up rail reflects the LIVE config (the editor); the result deck
|
|
60476
|
+
// reflects RANCONFIG — the config that produced `result` — so the result
|
|
60477
|
+
// strings never drift ahead of the numbers on a live picker edit.
|
|
60478
|
+
spec: deriveSpec(config2, cols, result, meta3, dataLen),
|
|
60479
|
+
verdict: result ? deriveVerdict(result.verdict) : null,
|
|
60480
|
+
answer: result && adj ? deriveAnswer(ranConfig, result, adj, meta3) : null,
|
|
60481
|
+
refusal: result && !adj ? deriveRefusal(ranConfig, result) : null,
|
|
60482
|
+
overlap: result ? deriveOverlap(result.overlap) : null,
|
|
60483
|
+
refute: refutation ? deriveRefute(refutation, adj) : null,
|
|
60484
|
+
dose: doseR && doseR.effect.length ? deriveDose(doseR, ranConfig, meta3) : null,
|
|
59449
60485
|
journal: journal ? journal.map((r2) => deriveJournalRow(r2, now2)) : null
|
|
59450
60486
|
};
|
|
59451
60487
|
}
|
|
60488
|
+
const DESIGN_HEADLINE = {
|
|
60489
|
+
detect_observed: "Confirm it with a controlled trial",
|
|
60490
|
+
de_bias: "Make it trustworthy with a controlled trial",
|
|
60491
|
+
resolve_vs_null: "Settle it with a bigger trial",
|
|
60492
|
+
restrict_to_overlap: "You can’t compare yet",
|
|
60493
|
+
create_control: "Hold some back next time"
|
|
60494
|
+
};
|
|
60495
|
+
function deriveDesignOption(o2) {
|
|
60496
|
+
return {
|
|
60497
|
+
label: o2.label,
|
|
60498
|
+
nTotal: Number(o2.n_total),
|
|
60499
|
+
nTreated: Number(o2.n_treated),
|
|
60500
|
+
nControl: Number(o2.n_control),
|
|
60501
|
+
treatedShare: o2.treated_share
|
|
60502
|
+
};
|
|
60503
|
+
}
|
|
60504
|
+
function deriveDesign(d2, result, ranConfig, meta3) {
|
|
60505
|
+
const basis = d2.basis.type;
|
|
60506
|
+
const outcome = ranConfig.outcome;
|
|
60507
|
+
const unit2 = unitOf(meta3, outcome);
|
|
60508
|
+
const options2 = d2.options.map(deriveDesignOption);
|
|
60509
|
+
const primary = options2[0] ?? { label: "Even split", nTotal: 0, nTreated: 0, nControl: 0, treatedShare: 0.5 };
|
|
60510
|
+
const balByCol = /* @__PURE__ */ new Map();
|
|
60511
|
+
for (const b2 of (result == null ? void 0 : result.balance) ?? []) {
|
|
60512
|
+
const prev2 = balByCol.get(b2.base_column);
|
|
60513
|
+
if (!prev2 || Math.abs(b2.std_diff) > Math.abs(prev2.std_diff)) balByCol.set(b2.base_column, b2);
|
|
60514
|
+
}
|
|
60515
|
+
const matchOn = d2.match_on.map((col) => {
|
|
60516
|
+
var _a2;
|
|
60517
|
+
const b2 = balByCol.get(col);
|
|
60518
|
+
const absStd = b2 ? Math.abs(b2.std_diff) : 0;
|
|
60519
|
+
const { tone } = band(absStd);
|
|
60520
|
+
return { col, frac: clamp01(absStd), tone, display: getOpt((_a2 = colMeta(meta3, col)) == null ? void 0 : _a2.label) ?? col };
|
|
60521
|
+
});
|
|
60522
|
+
const ns = arrI(d2.power_curve.n);
|
|
60523
|
+
const powers = arr(d2.power_curve.power);
|
|
60524
|
+
const len = Math.min(ns.length, powers.length);
|
|
60525
|
+
const nearest = (target) => {
|
|
60526
|
+
let bi = 0, bd = Infinity;
|
|
60527
|
+
for (let i = 0; i < len; i++) {
|
|
60528
|
+
const dd = Math.abs(ns[i] - target);
|
|
60529
|
+
if (dd < bd) {
|
|
60530
|
+
bd = dd;
|
|
60531
|
+
bi = i;
|
|
60532
|
+
}
|
|
60533
|
+
}
|
|
60534
|
+
return bi;
|
|
60535
|
+
};
|
|
60536
|
+
const xTicks = len ? [String(ns[0]), String(ns[Math.floor((len - 1) / 2)]), String(ns[len - 1])] : [];
|
|
60537
|
+
const marks = [];
|
|
60538
|
+
if (len) marks.push({ at: nearest(primary.nTotal), label: `target ${(d2.target_power * 100).toFixed(0)}%`, tone: "pos", help: "validate_power" });
|
|
60539
|
+
const cp = getOpt(d2.current_power);
|
|
60540
|
+
if (cp !== void 0 && len) {
|
|
60541
|
+
const curN = result ? Number(result.n_total) : 0;
|
|
60542
|
+
if (curN > 0) {
|
|
60543
|
+
const offScale = curN >= (ns[len - 1] ?? 0);
|
|
60544
|
+
marks.push({
|
|
60545
|
+
at: offScale ? len - 1 : nearest(curN),
|
|
60546
|
+
label: offScale ? `you’re here · ${(cp * 100).toFixed(0)}% (off-scale)` : `you’re here ${(cp * 100).toFixed(0)}%`,
|
|
60547
|
+
tone: "muted",
|
|
60548
|
+
help: "validate_power"
|
|
60549
|
+
});
|
|
60550
|
+
}
|
|
60551
|
+
}
|
|
60552
|
+
const faint = d2.outcome_sd > 0 && Math.abs(d2.target_effect) / d2.outcome_sd <= 0.011;
|
|
60553
|
+
return {
|
|
60554
|
+
headline: DESIGN_HEADLINE[basis] ?? "Validate this",
|
|
60555
|
+
rationale: d2.rationale,
|
|
60556
|
+
primary,
|
|
60557
|
+
alternates: options2.slice(1),
|
|
60558
|
+
matchOn,
|
|
60559
|
+
curve: { mid: powers.slice(0, len).map((p2) => p2 * 100), xTicks, marks },
|
|
60560
|
+
targetLabel: `${signed(d2.target_effect)}${unit2 ? ` ${unit2}` : ""}`,
|
|
60561
|
+
targetPctLabel: `${(d2.target_power * 100).toFixed(0)}%`,
|
|
60562
|
+
holdback: basis === "create_control",
|
|
60563
|
+
showOverlap: basis === "restrict_to_overlap",
|
|
60564
|
+
faint
|
|
60565
|
+
};
|
|
60566
|
+
}
|
|
60567
|
+
const SUBJECT_ONE = "record";
|
|
60568
|
+
const SUBJECT_MANY = "records";
|
|
60569
|
+
const AUTORUN_MAX_ROWS = 5e4;
|
|
59452
60570
|
const TONE_TOKEN = {
|
|
59453
60571
|
neg: "fg.danger",
|
|
59454
60572
|
pos: "fg.success",
|
|
@@ -59486,13 +60604,73 @@ function useColumns(workspace, source) {
|
|
|
59486
60604
|
};
|
|
59487
60605
|
}, [types == null ? void 0 : types.sourceType]);
|
|
59488
60606
|
}
|
|
59489
|
-
|
|
59490
|
-
|
|
59491
|
-
|
|
60607
|
+
function rowMatchesAll(row, preds) {
|
|
60608
|
+
return preds.every((p2) => {
|
|
60609
|
+
const { fieldId, op } = p2.value;
|
|
60610
|
+
const v3 = row[fieldId];
|
|
60611
|
+
const ov = op.value;
|
|
60612
|
+
switch (op.type) {
|
|
60613
|
+
case "eq":
|
|
60614
|
+
case "is":
|
|
60615
|
+
return v3 === ov;
|
|
60616
|
+
case "neq":
|
|
60617
|
+
return v3 !== ov;
|
|
60618
|
+
case "lt":
|
|
60619
|
+
return num(v3) != null && num(ov) != null ? num(v3) < num(ov) : true;
|
|
60620
|
+
case "lte":
|
|
60621
|
+
return num(v3) != null && num(ov) != null ? num(v3) <= num(ov) : true;
|
|
60622
|
+
case "gt":
|
|
60623
|
+
return num(v3) != null && num(ov) != null ? num(v3) > num(ov) : true;
|
|
60624
|
+
case "gte":
|
|
60625
|
+
return num(v3) != null && num(ov) != null ? num(v3) >= num(ov) : true;
|
|
60626
|
+
case "in":
|
|
60627
|
+
return ov instanceof Set ? ov.has(v3) : true;
|
|
60628
|
+
case "notIn":
|
|
60629
|
+
return ov instanceof Set ? !ov.has(v3) : true;
|
|
60630
|
+
default:
|
|
60631
|
+
return true;
|
|
60632
|
+
}
|
|
60633
|
+
});
|
|
60634
|
+
}
|
|
60635
|
+
const num = (x2) => typeof x2 === "number" ? x2 : typeof x2 === "bigint" ? Number(x2) : null;
|
|
60636
|
+
function Cap({ children: children2, help }) {
|
|
60637
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", mb: "2.5", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: children2 }) : children2 });
|
|
59492
60638
|
}
|
|
59493
60639
|
function Card({ children: children2, mt = "3" }) {
|
|
59494
60640
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", mt, children: children2 });
|
|
59495
60641
|
}
|
|
60642
|
+
function RunError({ error: error3 }) {
|
|
60643
|
+
const tail = (error3.stderr || error3.stdout || "").trim().split("\n").slice(-12).join("\n");
|
|
60644
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", flexDirection: "column", gap: "2", mt: "3", children: [
|
|
60645
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "flex-start", gap: "2", color: "fg.danger", children: [
|
|
60646
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", mt: "0.5", fontSize: "12px", flexShrink: "0", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
60647
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
60648
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Could not run the experiment." }),
|
|
60649
|
+
" ",
|
|
60650
|
+
error3.message
|
|
60651
|
+
] })
|
|
60652
|
+
] }),
|
|
60653
|
+
tail && /* @__PURE__ */ jsxRuntime.jsx(
|
|
60654
|
+
react.Box,
|
|
60655
|
+
{
|
|
60656
|
+
as: "pre",
|
|
60657
|
+
m: "0",
|
|
60658
|
+
p: "2",
|
|
60659
|
+
maxH: "160px",
|
|
60660
|
+
overflow: "auto",
|
|
60661
|
+
bg: "bg.canvas",
|
|
60662
|
+
borderRadius: "md",
|
|
60663
|
+
borderWidth: "1px",
|
|
60664
|
+
borderColor: "border.subtle",
|
|
60665
|
+
fontFamily: "mono",
|
|
60666
|
+
fontSize: "11px",
|
|
60667
|
+
color: "fg.muted",
|
|
60668
|
+
whiteSpace: "pre-wrap",
|
|
60669
|
+
children: tail
|
|
60670
|
+
}
|
|
60671
|
+
)
|
|
60672
|
+
] });
|
|
60673
|
+
}
|
|
59496
60674
|
function ActionButton({ button, variant: variant2, label, onClick, disabled: disabled2, pulse = false }) {
|
|
59497
60675
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
59498
60676
|
react.Box,
|
|
@@ -59508,8 +60686,46 @@ function ActionButton({ button, variant: variant2, label, onClick, disabled: dis
|
|
|
59508
60686
|
}
|
|
59509
60687
|
);
|
|
59510
60688
|
}
|
|
60689
|
+
function DeckSkeleton({ tab }) {
|
|
60690
|
+
const sk = react.useRecipe({ key: "skeleton" });
|
|
60691
|
+
const bar = (w2, h2) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: sk({ variant: "line" }), width: w2, height: h2 });
|
|
60692
|
+
const block = (h2) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: sk({ variant: "block" }), width: "100%", minHeight: h2 });
|
|
60693
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "flex", flexDirection: "column", gap: "4", children: [
|
|
60694
|
+
tab === "answer" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60695
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", gap: "4.5", alignItems: "flex-end", children: [
|
|
60696
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "2", children: [
|
|
60697
|
+
bar("64px", "10px"),
|
|
60698
|
+
bar("120px", "32px")
|
|
60699
|
+
] }),
|
|
60700
|
+
bar("170px", "24px")
|
|
60701
|
+
] }),
|
|
60702
|
+
block("120px"),
|
|
60703
|
+
block("120px")
|
|
60704
|
+
] }),
|
|
60705
|
+
tab === "trust" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60706
|
+
bar("70%", "14px"),
|
|
60707
|
+
block("160px")
|
|
60708
|
+
] }),
|
|
60709
|
+
tab === "dose" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60710
|
+
block("240px"),
|
|
60711
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", children: [
|
|
60712
|
+
block("90px"),
|
|
60713
|
+
block("90px")
|
|
60714
|
+
] })
|
|
60715
|
+
] }),
|
|
60716
|
+
tab === "validate" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
60717
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", gap: "5", alignItems: "flex-end", children: [
|
|
60718
|
+
bar("120px", "32px"),
|
|
60719
|
+
bar("200px", "10px")
|
|
60720
|
+
] }),
|
|
60721
|
+
block("110px"),
|
|
60722
|
+
block("150px")
|
|
60723
|
+
] })
|
|
60724
|
+
] });
|
|
60725
|
+
}
|
|
60726
|
+
const experimentValueEqual = east.equalFor(internal.Experiment.Component.schema);
|
|
59511
60727
|
const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }) {
|
|
59512
|
-
var _a2, _b2, _c2;
|
|
60728
|
+
var _a2, _b2, _c2, _d2;
|
|
59513
60729
|
const v3 = value;
|
|
59514
60730
|
const button = react.useRecipe({ key: "button" });
|
|
59515
60731
|
const chip = react.useRecipe({ key: "chip" });
|
|
@@ -59519,12 +60735,19 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59519
60735
|
const es = react.useSlotRecipe({ key: "eyebrowRow" })({});
|
|
59520
60736
|
const workspace = getReactiveDatasetCache().getConfig().workspace ?? "";
|
|
59521
60737
|
const data4 = useBindingValue(v3.data);
|
|
59522
|
-
const
|
|
60738
|
+
const configBind = useBindingValue(v3.config);
|
|
59523
60739
|
const journalBind = useBindingValue(v3.journal.type === "some" ? v3.journal.value : null);
|
|
60740
|
+
const populationBind = useBindingValue(v3.population.type === "some" ? v3.population.value : null);
|
|
59524
60741
|
const meta3 = getOpt(v3.columnMeta);
|
|
59525
60742
|
const readonly = getOpt(v3.readonly) ?? false;
|
|
59526
60743
|
const { columns, rowArrayType } = useColumns(workspace, v3.data.source);
|
|
59527
|
-
const
|
|
60744
|
+
const config2 = configBind.value;
|
|
60745
|
+
const [localPop, setLocalPop] = React.useState([]);
|
|
60746
|
+
const population = populationBind.value ?? localPop;
|
|
60747
|
+
const filteredRows = React.useMemo(() => {
|
|
60748
|
+
if (!data4.value) return null;
|
|
60749
|
+
return population.length ? data4.value.filter((r2) => rowMatchesAll(r2, population)) : data4.value;
|
|
60750
|
+
}, [data4.value, population]);
|
|
59528
60751
|
const fields = React.useMemo(
|
|
59529
60752
|
() => columns.filter((c2) => c2.kind !== "other").map((c2) => {
|
|
59530
60753
|
var _a3;
|
|
@@ -59536,132 +60759,176 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59536
60759
|
}),
|
|
59537
60760
|
[columns, meta3]
|
|
59538
60761
|
);
|
|
59539
|
-
const estInputs = React.useMemo(() => rowArrayType ? [rowArrayType, internal.Experiment.Types.
|
|
59540
|
-
const
|
|
59541
|
-
const
|
|
59542
|
-
const
|
|
59543
|
-
|
|
59544
|
-
|
|
59545
|
-
|
|
59546
|
-
|
|
59547
|
-
|
|
59548
|
-
|
|
60762
|
+
const estInputs = React.useMemo(() => rowArrayType ? [rowArrayType, internal.Experiment.Types.Config] : null, [rowArrayType]);
|
|
60763
|
+
const experiment = useFuncCall(v3.experiment.name, estInputs, internal.Experiment.Types.Result);
|
|
60764
|
+
const hasDesign = v3.design.type === "some";
|
|
60765
|
+
const designInputs = React.useMemo(
|
|
60766
|
+
() => rowArrayType ? [rowArrayType, internal.Experiment.Types.Config, internal.Experiment.Types.Result, internal.Experiment.Types.DesignConfig] : null,
|
|
60767
|
+
[rowArrayType]
|
|
60768
|
+
);
|
|
60769
|
+
const design = useFuncCall(v3.design.type === "some" ? v3.design.value.name : null, designInputs, internal.Experiment.Types.Design);
|
|
60770
|
+
const pendingConfigRef = React.useRef(null);
|
|
60771
|
+
const [ranConfig, setRanConfig] = React.useState(null);
|
|
59549
60772
|
const runAll = React.useCallback(() => {
|
|
59550
|
-
if (!
|
|
59551
|
-
|
|
59552
|
-
|
|
59553
|
-
|
|
59554
|
-
}, [data4.value, spec, estimate, refute, dose, doseFeature, v3.refute.type, v3.dose.type]);
|
|
60773
|
+
if (!filteredRows || !config2) return;
|
|
60774
|
+
pendingConfigRef.current = config2;
|
|
60775
|
+
experiment.call(filteredRows, config2);
|
|
60776
|
+
}, [filteredRows, config2, experiment]);
|
|
59555
60777
|
const autoRan = React.useRef(false);
|
|
59556
60778
|
React.useEffect(() => {
|
|
59557
60779
|
if (autoRan.current) return;
|
|
59558
|
-
if (!
|
|
59559
|
-
if (
|
|
60780
|
+
if (!filteredRows || !config2 || !rowArrayType) return;
|
|
60781
|
+
if (experiment.result !== null || experiment.status === "running") {
|
|
59560
60782
|
autoRan.current = true;
|
|
59561
60783
|
return;
|
|
59562
60784
|
}
|
|
59563
|
-
if (
|
|
60785
|
+
if (experiment.status !== "idle") return;
|
|
59564
60786
|
autoRan.current = true;
|
|
60787
|
+
if (filteredRows.length > AUTORUN_MAX_ROWS) return;
|
|
59565
60788
|
runAll();
|
|
59566
|
-
}, [
|
|
60789
|
+
}, [filteredRows, config2, rowArrayType, experiment.result, experiment.status, runAll]);
|
|
60790
|
+
React.useEffect(() => {
|
|
60791
|
+
if (experiment.result !== null) setRanConfig(pendingConfigRef.current);
|
|
60792
|
+
}, [experiment.result]);
|
|
59567
60793
|
const [stale, setStale] = React.useState(false);
|
|
59568
|
-
const
|
|
60794
|
+
const editConfig = React.useCallback((next2) => {
|
|
60795
|
+
if (readonly) return;
|
|
60796
|
+
setStale(true);
|
|
60797
|
+
queueMicrotask(() => configBind.mutate(next2));
|
|
60798
|
+
}, [readonly, configBind]);
|
|
60799
|
+
const editPopulation = React.useCallback((next2) => {
|
|
60800
|
+
if (readonly) return;
|
|
59569
60801
|
setStale(true);
|
|
59570
|
-
queueMicrotask(() =>
|
|
59571
|
-
|
|
60802
|
+
if (v3.population.type === "some") queueMicrotask(() => populationBind.mutate(next2));
|
|
60803
|
+
else setLocalPop(next2);
|
|
60804
|
+
}, [readonly, v3.population.type, populationBind]);
|
|
59572
60805
|
const onRun = React.useCallback(() => {
|
|
59573
60806
|
runAll();
|
|
59574
60807
|
setStale(false);
|
|
59575
60808
|
}, [runAll]);
|
|
59576
60809
|
const onCommit = React.useCallback(async () => {
|
|
59577
|
-
if (!
|
|
59578
|
-
const
|
|
60810
|
+
if (!config2 || !experiment.result) return;
|
|
60811
|
+
const r2 = experiment.result;
|
|
60812
|
+
const row = {
|
|
60813
|
+
config: config2,
|
|
60814
|
+
verdict: r2.verdict,
|
|
60815
|
+
naive: r2.naive,
|
|
60816
|
+
adjusted: r2.adjusted.type === "some" ? east.some(r2.adjusted.value.effect) : east.none,
|
|
60817
|
+
committed_at: /* @__PURE__ */ new Date(),
|
|
60818
|
+
committed_by: "you"
|
|
60819
|
+
};
|
|
59579
60820
|
journalBind.mutate([row, ...journalBind.value ?? []]);
|
|
59580
60821
|
try {
|
|
59581
60822
|
await journalBind.commit();
|
|
59582
|
-
await
|
|
60823
|
+
await configBind.commit();
|
|
60824
|
+
if (v3.population.type === "some") await populationBind.commit();
|
|
59583
60825
|
setStale(false);
|
|
59584
60826
|
} catch {
|
|
59585
60827
|
}
|
|
59586
|
-
}, [
|
|
60828
|
+
}, [config2, experiment.result, journalBind, configBind, populationBind, v3.population.type]);
|
|
59587
60829
|
const [tab, setTab] = React.useState(((_a2 = getOpt(v3.defaultTab)) == null ? void 0 : _a2.type) ?? "answer");
|
|
60830
|
+
const [guidance, setGuidance] = React.useState(true);
|
|
59588
60831
|
const now2 = React.useMemo(() => /* @__PURE__ */ new Date(), []);
|
|
59589
|
-
const nRows = (
|
|
60832
|
+
const nRows = (filteredRows == null ? void 0 : filteredRows.length) ?? 0;
|
|
60833
|
+
const helpVars = React.useMemo(() => {
|
|
60834
|
+
const rc = ranConfig ?? config2;
|
|
60835
|
+
const labelOf = (col) => {
|
|
60836
|
+
var _a3;
|
|
60837
|
+
return col ? getOpt((_a3 = meta3 == null ? void 0 : meta3.get(col)) == null ? void 0 : _a3.label) ?? col : "";
|
|
60838
|
+
};
|
|
60839
|
+
return { treatment: labelOf(rc == null ? void 0 : rc.treatment), outcome: labelOf(rc == null ? void 0 : rc.outcome), subject: SUBJECT_ONE, subjects: SUBJECT_MANY };
|
|
60840
|
+
}, [ranConfig, config2, meta3]);
|
|
59590
60841
|
const view = React.useMemo(() => {
|
|
59591
|
-
if (!
|
|
59592
|
-
return deriveView(
|
|
59593
|
-
}, [
|
|
59594
|
-
|
|
59595
|
-
|
|
59596
|
-
|
|
59597
|
-
|
|
59598
|
-
|
|
59599
|
-
|
|
59600
|
-
const
|
|
60842
|
+
if (!config2) return null;
|
|
60843
|
+
return deriveView(config2, ranConfig ?? config2, columns, experiment.result, journalBind.value, meta3, nRows, now2);
|
|
60844
|
+
}, [config2, ranConfig, columns, experiment.result, journalBind.value, meta3, nRows, now2]);
|
|
60845
|
+
const designConfigValue = React.useMemo(() => ({
|
|
60846
|
+
alpha: east.none,
|
|
60847
|
+
target_power: east.none,
|
|
60848
|
+
materiality: east.none,
|
|
60849
|
+
treated_shares: east.some([0.5, 0.3])
|
|
60850
|
+
}), []);
|
|
60851
|
+
const designSnapRef = React.useRef(null);
|
|
60852
|
+
React.useEffect(() => {
|
|
60853
|
+
var _a3;
|
|
60854
|
+
if (tab !== "validate" || !hasDesign || design.pending) return;
|
|
60855
|
+
if (!filteredRows || !config2 || !experiment.result) return;
|
|
60856
|
+
if (((_a3 = designSnapRef.current) == null ? void 0 : _a3.result) === experiment.result) return;
|
|
60857
|
+
designSnapRef.current = { result: experiment.result, config: ranConfig ?? config2 };
|
|
60858
|
+
design.call(filteredRows, ranConfig ?? config2, experiment.result, designConfigValue);
|
|
60859
|
+
}, [tab, hasDesign, filteredRows, config2, ranConfig, experiment.result, design, designConfigValue]);
|
|
60860
|
+
const vmDesign = React.useMemo(
|
|
60861
|
+
() => design.result && designSnapRef.current ? deriveDesign(design.result, designSnapRef.current.result, designSnapRef.current.config, meta3) : null,
|
|
60862
|
+
[design.result, meta3]
|
|
60863
|
+
);
|
|
60864
|
+
const designFresh = design.result !== null && ((_b2 = designSnapRef.current) == null ? void 0 : _b2.result) === experiment.result && !design.pending;
|
|
60865
|
+
if (!config2 || !view) {
|
|
60866
|
+
const failed2 = experiment.status === "failed";
|
|
60867
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame", p: "6", children: failed2 && experiment.error ? /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { className: failed2 ? void 0 : "elara-skeleton", textStyle: "body.sm", color: "fg.muted", children: failed2 ? "Could not run the experiment." : "Loading experiment…" }) });
|
|
60868
|
+
}
|
|
60869
|
+
const { spec: vs, answer: a2, refusal: ref, overlap: ov, refute: vr, dose: vd, journal, verdict } = view;
|
|
59601
60870
|
const higherBetter = getOpt((_c2 = meta3 == null ? void 0 : meta3.get(vs.outcome)) == null ? void 0 : _c2.higherIsBetter);
|
|
59602
|
-
const dirUp = ans.effect > 0;
|
|
59603
|
-
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
59604
|
-
const flip = Math.sign(ans.naive) !== Math.sign(ans.effect) && ans.naive !== 0;
|
|
59605
|
-
const top = ans.balance[0] ?? { col: "", display: "" };
|
|
59606
|
-
const lowerWord = ans.naive < 0 ? "lower" : "higher";
|
|
59607
|
-
const population = getOpt(spec.population) ?? [];
|
|
59608
60871
|
const dataStatus = statusR({ status: "success", size: "sm" });
|
|
59609
60872
|
const barList = (rows) => /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.root, children: rows.map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: bs.row, children: [
|
|
59610
60873
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: bs.label, textStyle: "mono.sm", color: "fg.default", truncate: true, children: r2.label }),
|
|
59611
60874
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.track, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.fill, width: `${Math.round(r2.frac * 100)}%`, bg: toneToken(r2.tone) }) }),
|
|
59612
60875
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: bs.value, children: r2.value })
|
|
59613
60876
|
] }, i)) });
|
|
59614
|
-
const specStaged =
|
|
60877
|
+
const specStaged = configBind.mode === "staged";
|
|
59615
60878
|
const hasJournal = v3.journal.type === "some";
|
|
59616
60879
|
const canRun = !readonly;
|
|
59617
60880
|
const canCommit = !readonly && specStaged && hasJournal;
|
|
59618
|
-
const runDisabled = !data4.value ||
|
|
59619
|
-
const commitDisabled =
|
|
59620
|
-
|
|
60881
|
+
const runDisabled = !data4.value || experiment.pending;
|
|
60882
|
+
const commitDisabled = experiment.status !== "succeeded" || experiment.pending || stale;
|
|
60883
|
+
const failed = experiment.status === "failed" && experiment.error;
|
|
60884
|
+
const showResult = experiment.result !== null && !experiment.pending && !failed;
|
|
60885
|
+
const tabKeys = [...["answer", "trust", "dose"], ...hasDesign ? ["validate"] : []];
|
|
60886
|
+
return /* @__PURE__ */ jsxRuntime.jsx(GuidanceProvider, { on: guidance, vars: helpVars, children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "frame", overflow: "visible", children: [
|
|
59621
60887
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "header.bar", display: "flex", alignItems: "center", gap: "3.5", children: [
|
|
59622
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59623
|
-
"Does
|
|
59624
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.
|
|
59625
|
-
"
|
|
59626
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.
|
|
60888
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.card", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "header", children: [
|
|
60889
|
+
"Does ",
|
|
60890
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.treatment }),
|
|
60891
|
+
" change ",
|
|
60892
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "brand.solid", fontWeight: "bold", children: vs.outcome }),
|
|
59627
60893
|
"?"
|
|
59628
|
-
] }),
|
|
60894
|
+
] }) }),
|
|
59629
60895
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: "1" }),
|
|
59630
60896
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: dataStatus.root, children: [
|
|
59631
60897
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.indicator }),
|
|
59632
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: vs.dataLabel })
|
|
60898
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: dataStatus.label, children: stale ? `${nRows} rows` : vs.dataLabel })
|
|
59633
60899
|
] }),
|
|
60900
|
+
/* @__PURE__ */ jsxRuntime.jsx(GuidanceToggle, { on: guidance, onToggle: () => setGuidance((g2) => !g2) }),
|
|
59634
60901
|
canRun && /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { button, variant: "solid", label: "Run", onClick: onRun, disabled: runDisabled, pulse: stale }),
|
|
59635
60902
|
canCommit && /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { button, variant: "ghost", label: "Commit", onClick: onCommit, disabled: commitDisabled })
|
|
59636
60903
|
] }),
|
|
59637
60904
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "304px minmax(0,1fr)", alignItems: "start", children: [
|
|
59638
60905
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { borderRightWidth: "1px", borderColor: "border.subtle", children: [
|
|
59639
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Step, { n: 1, title: "What did you change?", children: [
|
|
59640
|
-
/* @__PURE__ */ jsxRuntime.jsx(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) =>
|
|
60906
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Step, { n: 1, title: "What did you change?", help: "step_treatment", children: [
|
|
60907
|
+
/* @__PURE__ */ jsxRuntime.jsx(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 }) }),
|
|
59641
60908
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", mt: "1.5", children: [
|
|
59642
60909
|
"Treated = ",
|
|
59643
60910
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: vs.comparison })
|
|
59644
60911
|
] })
|
|
59645
60912
|
] }),
|
|
59646
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 2, title: "What did you want it to improve?", children: /* @__PURE__ */ jsxRuntime.jsx(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) =>
|
|
59647
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 3, title: "What else was different
|
|
59648
|
-
vs.confounders.map((c2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.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: [
|
|
60913
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 2, title: "What did you want it to improve?", help: "step_outcome", children: /* @__PURE__ */ jsxRuntime.jsx(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 }) }) }),
|
|
60914
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 3, title: "What else was different?", help: "step_confounders", children: /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "frame.flat", children: [
|
|
60915
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { maxH: "216px", overflowY: "auto", children: vs.confounders.map((c2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.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: [
|
|
59649
60916
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59650
60917
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "semibold", color: "fg.default", children: c2.col }),
|
|
59651
60918
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.35", mt: "px", children: c2.reason })
|
|
59652
60919
|
] }),
|
|
59653
60920
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59654
60921
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.track, children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: bs.fill, width: `${Math.round(c2.imbalance * 100)}%`, bg: toneToken(c2.tone) }) }),
|
|
59655
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: c2.level })
|
|
60922
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", fontSize: "9px", textAlign: "center", mt: "1.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "confounder_imbalance", children: c2.level }) })
|
|
59656
60923
|
] }),
|
|
59657
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.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: () =>
|
|
59658
|
-
] }, i)),
|
|
59659
|
-
vs.suggestion && /* @__PURE__ */ jsxRuntime.jsx(ColumnMenu, { choices: columns.filter((c2) => !(/* @__PURE__ */ new Set([
|
|
60924
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.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" ? east.some(config2.categorical.value.filter((x2) => x2 !== c2.col)) : east.none }), children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faXmark, style: { fontSize: "11px" } }) })
|
|
60925
|
+
] }, i)) }),
|
|
60926
|
+
vs.suggestion && /* @__PURE__ */ jsxRuntime.jsx(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__ */ jsxRuntime.jsxs(react.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: [
|
|
59660
60927
|
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faPlus, style: { fontSize: "9px" } }),
|
|
59661
60928
|
"add another"
|
|
59662
60929
|
] }) })
|
|
59663
60930
|
] }) }),
|
|
59664
|
-
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title:
|
|
60931
|
+
/* @__PURE__ */ jsxRuntime.jsx(Step, { n: 4, title: `Which ${SUBJECT_MANY}?`, help: "step_population", children: /* @__PURE__ */ jsxRuntime.jsx(FilterRail, { fields, population, onChange: editPopulation, chip, button }) }),
|
|
59665
60932
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "details", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59666
60933
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59667
60934
|
react.Box,
|
|
@@ -59694,173 +60961,168 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59694
60961
|
}
|
|
59695
60962
|
),
|
|
59696
60963
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: "4.5", pb: "3.5", pt: "0.5", children: [
|
|
59697
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "How to compare", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) =>
|
|
59698
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Answer for", left: "all
|
|
59699
|
-
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Drop un-matchable", left: "on", right: "off", active: vs.trim ? "left" : "right", onPick: (s2) => editSpec({ ...spec, trim: s2 === "left" ? east.some(east.variant("overlap", null)) : east.none }), last: true })
|
|
60964
|
+
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "How to compare", help: "adv_method", left: "regression", right: "reweighting", active: vs.method === "reweighting" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, method: east.some(s2 === "right" ? east.variant("propensity_score_weighting", { weighting_scheme: east.none }) : east.variant("linear_regression", null)) }) }),
|
|
60965
|
+
/* @__PURE__ */ jsxRuntime.jsx(Segmented, { label: "Answer for", help: "adv_estimand", left: "all", right: "only treated", active: vs.target === "treated" ? "right" : "left", onPick: (s2) => editConfig({ ...config2, estimand: east.some(east.variant(s2 === "right" ? "att" : "ate", null)) }), last: true })
|
|
59700
60966
|
] })
|
|
59701
60967
|
] })
|
|
59702
60968
|
] }),
|
|
59703
60969
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { minW: "0", children: [
|
|
59704
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59705
|
-
|
|
59706
|
-
|
|
59707
|
-
|
|
59708
|
-
|
|
59709
|
-
|
|
59710
|
-
|
|
59711
|
-
|
|
59712
|
-
|
|
59713
|
-
|
|
59714
|
-
|
|
59715
|
-
|
|
59716
|
-
|
|
59717
|
-
|
|
59718
|
-
|
|
59719
|
-
|
|
59720
|
-
|
|
59721
|
-
|
|
60970
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
60971
|
+
react.Box,
|
|
60972
|
+
{
|
|
60973
|
+
display: "flex",
|
|
60974
|
+
alignItems: "center",
|
|
60975
|
+
px: "4",
|
|
60976
|
+
borderBottomWidth: "1px",
|
|
60977
|
+
borderColor: "border.subtle",
|
|
60978
|
+
role: "tablist",
|
|
60979
|
+
"aria-label": "Result views",
|
|
60980
|
+
onKeyDown: (e3) => {
|
|
60981
|
+
var _a3;
|
|
60982
|
+
const i = tabKeys.indexOf(tab);
|
|
60983
|
+
let j2 = i;
|
|
60984
|
+
if (e3.key === "ArrowRight") j2 = (i + 1) % tabKeys.length;
|
|
60985
|
+
else if (e3.key === "ArrowLeft") j2 = (i - 1 + tabKeys.length) % tabKeys.length;
|
|
60986
|
+
else if (e3.key === "Home") j2 = 0;
|
|
60987
|
+
else if (e3.key === "End") j2 = tabKeys.length - 1;
|
|
60988
|
+
else return;
|
|
60989
|
+
e3.preventDefault();
|
|
60990
|
+
setTab(tabKeys[j2]);
|
|
60991
|
+
const btns = e3.currentTarget.querySelectorAll('[role="tab"]');
|
|
60992
|
+
(_a3 = btns[j2]) == null ? void 0 : _a3.focus();
|
|
59722
60993
|
},
|
|
59723
|
-
|
|
59724
|
-
|
|
59725
|
-
|
|
59726
|
-
|
|
59727
|
-
|
|
59728
|
-
|
|
59729
|
-
|
|
59730
|
-
|
|
59731
|
-
|
|
59732
|
-
|
|
59733
|
-
|
|
59734
|
-
|
|
59735
|
-
|
|
59736
|
-
|
|
59737
|
-
|
|
60994
|
+
children: [
|
|
60995
|
+
tabKeys.map((tk) => {
|
|
60996
|
+
const on3 = tab === tk;
|
|
60997
|
+
const tabHelp = tk === "answer" ? "tab_answer" : tk === "trust" ? "tab_trust" : tk === "dose" ? "tab_dose" : "tab_validate";
|
|
60998
|
+
const tabLabel = tk === "answer" ? "Answer" : tk === "trust" ? "Trust" : tk === "dose" ? "Dose" : "Validate";
|
|
60999
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
61000
|
+
react.Box,
|
|
61001
|
+
{
|
|
61002
|
+
as: "button",
|
|
61003
|
+
role: "tab",
|
|
61004
|
+
"aria-selected": on3,
|
|
61005
|
+
tabIndex: on3 ? 0 : -1,
|
|
61006
|
+
onClick: () => setTab(tk),
|
|
61007
|
+
cursor: "pointer",
|
|
61008
|
+
fontSize: "xs",
|
|
61009
|
+
fontWeight: "semibold",
|
|
61010
|
+
px: "3.5",
|
|
61011
|
+
py: "3",
|
|
61012
|
+
mb: "-1px",
|
|
61013
|
+
color: on3 ? "brand.fg" : "fg.muted",
|
|
61014
|
+
borderBottomWidth: "2px",
|
|
61015
|
+
borderColor: on3 ? "brand.solid" : "transparent",
|
|
61016
|
+
_hover: { color: on3 ? "brand.fg" : "fg.default" },
|
|
61017
|
+
_focusVisible: { outline: "2px solid", outlineColor: "brand.solid", outlineOffset: "-2px" },
|
|
61018
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: tabHelp, children: tabLabel })
|
|
61019
|
+
},
|
|
61020
|
+
tk
|
|
61021
|
+
);
|
|
61022
|
+
}),
|
|
61023
|
+
experiment.pending && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { ml: "auto", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", pr: "1", children: [
|
|
61024
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
61025
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: "Running…" })
|
|
59738
61026
|
] })
|
|
59739
|
-
]
|
|
59740
|
-
|
|
59741
|
-
|
|
59742
|
-
|
|
59743
|
-
|
|
59744
|
-
|
|
59745
|
-
flip && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
59746
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61027
|
+
]
|
|
61028
|
+
}
|
|
61029
|
+
),
|
|
61030
|
+
failed ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: experiment.error }) }) : !showResult ? /* @__PURE__ */ jsxRuntime.jsx(DeckSkeleton, { tab }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
61031
|
+
stale && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "center", gap: "2", mx: "4.5", mt: "4.5", children: [
|
|
61032
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
59747
61033
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
59748
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "
|
|
59749
|
-
"
|
|
59750
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: ans.treatment }),
|
|
59751
|
-
" batches sit ",
|
|
59752
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
59753
|
-
" on ",
|
|
59754
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: ans.outcome }),
|
|
59755
|
-
" (",
|
|
59756
|
-
signed(ans.naive),
|
|
59757
|
-
") — but they also differ most on ",
|
|
59758
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
59759
|
-
" (",
|
|
59760
|
-
top.display,
|
|
59761
|
-
"). Adjusting for it reverses the result."
|
|
61034
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Showing the previous setup." }),
|
|
61035
|
+
" Hit Run to update these results for your edits."
|
|
59762
61036
|
] })
|
|
59763
61037
|
] }),
|
|
59764
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59765
|
-
|
|
59766
|
-
|
|
59767
|
-
|
|
59768
|
-
|
|
59769
|
-
|
|
59770
|
-
|
|
59771
|
-
|
|
59772
|
-
|
|
59773
|
-
|
|
59774
|
-
|
|
59775
|
-
|
|
59776
|
-
|
|
59777
|
-
|
|
59778
|
-
|
|
59779
|
-
|
|
59780
|
-
|
|
59781
|
-
|
|
59782
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59783
|
-
|
|
59784
|
-
|
|
59785
|
-
|
|
59786
|
-
" ",
|
|
59787
|
-
label
|
|
59788
|
-
] }, i)) })
|
|
59789
|
-
] }),
|
|
59790
|
-
tab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
59791
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }),
|
|
59792
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
59793
|
-
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
59794
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(react.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: [
|
|
59795
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.indicator }) }),
|
|
59796
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
59797
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: c2.name }),
|
|
59798
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
59799
|
-
] }),
|
|
59800
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
59801
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
59802
|
-
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
59803
|
-
] })
|
|
59804
|
-
] }, i);
|
|
59805
|
-
}) }),
|
|
59806
|
-
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
59807
|
-
/* @__PURE__ */ jsxRuntime.jsx(Cap, { children: "Effect as a hidden cause is made stronger" }),
|
|
59808
|
-
/* @__PURE__ */ jsxRuntime.jsx(AreaRange, { lo: vr.sensLo, mid: vr.sensMid, hi: vr.sensHi, zero: 0, tone: "brand", xTicks: vr.sensXTicks, yTicks: vr.sensYTicks, height: 132 })
|
|
59809
|
-
] })
|
|
59810
|
-
] }),
|
|
59811
|
-
tab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
59812
|
-
vd.segments.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "2.5", mb: "3.5", children: [
|
|
59813
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: "Response for" }),
|
|
59814
|
-
/* @__PURE__ */ jsxRuntime.jsx(SegmentSelect, { options: vd.segments, active: 0, onPick: () => {
|
|
59815
|
-
} })
|
|
61038
|
+
tab === "answer" && a2 && /* @__PURE__ */ jsxRuntime.jsx(AnswerNumeric, { a: a2, verdict, higherBetter, badge, barList }),
|
|
61039
|
+
tab === "answer" && !a2 && ref && /* @__PURE__ */ jsxRuntime.jsx(RefusalZone, { refusal: ref, overlap: ov, naiveValue: ((_d2 = experiment.result) == null ? void 0 : _d2.naive) ?? 0, outcome: vs.outcome }),
|
|
61040
|
+
tab === "trust" && vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61041
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "trust_intro", children: "Before trusting the answer we tried to break it — colour shows pass / caution." }) }),
|
|
61042
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { layerStyle: "frame.flat", children: vr.checks.map((c2, i) => {
|
|
61043
|
+
const cs = statusR({ status: c2.passed ? "success" : "warning", size: "sm" });
|
|
61044
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.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: [
|
|
61045
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.root, mt: "1", children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: cs.indicator }) }),
|
|
61046
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { children: [
|
|
61047
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: c2.help, children: c2.name }) }),
|
|
61048
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", lineHeight: "1.45", mt: "0.5", children: c2.desc })
|
|
61049
|
+
] }),
|
|
61050
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", justifyContent: "flex-end", gap: "1.5", whiteSpace: "nowrap", color: c2.passed ? "fg.success" : "fg.warning", children: [
|
|
61051
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "bold", fontVariantNumeric: "tabular-nums", children: c2.value }),
|
|
61052
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: c2.passed ? faCheck : faTriangleExclamation, style: { fontSize: "11px" } })
|
|
61053
|
+
] })
|
|
61054
|
+
] }, i);
|
|
61055
|
+
}) }),
|
|
61056
|
+
vr.sens && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
61057
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "sensitivity", children: "Effect as a hidden cause is made stronger" }),
|
|
61058
|
+
/* @__PURE__ */ jsxRuntime.jsx(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 })
|
|
61059
|
+
] })
|
|
59816
61060
|
] }),
|
|
59817
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59818
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59819
|
-
|
|
59820
|
-
"
|
|
59821
|
-
vd.feature
|
|
61061
|
+
tab === "trust" && !vr && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61062
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.muted", children: [
|
|
61063
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "13px" } }),
|
|
61064
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.default", children: "Nothing to stress-test" })
|
|
59822
61065
|
] }),
|
|
59823
|
-
/* @__PURE__ */ jsxRuntime.
|
|
61066
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.muted", lineHeight: "1.5", children: [
|
|
61067
|
+
"There’s no adjusted estimate to try to break — the experiment couldn’t produce one (see the ",
|
|
61068
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: "Answer" }),
|
|
61069
|
+
" tab for why)."
|
|
61070
|
+
] })
|
|
59824
61071
|
] }),
|
|
59825
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, {
|
|
59826
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59827
|
-
/* @__PURE__ */ jsxRuntime.
|
|
59828
|
-
|
|
59829
|
-
|
|
59830
|
-
|
|
59831
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
59832
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
59833
|
-
vd.outcome,
|
|
59834
|
-
" · ",
|
|
59835
|
-
signed(vd.recoLo),
|
|
59836
|
-
" … ",
|
|
59837
|
-
signed(vd.recoHi)
|
|
59838
|
-
] })
|
|
59839
|
-
] })
|
|
61072
|
+
tab === "dose" && vd && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61073
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { mt: "0", children: [
|
|
61074
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "dose_curve", children: [
|
|
61075
|
+
vd.outcome,
|
|
61076
|
+
" gained vs. ",
|
|
61077
|
+
vd.feature
|
|
59840
61078
|
] }),
|
|
59841
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
61079
|
+
/* @__PURE__ */ jsxRuntime.jsx(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 })
|
|
59842
61080
|
] }),
|
|
59843
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, {
|
|
59844
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
59845
|
-
"
|
|
59846
|
-
|
|
59847
|
-
|
|
61081
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "3", mt: "3", children: [
|
|
61082
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
61083
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "dose_reco", children: "Recommended" }),
|
|
61084
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
61085
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", children: vd.recoLabel }),
|
|
61086
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2", children: [
|
|
61087
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", color: "brand.solid", children: signed(vd.recoEffect) }),
|
|
61088
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
61089
|
+
vd.outcome,
|
|
61090
|
+
" · ",
|
|
61091
|
+
signed(vd.recoLo),
|
|
61092
|
+
" … ",
|
|
61093
|
+
signed(vd.recoHi)
|
|
61094
|
+
] })
|
|
61095
|
+
] })
|
|
61096
|
+
] }),
|
|
61097
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption", mt: "2.5", pt: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: vd.tradeoff })
|
|
59848
61098
|
] }),
|
|
59849
|
-
/* @__PURE__ */ jsxRuntime.
|
|
61099
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "card", p: "3.5", borderRadius: "lg", children: [
|
|
61100
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "dose_marginal", children: [
|
|
61101
|
+
"Extra ",
|
|
61102
|
+
vd.outcome,
|
|
61103
|
+
" per step"
|
|
61104
|
+
] }),
|
|
61105
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", maxH: "200px", overflowY: "auto", children: barList(vd.marginal.map((m2, i) => ({ label: m2.label, frac: m2.frac, tone: i < 2 ? "brand" : "muted", value: signed(m2.value) }))) })
|
|
61106
|
+
] })
|
|
59850
61107
|
] })
|
|
59851
|
-
] })
|
|
61108
|
+
] }),
|
|
61109
|
+
tab === "validate" && (design.status === "failed" && design.error ? /* @__PURE__ */ jsxRuntime.jsx(react.Box, { px: "4.5", pt: "4.5", children: /* @__PURE__ */ jsxRuntime.jsx(RunError, { error: design.error }) }) : designFresh && vmDesign ? /* @__PURE__ */ jsxRuntime.jsx(ValidatePanel, { vm: vmDesign, barList }) : /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", display: "inline-flex", alignItems: "center", gap: "2", color: "fg.muted", children: [
|
|
61110
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Spinner, { size: "xs", borderWidth: "1.5px", color: "brand.solid" }),
|
|
61111
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", children: "Sizing the trial that would confirm this…" })
|
|
61112
|
+
] }))
|
|
59852
61113
|
] })
|
|
59853
61114
|
] })
|
|
59854
61115
|
] }),
|
|
59855
61116
|
journal && journal.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
59856
61117
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.root, bg: "bg.canvas", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59857
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: "Committed experiments" }),
|
|
61118
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: es.lbl, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "journal", children: "Committed experiments" }) }),
|
|
59858
61119
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: es.meta, children: [
|
|
59859
61120
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: journal.length }),
|
|
59860
|
-
" on record"
|
|
61121
|
+
" on record",
|
|
61122
|
+
journal.length > 50 ? " · showing newest 50" : ""
|
|
59861
61123
|
] })
|
|
59862
61124
|
] }),
|
|
59863
|
-
journal.map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
61125
|
+
journal.slice(0, 50).map((r2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "grid", gridTemplateColumns: "2fr 1fr 1fr 1fr", gap: "3", alignItems: "center", px: "4.5", py: "2.5", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
59864
61126
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", children: [
|
|
59865
61127
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { as: "span", fontWeight: "bold", children: [
|
|
59866
61128
|
r2.treatment,
|
|
@@ -59874,7 +61136,7 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59874
61136
|
] })
|
|
59875
61137
|
] }),
|
|
59876
61138
|
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", fontWeight: "semibold", textAlign: "right", fontVariantNumeric: "tabular-nums", color: r2.verdictTone === "pos" ? "fg.success" : "fg.default", children: r2.effect }),
|
|
59877
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textAlign: "right", color: r2.verdictTone
|
|
61139
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textAlign: "right", color: toneToken(r2.verdictTone), children: r2.verdict }),
|
|
59878
61140
|
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", textAlign: "right", color: "fg.muted", children: [
|
|
59879
61141
|
r2.who,
|
|
59880
61142
|
" · ",
|
|
@@ -59882,8 +61144,187 @@ const EastChakraExperiment = React.memo(function EastChakraExperiment2({ value }
|
|
|
59882
61144
|
] })
|
|
59883
61145
|
] }, i))
|
|
59884
61146
|
] })
|
|
61147
|
+
] }) });
|
|
61148
|
+
}, (prev2, next2) => experimentValueEqual(prev2.value, next2.value) && prev2.storageKey === next2.storageKey);
|
|
61149
|
+
function AnswerNumeric({ a: a2, verdict, higherBetter, badge, barList }) {
|
|
61150
|
+
const dirUp = a2.effect > 0;
|
|
61151
|
+
const statusWord = higherBetter === void 0 ? dirUp ? "Higher" : "Lower" : dirUp === higherBetter ? "Better" : "Worse";
|
|
61152
|
+
const lowerWord = a2.naive < 0 ? "lower" : "higher";
|
|
61153
|
+
const top = a2.balance[0] ?? { col: "", display: "" };
|
|
61154
|
+
const kpiColor = a2.clear && !a2.cautious ? "fg.success" : "fg.warning";
|
|
61155
|
+
const badgeOk = a2.clear && !a2.cautious;
|
|
61156
|
+
const badgeText = a2.cautious ? (verdict == null ? void 0 : verdict.label) ?? "Not trustworthy yet" : a2.clear ? `${statusWord} with ${a2.treatment}` : "No clear effect";
|
|
61157
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61158
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "center", gap: "4.5", flexWrap: "wrap", children: [
|
|
61159
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", gap: "0.5", children: [
|
|
61160
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: a2.outcome }),
|
|
61161
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", children: [
|
|
61162
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono-kpi", fontFamily: "heading", fontSize: "32px", color: kpiColor, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_effect", children: signed(a2.effect) }) }),
|
|
61163
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: /* @__PURE__ */ jsxRuntime.jsxs(Help, { id: "answer_ci", children: [
|
|
61164
|
+
"95% CI ",
|
|
61165
|
+
signed(a2.lo),
|
|
61166
|
+
" … ",
|
|
61167
|
+
signed(a2.hi)
|
|
61168
|
+
] }) })
|
|
61169
|
+
] })
|
|
61170
|
+
] }),
|
|
61171
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { as: "span", css: badge({ variant: badgeOk ? "ok" : "warn", size: "md" }), alignSelf: "flex-end", mb: "1", display: "inline-flex", alignItems: "center", gap: "1", children: [
|
|
61172
|
+
a2.clear && !a2.cautious && /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: dirUp ? faArrowUp : faArrowDown, style: { fontSize: "10px" } }),
|
|
61173
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: `verdict_${(verdict == null ? void 0 : verdict.tag) ?? "modest"}`, children: badgeText })
|
|
61174
|
+
] })
|
|
61175
|
+
] }),
|
|
61176
|
+
a2.cautious && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
61177
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61178
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
61179
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_cautious", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Treat this as provisional." }) }),
|
|
61180
|
+
" 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 ",
|
|
61181
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", children: "Can we trust it?" })
|
|
61182
|
+
] })
|
|
61183
|
+
] }),
|
|
61184
|
+
a2.flip && /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { layerStyle: "banner.stale", display: "flex", alignItems: "flex-start", gap: "2", mt: "3", children: [
|
|
61185
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", color: "fg.warning", flexShrink: "0", mt: "0.5", fontSize: "12px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation }) }),
|
|
61186
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "body.sm", color: "fg.default", children: [
|
|
61187
|
+
/* @__PURE__ */ jsxRuntime.jsx(Help, { id: "answer_flip", children: /* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "bold", children: "Raw and like-for-like disagree." }) }),
|
|
61188
|
+
" In the plain average, the ",
|
|
61189
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.treatment }),
|
|
61190
|
+
" group sits ",
|
|
61191
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontStyle: "italic", children: lowerWord }),
|
|
61192
|
+
" on ",
|
|
61193
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: a2.outcome }),
|
|
61194
|
+
" (",
|
|
61195
|
+
signed(a2.naive),
|
|
61196
|
+
") — but they also differ most on ",
|
|
61197
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", children: top.col }),
|
|
61198
|
+
" (",
|
|
61199
|
+
top.display,
|
|
61200
|
+
"). Adjusting for it reverses the result."
|
|
61201
|
+
] })
|
|
61202
|
+
] }),
|
|
61203
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
61204
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "forest_plot", children: "Raw average vs. like-for-like" }),
|
|
61205
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
61206
|
+
ForestPlot,
|
|
61207
|
+
{
|
|
61208
|
+
rows: [
|
|
61209
|
+
{ label: "Raw average", note: "unadjusted", est: a2.naive, lo: a2.naiveLo, hi: a2.naiveHi, tone: a2.naive < 0 ? "neg" : "pos" },
|
|
61210
|
+
{ label: "Like-for-like", note: "adjusted", est: a2.effect, lo: a2.lo, hi: a2.hi, tone: a2.clear && !a2.cautious ? "pos" : "warn" }
|
|
61211
|
+
],
|
|
61212
|
+
min: Math.floor(Math.min(0, a2.naiveLo, a2.lo) - 2),
|
|
61213
|
+
max: Math.ceil(Math.max(0, a2.naiveHi, a2.hi) + 2),
|
|
61214
|
+
unit: `change in ${a2.outcome}${a2.unit ? ` (${a2.unit})` : ""}`,
|
|
61215
|
+
height: 150,
|
|
61216
|
+
rowHelp: ["forest_naive", "forest_adjusted"]
|
|
61217
|
+
}
|
|
61218
|
+
)
|
|
61219
|
+
] }),
|
|
61220
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
61221
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "balance", children: "How unbalanced each one was — before adjusting" }),
|
|
61222
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.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 }))) })
|
|
61223
|
+
] }),
|
|
61224
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { mt: "3.5", children: /* @__PURE__ */ jsxRuntime.jsx(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__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
61225
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: Number(n2) }),
|
|
61226
|
+
" ",
|
|
61227
|
+
label
|
|
61228
|
+
] }, i)) }) })
|
|
59885
61229
|
] });
|
|
59886
|
-
}
|
|
61230
|
+
}
|
|
61231
|
+
function RefusalZone({ refusal, overlap, naiveValue, outcome }) {
|
|
61232
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61233
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "inline-flex", alignItems: "center", gap: "2", mb: "2", color: "fg.warning", children: [
|
|
61234
|
+
/* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faTriangleExclamation, style: { fontSize: "14px" } }),
|
|
61235
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.card", color: "fg.default", children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: refusal.kind === "positivity" ? "refusal_positivity" : "refusal_not_estimable", children: refusal.title }) })
|
|
61236
|
+
] }),
|
|
61237
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.muted", mb: "3", lineHeight: "1.5", children: refusal.body }),
|
|
61238
|
+
refusal.kind === "positivity" && overlap && /* @__PURE__ */ jsxRuntime.jsxs(Card, { mt: "1", children: [
|
|
61239
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Cap, { help: "overlap_histogram", children: [
|
|
61240
|
+
"Propensity overlap — ",
|
|
61241
|
+
overlap.supportLabel
|
|
61242
|
+
] }),
|
|
61243
|
+
/* @__PURE__ */ jsxRuntime.jsx(OverlapHistogram, { treated: overlap.treated, control: overlap.control, supportLabel: overlap.supportLabel, positivityOk: overlap.positivityOk, height: 170 })
|
|
61244
|
+
] }),
|
|
61245
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", gap: "4", mt: "3.5", children: refusal.evidence.map((e3, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "mono.sm", color: "fg.muted", children: [
|
|
61246
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", color: "fg.default", fontWeight: "semibold", children: e3.value }),
|
|
61247
|
+
" ",
|
|
61248
|
+
e3.label
|
|
61249
|
+
] }, i)) }),
|
|
61250
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.subtle", mt: "3.5", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: [
|
|
61251
|
+
"Raw average difference in ",
|
|
61252
|
+
outcome,
|
|
61253
|
+
" (context only, not an answer): ",
|
|
61254
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontFamily: "mono", color: "fg.muted", children: signed(naiveValue) })
|
|
61255
|
+
] })
|
|
61256
|
+
] });
|
|
61257
|
+
}
|
|
61258
|
+
function ValidatePanel({ vm, barList }) {
|
|
61259
|
+
const stat = react.useSlotRecipe({ key: "stat" })({ size: "lg" });
|
|
61260
|
+
const meter = react.useSlotRecipe({ key: "segmentedMeter" })({});
|
|
61261
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { p: "4.5", children: [
|
|
61262
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "tab_validate", children: vm.headline }),
|
|
61263
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "flex-end", gap: "5", flexWrap: "wrap", children: [
|
|
61264
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: stat.root, children: [
|
|
61265
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: stat.label, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "validate_size", children: vm.holdback ? "to hold back from" : "to run" }) }),
|
|
61266
|
+
vm.faint ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", fontWeight: "semibold", color: "fg.warning", maxW: "220px", children: "Effect too faint to size — set a materiality threshold to size a trial." }) : /* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: stat.valueText, color: "brand.solid", children: vm.primary.nTotal.toLocaleString() })
|
|
61267
|
+
] }),
|
|
61268
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.root, flex: "1", minW: "220px", children: [
|
|
61269
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { css: meter.label, children: /* @__PURE__ */ jsxRuntime.jsx(Help, { id: "validate_split", children: vm.holdback ? "Hold-back split" : "Split" }) }),
|
|
61270
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.track, children: [
|
|
61271
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.segment, flex: vm.primary.treatedShare, bg: "brand.solid" }),
|
|
61272
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.segment, flex: 1 - vm.primary.treatedShare, bg: "bg.emphasized" })
|
|
61273
|
+
] }),
|
|
61274
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.keyRow, justifyContent: "space-between", children: [
|
|
61275
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.keyItem, children: [
|
|
61276
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.keyDot, bg: "brand.solid" }),
|
|
61277
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
61278
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: vm.primary.nTreated.toLocaleString() }),
|
|
61279
|
+
" ",
|
|
61280
|
+
vm.holdback ? "treated" : "get it"
|
|
61281
|
+
] })
|
|
61282
|
+
] }),
|
|
61283
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { css: meter.keyItem, children: [
|
|
61284
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { css: meter.keyDot, bg: "bg.emphasized" }),
|
|
61285
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
61286
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", css: meter.valueText, children: vm.primary.nControl.toLocaleString() }),
|
|
61287
|
+
" ",
|
|
61288
|
+
vm.holdback ? "held back" : "left alone"
|
|
61289
|
+
] })
|
|
61290
|
+
] })
|
|
61291
|
+
] })
|
|
61292
|
+
] })
|
|
61293
|
+
] }),
|
|
61294
|
+
vm.matchOn.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
61295
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "validate_match", children: "Match both groups on" }),
|
|
61296
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { py: "0.5", children: barList(vm.matchOn.map((m2) => ({ label: m2.display, frac: m2.frac, tone: m2.tone, value: "" }))) })
|
|
61297
|
+
] }),
|
|
61298
|
+
vm.curve.mid.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
61299
|
+
/* @__PURE__ */ jsxRuntime.jsx(Cap, { help: "validate_power", children: "Chance of detecting it" }),
|
|
61300
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
61301
|
+
AreaRange,
|
|
61302
|
+
{
|
|
61303
|
+
lo: vm.curve.mid,
|
|
61304
|
+
mid: vm.curve.mid,
|
|
61305
|
+
hi: vm.curve.mid,
|
|
61306
|
+
tone: "brand",
|
|
61307
|
+
xTicks: vm.curve.xTicks,
|
|
61308
|
+
yTicks: ["100", "50", "0"],
|
|
61309
|
+
yFormat: "percent",
|
|
61310
|
+
marks: vm.curve.marks.map((m2) => ({ at: m2.at, label: m2.label, tone: m2.tone, help: m2.help })),
|
|
61311
|
+
height: 170
|
|
61312
|
+
}
|
|
61313
|
+
)
|
|
61314
|
+
] }),
|
|
61315
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "body.sm", color: "fg.default", lineHeight: "1.5", mt: "3.5", children: vm.rationale }),
|
|
61316
|
+
vm.alternates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Box, { display: "flex", flexDirection: "column", gap: "1.5", mt: "3", pt: "3", borderTopWidth: "1px", borderColor: "border.subtle", children: vm.alternates.map((o2, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { textStyle: "caption", color: "fg.muted", children: [
|
|
61317
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { as: "span", fontWeight: "semibold", color: "fg.default", children: o2.label }),
|
|
61318
|
+
" · ",
|
|
61319
|
+
o2.nTotal.toLocaleString(),
|
|
61320
|
+
" total (",
|
|
61321
|
+
o2.nTreated.toLocaleString(),
|
|
61322
|
+
" / ",
|
|
61323
|
+
o2.nControl.toLocaleString(),
|
|
61324
|
+
")"
|
|
61325
|
+
] }, i)) })
|
|
61326
|
+
] });
|
|
61327
|
+
}
|
|
59887
61328
|
function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
59888
61329
|
const [open, setOpen] = React.useState(null);
|
|
59889
61330
|
const replaceAt = (i, p2) => onChange2(population.map((f2, j2) => j2 === i ? p2 : f2));
|
|
@@ -59934,11 +61375,11 @@ function FilterRail({ fields, population, onChange: onChange2, chip, button }) {
|
|
|
59934
61375
|
)
|
|
59935
61376
|
] });
|
|
59936
61377
|
}
|
|
59937
|
-
function Step({ n: n2, title, children: children2 }) {
|
|
61378
|
+
function Step({ n: n2, title, help, children: children2 }) {
|
|
59938
61379
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { px: "4.5", py: "3", borderBottomWidth: "1px", borderColor: "border.subtle", children: [
|
|
59939
|
-
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2", mb: "2", children: [
|
|
59940
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", fontFamily: "mono", fontSize: "
|
|
59941
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.row", children: title })
|
|
61380
|
+
/* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", alignItems: "baseline", gap: "2.5", mb: "2", children: [
|
|
61381
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", fontFamily: "mono", fontSize: "11px", fontWeight: "bold", color: "brand.fg", flex: "0 0 auto", lineHeight: "1", children: n2 }),
|
|
61382
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "title.row", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: title }) : title })
|
|
59942
61383
|
] }),
|
|
59943
61384
|
children2
|
|
59944
61385
|
] });
|
|
@@ -59956,13 +61397,45 @@ function ColumnPick({ column: column2, kind, choices, onPick, badge, button }) {
|
|
|
59956
61397
|
/* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", display: "inline-flex", color: "fg.muted", fontSize: "10px", children: /* @__PURE__ */ jsxRuntime.jsx(FontAwesomeIcon, { icon: faChevronDown }) })
|
|
59957
61398
|
] }) });
|
|
59958
61399
|
}
|
|
59959
|
-
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "
|
|
61400
|
+
function SegmentSelect({ options: options2, active, onPick, fill, size: size3 = "xs" }) {
|
|
59960
61401
|
const s2 = react.useSlotRecipe({ key: "segmentGroup" })({ size: size3 });
|
|
59961
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
61402
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
61403
|
+
react.Box,
|
|
61404
|
+
{
|
|
61405
|
+
css: s2.root,
|
|
61406
|
+
...fill ? { width: "100%" } : {},
|
|
61407
|
+
role: "radiogroup",
|
|
61408
|
+
onKeyDown: (e3) => {
|
|
61409
|
+
var _a2;
|
|
61410
|
+
const d2 = e3.key === "ArrowRight" || e3.key === "ArrowDown" ? 1 : e3.key === "ArrowLeft" || e3.key === "ArrowUp" ? -1 : 0;
|
|
61411
|
+
if (!d2) return;
|
|
61412
|
+
e3.preventDefault();
|
|
61413
|
+
const next2 = (active + d2 + options2.length) % options2.length;
|
|
61414
|
+
onPick(next2);
|
|
61415
|
+
const btns = e3.currentTarget.querySelectorAll('[role="radio"]');
|
|
61416
|
+
(_a2 = btns[next2]) == null ? void 0 : _a2.focus();
|
|
61417
|
+
},
|
|
61418
|
+
children: options2.map((o2, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
61419
|
+
react.Box,
|
|
61420
|
+
{
|
|
61421
|
+
as: "button",
|
|
61422
|
+
role: "radio",
|
|
61423
|
+
"aria-checked": active === i,
|
|
61424
|
+
tabIndex: active === i ? 0 : -1,
|
|
61425
|
+
css: s2.item,
|
|
61426
|
+
...fill ? { flex: "1" } : {},
|
|
61427
|
+
...active === i ? { "data-state": "checked" } : {},
|
|
61428
|
+
onClick: () => onPick(i),
|
|
61429
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(react.Box, { as: "span", css: s2.itemText, children: o2 })
|
|
61430
|
+
},
|
|
61431
|
+
i
|
|
61432
|
+
))
|
|
61433
|
+
}
|
|
61434
|
+
);
|
|
59962
61435
|
}
|
|
59963
|
-
function Segmented({ label, left, right, active, onPick, last: last2 }) {
|
|
61436
|
+
function Segmented({ label, left, right, active, onPick, last: last2, help }) {
|
|
59964
61437
|
return /* @__PURE__ */ jsxRuntime.jsxs(react.Box, { display: "flex", flexDirection: "column", alignItems: "stretch", gap: "1.5", py: "2", borderBottomWidth: last2 ? "0" : "1px", borderColor: "border.subtle", children: [
|
|
59965
|
-
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: label }),
|
|
61438
|
+
/* @__PURE__ */ jsxRuntime.jsx(react.Text, { textStyle: "caption.eyebrow", textTransform: "none", letterSpacing: "normal", color: "fg.default", children: help ? /* @__PURE__ */ jsxRuntime.jsx(Help, { id: help, children: label }) : label }),
|
|
59966
61439
|
/* @__PURE__ */ jsxRuntime.jsx(SegmentSelect, { options: [left, right], active: active === "left" ? 0 : 1, onPick: (i) => onPick(i === 0 ? "left" : "right"), fill: true, size: "xs" })
|
|
59967
61440
|
] });
|
|
59968
61441
|
}
|
|
@@ -61664,10 +63137,10 @@ function formatPrimitive(type, value) {
|
|
|
61664
63137
|
case "Integer":
|
|
61665
63138
|
return String(value);
|
|
61666
63139
|
case "Float": {
|
|
61667
|
-
const
|
|
61668
|
-
if (Number.isNaN(
|
|
61669
|
-
if (!Number.isFinite(
|
|
61670
|
-
return String(
|
|
63140
|
+
const num2 = value;
|
|
63141
|
+
if (Number.isNaN(num2)) return "NaN";
|
|
63142
|
+
if (!Number.isFinite(num2)) return num2 > 0 ? "Infinity" : "-Infinity";
|
|
63143
|
+
return String(num2);
|
|
61671
63144
|
}
|
|
61672
63145
|
case "String":
|
|
61673
63146
|
return `"${value}"`;
|
|
@@ -62105,7 +63578,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
62105
63578
|
const manifest = React.useMemo(() => {
|
|
62106
63579
|
if (!details || !isUI) return null;
|
|
62107
63580
|
const meta3 = getTaskMetadata(details);
|
|
62108
|
-
return meta3 ? internal.decodeManifest(meta3) : { paths: [], functions: [] };
|
|
63581
|
+
return meta3 ? internal.decodeManifest(meta3) : { paths: [], functions: [], records: [] };
|
|
62109
63582
|
}, [details, isUI]);
|
|
62110
63583
|
const outputPath = details ? treePathToString$1(details.output) : null;
|
|
62111
63584
|
const preloads = React.useMemo(
|
|
@@ -62118,6 +63591,7 @@ const UITaskPreview = React.memo(function UITaskPreview2({
|
|
|
62118
63591
|
...eastUiComponents.StateImpl,
|
|
62119
63592
|
...createScopedBindPlatform(manifest),
|
|
62120
63593
|
...createScopedFuncPlatform(manifest.functions),
|
|
63594
|
+
...createScopedRecordPlatform(manifest.records),
|
|
62121
63595
|
...eastUiComponents.OverlayImpl
|
|
62122
63596
|
] : void 0,
|
|
62123
63597
|
[manifest]
|
|
@@ -62715,6 +64189,8 @@ exports.MemoryStagedAdapter = MemoryStagedAdapter;
|
|
|
62715
64189
|
exports.ReactiveDatasetCache = ReactiveDatasetCache;
|
|
62716
64190
|
exports.ReactiveDatasetLoader = ReactiveDatasetLoader;
|
|
62717
64191
|
exports.ReactiveDatasetProvider = ReactiveDatasetProvider;
|
|
64192
|
+
exports.RecordPlatform = RecordPlatform;
|
|
64193
|
+
exports.RecordRuntime = RecordRuntime;
|
|
62718
64194
|
exports.StagedStore = StagedStore;
|
|
62719
64195
|
exports.StatusDisplay = StatusDisplay;
|
|
62720
64196
|
exports.TaskPreview = TaskPreview;
|
|
@@ -62726,17 +64202,22 @@ exports.clearFunctionApi = clearFunctionApi;
|
|
|
62726
64202
|
exports.clearPendingWrites = clearPendingWrites;
|
|
62727
64203
|
exports.clearReactiveDatasetCache = clearReactiveDatasetCache;
|
|
62728
64204
|
exports.clearReactiveDatasetListCache = clearReactiveDatasetListCache;
|
|
64205
|
+
exports.clearRecordApi = clearRecordApi;
|
|
62729
64206
|
exports.clearStagedStoreSingleton = clearStagedStoreSingleton;
|
|
62730
64207
|
exports.createDefaultDatasetApi = createDefaultDatasetApi;
|
|
62731
64208
|
exports.createDefaultFunctionApi = createDefaultFunctionApi;
|
|
64209
|
+
exports.createDefaultRecordApi = createDefaultRecordApi;
|
|
62732
64210
|
exports.createInMemoryFunctionApi = createInMemoryFunctionApi;
|
|
64211
|
+
exports.createInMemoryRecordApi = createInMemoryRecordApi;
|
|
62733
64212
|
exports.createReactiveDatasetCache = createReactiveDatasetCache;
|
|
62734
64213
|
exports.createScopedBindPlatform = createScopedBindPlatform;
|
|
62735
64214
|
exports.createScopedFuncPlatform = createScopedFuncPlatform;
|
|
64215
|
+
exports.createScopedRecordPlatform = createScopedRecordPlatform;
|
|
62736
64216
|
exports.datasetCacheKey = datasetCacheKey;
|
|
62737
64217
|
exports.datasetPathToString = datasetPathToString;
|
|
62738
64218
|
exports.defaultBindRuntime = defaultBindRuntime;
|
|
62739
64219
|
exports.defaultFuncRuntime = defaultFuncRuntime;
|
|
64220
|
+
exports.defaultRecordRuntime = defaultRecordRuntime;
|
|
62740
64221
|
exports.disableBindingTracking = disableBindingTracking;
|
|
62741
64222
|
exports.enableBindingTracking = enableBindingTracking;
|
|
62742
64223
|
exports.formatApiError = formatApiError;
|
|
@@ -62749,12 +64230,14 @@ exports.getTaskKind = getTaskKind;
|
|
|
62749
64230
|
exports.getTaskMetadata = getTaskMetadata;
|
|
62750
64231
|
exports.initializeFunctionApi = initializeFunctionApi;
|
|
62751
64232
|
exports.initializeReactiveDatasetCache = initializeReactiveDatasetCache;
|
|
64233
|
+
exports.initializeRecordApi = initializeRecordApi;
|
|
62752
64234
|
exports.initializeStagedStore = initializeStagedStore;
|
|
62753
64235
|
exports.isBindingTracking = isBindingTracking;
|
|
62754
64236
|
exports.onWriteError = onWriteError;
|
|
62755
64237
|
exports.preloadReactiveDatasetList = preloadReactiveDatasetList;
|
|
62756
64238
|
exports.realClock = realClock;
|
|
62757
|
-
exports.
|
|
64239
|
+
exports.recordChannelKey = recordChannelKey;
|
|
64240
|
+
exports.signatureOfFuncHandleType = signatureOfFuncHandleType;
|
|
62758
64241
|
exports.trackDatasetPath = trackDatasetPath;
|
|
62759
64242
|
exports.useDataflowCancel = useDataflowCancel;
|
|
62760
64243
|
exports.useDataflowExecute = useDataflowExecute;
|