@opengeni/react 0.46.1 → 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 (341) hide show
  1. package/README.md +158 -14
  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-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
  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-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  23. package/dist/chunk-FK6VG4LL.js.map +1 -0
  24. package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
  25. package/dist/chunk-FWS3KC3Z.js.map +1 -0
  26. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  27. package/dist/chunk-HDBG7XGO.js.map +1 -0
  28. package/dist/chunk-HUIKIDM5.js +2148 -0
  29. package/dist/chunk-HUIKIDM5.js.map +1 -0
  30. package/dist/{chunk-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
  31. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  32. package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
  33. package/dist/chunk-JR3QFY6B.js.map +1 -0
  34. package/dist/chunk-JVYQRRC4.js +53 -0
  35. package/dist/chunk-JVYQRRC4.js.map +1 -0
  36. package/dist/chunk-KCGXPP5V.js +157 -0
  37. package/dist/chunk-KCGXPP5V.js.map +1 -0
  38. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  39. package/dist/chunk-KZ73RL76.js.map +1 -0
  40. package/dist/chunk-PEI6RANI.js +3234 -0
  41. package/dist/chunk-PEI6RANI.js.map +1 -0
  42. package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
  43. package/dist/chunk-ROIRTM37.js.map +1 -0
  44. package/dist/chunk-VZTQ73XT.js +86 -0
  45. package/dist/chunk-VZTQ73XT.js.map +1 -0
  46. package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
  47. package/dist/chunk-WSK7G5LE.js.map +1 -0
  48. package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
  49. package/dist/chunk-WYA65Y3F.js.map +1 -0
  50. package/dist/{chunk-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
  51. package/dist/chunk-X4S56Y2H.js.map +1 -0
  52. package/dist/client.d.ts +7 -1
  53. package/dist/clipboard.d.ts +1 -0
  54. package/dist/clipboard.js +7 -0
  55. package/dist/clipboard.js.map +1 -0
  56. package/dist/commands/index.d.ts +3 -3
  57. package/dist/commands/registry.d.ts +1 -1
  58. package/dist/commands/types.d.ts +1 -1
  59. package/dist/components/approval-surface.d.ts +2 -2
  60. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  61. package/dist/components/artifacts/document-editor.d.ts +172 -0
  62. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  63. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  64. package/dist/components/artifacts/editable-document.d.ts +24 -0
  65. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  66. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  67. package/dist/components/artifacts/index.d.ts +8 -0
  68. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  69. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  70. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  71. package/dist/components/browser-viewer.d.ts +29 -0
  72. package/dist/components/chat-composer.d.ts +13 -8
  73. package/dist/components/code-editor.d.ts +25 -3
  74. package/dist/components/command-palette.d.ts +2 -2
  75. package/dist/components/composer-transcription-control.d.ts +4 -2
  76. package/dist/components/composer.d.ts +29 -15
  77. package/dist/components/computer-viewer.d.ts +23 -0
  78. package/dist/components/copy-button.d.ts +2 -2
  79. package/dist/components/desktop-viewer.d.ts +25 -21
  80. package/dist/components/diff-view.d.ts +1 -1
  81. package/dist/components/enrollment-consent.d.ts +1 -1
  82. package/dist/components/enrollment-device-flow.d.ts +1 -1
  83. package/dist/components/file-browser.d.ts +2 -2
  84. package/dist/components/fleet-tile.d.ts +1 -1
  85. package/dist/components/generated-video-player.d.ts +10 -0
  86. package/dist/components/human-input-form.d.ts +1 -1
  87. package/dist/components/human-input-surface.d.ts +2 -2
  88. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  89. package/dist/components/machine-card.d.ts +2 -2
  90. package/dist/components/machine-dock-bar.d.ts +3 -3
  91. package/dist/components/machine-health-pill.d.ts +2 -2
  92. package/dist/components/machine-input-display.d.ts +1 -1
  93. package/dist/components/machine-metrics.d.ts +2 -2
  94. package/dist/components/machine-status-pill.d.ts +4 -4
  95. package/dist/components/machines/health.d.ts +1 -1
  96. package/dist/components/machines/machine-detail.d.ts +5 -3
  97. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  98. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  99. package/dist/components/machines/series.d.ts +2 -2
  100. package/dist/components/machines-dashboard.d.ts +5 -5
  101. package/dist/components/markdown.d.ts +1 -1
  102. package/dist/components/message-timeline.d.ts +22 -5
  103. package/dist/components/model-picker.d.ts +2 -2
  104. package/dist/components/model-policy-picker.d.ts +7 -7
  105. package/dist/components/pierre-diff.d.ts +1 -1
  106. package/dist/components/pierre-file.d.ts +1 -1
  107. package/dist/components/queue-draft-policy.d.ts +1 -1
  108. package/dist/components/queue-surface-implementation.d.ts +3 -3
  109. package/dist/components/queue-surface-state.d.ts +4 -4
  110. package/dist/components/queue-surface.d.ts +5 -5
  111. package/dist/components/sandbox-files.d.ts +5 -5
  112. package/dist/components/sandbox-terminal.d.ts +50 -4
  113. package/dist/components/sandbox-workspace.d.ts +30 -10
  114. package/dist/components/session-chrome.d.ts +4 -4
  115. package/dist/components/session-status.d.ts +2 -2
  116. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  117. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  118. package/dist/components/timeline-annotations.d.ts +13 -0
  119. package/dist/components/tooltip.d.ts +4 -4
  120. package/dist/components/user-message-body.d.ts +2 -2
  121. package/dist/components/workbench-changes.d.ts +2 -2
  122. package/dist/components/workspace-dock.d.ts +3 -1
  123. package/dist/composer.d.ts +11 -11
  124. package/dist/composer.js +7 -7
  125. package/dist/hooks/internal.d.ts +7 -1
  126. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  130. package/dist/hooks/use-browser-identities.d.ts +26 -0
  131. package/dist/hooks/use-browser-session.d.ts +28 -0
  132. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  133. package/dist/hooks/use-codex-accounts.d.ts +3 -3
  134. package/dist/hooks/use-composer.d.ts +36 -9
  135. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  136. package/dist/hooks/use-computer-session.d.ts +28 -0
  137. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  138. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  139. package/dist/hooks/use-environments.d.ts +11 -5
  140. package/dist/hooks/use-file-attachments.d.ts +1 -1
  141. package/dist/hooks/use-goal.d.ts +2 -2
  142. package/dist/hooks/use-human-input.d.ts +2 -2
  143. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  144. package/dist/hooks/use-machine-chip.d.ts +2 -2
  145. package/dist/hooks/use-machines.d.ts +10 -3
  146. package/dist/hooks/use-packs.d.ts +7 -2
  147. package/dist/hooks/use-rigs.d.ts +1 -1
  148. package/dist/hooks/use-sandbox-files.d.ts +25 -4
  149. package/dist/hooks/use-sandbox-git.d.ts +13 -5
  150. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  151. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  152. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  153. package/dist/hooks/use-session-control.d.ts +1 -1
  154. package/dist/hooks/use-session-events.d.ts +2 -2
  155. package/dist/hooks/use-session-lineage.d.ts +1 -1
  156. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  157. package/dist/hooks/use-session.d.ts +2 -2
  158. package/dist/hooks/use-slash-commands.d.ts +1 -1
  159. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  160. package/dist/hooks/use-transcription.d.ts +2 -2
  161. package/dist/hooks/use-turn-queue.d.ts +2 -2
  162. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  163. package/dist/hooks/use-voice-input.d.ts +6 -4
  164. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  165. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  166. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  167. package/dist/hooks/use-workspaces.d.ts +1 -1
  168. package/dist/index.d.ts +158 -154
  169. package/dist/index.js +1718 -908
  170. package/dist/index.js.map +1 -1
  171. package/dist/interaction.d.ts +23 -0
  172. package/dist/interaction.js +36 -0
  173. package/dist/interaction.js.map +1 -0
  174. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  175. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  176. package/dist/lib/composer-responsive-context.d.ts +9 -0
  177. package/dist/lib/relay-wire.d.ts +4 -0
  178. package/dist/lib/sandbox-liveness.d.ts +9 -0
  179. package/dist/lib/terminal-capability.d.ts +5 -0
  180. package/dist/lib/use-portal-token-style.d.ts +14 -3
  181. package/dist/lib/xterm-theme.d.ts +1 -1
  182. package/dist/machines.d.ts +30 -30
  183. package/dist/machines.js +3 -3
  184. package/dist/provider.d.ts +4 -4
  185. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
  186. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  187. package/dist/realtime/dropdown-menu.d.ts +8 -8
  188. package/dist/realtime/realtime-control.d.ts +15 -6
  189. package/dist/realtime.d.ts +4 -4
  190. package/dist/realtime.js +30 -33
  191. package/dist/realtime.js.map +1 -1
  192. package/dist/session-context.d.ts +19 -1
  193. package/dist/session-ui.d.ts +14 -14
  194. package/dist/session-ui.js +6 -5
  195. package/dist/session.d.ts +28 -28
  196. package/dist/session.js +5 -5
  197. package/dist/timeline/activity-rail.d.ts +6 -3
  198. package/dist/timeline/disclosure-context.d.ts +1 -1
  199. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  200. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  201. package/dist/timeline/index.d.ts +19 -19
  202. package/dist/timeline/parsers.d.ts +6 -3
  203. package/dist/timeline/projection.d.ts +3 -3
  204. package/dist/timeline/registry.d.ts +10 -1
  205. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  206. package/dist/timeline/shared.d.ts +21 -10
  207. package/dist/timeline/tool-diff.d.ts +2 -2
  208. package/dist/timeline/tool-renderers.d.ts +1 -1
  209. package/dist/timeline/turn-summary.d.ts +3 -3
  210. package/dist/timeline/types.d.ts +42 -2
  211. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  212. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  213. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  214. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  215. package/dist/voice-recording-store.d.ts +8 -2
  216. package/package.json +52 -7
  217. package/src/artifacts-document.ts +31 -0
  218. package/src/artifacts-presentation.ts +36 -0
  219. package/src/artifacts-spreadsheet.ts +21 -0
  220. package/src/artifacts.ts +86 -0
  221. package/src/client.ts +90 -1
  222. package/src/clipboard.ts +2 -0
  223. package/src/components/artifacts/artifact-surface.tsx +93 -0
  224. package/src/components/artifacts/document-editor.tsx +2255 -0
  225. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  226. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  227. package/src/components/artifacts/editable-document.tsx +657 -0
  228. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  229. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  230. package/src/components/artifacts/index.ts +99 -0
  231. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  232. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  233. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  234. package/src/components/browser-viewer.tsx +1592 -0
  235. package/src/components/chat-composer.tsx +25 -2
  236. package/src/components/code-editor.tsx +90 -31
  237. package/src/components/command-palette.tsx +1 -1
  238. package/src/components/composer-transcription-control.tsx +51 -22
  239. package/src/components/composer.tsx +49 -23
  240. package/src/components/computer-viewer.tsx +1139 -0
  241. package/src/components/copy-button.tsx +1 -1
  242. package/src/components/desktop-viewer.tsx +103 -75
  243. package/src/components/file-browser.tsx +39 -17
  244. package/src/components/generated-video-player.tsx +89 -0
  245. package/src/components/human-input-form.tsx +2 -2
  246. package/src/components/interactive-workbench-viewers.ts +2 -0
  247. package/src/components/machine-card.tsx +1 -1
  248. package/src/components/machine-input-display.ts +3 -0
  249. package/src/components/machines/machine-detail.tsx +39 -15
  250. package/src/components/machines-dashboard.tsx +7 -6
  251. package/src/components/message-timeline.tsx +327 -37
  252. package/src/components/model-picker.tsx +2 -2
  253. package/src/components/model-policy-picker.tsx +16 -15
  254. package/src/components/queue-surface-implementation.tsx +135 -75
  255. package/src/components/queue-surface-state.tsx +2 -2
  256. package/src/components/sandbox-files.tsx +142 -62
  257. package/src/components/sandbox-terminal.tsx +119 -35
  258. package/src/components/sandbox-workspace.tsx +394 -228
  259. package/src/components/session-chrome.tsx +67 -27
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/tooltip.tsx +13 -16
  264. package/src/components/user-message-body.tsx +1 -1
  265. package/src/components/workbench-changes.tsx +192 -35
  266. package/src/components/workspace-dock.tsx +13 -5
  267. package/src/composer.ts +3 -0
  268. package/src/hooks/internal.ts +132 -38
  269. package/src/hooks/use-attached-browsers.ts +152 -0
  270. package/src/hooks/use-browser-frame-stream.ts +316 -0
  271. package/src/hooks/use-browser-identities.ts +225 -0
  272. package/src/hooks/use-browser-session.ts +476 -0
  273. package/src/hooks/use-browser-sessions.ts +281 -0
  274. package/src/hooks/use-codex-accounts.ts +4 -4
  275. package/src/hooks/use-composer.ts +769 -24
  276. package/src/hooks/use-computer-frame-stream.ts +311 -0
  277. package/src/hooks/use-computer-session.ts +442 -0
  278. package/src/hooks/use-computer-sessions.ts +245 -0
  279. package/src/hooks/use-environments.ts +24 -5
  280. package/src/hooks/use-file-attachments.ts +70 -32
  281. package/src/hooks/use-goal.ts +17 -5
  282. package/src/hooks/use-machine-chip.ts +5 -3
  283. package/src/hooks/use-machines.ts +79 -3
  284. package/src/hooks/use-packs.ts +51 -0
  285. package/src/hooks/use-sandbox-files.ts +382 -88
  286. package/src/hooks/use-sandbox-git.ts +208 -46
  287. package/src/hooks/use-sandbox-terminal.ts +3 -2
  288. package/src/hooks/use-session-capabilities.ts +94 -70
  289. package/src/hooks/use-session-events.ts +18 -4
  290. package/src/hooks/use-terminal-stream.ts +102 -23
  291. package/src/hooks/use-transcription.ts +4 -14
  292. package/src/hooks/use-turn-queue.ts +17 -5
  293. package/src/hooks/use-video-artifact-playback.ts +28 -0
  294. package/src/hooks/use-voice-input.ts +163 -19
  295. package/src/hooks/use-windowed-sections.ts +9 -7
  296. package/src/hooks/use-workspace-edit.ts +2 -1
  297. package/src/index.ts +21 -6
  298. package/src/interaction.ts +71 -0
  299. package/src/lib/composer-responsive-context.ts +15 -0
  300. package/src/lib/relay-wire.ts +4 -0
  301. package/src/lib/sandbox-liveness.ts +11 -0
  302. package/src/lib/terminal-capability.ts +10 -0
  303. package/src/lib/use-portal-token-style.ts +60 -15
  304. package/src/provider.tsx +52 -7
  305. package/src/realtime/dropdown-menu.tsx +15 -19
  306. package/src/realtime/realtime-control.tsx +16 -5
  307. package/src/session-context.ts +90 -0
  308. package/src/timeline/activity-rail.tsx +31 -6
  309. package/src/timeline/index.ts +4 -1
  310. package/src/timeline/parsers.ts +46 -20
  311. package/src/timeline/projection.ts +233 -13
  312. package/src/timeline/registry.ts +19 -0
  313. package/src/timeline/screenshot-lightbox.tsx +30 -9
  314. package/src/timeline/shared.tsx +59 -11
  315. package/src/timeline/tool-renderers.tsx +421 -24
  316. package/src/timeline/turn-summary.tsx +2 -2
  317. package/src/timeline/types.ts +58 -2
  318. package/src/voice-recording-store.ts +15 -0
  319. package/styles/compiled.css +5298 -0
  320. package/styles/compiled.d.ts +2 -0
  321. package/styles/effective-tokens.css +27 -0
  322. package/styles/index.css +18 -0
  323. package/styles/responsive.css +208 -0
  324. package/styles/responsive.d.ts +2 -0
  325. package/styles/tokens.css +50 -17
  326. package/dist/chunk-3PB7MIT6.js.map +0 -1
  327. package/dist/chunk-6EDV6YFE.js.map +0 -1
  328. package/dist/chunk-EB67J347.js.map +0 -1
  329. package/dist/chunk-EKZH6IDG.js.map +0 -1
  330. package/dist/chunk-FGZUCXZF.js.map +0 -1
  331. package/dist/chunk-HFO4ERGQ.js.map +0 -1
  332. package/dist/chunk-JB5725BI.js +0 -48
  333. package/dist/chunk-JB5725BI.js.map +0 -1
  334. package/dist/chunk-LGVMUIRV.js.map +0 -1
  335. package/dist/chunk-LWR4MXSS.js.map +0 -1
  336. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  337. package/dist/chunk-U3CSCMWA.js.map +0 -1
  338. package/dist/chunk-VCBFUSUZ.js.map +0 -1
  339. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  340. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  341. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
@@ -96,7 +96,7 @@ export function CopyButton({
96
96
  "inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm pointer-coarse:size-11",
97
97
  "text-og-fg-subtle transition-[opacity,color,background-color] duration-150",
98
98
  "hover:bg-og-surface-2/80 hover:text-og-fg",
99
- "focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent/40",
99
+ "focus-visible:opacity-100 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent/40",
100
100
  reveal === "group-hover" &&
101
101
  "opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 pointer-coarse:opacity-70",
102
102
  copied && "opacity-100 text-og-accent",
@@ -28,22 +28,26 @@ export type DesktopViewerProps = {
28
28
  * Defaults to `new WebSocket(url)`. Mirrors `rfbFactory`. */
29
29
  webSocketFactory?: DesktopWebSocketFactory | undefined;
30
30
  /**
31
- * Consent gate for the un-redacted (and possibly shared) pixel plane. Rendered
32
- * BEFORE connecting whenever the desktop requires acknowledgment that hasn't
33
- * been given. Call `onAccept` to record consent (the host wires it to
34
- * `client.acknowledgeStream` + a re-negotiate). When omitted, a default
35
- * banner is shown.
31
+ * @deprecated Desktop acknowledgment is automatic. Retained as an ignored
32
+ * compatibility prop so existing embedders keep compiling.
36
33
  */
37
34
  renderConsentGate?: ((onAccept: () => void, shared: boolean) => ReactNode) | undefined;
38
- /** Called when the default consent gate's accept button is pressed. */
39
- onAcknowledge?: (() => void) | undefined;
40
35
  /**
41
- * Whether the host has the viewer attach engaged (i.e. the user has opted into
42
- * watching the parent's `watchDesktop`/`attachDesktop`). Drives the
43
- * cold-state behaviour: when watching, a cold-but-warmable lease AUTO-WARMS
44
- * (and re-warms when the box drains) instead of dead-ending. When omitted we
45
- * infer it from a recorded consent (the default gate's accept), so the
46
- * component still self-heals after the first acknowledgment.
36
+ * Called once when the viewer mounts. Hosts use this to engage the desktop
37
+ * holder immediately when the Desktop tab opens.
38
+ */
39
+ onActivate?: (() => void) | undefined;
40
+ /**
41
+ * Called automatically when the server requires acknowledgment of the
42
+ * un-redacted (and possibly shared) pixel plane. The viewer shows its ordinary
43
+ * opening state while this completes; no click-through gate is rendered.
44
+ */
45
+ onAcknowledge?: (() => void | Promise<void>) | undefined;
46
+ /**
47
+ * Whether the host has the viewer attach engaged. Drives the cold-state
48
+ * behaviour: when watching, a cold-but-warmable lease AUTO-WARMS (and re-warms
49
+ * when the box drains) instead of dead-ending. Mounting the viewer counts as
50
+ * watching when this is omitted.
47
51
  */
48
52
  watching?: boolean | undefined;
49
53
  /**
@@ -51,8 +55,7 @@ export type DesktopViewerProps = {
51
55
  * already been recorded — only the box drained). The host wires this to
52
56
  * "engage the viewer attach + re-negotiate". Called automatically when a
53
57
  * watched desktop is found cold-but-warmable, and behind the manual retry on
54
- * the warming notice. Distinct from `onAcknowledge`, which is the FIRST,
55
- * consent-bearing warm.
58
+ * the warming notice. Distinct from the automatic acknowledgment request.
56
59
  */
57
60
  onWarm?: (() => void) | undefined;
58
61
  /** Shown when transport is null (headless backend / degraded / disabled). */
@@ -75,13 +78,14 @@ export type DesktopViewerProps = {
75
78
  /**
76
79
  * The derived desktop surface state. A single source of truth so the overlay,
77
80
  * the scrim, the auto-warm effect, and the watchdog all agree. Priority order
78
- * (highest first): viewer-cap → unavailable → consent → warming → connect-failed
79
- * → error → connecting → connected.
81
+ * (highest first): viewer-cap → unavailable → acknowledgment-error → warming →
82
+ * connect-failed → error → opening/connecting → connected.
80
83
  */
81
84
  type DesktopUiState =
82
85
  | "viewer_cap" // 429 — the per-session live-viewer limit is reached.
83
86
  | "unavailable" // genuinely unsupported (headless/policy/os/backend/not-provisioned).
84
- | "consent" // un-redacted/shared plane needs acknowledgment first.
87
+ | "acknowledgment_error" // automatic un-redacted/shared acknowledgment failed.
88
+ | "opening" // acknowledgment or viewer attach is in flight.
85
89
  | "warming" // cold-but-warmable: box not running yet (we auto-warm + spin).
86
90
  | "connecting" // url is live, RFB negotiating/handshaking (we spin, with a watchdog).
87
91
  | "connect_failed" // watchdog fired: url present but never connected.
@@ -122,10 +126,11 @@ function isHardUnavailable(reason: CapabilityUnavailableReason | null): boolean
122
126
  * reason).
123
127
  *
124
128
  * Warming: a cold-but-warmable lease (`reason: "lease_cold"`) is NOT a dead end.
125
- * When the user is watching (consented), the viewer asks the host to (re)warm
126
- * the box (`onWarm`) and shows a "Warming…" spinner; if the box later drains to
127
- * cold it re-warms. Genuinely-unavailable surfaces (headless/policy/os/backend)
128
- * keep a clear, static unavailable notice.
129
+ * When the viewer is mounted, it asks the host to engage the desktop and records
130
+ * any required acknowledgment automatically. A cold-but-warmable box then
131
+ * (re)warms through `onWarm`; if it later drains to cold it re-warms.
132
+ * Genuinely-unavailable surfaces (headless/policy/os/backend) keep a clear,
133
+ * static unavailable notice.
129
134
  */
130
135
  export function DesktopViewer({
131
136
  capability,
@@ -134,7 +139,7 @@ export function DesktopViewer({
134
139
  scaleViewport,
135
140
  rfbFactory,
136
141
  webSocketFactory,
137
- renderConsentGate,
142
+ onActivate,
138
143
  onAcknowledge,
139
144
  watching,
140
145
  onWarm,
@@ -146,7 +151,13 @@ export function DesktopViewer({
146
151
  className,
147
152
  }: DesktopViewerProps) {
148
153
  const containerRef = useRef<HTMLDivElement | null>(null);
149
- const [consented, setConsented] = useState(false);
154
+ const onActivateRef = useRef(onActivate);
155
+ const onAcknowledgeRef = useRef(onAcknowledge);
156
+ onActivateRef.current = onActivate;
157
+ onAcknowledgeRef.current = onAcknowledge;
158
+ const acknowledgmentAttemptedRef = useRef(false);
159
+ const [acknowledgmentError, setAcknowledgmentError] = useState<Error | null>(null);
160
+ const [acknowledgmentRetryNonce, setAcknowledgmentRetryNonce] = useState(0);
150
161
  // Local control state when not externally controlled. The server gate
151
162
  // (`capability.mode`) is the hard ceiling — a read-only deployment can never
152
163
  // be flipped to interactive regardless of this toggle.
@@ -156,16 +167,16 @@ export function DesktopViewer({
156
167
  const wantControl = externallyControlled ? interactive : takeControl;
157
168
  const inControl = Boolean(wantControl) && serverAllowsControl;
158
169
 
159
- // Whether the host has the viewer attach engaged. Prefer the explicit prop;
160
- // fall back to a locally-recorded consent so the component still self-heals
161
- // (auto-warms / re-warms) once the user has accepted the gate at least once.
162
- const isWatching = watching ?? consented;
170
+ // Mounting this component means the Desktop surface is being viewed. A host
171
+ // may still pass its holder state explicitly while the activation callback
172
+ // flips that state on the first mount.
173
+ const isWatching = watching ?? true;
163
174
 
164
175
  // ── Decide the rendered state (before touching the stream hook) ─────────────
165
176
  const transportNull = !capability || capability.transport === null;
166
177
  const reason = capability?.reason ?? null;
167
- const needsAck =
168
- capability?.requiresAcknowledgment === true && capability.acknowledged !== true && !consented;
178
+ const needsAcknowledgment =
179
+ capability?.requiresAcknowledgment === true && capability.acknowledged !== true;
169
180
  // No live address yet on an otherwise-live transport (post-ack, mid-warm).
170
181
  const noLiveAddress = Boolean(capability) && !transportNull && !capability!.url;
171
182
 
@@ -176,9 +187,45 @@ export function DesktopViewer({
176
187
  // Genuinely-unavailable: transport null for a HARD reason (not lease_cold).
177
188
  const hardUnavailable = transportNull && isHardUnavailable(reason);
178
189
 
179
- const accept = () => {
180
- setConsented(true);
181
- onAcknowledge?.();
190
+ // Selecting Desktop mounts the viewer, which is itself the user's intent to
191
+ // open it. Engage the holder immediately — no separate "watch" click.
192
+ useEffect(() => {
193
+ onActivateRef.current?.();
194
+ }, []);
195
+
196
+ // Preserve the server-side acknowledgment/audit contract without making the
197
+ // user click through a disclosure card. One automatic attempt runs for each
198
+ // unacknowledged episode; a genuine request failure gets an explicit retry.
199
+ useEffect(() => {
200
+ if (!needsAcknowledgment) {
201
+ acknowledgmentAttemptedRef.current = false;
202
+ setAcknowledgmentError(null);
203
+ return;
204
+ }
205
+ if (acknowledgmentAttemptedRef.current) return;
206
+ acknowledgmentAttemptedRef.current = true;
207
+ setAcknowledgmentError(null);
208
+ const acknowledge = onAcknowledgeRef.current;
209
+ if (!acknowledge) {
210
+ setAcknowledgmentError(new Error("Desktop acknowledgment is not configured."));
211
+ return;
212
+ }
213
+ let active = true;
214
+ void Promise.resolve()
215
+ .then(() => acknowledge())
216
+ .catch((cause) => {
217
+ if (!active) return;
218
+ setAcknowledgmentError(cause instanceof Error ? cause : new Error(String(cause)));
219
+ });
220
+ return () => {
221
+ active = false;
222
+ };
223
+ }, [needsAcknowledgment, acknowledgmentRetryNonce]);
224
+
225
+ const retryAcknowledgment = () => {
226
+ acknowledgmentAttemptedRef.current = false;
227
+ setAcknowledgmentError(null);
228
+ setAcknowledgmentRetryNonce((nonce) => nonce + 1);
182
229
  };
183
230
 
184
231
  // Release control WITHOUT ever swallowing a key the desktop needs. Esc, and
@@ -218,7 +265,8 @@ export function DesktopViewer({
218
265
  // Do NOT open a socket while the viewer-cap (429) notice is showing — the slot
219
266
  // is already exhausted, so connecting would only burn a doomed attempt (and in
220
267
  // tests leak an unhandled ws error from the never-resolving tunnel URL).
221
- const connectCapability = !transportNull && !needsAck && !viewerCapReached ? capability : null;
268
+ const connectCapability =
269
+ !transportNull && !needsAcknowledgment && !viewerCapReached ? capability : null;
222
270
  const stream = useDesktopStream({
223
271
  capability: connectCapability,
224
272
  containerRef,
@@ -238,21 +286,19 @@ export function DesktopViewer({
238
286
 
239
287
  // ── AUTO-WARM ───────────────────────────────────────────────────────────────
240
288
  // When the user is watching and the desktop is cold-but-warmable, ask the host
241
- // to (re)warm the box. This covers BOTH (a) the user just accepted consent and
289
+ // to (re)warm the box. This covers BOTH (a) the desktop was just opened and
242
290
  // (b) a previously-warm box that drained back to cold under a live viewer. We
243
291
  // fire once per distinct cold episode (keyed on the lease epoch + url) so we
244
292
  // don't spam the attach while a single warm is in flight. `onWarm` is the
245
- // no-re-ack path; if the host only wired `onAcknowledge` (no `onWarm`), the
246
- // first warm still rides consent and subsequent drains fall back to it.
293
+ // no-re-ack path; acknowledgment remains a separate automatic request.
247
294
  const warmKeyRef = useRef<string | null>(null);
248
295
  useEffect(() => {
249
- if (!isWatching || !coldWarmable || needsAck || viewerCapReached) {
296
+ if (!isWatching || !coldWarmable || needsAcknowledgment || viewerCapReached) {
250
297
  // Reset the de-dupe once we leave the cold episode so a future drain warms.
251
298
  if (!coldWarmable) warmKeyRef.current = null;
252
299
  return;
253
300
  }
254
- const warm = onWarm ?? onAcknowledge;
255
- if (!warm) return;
301
+ if (!onWarm) return;
256
302
  // Key the de-dupe on the cell fields available to the viewer: while cold,
257
303
  // `reason`/`url`/`expiresAt` are stable, so we warm exactly once; once the box
258
304
  // warms the cell changes (url+expiresAt minted) and `coldWarmable` flips false,
@@ -260,10 +306,10 @@ export function DesktopViewer({
260
306
  const key = `${capability?.reason ?? ""}:${capability?.url ?? ""}:${capability?.expiresAt ?? ""}`;
261
307
  if (warmKeyRef.current === key) return; // already kicked this episode.
262
308
  warmKeyRef.current = key;
263
- warm();
309
+ onWarm();
264
310
  // capability identity is the trigger; leaseEpoch/expiresAt key the episode.
265
311
  // eslint-disable-next-line react-hooks/exhaustive-deps
266
- }, [isWatching, coldWarmable, needsAck, viewerCapReached, capability, onWarm, onAcknowledge]);
312
+ }, [isWatching, coldWarmable, needsAcknowledgment, viewerCapReached, capability, onWarm]);
267
313
 
268
314
  // ── TAB RE-ATTACH ───────────────────────────────────────────────────────────
269
315
  // The "stuck Watching, never connects" bug: the RFB socket never (re)fires when
@@ -331,7 +377,8 @@ export function DesktopViewer({
331
377
  let uiState: DesktopUiState;
332
378
  if (viewerCapReached) uiState = "viewer_cap";
333
379
  else if (hardUnavailable) uiState = "unavailable";
334
- else if (needsAck && !isWatching) uiState = "consent";
380
+ else if (acknowledgmentError) uiState = "acknowledgment_error";
381
+ else if (needsAcknowledgment) uiState = "opening";
335
382
  else if (coldWarmable) uiState = "warming";
336
383
  else if (connected) uiState = "connected";
337
384
  else if (stream.error) uiState = "error";
@@ -340,7 +387,7 @@ export function DesktopViewer({
340
387
 
341
388
  // An overlay blocks the surface for every state except connected; the toggle
342
389
  // and the live scrim are suppressed under an overlay.
343
- const overlayShown = uiState !== "connected" && uiState !== "connecting";
390
+ const overlayShown = uiState !== "connected" && uiState !== "connecting" && uiState !== "opening";
344
391
  const showToggle = showControlToggle && !overlayShown;
345
392
 
346
393
  let overlay: ReactNode = null;
@@ -358,11 +405,11 @@ export function DesktopViewer({
358
405
  renderUnavailable?.(reason) ??
359
406
  defaultNotice("Desktop unavailable", unavailableCopy(reason));
360
407
  break;
361
- case "consent":
362
- overlay = renderConsentGate ? (
363
- renderConsentGate(accept, capability?.shared ?? false)
364
- ) : (
365
- <DefaultConsentGate shared={capability?.shared ?? false} onAccept={accept} />
408
+ case "acknowledgment_error":
409
+ overlay = defaultNotice(
410
+ "Couldn’t open desktop",
411
+ acknowledgmentError?.message ?? "The desktop acknowledgment failed.",
412
+ retryAcknowledgment,
366
413
  );
367
414
  break;
368
415
  case "warming":
@@ -382,6 +429,7 @@ export function DesktopViewer({
382
429
  reconnect,
383
430
  );
384
431
  break;
432
+ case "opening":
385
433
  case "connecting":
386
434
  case "connected":
387
435
  overlay = null;
@@ -420,7 +468,7 @@ export function DesktopViewer({
420
468
  rectangle before the first framebuffer paints. Only shown in the
421
469
  `connecting` UI-state (every other non-connected state has an explicit
422
470
  overlay). A spinner + transitional copy makes it feel live. */}
423
- {uiState === "connecting" && (
471
+ {(uiState === "opening" || uiState === "connecting") && (
424
472
  <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 text-og-fg-subtle">
425
473
  <span className="relative flex items-center justify-center">
426
474
  <MonitorIcon className="size-8 opacity-30" strokeWidth={1.5} />
@@ -429,7 +477,9 @@ export function DesktopViewer({
429
477
  strokeWidth={1.25}
430
478
  />
431
479
  </span>
432
- <span className="text-og-sm">Connecting to the desktop…</span>
480
+ <span className="text-og-sm">
481
+ {uiState === "opening" ? "Opening the desktop…" : "Connecting to the desktop…"}
482
+ </span>
433
483
  </div>
434
484
  )}
435
485
 
@@ -507,7 +557,7 @@ function TakeControlCallToAction({
507
557
  "border-og-border",
508
558
  "bg-og-bg/85 backdrop-blur-md",
509
559
  "shadow-og-lg",
510
- "outline-none transition-all duration-150 ease-out",
560
+ "outline-hidden transition-all duration-150 ease-out",
511
561
  "focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
512
562
  disabled
513
563
  ? "cursor-not-allowed opacity-60"
@@ -568,7 +618,7 @@ function InControlBar({ shared, onRelease }: { shared: boolean; onRelease: () =>
568
618
  className={cn(
569
619
  "inline-flex items-center gap-1.5 rounded-og-sm border px-2.5 py-1 text-og-xs font-semibold shadow-og-md transition-colors",
570
620
  "border-og-accent bg-og-bg/90 text-og-fg backdrop-blur-sm",
571
- "outline-none hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent",
621
+ "outline-hidden hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent",
572
622
  )}
573
623
  >
574
624
  <MonitorIcon className="size-3.5" strokeWidth={2} aria-hidden />
@@ -630,28 +680,6 @@ function WarmingNotice({ onRetry }: { onRetry?: (() => void) | undefined }) {
630
680
  );
631
681
  }
632
682
 
633
- function DefaultConsentGate({ shared, onAccept }: { shared: boolean; onAccept: () => void }) {
634
- return (
635
- <div className="max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg">
636
- <div className="mb-1 font-medium">Watch the live desktop?</div>
637
- <p className="mb-3 text-og-sm text-og-fg-subtle">
638
- The desktop pixel stream is <strong>un-redacted</strong> — it can show secrets the agent
639
- prints on screen.
640
- {shared
641
- ? " This sandbox is shared: you'll also see other sessions' agents on the same screen."
642
- : ""}
643
- </p>
644
- <button
645
- type="button"
646
- onClick={onAccept}
647
- className="rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg pointer-coarse:min-h-11"
648
- >
649
- I understand — show the desktop
650
- </button>
651
- </div>
652
- );
653
- }
654
-
655
683
  function defaultNotice(title: string, body: string, onRetry?: () => void): ReactNode {
656
684
  return (
657
685
  <div className="max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg">
@@ -24,7 +24,11 @@ import {
24
24
  import { AlertDialog } from "radix-ui";
25
25
  import { VList, type VListHandle } from "virtua";
26
26
  import { cn } from "../lib/cn";
27
- import { type PortalTokenStyle, usePortalTokenStyle } from "../lib/use-portal-token-style";
27
+ import {
28
+ type PortalTokenStyle,
29
+ usePortalTokenSource,
30
+ usePortalTokenStyle,
31
+ } from "../lib/use-portal-token-style";
28
32
  import { useUnicodeFallbackFonts } from "../lib/use-unicode-fonts";
29
33
  import type { FileTreeNode, UseSandboxFilesResult } from "../hooks/use-sandbox-files";
30
34
 
@@ -155,10 +159,11 @@ export function FileBrowser({
155
159
  const [busy, setBusy] = useState(false);
156
160
  const [coarsePointer, setCoarsePointer] = useState(false);
157
161
  const treeId = useId();
158
- const containerRef = useRef<HTMLDivElement | null>(null);
162
+ const container = usePortalTokenSource<HTMLDivElement>();
163
+ const containerRef = container.currentRef;
159
164
  const deleteReturnFocusRef = useRef<HTMLElement | null>(null);
160
165
  const vlistRef = useRef<VListHandle>(null);
161
- const portalTokenStyle = usePortalTokenStyle(containerRef);
166
+ const portalTokenStyle = usePortalTokenStyle(container.source);
162
167
  const getTreeElement = useCallback(
163
168
  () => (typeof document === "undefined" ? null : document.getElementById(treeId)),
164
169
  [treeId],
@@ -569,6 +574,7 @@ export function FileBrowser({
569
574
  runDelete,
570
575
  supportsMutation,
571
576
  coarsePointer,
577
+ containerRef,
572
578
  rowIndexByPath,
573
579
  treeId,
574
580
  ],
@@ -594,7 +600,13 @@ export function FileBrowser({
594
600
  // Retry lives next to the state it explains (the degraded/empty notice), so do
595
601
  // not render a second toolbar retry for the same failure.
596
602
  const showToolbar = supportsMutation || result.loading;
597
- const usesVirtualTree = !showErrorEmpty && !showEmpty;
603
+ // Virtua caches the previous visible range when a lazy-expanded subtree is
604
+ // inserted in the middle of a short list. The spacer grows but later root
605
+ // siblings can remain unmounted until a scroll/resize (for example,
606
+ // `.bash_history` vanished after expanding `repositories/<repo>`). Small
607
+ // trees do not benefit from virtualization, so render them directly and keep
608
+ // Virtua only for genuinely large workspaces.
609
+ const usesVirtualTree = !showErrorEmpty && !showEmpty && renderItems.length > 500;
598
610
 
599
611
  // Render a SINGLE node row (no recursion — children are separate flat items the
600
612
  // virtualizer renders). Returns the row element; the caller assigns the key.
@@ -854,7 +866,7 @@ export function FileBrowser({
854
866
  type="button"
855
867
  onClick={() => void result.refresh()}
856
868
  disabled={result.loading}
857
- className="inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11"
869
+ className="inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11"
858
870
  >
859
871
  <RefreshCwIcon
860
872
  className={cn("size-3", result.loading && "animate-spin motion-reduce:animate-none")}
@@ -868,13 +880,7 @@ export function FileBrowser({
868
880
  {/* The tree itself. The root is a drop target so a node can be moved to "". */}
869
881
  {/* biome-ignore lint/a11y/noNoninteractiveTabindex: the tree owns keyboard nav */}
870
882
  <div
871
- ref={containerRef}
872
- id={usesVirtualTree ? undefined : treeId}
873
- role={usesVirtualTree ? undefined : "tree"}
874
- aria-label={usesVirtualTree ? undefined : "Workspace files"}
875
- aria-activedescendant={usesVirtualTree ? undefined : activeDescendantId}
876
- tabIndex={usesVirtualTree ? undefined : 0}
877
- aria-multiselectable={usesVirtualTree ? undefined : false}
883
+ ref={container.ref}
878
884
  onFocus={(event) => {
879
885
  if ((event.target as HTMLElement).id === treeId && cursorIndex < 0) {
880
886
  const first = flatRows[0];
@@ -901,7 +907,7 @@ export function FileBrowser({
901
907
  : undefined
902
908
  }
903
909
  className={cn(
904
- "flex min-h-0 min-w-0 flex-1 flex-col p-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
910
+ "flex min-h-0 min-w-0 flex-1 flex-col p-1",
905
911
  dragOver === "" && "ring-1 ring-inset ring-og-accent",
906
912
  )}
907
913
  data-opengeni-file-tree
@@ -929,6 +935,22 @@ export function FileBrowser({
929
935
  <div className="p-2 text-og-sm text-og-fg-subtle">
930
936
  {emptyState ?? "This directory is empty"}
931
937
  </div>
938
+ ) : !usesVirtualTree ? (
939
+ <div
940
+ id={treeId}
941
+ role="tree"
942
+ aria-label="Workspace files"
943
+ aria-activedescendant={activeDescendantId}
944
+ aria-multiselectable={false}
945
+ tabIndex={0}
946
+ className="min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent"
947
+ >
948
+ {renderItems.map((item) => (
949
+ <div key={itemKey(item)} className="min-w-0">
950
+ {renderItem(item)}
951
+ </div>
952
+ ))}
953
+ </div>
932
954
  ) : (
933
955
  // Virtualized: only the rows in/near the viewport mount, so a 3k-node
934
956
  // tree stays responsive. The flat item list already carries the
@@ -941,7 +963,7 @@ export function FileBrowser({
941
963
  aria-activedescendant={activeDescendantId}
942
964
  aria-multiselectable={false}
943
965
  tabIndex={0}
944
- className="min-h-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent"
966
+ className="min-h-0 flex-1 outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent"
945
967
  itemSize={coarsePointer ? 44 : 28}
946
968
  ssrCount={Math.min(32, renderItems.length)}
947
969
  >
@@ -1041,9 +1063,9 @@ function DeleteDialog({
1041
1063
  }}
1042
1064
  >
1043
1065
  <AlertDialog.Portal container={typeof document === "undefined" ? null : document.body}>
1044
- <AlertDialog.Overlay className="fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" />
1066
+ <AlertDialog.Overlay className="og-root fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" />
1045
1067
  <AlertDialog.Content
1046
- className="og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-none"
1068
+ className="og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-hidden"
1047
1069
  style={portalTokenStyle}
1048
1070
  onOpenAutoFocus={(event) => {
1049
1071
  event.preventDefault();
@@ -1208,7 +1230,7 @@ function InlineInput({
1208
1230
  className={cn(
1209
1231
  "min-w-0 flex-1 rounded-og-sm border bg-og-bg px-1 py-0 text-og-sm",
1210
1232
  "border-og-accent text-og-fg",
1211
- "outline-none",
1233
+ "outline-hidden",
1212
1234
  )}
1213
1235
  />
1214
1236
  </div>
@@ -0,0 +1,89 @@
1
+ import type { GeneratedVideoReceipt, VideoArtifactPlaybackSource } from "@opengeni/sdk";
2
+ import { useEffect, useState } from "react";
3
+ import { cn } from "../lib/cn";
4
+ import type { VideoArtifactPlaybackLoader } from "../timeline";
5
+
6
+ export type GeneratedVideoPlayerProps = {
7
+ receipt: GeneratedVideoReceipt;
8
+ loadPlaybackSource: VideoArtifactPlaybackLoader;
9
+ className?: string | undefined;
10
+ label?: string | undefined;
11
+ };
12
+
13
+ type SourceState =
14
+ | { kind: "loading" }
15
+ | { kind: "ready"; source: VideoArtifactPlaybackSource }
16
+ | { kind: "error"; message: string };
17
+
18
+ /** Native, Range-based playback. Video bytes never pass through React or the SDK heap. */
19
+ export function GeneratedVideoPlayer({
20
+ receipt,
21
+ loadPlaybackSource,
22
+ className,
23
+ label = "Generated video",
24
+ }: GeneratedVideoPlayerProps) {
25
+ const [retry, setRetry] = useState(0);
26
+ const [state, setState] = useState<SourceState>({ kind: "loading" });
27
+
28
+ useEffect(() => {
29
+ const controller = new AbortController();
30
+ setState({ kind: "loading" });
31
+ void loadPlaybackSource(receipt.artifact.artifactId, controller.signal).then(
32
+ (source) => {
33
+ if (!controller.signal.aborted) setState({ kind: "ready", source });
34
+ },
35
+ (error: unknown) => {
36
+ if (controller.signal.aborted) return;
37
+ setState({
38
+ kind: "error",
39
+ message: error instanceof Error ? error.message : "Playback is unavailable.",
40
+ });
41
+ },
42
+ );
43
+ return () => controller.abort();
44
+ }, [loadPlaybackSource, receipt.artifact.artifactId, retry]);
45
+
46
+ if (state.kind === "loading") {
47
+ return (
48
+ <div
49
+ aria-label={`Loading ${label.toLowerCase()}`}
50
+ className={cn(
51
+ "aspect-video w-full animate-pulse rounded-og-md bg-og-surface-2 motion-reduce:animate-none",
52
+ className,
53
+ )}
54
+ />
55
+ );
56
+ }
57
+ if (state.kind === "error") {
58
+ return (
59
+ <div
60
+ role="status"
61
+ className={cn(
62
+ "rounded-og-md border border-og-status-failed/30 bg-og-status-failed/5 px-3 py-2 text-og-sm text-og-status-failed",
63
+ className,
64
+ )}
65
+ >
66
+ {state.message}
67
+ </div>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <video
73
+ key={state.source.url}
74
+ aria-label={label}
75
+ className={cn(
76
+ "max-h-[32rem] w-full rounded-og-md bg-black object-contain shadow-sm",
77
+ className,
78
+ )}
79
+ controls
80
+ playsInline
81
+ preload="metadata"
82
+ src={state.source.url}
83
+ onError={() => {
84
+ if (retry === 0) setRetry(1);
85
+ else setState({ kind: "error", message: "Video playback failed." });
86
+ }}
87
+ />
88
+ );
89
+ }
@@ -559,7 +559,7 @@ function QuestionControls({
559
559
  aria-describedby={describedBy}
560
560
  autoFocus={autoFocus}
561
561
  rows={2}
562
- className="min-h-14 w-full resize-y rounded-og-md border border-og-border bg-og-surface-1 px-3 py-2 text-og-sm text-og-fg outline-none placeholder:text-og-fg-subtle focus:border-og-accent"
562
+ className="min-h-14 w-full resize-y rounded-og-md border border-og-border bg-og-surface-1 px-3 py-2 text-og-sm text-og-fg outline-hidden placeholder:text-og-fg-subtle focus:border-og-accent"
563
563
  />
564
564
  ) : (
565
565
  <div
@@ -651,7 +651,7 @@ function QuestionControls({
651
651
  other: event.target.value,
652
652
  }))
653
653
  }
654
- className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-none focus:border-og-accent disabled:opacity-50"
654
+ className="mt-1.5 w-full rounded-og-sm border border-og-border bg-og-surface-1 px-2 py-1.5 text-og-sm text-og-fg outline-hidden focus:border-og-accent disabled:opacity-50"
655
655
  />
656
656
  </span>
657
657
  </label>
@@ -0,0 +1,2 @@
1
+ export { BrowserViewer } from "./browser-viewer";
2
+ export { ComputerViewer } from "./computer-viewer";
@@ -81,7 +81,7 @@ export function MachineCard({
81
81
  "og-root group relative flex flex-col gap-3 overflow-hidden rounded-og-lg border border-og-border",
82
82
  "bg-og-surface-1 p-4 shadow-og-sm transition-colors",
83
83
  openable &&
84
- "cursor-pointer hover:border-og-border-strong focus-visible:border-og-accent/60 focus-visible:outline-none",
84
+ "cursor-pointer hover:border-og-border-strong focus-visible:border-og-accent/60 focus-visible:outline-hidden",
85
85
  machine.active && "border-og-accent/40",
86
86
  className,
87
87
  )}
@@ -6,6 +6,7 @@ export const MACHINE_INPUT_META: Record<MachineInputMember["kind"], string> = {
6
6
  agent_message: "Agent update",
7
7
  agent_steer_instruction: "Agent direction",
8
8
  child_terminal_result: "Agent finished",
9
+ media_generation_result: "Video update",
9
10
  };
10
11
 
11
12
  /**
@@ -32,6 +33,8 @@ export function machineInputBatchLabel(members: readonly MachineInputMember[]):
32
33
  return n === 1 ? "Agent update" : `${n} agent updates`;
33
34
  case "agent_steer_instruction":
34
35
  return n === 1 ? "Agent direction" : `${n} agent directions`;
36
+ case "media_generation_result":
37
+ return n === 1 ? "Video ready" : `${n} video updates`;
35
38
  }
36
39
  }
37
40
  const parts = [...counts.entries()].map(([kind, count]) => {