@elaraai/e3-ui-components 1.0.67 → 1.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -18,21 +18,22 @@ var __privateWrapper = (obj, member, setter, getter) => ({
18
18
  }
19
19
  });
20
20
  var _focused, _cleanup, _setup, _a, _provider, _providerCalled, _b, _online, _cleanup2, _setup2, _c, _gcTimeout, _d, _initialState, _revertState, _cache, _client, _retryer, _defaultOptions, _abortSignalConsumed, _Query_instances, isInitialPausedFetch_fn, dispatch_fn, _e, _client2, _observers, _mutationCache, _retryer2, _Mutation_instances, dispatch_fn2, _f, _mutations, _scopes, _mutationId, _g, _queries, _h, _queryCache, _mutationCache2, _defaultOptions2, _queryDefaults, _mutationDefaults, _mountCount, _unsubscribeFocus, _unsubscribeOnline, _i, _j;
21
- import { variant, SortedMap, compareFor, EastError, fromEastTypeValue, East, none as none$1, some, OptionType, BooleanType, NullType, ConflictError, equalFor, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, StructType, StringType, walkPatch, pathToString, pathDisplay, printFor, isVariant, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, isEastSet, isEastDict, ArrayType, parseFor } from "@elaraai/east";
21
+ import { variant, SortedMap, compareFor, EastError, fromEastTypeValue, East, none as none$1, some, OptionType, BooleanType, NullType, ConflictError, equalFor, toEastTypeValue, PatchType, diffFor, applyFor, encodeBeast2For, decodeBeast2For, BlobType, IntegerType, StructType, StringType, walkPatch, pathToString, pathDisplay, printFor, isVariant, prunePatchFor, detectConflictsFor, mergeWithResolutionsFor, validatePatchFor, isEastSet, isEastDict, ArrayType, parseFor } from "@elaraai/east";
22
22
  import { EastTypeType } from "@elaraai/east/internal";
23
- import { DataBindModeType, DataBindPrimitives, bindPlatformFn, FuncBindPrimitives, FuncErrorType, FuncStatusType, funcBindPlatformFn, RecordBindPrimitives, recordBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionBindPrimitives, DecisionConstraintType, DiffBindingType, CommitStateType, VerdictType, AnswerType, judgementInputType, DecisionType, DecisionQueue, DecisionJournal, decodeManifest } from "@elaraai/e3-ui/internal";
23
+ import { DataBindModeType, DataBindPrimitives, bindPlatformFn, DataPagedPrimitives, bindPagedPlatformFn, FuncBindPrimitives, FuncErrorType, FuncStatusType, funcBindPlatformFn, RecordBindPrimitives, recordBindPlatformFn, Diff, Ontology, FuncBindHandleType, Experiment, decisionBindPlatformFn, DecisionBindPrimitives, DecisionConstraintType, DiffBindingType, CommitStateType, VerdictType, AnswerType, judgementInputType, DecisionType, DecisionQueue, DecisionJournal, decodeManifest } from "@elaraai/e3-ui/internal";
24
24
  import { registerReactiveTracker, registerPlatformImplementation, StateRuntime, getRegisteredPlatformImplementations, boundSliceConfig } from "@elaraai/east-ui-components/platform";
25
25
  import { TreePathType, DatasetStatusType } from "@elaraai/e3-types";
26
- import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, ApiError, workspaceFunctionCall, workspaceFunctionList, workspaceRecordHistory, workspaceRecordMutate, workspaceRecordDescribe, repoCreate, repoGc, repoGcStart, repoGcStatus, repoList, repoRemove, repoStatus, packageExport, packageGet, packageImport, packageList, packageRemove, workspaceCreate, workspaceDeploy, workspaceExport, workspaceGet, workspaceList, workspaceRemove, datasetGetStatus, datasetListRecursive, datasetListRecursivePaths, datasetListWithStatus, taskExecutionList, taskGet, taskList, dataflowCancel, dataflowExecute, dataflowExecutePoll, dataflowGraph, taskLogs, datasetGetPage, datasetFindKey } from "@elaraai/e3-api-client";
26
+ import { workspaceStatus, datasetListAt, datasetList, dataflowExecuteLaunch, datasetSet, datasetGet, ApiError, datasetFindKey, datasetGetPage, workspaceFunctionCall, workspaceFunctionList, workspaceRecordHistory, workspaceRecordMutate, workspaceRecordDescribe, repoCreate, repoGc, repoGcStart, repoGcStatus, repoList, repoRemove, repoStatus, packageExport, packageGet, packageImport, packageList, packageRemove, workspaceCreate, workspaceDeploy, workspaceExport, workspaceGet, workspaceList, workspaceRemove, datasetGetStatus, datasetListRecursive, datasetListRecursivePaths, datasetListWithStatus, taskExecutionList, taskGet, taskList, dataflowCancel, dataflowExecute, dataflowExecutePoll, dataflowGraph, taskLogs } from "@elaraai/e3-api-client";
27
+ import { SeekQueryType, SeekRangeType, ValueTree } from "@elaraai/east-ui";
27
28
  import { jsx as jsx$1, Fragment, jsxs } from "react/jsx-runtime";
28
29
  import * as React from "react";
29
30
  import React__default, { useId, useMemo, useContext, createContext, useRef, useEffect, useState, useCallback, useSyncExternalStore, memo, forwardRef, useLayoutEffect, Fragment as Fragment$1, Component } from "react";
30
31
  import { Box, Flex, Text, HStack, Input, useSlotRecipe, useRecipe, VStack, useToken, Drawer, Portal, Field, NativeSelect, Textarea, chakra, Tooltip, SegmentGroup, Menu, HoverCard, useChakraContext, Spinner, Code, Clipboard, IconButton, Button, Tabs, Badge, useTabs } from "@chakra-ui/react";
31
- import { implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, useContainerBelow, usePersistedState, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, LoadingIcon, EastChakraCombobox, EastChakraValueTree, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
32
+ import { implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, useContainerBelow, usePersistedState, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, LoadingIcon, DatasetKeySearch, EastChakraValueTree, keyRangePredicates, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
33
+ import { DatasetKeySearch as DatasetKeySearch2 } from "@elaraai/east-ui-components";
32
34
  import { createPortal } from "react-dom";
33
35
  import { sliceMatches } from "@elaraai/east-ui/internal";
34
36
  import { QueryClient as QueryClient$1, QueryClientProvider, useMutation, useQuery, keepPreviousData, useQueryClient } from "@tanstack/react-query";
35
- import { ValueTree } from "@elaraai/east-ui";
36
37
  import { useVirtualizer } from "@tanstack/react-virtual";
37
38
  var Subscribable = class {
38
39
  constructor() {
@@ -3593,6 +3594,14 @@ class TrackedChannelStore {
3593
3594
  isTracking() {
3594
3595
  return this.trackingContext !== null;
3595
3596
  }
3597
+ /** Whether the CURRENT evaluation has already read this channel. A cache
3598
+ * that evicts by least-recently-read must never drop one of these: a read
3599
+ * pass longer than the cache would otherwise evict its own head, and the
3600
+ * next pass would find it gone (#581). */
3601
+ isTracked(key) {
3602
+ var _a2;
3603
+ return ((_a2 = this.trackingContext) == null ? void 0 : _a2.has(key)) ?? false;
3604
+ }
3596
3605
  /** Record a channel-key dependency for the current render (no-op when not
3597
3606
  * tracking). */
3598
3607
  track(key) {
@@ -3674,6 +3683,397 @@ class TrackedChannelStore {
3674
3683
  this.keyVersions.clear();
3675
3684
  }
3676
3685
  }
3686
+ function createDefaultPagedApi(apiUrl, repo, getToken) {
3687
+ const opts = () => ({ token: getToken() });
3688
+ return {
3689
+ async getPage(workspace, path2, window3) {
3690
+ return datasetGetPage(apiUrl, repo, workspace, path2, window3, opts());
3691
+ },
3692
+ async findKey(workspace, path2, query) {
3693
+ return datasetFindKey(apiUrl, repo, workspace, path2, query, opts());
3694
+ }
3695
+ };
3696
+ }
3697
+ const RETRY_AFTER_MS = 2e3;
3698
+ const MAX_RETAINED_WINDOWS = 24;
3699
+ const PERMANENT_PAGE_ERRORS = /* @__PURE__ */ new Set(["dataset_not_pageable"]);
3700
+ function isPermanentPageError(err) {
3701
+ const code = err == null ? void 0 : err.code;
3702
+ return typeof code === "string" && PERMANENT_PAGE_ERRORS.has(code);
3703
+ }
3704
+ function keyTypeOf(sourceType) {
3705
+ if (sourceType.type === "Dict") return sourceType.value.key;
3706
+ if (sourceType.type === "Set") return sourceType.value;
3707
+ return null;
3708
+ }
3709
+ function buildSeek(sourceType, T2, pathExpr, platform) {
3710
+ if (keyTypeOf(sourceType) === null) return none$1;
3711
+ const { seek } = DataPagedPrimitives;
3712
+ return some(East.compile(
3713
+ East.function([SeekQueryType], OptionType(SeekRangeType), ($3, query) => {
3714
+ $3.return(seek([T2], pathExpr, query));
3715
+ }),
3716
+ platform
3717
+ ));
3718
+ }
3719
+ function pagedWindowKey(workspace, path2, offset, limit2) {
3720
+ return `paged:${workspace}:${datasetPathToString(path2)}#${offset}+${limit2}`;
3721
+ }
3722
+ function pagedTotalKey(workspace, path2) {
3723
+ return `paged:${workspace}:${datasetPathToString(path2)}#total`;
3724
+ }
3725
+ function pagedSeekKey(workspace, path2, query) {
3726
+ const q = "key" in query ? `k=${query.key}` : "fields" in query ? `f=${query.fields.join("\0")}|p=${query.prefix ?? ""}` : `p=${query.prefix}`;
3727
+ return `paged:${workspace}:${datasetPathToString(path2)}#seek:${q}`;
3728
+ }
3729
+ function toFindQuery(query) {
3730
+ const q = query;
3731
+ if (q.type === "key") return { key: q.value };
3732
+ if (q.type === "prefix") return { prefix: q.value };
3733
+ const f2 = q.value;
3734
+ const fields = [...f2.values];
3735
+ return f2.prefix.type === "some" ? { fields, prefix: f2.prefix.value } : { fields };
3736
+ }
3737
+ class PagedRuntime extends TrackedChannelStore {
3738
+ constructor() {
3739
+ super(...arguments);
3740
+ __publicField(this, "api", null);
3741
+ __publicField(this, "workspace", null);
3742
+ // Compiled-handle cache (issue #106 perf): buildHandle compiles 2
3743
+ // East.functions per bind, and binds re-run every reactive frame. The
3744
+ // method IR is a pure function of (sourceType, sourcePath), and the methods
3745
+ // resolve api/workspace LIVE, so a cached handle still re-binds.
3746
+ //
3747
+ // Keyed by TYPE first, then path — never by path alone. The window decoder
3748
+ // is baked from `sourceType`, so a path re-bound at a different type (a
3749
+ // redeployed dataset whose schema changed, inside a live session) must not
3750
+ // hand back the handle compiled against the old type. Structural key for
3751
+ // the same reason `bind-runtime` uses one: every bind builds a fresh
3752
+ // `EastTypeValue` from IR, so a by-identity cache would miss every render.
3753
+ __publicField(this, "handleCache", new SortedMap(
3754
+ void 0,
3755
+ compareFor(EastTypeType)
3756
+ ));
3757
+ /** Loaded window keys in least-recently-READ order (a Map preserves
3758
+ * insertion order; a read re-inserts). Bounds the decoded-window cache. */
3759
+ __publicField(this, "loadedWindows", /* @__PURE__ */ new Map());
3760
+ }
3761
+ /** Monotonic clock seam so tests can drive the retry gate. */
3762
+ now() {
3763
+ return Date.now();
3764
+ }
3765
+ createEntry() {
3766
+ return { status: "idle", launchSeq: 0 };
3767
+ }
3768
+ // ----- wiring ----------------------------------------------------------
3769
+ /** Install the API adapter + workspace — called by the React provider
3770
+ * (or a test/showcase harness) before any handle is used. */
3771
+ initialize(api3, workspace) {
3772
+ this.api = api3;
3773
+ this.workspace = workspace;
3774
+ }
3775
+ /** Tear down the adapter and all window state. */
3776
+ clear() {
3777
+ this.api = null;
3778
+ this.workspace = null;
3779
+ this.clearChannels();
3780
+ this.handleCache.clear();
3781
+ this.loadedWindows.clear();
3782
+ }
3783
+ resolveWorkspace() {
3784
+ if (!this.workspace) {
3785
+ throw new Error(
3786
+ "Data.bindPaged: no paging service — a paged source is served BY THE SERVER (datasetGetPage), so it resolves only inside a live workspace. Render this component against a deployed workspace, or bind the whole value with Data.bind."
3787
+ );
3788
+ }
3789
+ return this.workspace;
3790
+ }
3791
+ // ----- window loading --------------------------------------------------
3792
+ /** Note a window as most-recently-read, and drop the coldest decoded
3793
+ * windows once the cache exceeds its cap. An evicted window returns to
3794
+ * `idle`, so the next read refetches it rather than reading a hole. */
3795
+ touchWindow(key) {
3796
+ this.loadedWindows.delete(key);
3797
+ this.loadedWindows.set(key, true);
3798
+ if (this.loadedWindows.size <= MAX_RETAINED_WINDOWS) return;
3799
+ for (const candidate of [...this.loadedWindows.keys()]) {
3800
+ if (this.loadedWindows.size <= MAX_RETAINED_WINDOWS) break;
3801
+ if (this.isTracked(candidate)) continue;
3802
+ const entry = this.entries.get(candidate);
3803
+ if (entry === void 0 || entry.status !== "loaded") continue;
3804
+ this.loadedWindows.delete(candidate);
3805
+ entry.status = "idle";
3806
+ delete entry.window;
3807
+ }
3808
+ }
3809
+ /**
3810
+ * Start the fetch for a window if it isn't loaded or already in flight.
3811
+ *
3812
+ * Deliberately does NOT notify on launch — the read that triggers it runs
3813
+ * inside a render pass, and notifying there would re-enter the renderer.
3814
+ * Only the settle notifies.
3815
+ */
3816
+ ensureWindow(sourceType, workspace, path2, offset, limit2) {
3817
+ const key = pagedWindowKey(workspace, path2, offset, limit2);
3818
+ const entry = this.entry(key);
3819
+ if (entry.status === "running" || entry.status === "loaded") return;
3820
+ if (entry.status === "failed") {
3821
+ if (entry.permanent) return;
3822
+ const since = this.now() - (entry.failedAtMs ?? 0);
3823
+ if (since < RETRY_AFTER_MS) return;
3824
+ }
3825
+ entry.status = "running";
3826
+ entry.launchSeq += 1;
3827
+ const mySeq = entry.launchSeq;
3828
+ const api3 = this.api;
3829
+ void (async () => {
3830
+ const settle = (mutate) => {
3831
+ const current = this.entries.get(key);
3832
+ if (!current || current.launchSeq !== mySeq) return;
3833
+ mutate(current);
3834
+ this.notify(key);
3835
+ };
3836
+ if (!api3) {
3837
+ settle((e3) => {
3838
+ e3.status = "failed";
3839
+ e3.failedAtMs = this.now();
3840
+ });
3841
+ console.error("Data.bindPaged: no PagedApi installed");
3842
+ return;
3843
+ }
3844
+ let page;
3845
+ try {
3846
+ page = await api3.getPage(workspace, path2, { offset, limit: limit2 });
3847
+ } catch (err) {
3848
+ const permanent = isPermanentPageError(err);
3849
+ settle((e3) => {
3850
+ e3.status = "failed";
3851
+ e3.failedAtMs = this.now();
3852
+ e3.permanent = permanent;
3853
+ });
3854
+ console.error(
3855
+ permanent ? `Data.bindPaged: ${datasetPathToString(path2)} is not a pageable dataset — bind a collection (Array / Set / Dict), or use Data.bind for a whole value:` : `Data.bindPaged: fetch failed for ${key}:`,
3856
+ err
3857
+ );
3858
+ return;
3859
+ }
3860
+ let decoded;
3861
+ try {
3862
+ decoded = decodeBeast2For(sourceType)(page.data);
3863
+ } catch (err) {
3864
+ settle((e3) => {
3865
+ e3.status = "failed";
3866
+ e3.failedAtMs = this.now();
3867
+ });
3868
+ console.error(`Data.bindPaged: decode failed for ${key}:`, err);
3869
+ return;
3870
+ }
3871
+ settle((e3) => {
3872
+ e3.status = "loaded";
3873
+ e3.window = decoded;
3874
+ e3.total = page.totalElements;
3875
+ });
3876
+ this.touchWindow(key);
3877
+ const totalKey = pagedTotalKey(workspace, path2);
3878
+ const totalEntry = this.entry(totalKey);
3879
+ if (totalEntry.total !== page.totalElements) {
3880
+ totalEntry.total = page.totalElements;
3881
+ totalEntry.status = "loaded";
3882
+ this.notify(totalKey);
3883
+ }
3884
+ })();
3885
+ }
3886
+ /**
3887
+ * Start the fence search for one key query if it isn't answered or already
3888
+ * in flight — the seek sibling of {@link ensureWindow}, with the same
3889
+ * launch-does-not-notify rule (the read runs inside a render pass).
3890
+ */
3891
+ ensureSeek(workspace, path2, query, key) {
3892
+ const entry = this.entry(key);
3893
+ if (entry.status === "running" || entry.status === "loaded") return;
3894
+ if (entry.status === "failed") {
3895
+ if (entry.permanent) return;
3896
+ const since = this.now() - (entry.failedAtMs ?? 0);
3897
+ if (since < RETRY_AFTER_MS) return;
3898
+ }
3899
+ entry.status = "running";
3900
+ entry.launchSeq += 1;
3901
+ const mySeq = entry.launchSeq;
3902
+ const api3 = this.api;
3903
+ void (async () => {
3904
+ const settle = (mutate) => {
3905
+ const current = this.entries.get(key);
3906
+ if (!current || current.launchSeq !== mySeq) return;
3907
+ mutate(current);
3908
+ this.notify(key);
3909
+ };
3910
+ if (!api3) {
3911
+ settle((e3) => {
3912
+ e3.status = "failed";
3913
+ e3.failedAtMs = this.now();
3914
+ });
3915
+ console.error("Data.bindPaged: no PagedApi installed");
3916
+ return;
3917
+ }
3918
+ try {
3919
+ const range = await api3.findKey(workspace, path2, query);
3920
+ settle((e3) => {
3921
+ e3.status = "loaded";
3922
+ e3.range = range;
3923
+ });
3924
+ } catch (err) {
3925
+ const permanent = isPermanentPageError(err);
3926
+ settle((e3) => {
3927
+ e3.status = "failed";
3928
+ e3.failedAtMs = this.now();
3929
+ e3.permanent = permanent;
3930
+ });
3931
+ console.error(`Data.bindPaged: key search failed for ${key}:`, err);
3932
+ }
3933
+ })();
3934
+ }
3935
+ /**
3936
+ * The low-level primitives backing handle methods, bound to THIS runtime.
3937
+ * Registered globally (extension registry) and included by the scoped
3938
+ * platform (e3 `ui()` tasks) so a decoded handle re-binds to whatever
3939
+ * runtime resolves the primitives on the decode side.
3940
+ */
3941
+ buildPrimitives() {
3942
+ return [
3943
+ DataPagedPrimitives.page.implement((sourceType) => (pathArg, offsetArg, limitArg) => {
3944
+ const workspace = this.resolveWorkspace();
3945
+ const path2 = pathArg;
3946
+ const offset = Number(offsetArg);
3947
+ const limit2 = Number(limitArg);
3948
+ const key = pagedWindowKey(workspace, path2, offset, limit2);
3949
+ this.track(key);
3950
+ this.ensureWindow(sourceType, workspace, path2, offset, limit2);
3951
+ const entry = this.entry(key);
3952
+ if (entry.status === "loaded" && entry.window !== void 0) {
3953
+ this.touchWindow(key);
3954
+ return variant("some", entry.window);
3955
+ }
3956
+ return variant("none", null);
3957
+ }),
3958
+ DataPagedPrimitives.total.implement((_sourceType) => (pathArg) => {
3959
+ const workspace = this.resolveWorkspace();
3960
+ const key = pagedTotalKey(workspace, pathArg);
3961
+ this.track(key);
3962
+ const entry = this.entry(key);
3963
+ return entry.total !== void 0 ? variant("some", BigInt(entry.total)) : variant("none", null);
3964
+ }),
3965
+ DataPagedPrimitives.seek.implement((_sourceType) => (pathArg, queryArg) => {
3966
+ const workspace = this.resolveWorkspace();
3967
+ const path2 = pathArg;
3968
+ const query = toFindQuery(queryArg);
3969
+ const key = pagedSeekKey(workspace, path2, query);
3970
+ this.track(key);
3971
+ this.ensureSeek(workspace, path2, query, key);
3972
+ const entry = this.entry(key);
3973
+ if (entry.status !== "loaded" || entry.range === void 0) return none$1;
3974
+ const r2 = entry.range;
3975
+ return some({ found: r2.found, row: BigInt(r2.row), count: BigInt(r2.count) });
3976
+ })
3977
+ ];
3978
+ }
3979
+ /**
3980
+ * Build the handle value for one `Data.bindPaged` evaluation. Both methods
3981
+ * are thin IR-bearing `East.function`s over {@link buildPrimitives},
3982
+ * capturing only the plain-data source path (the value type rides as a
3983
+ * type-arg) — so the handle is ordinary serializable East data (issue #106).
3984
+ */
3985
+ buildHandle(sourceType, path2) {
3986
+ const pathKey = datasetPathToString(path2);
3987
+ let byPath = this.handleCache.get(sourceType);
3988
+ if (byPath) {
3989
+ const hit = byPath.get(pathKey);
3990
+ if (hit) return hit;
3991
+ } else {
3992
+ byPath = /* @__PURE__ */ new Map();
3993
+ this.handleCache.set(sourceType, byPath);
3994
+ }
3995
+ const T2 = fromEastTypeValue(sourceType);
3996
+ const pathExpr = East.value(path2, TreePathType);
3997
+ const platform = this.buildPrimitives();
3998
+ const { page, total } = DataPagedPrimitives;
3999
+ const handle = {
4000
+ // The comparable identity east-ui's `PagedSourceType` requires:
4001
+ // East compares every function as EQUAL, so a struct of nothing but
4002
+ // closures is indistinguishable from any other and a memoized
4003
+ // component would never re-render on a source swap (#567 D4). The
4004
+ // dataset path is the natural identity — same path, same rows.
4005
+ id: pathKey,
4006
+ page: East.compile(
4007
+ East.function([IntegerType, IntegerType], OptionType(T2), ($3, offset, limit2) => {
4008
+ $3.return(page([T2], pathExpr, offset, limit2));
4009
+ }),
4010
+ platform
4011
+ ),
4012
+ total: East.compile(
4013
+ East.function([], OptionType(IntegerType), ($3) => {
4014
+ $3.return(total([T2], pathExpr));
4015
+ }),
4016
+ platform
4017
+ ),
4018
+ // Key search is a KEY-ORDER capability: Set/Dict element windows
4019
+ // ride the canonical East key order, so a key locates in O(log
4020
+ // segments) against the stored fences; an Array's stream order has
4021
+ // nothing to search. Resolved at bind time from the dataset's own
4022
+ // type, so a component renders the affordance only when it works.
4023
+ seek: buildSeek(sourceType, T2, pathExpr, platform)
4024
+ };
4025
+ byPath.set(pathKey, handle);
4026
+ return handle;
4027
+ }
4028
+ // ----- platform building -------------------------------------------------
4029
+ /** Build a `Data.bindPaged` PlatformFunction bound to this runtime. Pass
4030
+ * `allowed=null` for an unscoped impl; pass a Set of path strings for
4031
+ * manifest scoping. */
4032
+ buildPlatform(allowed) {
4033
+ return bindPagedPlatformFn.implement(
4034
+ (sourceType) => (pathArg) => {
4035
+ const path2 = pathArg;
4036
+ if (allowed) {
4037
+ const pathStr = datasetPathToString(path2);
4038
+ if (!allowed.has(pathStr)) {
4039
+ throw new Error(
4040
+ `Data.bindPaged: source path "${pathStr}" not declared in manifest — bind it in the task body so derivation records it`
4041
+ );
4042
+ }
4043
+ }
4044
+ return this.buildHandle(sourceType, path2);
4045
+ }
4046
+ );
4047
+ }
4048
+ }
4049
+ const defaultPagedRuntime = new PagedRuntime();
4050
+ function initializePagedApi(api3, workspace) {
4051
+ defaultPagedRuntime.initialize(api3, workspace);
4052
+ }
4053
+ function clearPagedApi() {
4054
+ defaultPagedRuntime.clear();
4055
+ }
4056
+ const PagedPlatform = [
4057
+ defaultPagedRuntime.buildPlatform(null),
4058
+ ...defaultPagedRuntime.buildPrimitives()
4059
+ ];
4060
+ function createScopedPagedPlatform(pages) {
4061
+ const allowed = new Set(pages.map((p2) => datasetPathToString(p2)));
4062
+ return [
4063
+ defaultPagedRuntime.buildPlatform(allowed),
4064
+ ...defaultPagedRuntime.buildPrimitives()
4065
+ ];
4066
+ }
4067
+ registerReactiveTracker({
4068
+ id: "data-bind-paged",
4069
+ enableTracking: () => defaultPagedRuntime.enableTracking(),
4070
+ disableTracking: () => defaultPagedRuntime.disableTracking(),
4071
+ getStore: () => ({
4072
+ subscribe: (key, cb) => defaultPagedRuntime.subscribe(key, cb),
4073
+ getKeyVersion: (key) => defaultPagedRuntime.getKeyVersion(key)
4074
+ })
4075
+ });
4076
+ registerPlatformImplementation(PagedPlatform);
3677
4077
  function createDefaultFunctionApi(apiUrl, repo, getToken) {
3678
4078
  const opts = () => ({ token: getToken() });
3679
4079
  return {
@@ -8299,6 +8699,14 @@ function ReactiveDatasetProvider({
8299
8699
  );
8300
8700
  }
8301
8701
  }, [e3.apiUrl, e3.repo, e3.workspace]);
8702
+ useMemo(() => {
8703
+ if (e3.workspace !== void 0) {
8704
+ initializePagedApi(
8705
+ createDefaultPagedApi(e3.apiUrl, e3.repo ?? "default", () => tokenRef.current),
8706
+ e3.workspace
8707
+ );
8708
+ }
8709
+ }, [e3.apiUrl, e3.repo, e3.workspace]);
8302
8710
  useMemo(() => {
8303
8711
  if (e3.workspace !== void 0) {
8304
8712
  initializeRecordApi(
@@ -8313,6 +8721,7 @@ function ReactiveDatasetProvider({
8313
8721
  const ws = cache3.getConfig().workspace;
8314
8722
  clearReactiveDatasetCache();
8315
8723
  clearFunctionApi();
8724
+ clearPagedApi();
8316
8725
  clearRecordApi();
8317
8726
  cache3.destroy();
8318
8727
  if (ws) clearBindingRegistry(ws);
@@ -9272,6 +9681,7 @@ const EastChakraDiff = memo(function EastChakraDiff2({ value }) {
9272
9681
  const es = eyebrow({});
9273
9682
  const commit = useSlotRecipe({ key: "commitBar" });
9274
9683
  const cs = commit({});
9684
+ const btn = useRecipe({ key: "button" });
9275
9685
  const allDatasetPaths = useMemo(() => {
9276
9686
  const paths = [];
9277
9687
  for (const b2 of irBindings) {
@@ -9573,12 +9983,12 @@ const EastChakraDiff = memo(function EastChakraDiff2({ value }) {
9573
9983
  " · clean merge"
9574
9984
  ] }) }),
9575
9985
  /* @__PURE__ */ jsxs(Box, { css: cs.btnRow, children: [
9576
- /* @__PURE__ */ jsx$1(Box, { as: "button", css: cs.btnDanger, onClick: onDiscardAll, children: "Discard all" }),
9986
+ /* @__PURE__ */ jsx$1(Box, { as: "button", css: btn({ variant: "danger", size: "md" }), onClick: onDiscardAll, children: "Discard all" }),
9577
9987
  /* @__PURE__ */ jsx$1(
9578
9988
  Box,
9579
9989
  {
9580
9990
  as: "button",
9581
- css: cs.btnPrimary,
9991
+ css: btn({ variant: "solid", size: "md" }),
9582
9992
  "aria-disabled": applyDisabled,
9583
9993
  onClick: applyDisabled ? void 0 : onApply,
9584
9994
  title: hasStaleLeaves ? "Discard or re-edit the stale changes before applying." : void 0,
@@ -63771,6 +64181,7 @@ const defaultPlatformImplementations = [
63771
64181
  ...ShareImpl,
63772
64182
  ...DecisionBindPlatform,
63773
64183
  ...BindPlatform,
64184
+ ...PagedPlatform,
63774
64185
  ...FuncPlatform,
63775
64186
  ...RecordPlatform
63776
64187
  ];
@@ -63988,271 +64399,6 @@ class ErrorBoundary extends Component {
63988
64399
  return this.props.children;
63989
64400
  }
63990
64401
  }
63991
- const FIND_DEBOUNCE_MS = 250;
63992
- const POPUP_LIMIT = 20;
63993
- function structFields(keyType) {
63994
- if (keyType.type !== "Struct") return null;
63995
- const fields = keyType.value;
63996
- return fields.length > 0 ? fields : null;
63997
- }
63998
- function splitTopLevel(text) {
63999
- const parts = [];
64000
- let current = "";
64001
- let inString = false;
64002
- let depth = 0;
64003
- for (let i = 0; i < text.length; i++) {
64004
- const ch = text[i];
64005
- if (inString) {
64006
- current += ch;
64007
- if (ch === "\\" && i + 1 < text.length) {
64008
- current += text[i + 1];
64009
- i++;
64010
- } else if (ch === '"') {
64011
- inString = false;
64012
- }
64013
- continue;
64014
- }
64015
- if (ch === '"') {
64016
- inString = true;
64017
- current += ch;
64018
- } else if (ch === "(" || ch === "[" || ch === "{") {
64019
- depth++;
64020
- current += ch;
64021
- } else if (ch === ")" || ch === "]" || ch === "}") {
64022
- depth--;
64023
- current += ch;
64024
- } else if (ch === "," && depth === 0) {
64025
- parts.push(current);
64026
- current = "";
64027
- } else {
64028
- current += ch;
64029
- }
64030
- }
64031
- parts.push(current);
64032
- return parts;
64033
- }
64034
- function keySignature(keyType) {
64035
- const fields = structFields(keyType);
64036
- if (fields === null) return keyType.type;
64037
- return `(${fields.map((f2) => `${f2.name}: ${f2.type.type}`).join(", ")})`;
64038
- }
64039
- function parseKeyInput(keyType, text) {
64040
- const fields = structFields(keyType);
64041
- if (fields === null) {
64042
- if (keyType.type === "String") return { kind: "query", query: { prefix: text } };
64043
- const parsed = parseFor(keyType)(text);
64044
- if (!parsed.success) return { kind: "hint", hint: `Key is ${keyType.type}` };
64045
- return { kind: "query", query: { key: printFor(keyType)(parsed.value) } };
64046
- }
64047
- if (text.trimStart().startsWith("(")) {
64048
- const parsed = parseFor(keyType)(text);
64049
- if (!parsed.success) return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64050
- return { kind: "query", query: { key: printFor(keyType)(parsed.value) } };
64051
- }
64052
- const segments = splitTopLevel(text);
64053
- if (segments.length > fields.length) {
64054
- return { kind: "hint", hint: `Key is ${keySignature(keyType)}` };
64055
- }
64056
- const exact = [];
64057
- for (let i = 0; i < segments.length; i++) {
64058
- const field = fields[i];
64059
- const raw = segments[i].trim();
64060
- const isLast = i === segments.length - 1;
64061
- if (isLast && raw === "" && i > 0) {
64062
- return { kind: "query", query: { fields: exact } };
64063
- }
64064
- if (field.type.type === "String" && !raw.startsWith('"')) {
64065
- if (isLast) {
64066
- return exact.length === 0 ? { kind: "query", query: { prefix: raw } } : { kind: "query", query: { fields: exact, prefix: raw } };
64067
- }
64068
- exact.push(printFor(StringType)(raw));
64069
- continue;
64070
- }
64071
- const parsed = parseFor(field.type)(raw);
64072
- if (!parsed.success) {
64073
- return { kind: "hint", hint: `${field.name} is ${field.type.type} — key is ${keySignature(keyType)}` };
64074
- }
64075
- exact.push(printFor(field.type)(parsed.value));
64076
- }
64077
- return { kind: "query", query: { fields: exact } };
64078
- }
64079
- const MATCH_NOTHING = { lower: () => false, upper: () => false };
64080
- function keyRangePredicates(keyType, query) {
64081
- if ("key" in query) return null;
64082
- if (keyType.type === "Struct") {
64083
- const meta3 = keyType.value;
64084
- const literals = "fields" in query ? query.fields : [];
64085
- if (literals.length > meta3.length) return MATCH_NOTHING;
64086
- const values = [];
64087
- for (let j2 = 0; j2 < literals.length; j2++) {
64088
- const parsed = parseFor(meta3[j2].type)(literals[j2]);
64089
- if (!parsed.success) return MATCH_NOTHING;
64090
- values.push(parsed.value);
64091
- }
64092
- const cmps = meta3.map((f2) => compareFor(f2.type));
64093
- const lead = (k2) => {
64094
- for (let j2 = 0; j2 < values.length; j2++) {
64095
- const c2 = cmps[j2](k2[meta3[j2].name], values[j2]);
64096
- if (c2 !== 0) return c2;
64097
- }
64098
- return 0;
64099
- };
64100
- const prefix22 = query.prefix;
64101
- if (prefix22 === void 0) {
64102
- return { lower: (k2) => lead(k2) >= 0, upper: (k2) => lead(k2) > 0 };
64103
- }
64104
- const prefixIdx = values.length;
64105
- if (prefixIdx >= meta3.length || meta3[prefixIdx].type.type !== "String") return MATCH_NOTHING;
64106
- const prefixName = meta3[prefixIdx].name;
64107
- const prefixCmp = cmps[prefixIdx];
64108
- return {
64109
- lower: (k2) => {
64110
- const c2 = lead(k2);
64111
- return c2 !== 0 ? c2 > 0 : prefixCmp(k2[prefixName], prefix22) >= 0;
64112
- },
64113
- upper: (k2) => {
64114
- const c2 = lead(k2);
64115
- if (c2 !== 0) return c2 > 0;
64116
- const field = k2[prefixName];
64117
- return prefixCmp(field, prefix22) > 0 && !field.startsWith(prefix22);
64118
- }
64119
- };
64120
- }
64121
- if (!("prefix" in query) || query.prefix === void 0 || keyType.type !== "String") return MATCH_NOTHING;
64122
- const prefix2 = query.prefix;
64123
- const cmp = compareFor(keyType);
64124
- return {
64125
- lower: (k2) => cmp(k2, prefix2) >= 0,
64126
- upper: (k2) => cmp(k2, prefix2) > 0 && !k2.startsWith(prefix2)
64127
- };
64128
- }
64129
- const DatasetKeySearch = memo(function DatasetKeySearch2({ keyType, onFind, onListRange, onJump, onClear }) {
64130
- const [range, setRange] = useState(null);
64131
- const [items, setItems] = useState([]);
64132
- const [activeIdx, setActiveIdx] = useState(-1);
64133
- const [hint, setHint] = useState(null);
64134
- const [resetSeq, setResetSeq] = useState(0);
64135
- const debounceRef = useRef(void 0);
64136
- const findSeqRef = useRef(0);
64137
- useEffect(() => () => clearTimeout(debounceRef.current), []);
64138
- const runFind = useCallback((query) => {
64139
- const seq = ++findSeqRef.current;
64140
- void (async () => {
64141
- try {
64142
- const result = await onFind(query);
64143
- if (seq !== findSeqRef.current) return;
64144
- setRange(result);
64145
- setActiveIdx(-1);
64146
- if (result.found) {
64147
- const labels = await onListRange(result.row, Math.min(POPUP_LIMIT, result.count));
64148
- if (seq !== findSeqRef.current) return;
64149
- setItems(labels.map((label, i) => ({ row: result.row + i, label })));
64150
- } else {
64151
- setItems([]);
64152
- }
64153
- } catch {
64154
- if (seq !== findSeqRef.current) return;
64155
- setRange(null);
64156
- setItems([]);
64157
- }
64158
- })();
64159
- }, [onFind, onListRange]);
64160
- const resetQueryState = useCallback(() => {
64161
- clearTimeout(debounceRef.current);
64162
- findSeqRef.current++;
64163
- setRange(null);
64164
- setItems([]);
64165
- setActiveIdx(-1);
64166
- setHint(null);
64167
- }, []);
64168
- const handleInput = useCallback((text) => {
64169
- clearTimeout(debounceRef.current);
64170
- if (text === "") {
64171
- resetQueryState();
64172
- onClear == null ? void 0 : onClear();
64173
- return;
64174
- }
64175
- const parsed = parseKeyInput(keyType, text);
64176
- if (parsed.kind === "hint") {
64177
- findSeqRef.current++;
64178
- setRange(null);
64179
- setItems([]);
64180
- setActiveIdx(-1);
64181
- setHint(parsed.hint);
64182
- return;
64183
- }
64184
- setHint(null);
64185
- debounceRef.current = setTimeout(() => runFind(parsed.query), FIND_DEBOUNCE_MS);
64186
- }, [keyType, runFind, resetQueryState, onClear]);
64187
- const clearSearch = useCallback(() => {
64188
- resetQueryState();
64189
- setResetSeq((s2) => s2 + 1);
64190
- onClear == null ? void 0 : onClear();
64191
- }, [resetQueryState, onClear]);
64192
- const commit = useCallback((row) => {
64193
- onJump(row);
64194
- if (range !== null) setActiveIdx(row - range.row);
64195
- }, [onJump, range]);
64196
- const step3 = useCallback((delta) => {
64197
- if (range === null || !range.found) return;
64198
- const next2 = Math.max(0, Math.min(range.count - 1, (activeIdx === -1 ? 0 : activeIdx) + delta));
64199
- commit(range.row + next2);
64200
- }, [range, activeIdx, commit]);
64201
- const onKeyDownCapture = useCallback((e3) => {
64202
- if (e3.key !== "Enter" || range === null || !range.found) return;
64203
- if (document.querySelector('[data-scope="combobox"][data-part="content"] [data-highlighted]') !== null) return;
64204
- commit(range.row + Math.max(0, activeIdx));
64205
- }, [range, activeIdx, commit]);
64206
- const placeholder = useMemo(() => {
64207
- const fields = structFields(keyType);
64208
- if (fields !== null) return `Search keys (${fields.map((f2) => f2.name).join(", ")})`;
64209
- return keyType.type === "String" ? "Search keys" : `Find key (${keyType.type})`;
64210
- }, [keyType]);
64211
- const payload = useMemo(() => ({
64212
- value: none$1,
64213
- items: items.map((it) => ({ value: String(it.row), label: it.label, disabled: none$1 })),
64214
- placeholder: some(placeholder),
64215
- multiple: none$1,
64216
- disabled: none$1,
64217
- allowCustomValue: some(true),
64218
- onChange: some((v3) => commit(Number(v3))),
64219
- onChangeMultiple: none$1,
64220
- onInputValueChange: some(handleInput),
64221
- onOpenChange: none$1,
64222
- style: some({ size: some(variant("xs", null)), color: none$1, background: none$1, borderColor: none$1 })
64223
- }), [items, placeholder, commit, handleInput]);
64224
- const status = hint !== null ? hint : range === null ? null : !range.found ? "No matches" : activeIdx === -1 ? `${range.count.toLocaleString()} ${range.count === 1 ? "match" : "matches"}` : `${(activeIdx + 1).toLocaleString()} of ${range.count.toLocaleString()}`;
64225
- const canStep = range !== null && range.found && range.count > 1;
64226
- return /* @__PURE__ */ jsxs(Flex, { gap: 1, align: "center", minW: "0", onKeyDownCapture, "data-part": "dataset-key-search", children: [
64227
- /* @__PURE__ */ jsx$1(Flex, { width: `min(${placeholder.length + 10}ch, 40rem)`, minW: "12rem", flexShrink: 1, children: /* @__PURE__ */ jsx$1(EastChakraCombobox, { value: payload, selectionBehavior: "preserve" }, resetSeq) }),
64228
- status !== null && /* @__PURE__ */ jsx$1(Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: status }),
64229
- canStep && /* @__PURE__ */ jsxs(Fragment, { children: [
64230
- /* @__PURE__ */ jsx$1(
64231
- IconButton,
64232
- {
64233
- "aria-label": "Previous match",
64234
- size: "2xs",
64235
- variant: "ghost",
64236
- disabled: activeIdx <= 0,
64237
- onClick: () => step3(-1),
64238
- children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faChevronUp })
64239
- }
64240
- ),
64241
- /* @__PURE__ */ jsx$1(
64242
- IconButton,
64243
- {
64244
- "aria-label": "Next match",
64245
- size: "2xs",
64246
- variant: "ghost",
64247
- disabled: activeIdx >= range.count - 1,
64248
- onClick: () => step3(1),
64249
- children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faChevronDown })
64250
- }
64251
- )
64252
- ] }),
64253
- (range !== null || hint !== null) && /* @__PURE__ */ jsx$1(IconButton, { "aria-label": "Clear search", size: "2xs", variant: "ghost", onClick: clearSearch, children: /* @__PURE__ */ jsx$1(FontAwesomeIcon, { icon: faXmark }) })
64254
- ] });
64255
- });
64256
64402
  function pagingDebug(...args) {
64257
64403
  try {
64258
64404
  if (typeof localStorage === "undefined") return;
@@ -64684,7 +64830,7 @@ const UITaskPreview = memo(function UITaskPreview2({
64684
64830
  const manifest = useMemo(() => {
64685
64831
  if (!details || !isUI) return null;
64686
64832
  const meta3 = getTaskMetadata(details);
64687
- return meta3 ? decodeManifest(meta3) : { paths: [], functions: [], records: [] };
64833
+ return meta3 ? decodeManifest(meta3) : { paths: [], functions: [], records: [], pages: [] };
64688
64834
  }, [details, isUI]);
64689
64835
  const outputPath = details ? treePathToString$1(details.output) : null;
64690
64836
  const preloads = useMemo(
@@ -64708,6 +64854,7 @@ const UITaskPreview = memo(function UITaskPreview2({
64708
64854
  ...ShareImpl,
64709
64855
  ...DecisionBindPlatform,
64710
64856
  ...createScopedBindPlatform(manifest),
64857
+ ...createScopedPagedPlatform(manifest.pages),
64711
64858
  ...createScopedFuncPlatform(manifest.functions),
64712
64859
  ...createScopedRecordPlatform(manifest.records)
64713
64860
  ] : void 0,
@@ -65621,7 +65768,7 @@ export {
65621
65768
  BindPlatform,
65622
65769
  BindRuntime,
65623
65770
  DataTaskPreview,
65624
- DatasetKeySearch,
65771
+ DatasetKeySearch2 as DatasetKeySearch,
65625
65772
  DatasetPreview,
65626
65773
  E3Provider,
65627
65774
  EastChakraDecisionJournal,
@@ -65636,6 +65783,8 @@ export {
65636
65783
  IndexedDBStagedAdapter,
65637
65784
  InputPreview,
65638
65785
  MemoryStagedAdapter,
65786
+ PagedPlatform,
65787
+ PagedRuntime,
65639
65788
  ReactiveDatasetCache,
65640
65789
  ReactiveDatasetLoader,
65641
65790
  ReactiveDatasetProvider,
@@ -65649,6 +65798,7 @@ export {
65649
65798
  awaitPendingWrites,
65650
65799
  clearBindingRegistry,
65651
65800
  clearFunctionApi,
65801
+ clearPagedApi,
65652
65802
  clearPendingWrites,
65653
65803
  clearReactiveDatasetCache,
65654
65804
  clearReactiveDatasetListCache,
@@ -65656,17 +65806,20 @@ export {
65656
65806
  clearStagedStoreSingleton,
65657
65807
  createDefaultDatasetApi,
65658
65808
  createDefaultFunctionApi,
65809
+ createDefaultPagedApi,
65659
65810
  createDefaultRecordApi,
65660
65811
  createInMemoryFunctionApi,
65661
65812
  createInMemoryRecordApi,
65662
65813
  createReactiveDatasetCache,
65663
65814
  createScopedBindPlatform,
65664
65815
  createScopedFuncPlatform,
65816
+ createScopedPagedPlatform,
65665
65817
  createScopedRecordPlatform,
65666
65818
  datasetCacheKey,
65667
65819
  datasetPathToString,
65668
65820
  defaultBindRuntime,
65669
65821
  defaultFuncRuntime,
65822
+ defaultPagedRuntime,
65670
65823
  defaultRecordRuntime,
65671
65824
  disableBindingTracking,
65672
65825
  enableBindingTracking,
@@ -65679,11 +65832,14 @@ export {
65679
65832
  getTaskKind,
65680
65833
  getTaskMetadata,
65681
65834
  initializeFunctionApi,
65835
+ initializePagedApi,
65682
65836
  initializeReactiveDatasetCache,
65683
65837
  initializeRecordApi,
65684
65838
  initializeStagedStore,
65685
65839
  isBindingTracking,
65686
65840
  onWriteError,
65841
+ pagedTotalKey,
65842
+ pagedWindowKey,
65687
65843
  preloadReactiveDatasetList,
65688
65844
  realClock,
65689
65845
  recordChannelKey,