@elaraai/e3-ui-components 1.0.56 → 1.0.58

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.
@@ -13,5 +13,12 @@ export interface DatasetPreviewProps {
13
13
  * this dataset path (for mutable inputs). Task outputs stay read-only. */
14
14
  editable?: boolean;
15
15
  }
16
+ /** Human-readable byte size, shared with the paged preview. */
17
+ export declare function formatSize(bytes: number): string;
18
+ /** Download trigger with its own in-flight state, shared with the paged preview. */
19
+ export declare function DownloadButton({ onClick, label }: {
20
+ onClick: () => void;
21
+ label?: string;
22
+ }): import("react/jsx-runtime").JSX.Element;
16
23
  export declare const DatasetPreview: import('react').NamedExoticComponent<DatasetPreviewProps>;
17
24
  //# sourceMappingURL=DatasetPreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/DatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAiB7D,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,cAAc,2DAoGzB,CAAC"}
1
+ {"version":3,"file":"DatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/DatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAkB7D,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mDAAmD;IACnD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;+EAC2E;IAC3E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,2CAYzF;AAED,eAAO,MAAM,cAAc,2DAgIzB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { RequestOptions } from '@elaraai/e3-api-client';
2
+ import { EastTypeValue } from '@elaraai/east';
3
+ export interface PagedDatasetPreviewProps {
4
+ apiUrl: string;
5
+ repo: string;
6
+ workspace: string;
7
+ /** Dotted path string, e.g. "inputs.rows". */
8
+ path: string;
9
+ /** The dataset's East type (an Array/Set/Dict root). */
10
+ type: EastTypeValue;
11
+ /** Content hash of the current value — resets pages when it changes. */
12
+ hash: string;
13
+ /** Stored blob size, shown alongside the totals. */
14
+ sizeBytes: number;
15
+ requestOptions?: RequestOptions;
16
+ /** Triggers a whole-value download (the existing download flow). */
17
+ onDownload: () => void | Promise<void>;
18
+ }
19
+ export declare const PagedDatasetPreview: import('react').NamedExoticComponent<PagedDatasetPreviewProps>;
20
+ //# sourceMappingURL=PagedDatasetPreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PagedDatasetPreview.d.ts","sourceRoot":"","sources":["../../src/components/PagedDatasetPreview.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAkD,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAenG,MAAM,WAAW,wBAAwB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,EAAE,aAAa,CAAC;IACpB,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oEAAoE;IACpE,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAkCD,eAAO,MAAM,mBAAmB,gEAkIgH,CAAC"}
@@ -8,6 +8,7 @@ export { TaskLogs, type TaskLogsProps } from './TaskLogs.js';
8
8
  export { UITaskPreview, type UITaskPreviewProps } from './UITaskPreview.js';
9
9
  export { DataTaskPreview, type DataTaskPreviewProps } from './DataTaskPreview.js';
10
10
  export { DatasetPreview, type DatasetPreviewProps } from './DatasetPreview.js';
11
+ export { PagedDatasetPreview, type PagedDatasetPreviewProps } from './PagedDatasetPreview.js';
11
12
  export { StatusDisplay, type StatusDisplayProps } from './StatusDisplay.js';
12
13
  export { EastValueViewer, type EastValueViewerProps } from './EastValueViewer.js';
13
14
  export { VirtualizedLogViewer, type VirtualizedLogViewerProps } from './VirtualizedLogViewer.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * Opt-in debug logging for the paged dataset preview (#497).
7
+ *
8
+ * Silent by default. Enable with `localStorage['e3-paging-debug'] = '1'`
9
+ * (any value except 'off') to trace page fetches, resets, and short-page
10
+ * clamps in the console — everything is prefixed `[e3-paging]`.
11
+ */
12
+ export declare function pagingDebug(...args: unknown[]): void;
13
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../src/debug.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CASpD"}
@@ -11,5 +11,6 @@ export * from './tasks.js';
11
11
  export * from './executions.js';
12
12
  export { useDatasetStatus, type DatasetStatus, type UseDatasetStatusOptions } from './useDatasetStatus.js';
13
13
  export { useDatasetValue, useDatasetDownload, type DatasetValueResult, type UseDatasetValueOptions } from './useDatasetValue.js';
14
+ export { useDatasetPage, type DatasetPageResult, type UseDatasetPageOptions, type DatasetPageWindow } from './useDatasetPage.js';
14
15
  export { useTaskDetails, getTaskKind, getTaskMetadata, type UseTaskDetailsOptions } from './useTaskDetails.js';
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACjI,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACjI,OAAO,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACjI,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { UseQueryResult } from '@tanstack/react-query';
2
+ import { RequestOptions, DatasetPageWindow } from '@elaraai/e3-api-client';
3
+ import { EastTypeValue } from '@elaraai/east';
4
+ import { QueryOverrides } from './types.js';
5
+ export type { DatasetPageWindow } from '@elaraai/e3-api-client';
6
+ export interface UseDatasetPageOptions {
7
+ requestOptions?: RequestOptions;
8
+ queryOverrides?: QueryOverrides;
9
+ /** Set false to skip the fetch. Defaults to true. */
10
+ enabled?: boolean;
11
+ /**
12
+ * Content hash of the current dataset version — part of the query key so
13
+ * cached pages invalidate when the underlying data changes. The fetch is
14
+ * gated on it being non-null.
15
+ */
16
+ hash?: string | null;
17
+ /** East type to decode pages against. Required. */
18
+ type: EastTypeValue;
19
+ /** The window to read: an element window or one writer segment. */
20
+ window: DatasetPageWindow;
21
+ }
22
+ export interface DatasetPageResult {
23
+ /** The decoded window — a value of the dataset's own type. */
24
+ decoded: unknown;
25
+ /** Total elements in the dataset (pairs for Dict datasets). */
26
+ totalElements: number;
27
+ /** Byte size of the whole stored blob. */
28
+ totalBytes: number;
29
+ /** Whether `totalElements` is exact (segment windows of Set/Dict
30
+ * datasets report an upper bound). */
31
+ totalExact: boolean;
32
+ /** Segments in the stored blob; 0 when the blob carries no index. */
33
+ segmentCount: number;
34
+ /** Global element offset of the window's first element. */
35
+ offset: number;
36
+ /** Elements actually in this page (the server may clamp the limit). */
37
+ count: number;
38
+ }
39
+ /**
40
+ * Fetch + decode one window of a collection (Array/Set/Dict) dataset.
41
+ *
42
+ * Pages are keyed by content hash and window, and the previous page is held
43
+ * on screen while the next loads, so stepping through windows never flashes
44
+ * the loading state.
45
+ */
46
+ export declare function useDatasetPage(apiUrl: string, repo: string, workspace: string | null, datasetPath: string | null, options: UseDatasetPageOptions): UseQueryResult<DatasetPageResult, Error>;
47
+ //# sourceMappingURL=useDatasetPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDatasetPage.d.ts","sourceRoot":"","sources":["../../src/hooks/useDatasetPage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAExF,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,qBAAqB;IAClC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,mDAAmD;IACnD,IAAI,EAAE,aAAa,CAAC;IACpB,mEAAmE;IACnE,MAAM,EAAE,iBAAiB,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB;2CACuC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,EAAE,qBAAqB,GAC/B,cAAc,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAgC1C"}
package/dist/index.cjs CHANGED
@@ -63857,6 +63857,35 @@ function useDatasetDownload(apiUrl, repo, workspace, datasetPath, requestOptions
63857
63857
  URL.revokeObjectURL(url);
63858
63858
  }, [apiUrl, repo, workspace, datasetPath, pathParts, reqOpts]);
63859
63859
  }
63860
+ function useDatasetPage(apiUrl, repo, workspace, datasetPath, options2) {
63861
+ const { requestOptions, queryOverrides, enabled = true, hash: hash2, type, window: window3 } = options2;
63862
+ const reqOpts = requestOptions ?? { token: null };
63863
+ const pathParts = React.useMemo(
63864
+ () => (datasetPath == null ? void 0 : datasetPath.split(".").filter(Boolean).map((v3) => east.variant("field", v3))) ?? [],
63865
+ [datasetPath]
63866
+ );
63867
+ const windowKey = "segment" in window3 ? `s${window3.segment}` : `${window3.offset}+${window3.limit}`;
63868
+ return reactQuery.useQuery({
63869
+ queryKey: ["datasetPage", apiUrl, repo, workspace, datasetPath, hash2 ?? null, windowKey],
63870
+ queryFn: async () => {
63871
+ const pinned = hash2 != null ? { ...window3, hash: hash2 } : window3;
63872
+ const page = await e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts, pinned, reqOpts);
63873
+ const decoded = east.decodeBeast2For(type)(page.data);
63874
+ return {
63875
+ decoded,
63876
+ totalElements: page.totalElements,
63877
+ totalBytes: page.totalBytes,
63878
+ totalExact: page.totalExact,
63879
+ segmentCount: page.segmentCount,
63880
+ offset: page.offset,
63881
+ count: page.count
63882
+ };
63883
+ },
63884
+ enabled: enabled && !!workspace && !!datasetPath && hash2 != null,
63885
+ placeholderData: reactQuery.keepPreviousData,
63886
+ ...queryOverrides
63887
+ });
63888
+ }
63860
63889
  function useTaskDetails(apiUrl, repo, workspace, taskName, options2) {
63861
63890
  const reqOpts = (options2 == null ? void 0 : options2.requestOptions) ?? { token: null };
63862
63891
  return reactQuery.useQuery({
@@ -64000,6 +64029,158 @@ class ErrorBoundary extends React.Component {
64000
64029
  return this.props.children;
64001
64030
  }
64002
64031
  }
64032
+ function pagingDebug(...args) {
64033
+ try {
64034
+ if (typeof localStorage === "undefined") return;
64035
+ const flag = localStorage.getItem("e3-paging-debug");
64036
+ if (flag === null || flag === "off") return;
64037
+ } catch {
64038
+ return;
64039
+ }
64040
+ console.info("[e3-paging]", ...args);
64041
+ }
64042
+ const PAGE_SIZE = 500;
64043
+ function pageRows(type, decoded, offset) {
64044
+ if (type.type === "Array") {
64045
+ const elemType2 = type.value;
64046
+ return decoded.map((el, i) => ({
64047
+ node: eastUi.ValueTree.materialize(elemType2, el),
64048
+ step: east.variant("index", BigInt(offset + i))
64049
+ }));
64050
+ }
64051
+ if (type.type === "Dict") {
64052
+ const { key: keyType, value: valueType } = type.value;
64053
+ const printKey = east.printFor(keyType);
64054
+ const stringKeys = keyType.type === "String";
64055
+ return Array.from(decoded.entries()).map(([k2, v3], i) => ({
64056
+ node: eastUi.ValueTree.materialize(valueType, v3),
64057
+ step: stringKeys ? east.variant("key", k2) : east.variant("index", BigInt(offset + i)),
64058
+ label: stringKeys ? k2 : printKey(k2)
64059
+ }));
64060
+ }
64061
+ const elemType = type.value;
64062
+ return Array.from(decoded.values()).map((el, i) => ({
64063
+ node: eastUi.ValueTree.materialize(elemType, el),
64064
+ step: east.variant("index", BigInt(offset + i))
64065
+ }));
64066
+ }
64067
+ function emptyOf(type) {
64068
+ return type.type === "Array" ? [] : type.type === "Dict" ? /* @__PURE__ */ new Map() : /* @__PURE__ */ new Set();
64069
+ }
64070
+ const PagedDatasetPreview = React.memo(function PagedDatasetPreview2({
64071
+ apiUrl,
64072
+ repo,
64073
+ workspace,
64074
+ path: path2,
64075
+ type,
64076
+ hash: hash2,
64077
+ sizeBytes,
64078
+ requestOptions,
64079
+ onDownload
64080
+ }) {
64081
+ const queryClient = reactQuery.useQueryClient();
64082
+ const [pages, setPages] = React.useState(/* @__PURE__ */ new Map());
64083
+ const [totals, setTotals] = React.useState(null);
64084
+ const [loadingCount, setLoadingCount] = React.useState(0);
64085
+ const [error3, setError] = React.useState(null);
64086
+ const inflightRef = React.useRef(/* @__PURE__ */ new Set());
64087
+ React.useEffect(() => {
64088
+ pagingDebug(`reset (path=${path2}, hash=${hash2.slice(0, 8)})`);
64089
+ setPages(/* @__PURE__ */ new Map());
64090
+ setTotals(null);
64091
+ setError(null);
64092
+ inflightRef.current.clear();
64093
+ }, [path2, hash2]);
64094
+ const loadPage = React.useCallback((pageIdx) => {
64095
+ if (inflightRef.current.has(pageIdx)) return;
64096
+ inflightRef.current.add(pageIdx);
64097
+ setLoadingCount((n2) => n2 + 1);
64098
+ pagingDebug(`fetch p${pageIdx} (offset=${pageIdx * PAGE_SIZE}, limit=${PAGE_SIZE}, hash=${hash2.slice(0, 8)})`);
64099
+ const pathParts = path2.split(".").filter(Boolean).map((v3) => east.variant("field", v3));
64100
+ const reqOpts = requestOptions ?? { token: null };
64101
+ queryClient.fetchQuery({
64102
+ queryKey: ["datasetPage", apiUrl, repo, workspace, path2, hash2, `p${pageIdx}`],
64103
+ // Hash-pinned: the URL is a pure function of the bytes, so any
64104
+ // HTTP cache between here and the server can hold it immutably.
64105
+ queryFn: () => e3ApiClient.datasetGetPage(apiUrl, repo, workspace, pathParts, { offset: pageIdx * PAGE_SIZE, limit: PAGE_SIZE, hash: hash2 }, reqOpts),
64106
+ staleTime: Infinity
64107
+ // pages of one content hash are immutable
64108
+ }).then((page) => {
64109
+ const decoded = east.decodeBeast2For(type)(page.data);
64110
+ const rows = pageRows(type, decoded, page.offset);
64111
+ pagingDebug(
64112
+ `p${pageIdx} ok: count=${page.count} rows=${rows.length} offset=${page.offset} total=${page.totalElements} segs=${page.segmentCount}`,
64113
+ rows.length > 0 ? `first=${JSON.stringify(rows[0].label ?? "(derived)")}` : "(empty)"
64114
+ );
64115
+ if (page.count < PAGE_SIZE && page.offset + page.count < page.totalElements) {
64116
+ pagingDebug(`p${pageIdx} SHORT page: server clamped ${PAGE_SIZE} → ${page.count}`);
64117
+ }
64118
+ setTotals({ elements: page.totalElements, bytes: page.totalBytes });
64119
+ setPages((prev2) => new Map(prev2).set(pageIdx, rows));
64120
+ }).catch((err) => {
64121
+ pagingDebug(`p${pageIdx} FAILED:`, err);
64122
+ setError(err instanceof Error ? err : new Error(String(err)));
64123
+ }).finally(() => {
64124
+ inflightRef.current.delete(pageIdx);
64125
+ setLoadingCount((n2) => n2 - 1);
64126
+ });
64127
+ }, [queryClient, apiUrl, repo, workspace, path2, hash2, requestOptions, type]);
64128
+ React.useEffect(() => {
64129
+ if (totals === null && error3 === null) loadPage(0);
64130
+ }, [totals, error3, loadPage]);
64131
+ const onNeedRows = React.useCallback((startRow, endRow) => {
64132
+ const first2 = Math.max(0, Math.floor(startRow / PAGE_SIZE));
64133
+ const last2 = Math.max(first2, Math.ceil(endRow / PAGE_SIZE) - 1);
64134
+ const missing = [];
64135
+ for (let p2 = first2; p2 <= last2; p2++) {
64136
+ if (!pages.has(p2)) missing.push(p2);
64137
+ }
64138
+ pagingDebug(`need rows [${startRow}, ${endRow}) → pages ${first2}..${last2}, missing=[${missing.join(",")}], inflight=[${[...inflightRef.current].join(",")}]`);
64139
+ for (const p2 of missing) loadPage(p2);
64140
+ }, [pages, loadPage]);
64141
+ const treeValue = React.useMemo(() => ({
64142
+ root: eastUi.ValueTree.materialize(type, emptyOf(type)),
64143
+ onEdit: east.none,
64144
+ onInsert: east.none,
64145
+ onRemove: east.none,
64146
+ onTag: east.none,
64147
+ style: east.some({ height: east.some("100%"), maxHeight: east.none, openDepth: east.none, toolbar: east.some(true) })
64148
+ }), [type]);
64149
+ const paging = React.useMemo(() => totals === null ? null : {
64150
+ totalRows: totals.elements,
64151
+ pageSize: PAGE_SIZE,
64152
+ pages,
64153
+ onNeedRows
64154
+ }, [totals, pages, onNeedRows]);
64155
+ if (error3 !== null) {
64156
+ if (error3 instanceof e3ApiClient.ApiError && error3.code === "dataset_too_large") {
64157
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { height: "100%", direction: "column", align: "center", justify: "center", gap: 3, p: 6, children: [
64158
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "lg", color: "fg.warning", fontWeight: "bold", children: "Too large to page" }),
64159
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { color: "fg.muted", fontSize: "sm", textAlign: "center", maxW: "32rem", children: typeof error3.details === "string" ? error3.details : "This dataset cannot be paged server-side." }),
64160
+ /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: onDownload, label: "Download value" })
64161
+ ] });
64162
+ }
64163
+ return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Paged load failed", message: error3.message });
64164
+ }
64165
+ if (paging === null || totals === null) {
64166
+ return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64167
+ }
64168
+ const itemNoun = type.type === "Dict" ? "entries" : "items";
64169
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64170
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, gap: 2, align: "center", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64171
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", whiteSpace: "nowrap", children: [
64172
+ totals.elements.toLocaleString(),
64173
+ " ",
64174
+ itemNoun,
64175
+ " · ",
64176
+ formatSize(totals.bytes > 0 ? totals.bytes : sizeBytes)
64177
+ ] }),
64178
+ loadingCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "Loading…" }),
64179
+ /* @__PURE__ */ jsxRuntime.jsx(react.Flex, { flex: 1, justify: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: onDownload }) })
64180
+ ] }),
64181
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: `${path2}:page`, paging }) })
64182
+ ] });
64183
+ }, (prev2, next2) => prev2.path === next2.path && prev2.workspace === next2.workspace && prev2.hash === next2.hash && prev2.sizeBytes === next2.sizeBytes);
64003
64184
  const DEFAULT_SIZE_LIMIT = 200 * 1024;
64004
64185
  function formatSize(bytes) {
64005
64186
  if (bytes < 1024) return `${bytes} B`;
@@ -64072,7 +64253,7 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64072
64253
  onRemove: east.some((p2) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "remove" }))),
64073
64254
  onTag: east.some((p2, tag) => write(eastUi.ValueTree.applyEdit(type, decoded, p2, { kind: "tag", tag })))
64074
64255
  } : { onEdit: east.none, onInsert: east.none, onRemove: east.none, onTag: east.none };
64075
- return { root: root2, ...wire, style: east.some({ height: east.some("100%"), maxHeight: east.none }) };
64256
+ return { root: root2, ...wire, style: east.some({ height: east.some("100%"), maxHeight: east.none, openDepth: east.none, toolbar: east.some(true) }) };
64076
64257
  }, [type, decoded, editable, write]);
64077
64258
  if (statusQuery.isLoading) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64078
64259
  if (statusQuery.error) {
@@ -64082,6 +64263,23 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64082
64263
  if (!status) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No status" });
64083
64264
  if (!hasValue) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "info", title: "No data available", message: "Waiting for a value to be set" });
64084
64265
  if (isOversized) {
64266
+ const pageable = type !== void 0 && (type.type === "Array" || type.type === "Set" || type.type === "Dict");
64267
+ if (pageable && workspace != null && path2 != null && status.hash != null) {
64268
+ return /* @__PURE__ */ jsxRuntime.jsx(
64269
+ PagedDatasetPreview,
64270
+ {
64271
+ apiUrl,
64272
+ repo,
64273
+ workspace,
64274
+ path: path2,
64275
+ type,
64276
+ hash: status.hash,
64277
+ sizeBytes,
64278
+ ...requestOptions != null && { requestOptions },
64279
+ onDownload: download
64280
+ }
64281
+ );
64282
+ }
64085
64283
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { height: "100%", direction: "column", align: "center", justify: "center", layerStyle: "banner.stale", borderRadius: "0", gap: 3, p: 6, children: [
64086
64284
  /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "lg", color: "fg.warning", fontWeight: "bold", children: "Value too large to display" }),
64087
64285
  /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { color: "fg.muted", fontSize: "sm", children: [
@@ -64096,9 +64294,14 @@ const DatasetPreview = React.memo(function DatasetPreview2({
64096
64294
  }
64097
64295
  if (valueQuery.isLoading || !valueQuery.data) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "loading", title: "Loading..." });
64098
64296
  if (valueQuery.error) return /* @__PURE__ */ jsxRuntime.jsx(StatusDisplay, { variant: "error", title: "Decode failed", message: valueQuery.error.message });
64297
+ const count = type === void 0 || decoded === void 0 ? null : type.type === "Array" ? decoded.length : type.type === "Set" || type.type === "Dict" ? decoded.size : null;
64298
+ const countText = count === null ? "" : `${count.toLocaleString()} ${(type == null ? void 0 : type.type) === "Dict" ? "entries" : "items"} · `;
64099
64299
  return /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { direction: "column", height: "100%", overflow: "hidden", children: [
64100
64300
  /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { px: 4, py: 2, justify: "flex-end", flexShrink: 0, borderBottom: "1px solid", borderColor: "border.subtle", children: [
64101
- /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", mr: 2, alignSelf: "center", children: formatSize(sizeBytes) }),
64301
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "xs", color: "fg.muted", mr: 2, alignSelf: "center", children: [
64302
+ countText,
64303
+ formatSize(sizeBytes)
64304
+ ] }),
64102
64305
  /* @__PURE__ */ jsxRuntime.jsx(DownloadButton, { onClick: download })
64103
64306
  ] }),
64104
64307
  /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flex: 1, minHeight: 0, overflow: "hidden", children: treeValue !== null && /* @__PURE__ */ jsxRuntime.jsx(eastUiComponents.EastChakraValueTree, { value: treeValue, storageKey: path2 ?? "value" }) })
@@ -65172,6 +65375,7 @@ exports.useDatasetListAt = useDatasetListAt;
65172
65375
  exports.useDatasetListRecursive = useDatasetListRecursive;
65173
65376
  exports.useDatasetListRecursivePaths = useDatasetListRecursivePaths;
65174
65377
  exports.useDatasetListWithStatus = useDatasetListWithStatus;
65378
+ exports.useDatasetPage = useDatasetPage;
65175
65379
  exports.useDatasetSet = useDatasetSet;
65176
65380
  exports.useDatasetStatus = useDatasetStatus;
65177
65381
  exports.useDatasetValue = useDatasetValue;