@opengeni/react 0.48.0 → 0.52.1

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.
Files changed (284) hide show
  1. package/README.md +90 -2
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +170 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/chunk-6BBWWOOU.js +82 -0
  15. package/dist/chunk-6BBWWOOU.js.map +1 -0
  16. package/dist/{chunk-FWXGSNXT.js → chunk-724BW4TW.js} +585 -34
  17. package/dist/chunk-724BW4TW.js.map +1 -0
  18. package/dist/chunk-7I2KB3OK.js +3895 -0
  19. package/dist/chunk-7I2KB3OK.js.map +1 -0
  20. package/dist/chunk-7WCLAEFV.js +2828 -0
  21. package/dist/chunk-7WCLAEFV.js.map +1 -0
  22. package/dist/{chunk-I4MPFDRP.js → chunk-FWS3KC3Z.js} +3 -3
  23. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  24. package/dist/chunk-HDBG7XGO.js.map +1 -0
  25. package/dist/chunk-HUIKIDM5.js +2148 -0
  26. package/dist/chunk-HUIKIDM5.js.map +1 -0
  27. package/dist/{chunk-XCNTXWBB.js → chunk-JAGDUCZQ.js} +854 -340
  28. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  29. package/dist/{chunk-LHLYWG5R.js → chunk-JR3QFY6B.js} +413 -37
  30. package/dist/chunk-JR3QFY6B.js.map +1 -0
  31. package/dist/chunk-JVYQRRC4.js +53 -0
  32. package/dist/chunk-JVYQRRC4.js.map +1 -0
  33. package/dist/chunk-KCGXPP5V.js +157 -0
  34. package/dist/chunk-KCGXPP5V.js.map +1 -0
  35. package/dist/chunk-PEI6RANI.js +3234 -0
  36. package/dist/chunk-PEI6RANI.js.map +1 -0
  37. package/dist/{chunk-NJ7M2B6Z.js → chunk-ROIRTM37.js} +18 -10
  38. package/dist/{chunk-NJ7M2B6Z.js.map → chunk-ROIRTM37.js.map} +1 -1
  39. package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
  40. package/dist/chunk-WSK7G5LE.js.map +1 -0
  41. package/dist/{chunk-OL6QVXGF.js → chunk-X4S56Y2H.js} +27 -7
  42. package/dist/{chunk-OL6QVXGF.js.map → chunk-X4S56Y2H.js.map} +1 -1
  43. package/dist/client.d.ts +7 -1
  44. package/dist/clipboard.d.ts +1 -0
  45. package/dist/clipboard.js +7 -0
  46. package/dist/clipboard.js.map +1 -0
  47. package/dist/commands/index.d.ts +3 -3
  48. package/dist/commands/registry.d.ts +1 -1
  49. package/dist/commands/types.d.ts +1 -1
  50. package/dist/components/approval-surface.d.ts +2 -2
  51. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  52. package/dist/components/artifacts/document-editor.d.ts +172 -0
  53. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  54. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  55. package/dist/components/artifacts/editable-document.d.ts +24 -0
  56. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  57. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  58. package/dist/components/artifacts/index.d.ts +8 -0
  59. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  60. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  61. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  62. package/dist/components/browser-viewer.d.ts +29 -0
  63. package/dist/components/chat-composer.d.ts +8 -8
  64. package/dist/components/code-editor.d.ts +25 -3
  65. package/dist/components/command-palette.d.ts +2 -2
  66. package/dist/components/composer-transcription-control.d.ts +2 -2
  67. package/dist/components/composer.d.ts +17 -17
  68. package/dist/components/computer-viewer.d.ts +23 -0
  69. package/dist/components/copy-button.d.ts +2 -2
  70. package/dist/components/desktop-viewer.d.ts +25 -21
  71. package/dist/components/diff-view.d.ts +1 -1
  72. package/dist/components/enrollment-consent.d.ts +1 -1
  73. package/dist/components/enrollment-device-flow.d.ts +1 -1
  74. package/dist/components/file-browser.d.ts +2 -2
  75. package/dist/components/fleet-tile.d.ts +1 -1
  76. package/dist/components/generated-video-player.d.ts +10 -0
  77. package/dist/components/human-input-form.d.ts +1 -1
  78. package/dist/components/human-input-surface.d.ts +2 -2
  79. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  80. package/dist/components/machine-card.d.ts +2 -2
  81. package/dist/components/machine-dock-bar.d.ts +3 -3
  82. package/dist/components/machine-health-pill.d.ts +2 -2
  83. package/dist/components/machine-input-display.d.ts +1 -1
  84. package/dist/components/machine-metrics.d.ts +2 -2
  85. package/dist/components/machine-status-pill.d.ts +4 -4
  86. package/dist/components/machines/health.d.ts +1 -1
  87. package/dist/components/machines/machine-detail.d.ts +3 -3
  88. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  89. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  90. package/dist/components/machines/series.d.ts +2 -2
  91. package/dist/components/machines-dashboard.d.ts +2 -2
  92. package/dist/components/markdown.d.ts +1 -1
  93. package/dist/components/message-timeline.d.ts +12 -5
  94. package/dist/components/model-picker.d.ts +2 -2
  95. package/dist/components/model-policy-picker.d.ts +7 -7
  96. package/dist/components/pierre-diff.d.ts +1 -1
  97. package/dist/components/pierre-file.d.ts +1 -1
  98. package/dist/components/queue-draft-policy.d.ts +1 -1
  99. package/dist/components/queue-surface-implementation.d.ts +3 -3
  100. package/dist/components/queue-surface-state.d.ts +4 -4
  101. package/dist/components/queue-surface.d.ts +5 -5
  102. package/dist/components/sandbox-files.d.ts +3 -3
  103. package/dist/components/sandbox-terminal.d.ts +21 -4
  104. package/dist/components/sandbox-workspace.d.ts +25 -9
  105. package/dist/components/session-chrome.d.ts +4 -4
  106. package/dist/components/session-status.d.ts +2 -2
  107. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  108. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  109. package/dist/components/timeline-annotations.d.ts +13 -0
  110. package/dist/components/tooltip.d.ts +4 -4
  111. package/dist/components/user-message-body.d.ts +2 -2
  112. package/dist/components/workbench-changes.d.ts +2 -2
  113. package/dist/components/workspace-dock.d.ts +1 -1
  114. package/dist/composer.d.ts +11 -11
  115. package/dist/composer.js +5 -5
  116. package/dist/hooks/internal.d.ts +1 -1
  117. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  118. package/dist/hooks/use-available-models.d.ts +2 -2
  119. package/dist/hooks/use-billing-usage.d.ts +1 -1
  120. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  121. package/dist/hooks/use-browser-identities.d.ts +26 -0
  122. package/dist/hooks/use-browser-session.d.ts +28 -0
  123. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  124. package/dist/hooks/use-codex-accounts.d.ts +2 -2
  125. package/dist/hooks/use-composer.d.ts +36 -9
  126. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  127. package/dist/hooks/use-computer-session.d.ts +28 -0
  128. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  129. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  130. package/dist/hooks/use-environments.d.ts +1 -1
  131. package/dist/hooks/use-file-attachments.d.ts +1 -1
  132. package/dist/hooks/use-goal.d.ts +2 -2
  133. package/dist/hooks/use-human-input.d.ts +2 -2
  134. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  135. package/dist/hooks/use-machine-chip.d.ts +2 -2
  136. package/dist/hooks/use-machines.d.ts +4 -4
  137. package/dist/hooks/use-packs.d.ts +7 -2
  138. package/dist/hooks/use-rigs.d.ts +1 -1
  139. package/dist/hooks/use-sandbox-files.d.ts +6 -4
  140. package/dist/hooks/use-sandbox-git.d.ts +6 -5
  141. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  142. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  143. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  144. package/dist/hooks/use-session-control.d.ts +1 -1
  145. package/dist/hooks/use-session-events.d.ts +2 -2
  146. package/dist/hooks/use-session-lineage.d.ts +1 -1
  147. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  148. package/dist/hooks/use-session.d.ts +2 -2
  149. package/dist/hooks/use-slash-commands.d.ts +1 -1
  150. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  151. package/dist/hooks/use-turn-queue.d.ts +2 -2
  152. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  153. package/dist/hooks/use-voice-input.d.ts +1 -1
  154. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  155. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  156. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  157. package/dist/hooks/use-workspaces.d.ts +1 -1
  158. package/dist/index.d.ts +158 -155
  159. package/dist/index.js +906 -462
  160. package/dist/index.js.map +1 -1
  161. package/dist/interaction.d.ts +23 -0
  162. package/dist/interaction.js +36 -0
  163. package/dist/interaction.js.map +1 -0
  164. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  165. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  166. package/dist/lib/relay-wire.d.ts +4 -0
  167. package/dist/lib/sandbox-liveness.d.ts +9 -0
  168. package/dist/lib/terminal-capability.d.ts +5 -0
  169. package/dist/lib/xterm-theme.d.ts +1 -1
  170. package/dist/machines.d.ts +30 -30
  171. package/dist/machines.js +3 -3
  172. package/dist/provider.d.ts +4 -4
  173. package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
  174. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  175. package/dist/realtime/dropdown-menu.d.ts +8 -8
  176. package/dist/realtime/realtime-control.d.ts +6 -6
  177. package/dist/realtime.d.ts +4 -4
  178. package/dist/realtime.js +3 -3
  179. package/dist/session-context.d.ts +19 -1
  180. package/dist/session-ui.d.ts +14 -14
  181. package/dist/session-ui.js +5 -4
  182. package/dist/session.d.ts +28 -28
  183. package/dist/session.js +5 -5
  184. package/dist/timeline/activity-rail.d.ts +5 -3
  185. package/dist/timeline/disclosure-context.d.ts +1 -1
  186. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  187. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  188. package/dist/timeline/index.d.ts +19 -19
  189. package/dist/timeline/parsers.d.ts +4 -1
  190. package/dist/timeline/projection.d.ts +3 -3
  191. package/dist/timeline/registry.d.ts +8 -2
  192. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  193. package/dist/timeline/shared.d.ts +21 -10
  194. package/dist/timeline/tool-diff.d.ts +2 -2
  195. package/dist/timeline/tool-renderers.d.ts +1 -1
  196. package/dist/timeline/turn-summary.d.ts +3 -3
  197. package/dist/timeline/types.d.ts +39 -3
  198. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  199. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  200. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  201. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  202. package/package.json +36 -7
  203. package/src/artifacts-document.ts +31 -0
  204. package/src/artifacts-presentation.ts +36 -0
  205. package/src/artifacts-spreadsheet.ts +21 -0
  206. package/src/artifacts.ts +86 -0
  207. package/src/client.ts +87 -1
  208. package/src/clipboard.ts +2 -0
  209. package/src/components/artifacts/artifact-surface.tsx +93 -0
  210. package/src/components/artifacts/document-editor.tsx +2255 -0
  211. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  212. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  213. package/src/components/artifacts/editable-document.tsx +657 -0
  214. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  215. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  216. package/src/components/artifacts/index.ts +99 -0
  217. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  218. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  219. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  220. package/src/components/browser-viewer.tsx +1592 -0
  221. package/src/components/chat-composer.tsx +13 -0
  222. package/src/components/code-editor.tsx +89 -30
  223. package/src/components/composer-transcription-control.tsx +10 -6
  224. package/src/components/computer-viewer.tsx +1139 -0
  225. package/src/components/desktop-viewer.tsx +101 -73
  226. package/src/components/generated-video-player.tsx +89 -0
  227. package/src/components/interactive-workbench-viewers.ts +2 -0
  228. package/src/components/machine-input-display.ts +3 -0
  229. package/src/components/message-timeline.tsx +289 -32
  230. package/src/components/queue-surface-implementation.tsx +116 -60
  231. package/src/components/sandbox-files.tsx +2 -2
  232. package/src/components/sandbox-terminal.tsx +106 -41
  233. package/src/components/sandbox-workspace.tsx +215 -87
  234. package/src/components/session-chrome.tsx +53 -13
  235. package/src/components/timeline-annotation-selection.tsx +221 -0
  236. package/src/components/timeline-annotations-dialog.tsx +213 -0
  237. package/src/components/timeline-annotations.tsx +84 -0
  238. package/src/components/workbench-changes.tsx +188 -31
  239. package/src/hooks/use-attached-browsers.ts +152 -0
  240. package/src/hooks/use-browser-frame-stream.ts +316 -0
  241. package/src/hooks/use-browser-identities.ts +225 -0
  242. package/src/hooks/use-browser-session.ts +476 -0
  243. package/src/hooks/use-browser-sessions.ts +281 -0
  244. package/src/hooks/use-composer.ts +769 -24
  245. package/src/hooks/use-computer-frame-stream.ts +311 -0
  246. package/src/hooks/use-computer-session.ts +442 -0
  247. package/src/hooks/use-computer-sessions.ts +245 -0
  248. package/src/hooks/use-machine-chip.ts +5 -3
  249. package/src/hooks/use-machines.ts +33 -3
  250. package/src/hooks/use-packs.ts +51 -0
  251. package/src/hooks/use-sandbox-files.ts +98 -55
  252. package/src/hooks/use-sandbox-git.ts +93 -38
  253. package/src/hooks/use-sandbox-terminal.ts +3 -2
  254. package/src/hooks/use-session-capabilities.ts +59 -46
  255. package/src/hooks/use-terminal-stream.ts +102 -23
  256. package/src/hooks/use-video-artifact-playback.ts +28 -0
  257. package/src/hooks/use-workspace-edit.ts +2 -1
  258. package/src/index.ts +10 -0
  259. package/src/interaction.ts +71 -0
  260. package/src/lib/relay-wire.ts +4 -0
  261. package/src/lib/sandbox-liveness.ts +11 -0
  262. package/src/lib/terminal-capability.ts +10 -0
  263. package/src/session-context.ts +90 -0
  264. package/src/timeline/activity-rail.tsx +19 -3
  265. package/src/timeline/index.ts +3 -0
  266. package/src/timeline/parsers.ts +14 -1
  267. package/src/timeline/projection.ts +211 -14
  268. package/src/timeline/registry.ts +13 -1
  269. package/src/timeline/screenshot-lightbox.tsx +19 -9
  270. package/src/timeline/shared.tsx +56 -8
  271. package/src/timeline/tool-renderers.tsx +303 -50
  272. package/src/timeline/types.ts +55 -3
  273. package/styles/compiled.css +248 -15
  274. package/styles/index.css +15 -0
  275. package/styles/responsive.css +13 -1
  276. package/dist/chunk-EKZH6IDG.js.map +0 -1
  277. package/dist/chunk-FWXGSNXT.js.map +0 -1
  278. package/dist/chunk-LHLYWG5R.js.map +0 -1
  279. package/dist/chunk-O4VXOBGF.js.map +0 -1
  280. package/dist/chunk-XCNTXWBB.js.map +0 -1
  281. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  282. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  283. package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
  284. /package/dist/{chunk-I4MPFDRP.js.map → chunk-FWS3KC3Z.js.map} +0 -0
@@ -0,0 +1,733 @@
1
+ import {
2
+ SPREADSHEET_ARTIFACT_COMMAND_VERSION,
3
+ editableArtifactStableId,
4
+ spreadsheetSheetId,
5
+ type EditableArtifactSession,
6
+ type EditableSpreadsheetCellValue,
7
+ type EditableSpreadsheetMetadataProjection,
8
+ type EditableSpreadsheetSheetMetadata,
9
+ type EditableSpreadsheetViewportProjection,
10
+ type EditableSpreadsheetViewportQuery,
11
+ type SpreadsheetCellInput,
12
+ type SpreadsheetSheetGeneration,
13
+ } from "@opengeni/sdk/editable-artifacts";
14
+ import { PlusIcon } from "lucide-react";
15
+ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
16
+
17
+ import { cn } from "../../lib/cn";
18
+ import { ArtifactSurface } from "./artifact-surface";
19
+ import {
20
+ editableArtifactAccessRevoked,
21
+ editableArtifactStatusLabel,
22
+ EditableArtifactMessage,
23
+ useEditableArtifactView,
24
+ } from "./editable-artifact-ui";
25
+ import { SparseSpreadsheetCellIndex } from "./spreadsheet-canvas";
26
+ import {
27
+ SpreadsheetProjectionGrid,
28
+ type SpreadsheetCommit,
29
+ type SpreadsheetGridProjection,
30
+ type SpreadsheetRangeCommit,
31
+ type SpreadsheetSelection,
32
+ type SpreadsheetViewport,
33
+ } from "./spreadsheet-grid";
34
+
35
+ const EXCEL_MAX_ROWS = 1_048_576;
36
+ const EXCEL_MAX_COLUMNS = 16_384;
37
+ const INITIAL_VIEWPORT_ROWS = 64;
38
+ const INITIAL_VIEWPORT_COLUMNS = 32;
39
+ const MAX_INTERACTIVE_QUERY_CELLS = 65_536;
40
+ const MAX_INTERACTIVE_QUERY_BYTES = 8 * 1024 * 1024;
41
+ const EMPTY_FORMAT = Object.freeze({});
42
+ const EMPTY_SHEETS: readonly EditableSpreadsheetSheetMetadata[] = [];
43
+
44
+ export type EditableSpreadsheetGridProps = {
45
+ session: EditableArtifactSession;
46
+ sheet: EditableSpreadsheetSheetMetadata;
47
+ metadataRevision: bigint;
48
+ readOnly?: boolean | undefined;
49
+ rowCount?: number | undefined;
50
+ columnCount?: number | undefined;
51
+ overscanRows?: number | undefined;
52
+ overscanColumns?: number | undefined;
53
+ onSelectionChange?: ((selection: SpreadsheetSelection) => void) | undefined;
54
+ onCommit?: ((commit: SpreadsheetCommit) => void) | undefined;
55
+ onCommandError?: ((error: Error) => void) | undefined;
56
+ onViewportChange?: ((viewport: SpreadsheetViewport) => void) | undefined;
57
+ className?: string | undefined;
58
+ };
59
+
60
+ export type EditableSpreadsheetArtifactSurfaceProps = Omit<
61
+ EditableSpreadsheetGridProps,
62
+ "metadataRevision" | "sheet"
63
+ > & {
64
+ title?: string | undefined;
65
+ subtitle?: ReactNode | undefined;
66
+ actions?: ReactNode | undefined;
67
+ initialSheetId?: string | undefined;
68
+ allowAddSheet?: boolean | undefined;
69
+ };
70
+
71
+ type ProjectionState = {
72
+ query: EditableSpreadsheetViewportQuery;
73
+ projection: EditableSpreadsheetViewportProjection | null;
74
+ error: Error | null;
75
+ };
76
+
77
+ /**
78
+ * Durable spreadsheet editor over one SDK session. Canonical state remains in
79
+ * the dedicated Worker; React receives one bounded immutable viewport only.
80
+ */
81
+ export function EditableSpreadsheetGrid({
82
+ session,
83
+ sheet,
84
+ metadataRevision,
85
+ readOnly = false,
86
+ rowCount: requestedRowCount,
87
+ columnCount: requestedColumnCount,
88
+ overscanRows = 3,
89
+ overscanColumns = 2,
90
+ onSelectionChange,
91
+ onCommit,
92
+ onCommandError,
93
+ onViewportChange,
94
+ className,
95
+ }: EditableSpreadsheetGridProps) {
96
+ const rowCount = boundedSheetCount(requestedRowCount, EXCEL_MAX_ROWS, sheet.usedBounds?.endRow);
97
+ const columnCount = boundedSheetCount(
98
+ requestedColumnCount,
99
+ EXCEL_MAX_COLUMNS,
100
+ sheet.usedBounds?.endColumn,
101
+ );
102
+ const [state, setState] = useState<ProjectionState>(() => ({
103
+ query: initialViewportQuery(sheet.sheetId, rowCount, columnCount),
104
+ projection: null,
105
+ error: null,
106
+ }));
107
+ const activeCellRef = useRef({ row: 0, column: 0 });
108
+
109
+ useEffect(() => {
110
+ setState({
111
+ query: initialViewportQuery(sheet.sheetId, rowCount, columnCount),
112
+ projection: null,
113
+ error: null,
114
+ });
115
+ activeCellRef.current = { row: 0, column: 0 };
116
+ }, [columnCount, rowCount, session, sheet.sheetId]);
117
+
118
+ useEffect(() => {
119
+ const query = state.query;
120
+ return session.subscribeSpreadsheetViewport(
121
+ query,
122
+ (projection) => {
123
+ if (!sameViewport(projection, query)) return;
124
+ if (sheet.generationId !== null && projection.generationId !== sheet.generationId) {
125
+ setState((current) =>
126
+ sameViewportQuery(current.query, query)
127
+ ? {
128
+ ...current,
129
+ projection: null,
130
+ error: new Error("Spreadsheet generation changed; refreshing metadata"),
131
+ }
132
+ : current,
133
+ );
134
+ return;
135
+ }
136
+ setState((current) =>
137
+ sameViewportQuery(current.query, query) ? { query, projection, error: null } : current,
138
+ );
139
+ },
140
+ {
141
+ onError(error) {
142
+ setState((current) =>
143
+ sameViewportQuery(current.query, query) ? { ...current, error } : current,
144
+ );
145
+ },
146
+ },
147
+ );
148
+ }, [session, sheet.generationId, state.query]);
149
+
150
+ const projection = useMemo(
151
+ () =>
152
+ projectSdkViewport(
153
+ sheet,
154
+ metadataRevision,
155
+ state.query,
156
+ state.projection,
157
+ rowCount,
158
+ columnCount,
159
+ ),
160
+ [columnCount, metadataRevision, rowCount, sheet, state.projection, state.query],
161
+ );
162
+ const generation = useMemo(() => sheetGeneration(sheet), [sheet]);
163
+ const editable = !readOnly && generation !== null;
164
+
165
+ const handleCommit = useCallback(
166
+ async (commit: SpreadsheetCommit) => {
167
+ if (!generation) throw new Error("This sheet generation is not writable yet");
168
+ const input = spreadsheetCellInput(commit.input, commit.kind);
169
+ await session.applySpreadsheetCommands({
170
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
171
+ commands: [
172
+ {
173
+ kind: "cells.set",
174
+ sheet: generation,
175
+ anchor: { row: commit.cell.row, column: commit.cell.col },
176
+ rows: 1,
177
+ columns: 1,
178
+ cells: [input],
179
+ },
180
+ ],
181
+ });
182
+ onCommit?.(commit);
183
+ },
184
+ [generation, onCommit, session],
185
+ );
186
+
187
+ const handleClear = useCallback(
188
+ async (selection: SpreadsheetSelection) => {
189
+ if (!generation) throw new Error("This sheet generation is not writable yet");
190
+ const top = Math.min(selection.anchor.row, selection.focus.row);
191
+ const bottom = Math.max(selection.anchor.row, selection.focus.row);
192
+ const left = Math.min(selection.anchor.col, selection.focus.col);
193
+ const right = Math.max(selection.anchor.col, selection.focus.col);
194
+ await session.applySpreadsheetCommands({
195
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
196
+ commands: [
197
+ {
198
+ kind: "range.clear",
199
+ sheet: generation,
200
+ range: {
201
+ start: { row: top, column: left },
202
+ end: { row: bottom, column: right },
203
+ },
204
+ },
205
+ ],
206
+ });
207
+ },
208
+ [generation, session],
209
+ );
210
+
211
+ const handleCommitRange = useCallback(
212
+ async (commit: SpreadsheetRangeCommit) => {
213
+ if (!generation) throw new Error("This sheet generation is not writable yet");
214
+ await session.applySpreadsheetCommands({
215
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
216
+ commands: [
217
+ {
218
+ kind: "cells.set",
219
+ sheet: generation,
220
+ anchor: { row: commit.anchor.row, column: commit.anchor.col },
221
+ rows: commit.rows,
222
+ columns: commit.columns,
223
+ cells: commit.inputs.map((input) =>
224
+ spreadsheetCellInput(input, input.startsWith("=") ? "formula" : "value"),
225
+ ),
226
+ },
227
+ ],
228
+ });
229
+ },
230
+ [generation, session],
231
+ );
232
+
233
+ const handleSelection = useCallback(
234
+ (selection: SpreadsheetSelection) => {
235
+ activeCellRef.current = {
236
+ row: selection.focus.row,
237
+ column: selection.focus.col,
238
+ };
239
+ setState((current) => {
240
+ if (queryContains(current.query, selection.focus.row, selection.focus.col)) return current;
241
+ return {
242
+ query: boundedViewportQuery(
243
+ sheet.sheetId,
244
+ selection.focus.row,
245
+ selection.focus.row + 1,
246
+ selection.focus.col,
247
+ selection.focus.col + 1,
248
+ rowCount,
249
+ columnCount,
250
+ activeCellRef.current,
251
+ ),
252
+ projection: current.projection,
253
+ error: null,
254
+ };
255
+ });
256
+ onSelectionChange?.(selection);
257
+ },
258
+ [columnCount, onSelectionChange, rowCount, sheet.sheetId],
259
+ );
260
+
261
+ const handleViewport = useCallback(
262
+ (viewport: SpreadsheetViewport) => {
263
+ const next = boundedViewportQuery(
264
+ sheet.sheetId,
265
+ viewport.overscanRowStart,
266
+ viewport.overscanRowEnd,
267
+ viewport.overscanColumnStart,
268
+ viewport.overscanColumnEnd,
269
+ rowCount,
270
+ columnCount,
271
+ activeCellRef.current,
272
+ );
273
+ setState((current) =>
274
+ sameViewportQuery(current.query, next)
275
+ ? current
276
+ : { query: next, projection: current.projection, error: null },
277
+ );
278
+ onViewportChange?.(viewport);
279
+ },
280
+ [columnCount, onViewportChange, rowCount, sheet.sheetId],
281
+ );
282
+
283
+ return (
284
+ <div className={cn("relative h-full min-h-0", className)}>
285
+ <SpreadsheetProjectionGrid
286
+ projection={projection}
287
+ readOnly={!editable}
288
+ overscanRows={overscanRows}
289
+ overscanColumns={overscanColumns}
290
+ onSelectionChange={handleSelection}
291
+ commit={editable ? handleCommit : undefined}
292
+ commitRange={editable ? handleCommitRange : undefined}
293
+ clear={editable ? handleClear : undefined}
294
+ onCommandError={onCommandError}
295
+ onViewportChange={handleViewport}
296
+ />
297
+ {state.error ? (
298
+ <output
299
+ role="status"
300
+ className="pointer-events-none absolute bottom-2 left-2 z-50 rounded-og-sm border border-og-status-failed/30 bg-og-surface-1/95 px-2 py-1 text-og-xs text-og-status-failed shadow-og-sm"
301
+ style={{ maxWidth: "min(28rem, calc(100% - 1rem))" }}
302
+ >
303
+ {state.error.message}
304
+ </output>
305
+ ) : null}
306
+ </div>
307
+ );
308
+ }
309
+
310
+ /** Artifact chrome, sheet navigation, and one Worker-backed spreadsheet grid. */
311
+ export function EditableSpreadsheetArtifactSurface({
312
+ session,
313
+ title = "Workbook",
314
+ subtitle,
315
+ actions,
316
+ initialSheetId,
317
+ allowAddSheet = true,
318
+ readOnly = false,
319
+ ...gridProps
320
+ }: EditableSpreadsheetArtifactSurfaceProps) {
321
+ const { metadata, error: metadataError } = useSpreadsheetMetadata(session);
322
+ const view = useEditableArtifactView(session);
323
+ const [activeSheetId, setActiveSheetId] = useState<string | null>(initialSheetId ?? null);
324
+ const [creatingSheet, setCreatingSheet] = useState(false);
325
+ const [surfaceError, setSurfaceError] = useState<Error | null>(null);
326
+ const sheets = metadata?.sheets ?? EMPTY_SHEETS;
327
+ const activeSheet =
328
+ sheets.find((sheet) => sheet.sheetId === activeSheetId) ??
329
+ (initialSheetId ? sheets.find((sheet) => sheet.sheetId === initialSheetId) : undefined) ??
330
+ sheets[0] ??
331
+ null;
332
+ const writable = !readOnly && view.writable;
333
+ const accessRevoked = editableArtifactAccessRevoked(view);
334
+
335
+ const addSheet = useCallback(async () => {
336
+ if (!writable || creatingSheet) return;
337
+ setCreatingSheet(true);
338
+ setSurfaceError(null);
339
+ try {
340
+ const after = activeSheet ? sheetGeneration(activeSheet) : null;
341
+ const created = await session.createSpreadsheetSheet({
342
+ name: nextAvailableSheetName(sheets),
343
+ after,
344
+ });
345
+ setActiveSheetId(created.sheetId);
346
+ } catch (cause) {
347
+ setSurfaceError(asError(cause));
348
+ } finally {
349
+ setCreatingSheet(false);
350
+ }
351
+ }, [activeSheet, creatingSheet, session, sheets, writable]);
352
+
353
+ const footer = (
354
+ <div
355
+ className="flex min-h-9 items-center gap-1 overflow-x-auto px-2"
356
+ role="tablist"
357
+ aria-label="Worksheets"
358
+ >
359
+ {sheets.map((sheet) => (
360
+ <button
361
+ key={`${sheet.sheetId}:${sheet.generationId ?? "pending"}`}
362
+ type="button"
363
+ role="tab"
364
+ aria-selected={sheet.sheetId === activeSheet?.sheetId}
365
+ onClick={() => setActiveSheetId(sheet.sheetId)}
366
+ className={cn(
367
+ "h-7 shrink-0 rounded-og-sm px-2.5 text-og-sm outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-og-accent",
368
+ sheet.sheetId === activeSheet?.sheetId
369
+ ? "bg-og-surface-3 font-medium text-og-fg"
370
+ : "text-og-fg-muted hover:bg-og-surface-3 hover:text-og-fg",
371
+ )}
372
+ >
373
+ {sheet.name}
374
+ </button>
375
+ ))}
376
+ {writable && allowAddSheet ? (
377
+ <button
378
+ type="button"
379
+ onClick={() => void addSheet()}
380
+ disabled={creatingSheet}
381
+ aria-label={creatingSheet ? "Adding worksheet" : "Add worksheet"}
382
+ className="grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted outline-hidden hover:bg-og-surface-3 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50 [&>svg]:size-3.5"
383
+ >
384
+ <PlusIcon />
385
+ </button>
386
+ ) : null}
387
+ </div>
388
+ );
389
+ const error = surfaceError ?? metadataError;
390
+
391
+ return (
392
+ <ArtifactSurface
393
+ modality="spreadsheet"
394
+ title={title}
395
+ subtitle={
396
+ subtitle ??
397
+ (accessRevoked
398
+ ? editableArtifactStatusLabel(view)
399
+ : metadata
400
+ ? `${sheets.length} sheet${sheets.length === 1 ? "" : "s"}`
401
+ : editableArtifactStatusLabel(view))
402
+ }
403
+ actions={actions}
404
+ footer={accessRevoked ? undefined : footer}
405
+ busy={!accessRevoked && !metadata && !error}
406
+ >
407
+ {accessRevoked ? (
408
+ <EditableArtifactMessage
409
+ title="Access changed"
410
+ detail={editableArtifactStatusLabel(view)}
411
+ />
412
+ ) : activeSheet && metadata ? (
413
+ <EditableSpreadsheetGrid
414
+ key={`${activeSheet.sheetId}:${activeSheet.generationId ?? "pending"}`}
415
+ {...gridProps}
416
+ session={session}
417
+ sheet={activeSheet}
418
+ metadataRevision={metadata.revision}
419
+ readOnly={!writable}
420
+ />
421
+ ) : error ? (
422
+ <EditableArtifactMessage title="Could not open this workbook" detail={error.message} />
423
+ ) : metadata ? (
424
+ <div className="grid h-full place-items-center bg-og-bg p-6 text-center">
425
+ <div>
426
+ <p className="text-og-base font-medium text-og-fg">This workbook has no worksheets.</p>
427
+ {writable && allowAddSheet ? (
428
+ <button
429
+ type="button"
430
+ onClick={() => void addSheet()}
431
+ disabled={creatingSheet}
432
+ className="mt-3 rounded-og-sm bg-og-accent-deep px-3 py-1.5 text-og-sm font-medium text-og-accent-fg outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50"
433
+ >
434
+ {creatingSheet ? "Adding…" : "Add worksheet"}
435
+ </button>
436
+ ) : null}
437
+ </div>
438
+ </div>
439
+ ) : (
440
+ <EditableArtifactMessage
441
+ title="Opening workbook"
442
+ detail={editableArtifactStatusLabel(view)}
443
+ />
444
+ )}
445
+ </ArtifactSurface>
446
+ );
447
+ }
448
+
449
+ function useSpreadsheetMetadata(session: EditableArtifactSession): {
450
+ metadata: EditableSpreadsheetMetadataProjection | null;
451
+ error: Error | null;
452
+ } {
453
+ const [state, setState] = useState<{
454
+ metadata: EditableSpreadsheetMetadataProjection | null;
455
+ error: Error | null;
456
+ }>({ metadata: null, error: null });
457
+ useEffect(
458
+ () =>
459
+ session.subscribeSpreadsheetMetadata({}, (metadata) => setState({ metadata, error: null }), {
460
+ onError: (error) => setState((current) => ({ ...current, error })),
461
+ }),
462
+ [session],
463
+ );
464
+ return state;
465
+ }
466
+
467
+ function projectSdkViewport(
468
+ sheet: EditableSpreadsheetSheetMetadata,
469
+ metadataRevision: bigint,
470
+ query: EditableSpreadsheetViewportQuery,
471
+ viewport: EditableSpreadsheetViewportProjection | null,
472
+ rowCount: number,
473
+ columnCount: number,
474
+ ): SpreadsheetGridProjection {
475
+ const current = viewport && sameViewport(viewport, query) ? viewport : null;
476
+ const projectedCells = (current?.cells ?? []).map((cell) => ({
477
+ row: cell.row,
478
+ col: cell.column,
479
+ value: projectedCellValue(cell.value),
480
+ formula: cell.formula,
481
+ format: EMPTY_FORMAT,
482
+ }));
483
+ const cells = new SparseSpreadsheetCellIndex(projectedCells);
484
+ const byCoordinate = new Map(
485
+ projectedCells.map((cell) => [`${cell.row}:${cell.col}`, cell] as const),
486
+ );
487
+ const used = sheet.usedBounds;
488
+ return {
489
+ sheetId: sheet.sheetId,
490
+ sheetName: sheet.name,
491
+ generationId: current?.generationId ?? sheet.generationId,
492
+ revision: `${metadataRevision}:${current?.revision ?? "loading"}`,
493
+ dimensionRevision: metadataRevision.toString(),
494
+ rowCount,
495
+ columnCount,
496
+ usedRange: used
497
+ ? {
498
+ row: used.startRow,
499
+ col: used.startColumn,
500
+ rowCount: used.endRow - used.startRow + 1,
501
+ colCount: used.endColumn - used.startColumn + 1,
502
+ }
503
+ : null,
504
+ coverage: current
505
+ ? {
506
+ rowStart: current.startRow,
507
+ rowEnd: current.startRow + current.rowCount,
508
+ columnStart: current.startColumn,
509
+ columnEnd: current.startColumn + current.columnCount,
510
+ }
511
+ : {
512
+ rowStart: query.startRow,
513
+ rowEnd: query.startRow,
514
+ columnStart: query.startColumn,
515
+ columnEnd: query.startColumn,
516
+ },
517
+ cells,
518
+ valueAt: (cell) => cell.value,
519
+ readCell: (row, column) => {
520
+ const cell = byCoordinate.get(`${row}:${column}`);
521
+ return cell
522
+ ? {
523
+ value: cell.value,
524
+ input: cell.formula ?? displayValue(cell.value),
525
+ format: cell.format,
526
+ }
527
+ : null;
528
+ },
529
+ };
530
+ }
531
+
532
+ function initialViewportQuery(
533
+ sheetId: string,
534
+ rowCount: number,
535
+ columnCount: number,
536
+ ): EditableSpreadsheetViewportQuery {
537
+ return {
538
+ sheetId,
539
+ startRow: 0,
540
+ startColumn: 0,
541
+ rowCount: Math.min(rowCount, INITIAL_VIEWPORT_ROWS),
542
+ columnCount: Math.min(columnCount, INITIAL_VIEWPORT_COLUMNS),
543
+ maxCells: MAX_INTERACTIVE_QUERY_CELLS,
544
+ maxBytes: MAX_INTERACTIVE_QUERY_BYTES,
545
+ };
546
+ }
547
+
548
+ function boundedViewportQuery(
549
+ sheetId: string,
550
+ desiredRowStart: number,
551
+ desiredRowEnd: number,
552
+ desiredColumnStart: number,
553
+ desiredColumnEnd: number,
554
+ totalRows: number,
555
+ totalColumns: number,
556
+ focus: { row: number; column: number },
557
+ ): EditableSpreadsheetViewportQuery {
558
+ let rowStart = clampInteger(desiredRowStart, 0, totalRows - 1);
559
+ let rowEnd = clampInteger(desiredRowEnd, rowStart + 1, totalRows);
560
+ let columnStart = clampInteger(desiredColumnStart, 0, totalColumns - 1);
561
+ let columnEnd = clampInteger(desiredColumnEnd, columnStart + 1, totalColumns);
562
+ const desiredRows = rowEnd - rowStart;
563
+ const desiredColumns = columnEnd - columnStart;
564
+ if (desiredRows * desiredColumns > MAX_INTERACTIVE_QUERY_CELLS) {
565
+ const columns = Math.min(
566
+ desiredColumns,
567
+ Math.max(1, Math.floor(Math.sqrt(MAX_INTERACTIVE_QUERY_CELLS * 2))),
568
+ );
569
+ const rows = Math.max(1, Math.floor(MAX_INTERACTIVE_QUERY_CELLS / columns));
570
+ columnStart = clampWindowStart(columnStart, columnEnd, focus.column, columns, totalColumns);
571
+ columnEnd = Math.min(totalColumns, columnStart + columns);
572
+ rowStart = clampWindowStart(rowStart, rowEnd, focus.row, rows, totalRows);
573
+ rowEnd = Math.min(totalRows, rowStart + rows);
574
+ }
575
+ return {
576
+ sheetId,
577
+ startRow: rowStart,
578
+ startColumn: columnStart,
579
+ rowCount: rowEnd - rowStart,
580
+ columnCount: columnEnd - columnStart,
581
+ maxCells: MAX_INTERACTIVE_QUERY_CELLS,
582
+ maxBytes: MAX_INTERACTIVE_QUERY_BYTES,
583
+ };
584
+ }
585
+
586
+ function clampWindowStart(
587
+ desiredStart: number,
588
+ desiredEnd: number,
589
+ focus: number,
590
+ size: number,
591
+ total: number,
592
+ ): number {
593
+ if (desiredEnd - desiredStart <= size) return Math.min(desiredStart, Math.max(0, total - size));
594
+ const centered = focus - Math.floor(size / 2);
595
+ return Math.max(desiredStart, Math.min(desiredEnd - size, centered));
596
+ }
597
+
598
+ function queryContains(
599
+ query: EditableSpreadsheetViewportQuery,
600
+ row: number,
601
+ column: number,
602
+ ): boolean {
603
+ return (
604
+ row >= query.startRow &&
605
+ row < query.startRow + query.rowCount &&
606
+ column >= query.startColumn &&
607
+ column < query.startColumn + query.columnCount
608
+ );
609
+ }
610
+
611
+ function sameViewport(
612
+ projection: EditableSpreadsheetViewportProjection,
613
+ query: EditableSpreadsheetViewportQuery,
614
+ ): boolean {
615
+ return (
616
+ projection.sheetId === query.sheetId &&
617
+ projection.startRow === query.startRow &&
618
+ projection.startColumn === query.startColumn &&
619
+ projection.rowCount === query.rowCount &&
620
+ projection.columnCount === query.columnCount
621
+ );
622
+ }
623
+
624
+ function sameViewportQuery(
625
+ left: EditableSpreadsheetViewportQuery,
626
+ right: EditableSpreadsheetViewportQuery,
627
+ ): boolean {
628
+ return (
629
+ left.sheetId === right.sheetId &&
630
+ left.startRow === right.startRow &&
631
+ left.startColumn === right.startColumn &&
632
+ left.rowCount === right.rowCount &&
633
+ left.columnCount === right.columnCount &&
634
+ left.maxCells === right.maxCells &&
635
+ left.maxBytes === right.maxBytes
636
+ );
637
+ }
638
+
639
+ function sheetGeneration(
640
+ sheet: EditableSpreadsheetSheetMetadata,
641
+ ): SpreadsheetSheetGeneration | null {
642
+ if (sheet.generationId === null) return null;
643
+ return {
644
+ kind: "generation",
645
+ sheetId: spreadsheetSheetId(sheet.sheetId),
646
+ creationOperationId: editableArtifactStableId(sheet.generationId),
647
+ };
648
+ }
649
+
650
+ function spreadsheetCellInput(
651
+ input: string,
652
+ kind: SpreadsheetCommit["kind"],
653
+ ): SpreadsheetCellInput {
654
+ if (kind === "formula") return { formula: input, cached: null };
655
+ if (input === "") return null;
656
+ const normalized = input.trim();
657
+ if (/^(?:true|false)$/i.test(normalized)) return normalized.toLowerCase() === "true";
658
+ if (/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) {
659
+ const value = Number(normalized);
660
+ if (Number.isFinite(value)) return value;
661
+ }
662
+ return input;
663
+ }
664
+
665
+ function projectedCellValue(value: EditableSpreadsheetCellValue): unknown {
666
+ switch (value.kind) {
667
+ case "empty":
668
+ return null;
669
+ case "boolean":
670
+ case "number":
671
+ case "text":
672
+ return value.value;
673
+ case "date":
674
+ return new Date(value.value);
675
+ case "error":
676
+ return spreadsheetErrorText(value.value);
677
+ }
678
+ }
679
+
680
+ function spreadsheetErrorText(
681
+ value: Extract<EditableSpreadsheetCellValue, { kind: "error" }>["value"],
682
+ ): string {
683
+ if (typeof value === "object") return value.custom;
684
+ return {
685
+ null: "#NULL!",
686
+ divide_by_zero: "#DIV/0!",
687
+ value: "#VALUE!",
688
+ reference: "#REF!",
689
+ name: "#NAME?",
690
+ number: "#NUM!",
691
+ not_available: "#N/A",
692
+ spill: "#SPILL!",
693
+ calculation: "#CALC!",
694
+ }[value];
695
+ }
696
+
697
+ function boundedSheetCount(
698
+ requested: number | undefined,
699
+ maximum: number,
700
+ usedEnd: number | undefined,
701
+ ): number {
702
+ const requestedCount =
703
+ requested === undefined || !Number.isFinite(requested)
704
+ ? maximum
705
+ : Math.max(1, Math.min(maximum, Math.floor(requested)));
706
+ const usedCount =
707
+ usedEnd === undefined || !Number.isSafeInteger(usedEnd)
708
+ ? 1
709
+ : Math.max(1, Math.min(maximum, usedEnd + 1));
710
+ return Math.max(requestedCount, usedCount);
711
+ }
712
+
713
+ function displayValue(value: unknown): string {
714
+ if (value === null || value === undefined) return "";
715
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : value.toISOString();
716
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
717
+ return String(value);
718
+ }
719
+
720
+ function nextAvailableSheetName(sheets: readonly EditableSpreadsheetSheetMetadata[]): string {
721
+ let index = sheets.length + 1;
722
+ while (sheets.some((sheet) => sheet.name === `Sheet${index}`)) index += 1;
723
+ return `Sheet${index}`;
724
+ }
725
+
726
+ function clampInteger(value: number, minimum: number, maximum: number): number {
727
+ if (!Number.isFinite(value)) return minimum;
728
+ return Math.min(maximum, Math.max(minimum, Math.floor(value)));
729
+ }
730
+
731
+ function asError(cause: unknown): Error {
732
+ return cause instanceof Error ? cause : new Error("Spreadsheet change failed");
733
+ }