@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
@@ -29,16 +29,23 @@ import {
29
29
  useChatComposerController,
30
30
  type ChatComposerMessages,
31
31
  type ComposerControlLinks,
32
+ type ResponsiveBasis,
32
33
  } from "./composer";
33
34
  import {
34
35
  ComposerTranscriptionControl,
35
36
  type ComposerTranscriptionControlProps,
36
37
  } from "./composer-transcription-control";
38
+ import { TimelineAnnotationsChip } from "./timeline-annotations";
37
39
 
38
40
  export { OPEN_WORKSTREAM_CONTROL_EVENT };
39
41
 
40
42
  export type ChatComposerProps = {
41
43
  composer: ComposerState;
44
+ /**
45
+ * Measurement surface for responsive chrome. Defaults to the historical
46
+ * viewport breakpoints; opt into `container` for narrow embedded panels.
47
+ */
48
+ responsiveBasis?: ResponsiveBasis | undefined;
42
49
  /** Canonical workstream control, separate from lifecycle status. */
43
50
  effectiveControl?: EffectiveSessionControl | null | undefined;
44
51
  /** Waiting prompts already ahead of a normal Send. */
@@ -91,6 +98,7 @@ export type ChatComposerProps = {
91
98
  */
92
99
  export function ChatComposer({
93
100
  composer,
101
+ responsiveBasis,
94
102
  effectiveControl,
95
103
  queuedAheadCount,
96
104
  canControlWorkspace,
@@ -140,7 +148,7 @@ export function ChatComposer({
140
148
  const stackActions = hasControls && Boolean(actionsStart);
141
149
 
142
150
  return (
143
- <Root controller={controller} className={className}>
151
+ <Root controller={controller} responsiveBasis={responsiveBasis} className={className}>
144
152
  <Frame>
145
153
  <CommandPalette />
146
154
  <Surface>
@@ -148,11 +156,26 @@ export function ChatComposer({
148
156
  <RestoredResources />
149
157
  <Attachments />
150
158
  {header}
159
+ {composer.annotations && composer.annotations.length > 0 ? (
160
+ <div className="px-3 pt-2 sm:px-4">
161
+ <TimelineAnnotationsChip
162
+ annotations={composer.annotations}
163
+ editable
164
+ focusAnnotationId={composer.annotationReviewTargetId}
165
+ onFocusConsumed={composer.clearAnnotationReviewTarget}
166
+ onUpdate={composer.updateAnnotation}
167
+ onRemove={composer.removeAnnotation}
168
+ />
169
+ </div>
170
+ ) : null}
151
171
  <Input placeholder={placeholder} autoFocus={autoFocus} />
152
172
  {controller.confirmState ? (
153
173
  <Confirmation />
154
174
  ) : (
155
- <Footer className={stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : undefined}>
175
+ <Footer
176
+ data-og-stack-actions={stackActions ? "" : undefined}
177
+ className={stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : undefined}
178
+ >
156
179
  <LayoutGroup id="og-composer-footer">
157
180
  {hasControls ? (
158
181
  <Controls
@@ -95,8 +95,13 @@ export type CodeEditorProps = {
95
95
  path: string;
96
96
  /** The decoded text contents to seed the editor with. */
97
97
  initialContents: string;
98
- /** Persist the current buffer. Resolves when the write lands; rejects to surface an error. */
99
- onSave: (contents: string) => Promise<unknown>;
98
+ /**
99
+ * Persist the current buffer against the exact contents it was edited from.
100
+ * The second argument is the immutable compare-and-swap baseline; hosts must
101
+ * not replace it with a newer remote snapshot or concurrent writes can be
102
+ * silently overwritten.
103
+ */
104
+ onSave: (contents: string, expectedContents: string) => Promise<unknown>;
100
105
  /** Explicitly overwrite after a visible expected-content conflict. */
101
106
  onOverwrite?: ((contents: string) => Promise<unknown>) | undefined;
102
107
  /** Re-read the selected file after a visible save conflict. */
@@ -115,6 +120,41 @@ export type CodeEditorProps = {
115
120
  className?: string | undefined;
116
121
  };
117
122
 
123
+ export type EditorBufferState = {
124
+ path: string;
125
+ value: string;
126
+ baseline: string;
127
+ };
128
+
129
+ /**
130
+ * Reconcile a remote file snapshot without destroying unsaved local work.
131
+ *
132
+ * A different path is a new document and always resets. For the same path, a
133
+ * clean editor follows the remote snapshot while a dirty editor retains both
134
+ * its local value and the original CAS baseline. The eventual save will then
135
+ * either succeed against that baseline or surface a typed conflict.
136
+ */
137
+ export function reconcileEditorBuffer(
138
+ current: EditorBufferState,
139
+ incoming: { path: string; contents: string },
140
+ ): EditorBufferState {
141
+ if (current.path === incoming.path && current.value !== current.baseline) {
142
+ return current;
143
+ }
144
+ if (
145
+ current.path === incoming.path &&
146
+ current.value === incoming.contents &&
147
+ current.baseline === incoming.contents
148
+ ) {
149
+ return current;
150
+ }
151
+ return {
152
+ path: incoming.path,
153
+ value: incoming.contents,
154
+ baseline: incoming.contents,
155
+ };
156
+ }
157
+
118
158
  /**
119
159
  * The EDITABLE single-file pane: CodeMirror 6 with a per-language grammar chosen
120
160
  * from the filename, og-* themed, with dirty tracking and a save path wired to
@@ -143,42 +183,45 @@ export function CodeEditor({
143
183
  const [failed, setFailed] = useState(false);
144
184
  const [bundle, setBundle] = useState<EditorBundle | null>(null);
145
185
 
146
- // The live buffer + the baseline it's compared against for dirtiness. The
147
- // baseline resets whenever a *new* file is loaded (path/initialContents change)
148
- // or after a successful save.
149
- const [value, setValue] = useState(initialContents);
150
- const [baseline, setBaseline] = useState(initialContents);
186
+ // The live buffer and its compare-and-swap baseline are one state value so a
187
+ // remote refresh cannot update one without the other.
188
+ const [buffer, setBuffer] = useState<EditorBufferState>(() => ({
189
+ path,
190
+ value: initialContents,
191
+ baseline: initialContents,
192
+ }));
193
+ const { value, baseline } = buffer;
151
194
  const [saving, setSaving] = useState(false);
152
195
  const [saveError, setSaveError] = useState<Error | null>(null);
153
196
  const [savedTick, setSavedTick] = useState(false);
197
+ const saveInFlightRef = useRef(false);
154
198
 
155
199
  const dirty = value !== baseline;
156
200
  const saveConflict =
157
201
  saveError !== null && (saveError as Error & { code?: unknown }).code === "file_write_conflict";
158
202
 
159
- // Reload the buffer when the file identity changes. Guard on initialContents
160
- // too so an external refresh of the SAME path (e.g. fs.changed re-read) reseeds
161
- // a clean buffer but only when the user hasn't got unsaved edits in flight.
162
- // Fire the wake-on-edit intent at most once per opened file — reset on re-seed.
203
+ // Follow path changes and clean same-file refreshes. Dirty same-file buffers
204
+ // intentionally ignore remote snapshots: the original baseline must survive
205
+ // until guarded save, explicit reload, or navigation.
163
206
  const editIntentFiredRef = useRef(false);
164
- const lastSeed = useRef<{ path: string; contents: string }>({ path, contents: initialContents });
165
207
  useEffect(() => {
166
- const seedChanged =
167
- lastSeed.current.path !== path || lastSeed.current.contents !== initialContents;
168
- if (!seedChanged) return;
169
- lastSeed.current = { path, contents: initialContents };
208
+ setBuffer((current) => reconcileEditorBuffer(current, { path, contents: initialContents }));
209
+ }, [path, initialContents]);
210
+
211
+ // A new document is a new edit-intent/error lifecycle. The normal
212
+ // SandboxFiles host also keys the component by path; this keeps standalone
213
+ // consumers correct without relying on that implementation detail.
214
+ useEffect(() => {
170
215
  editIntentFiredRef.current = false;
171
- setValue(initialContents);
172
- setBaseline(initialContents);
173
216
  setSaveError(null);
174
217
  setSavedTick(false);
175
- }, [path, initialContents]);
218
+ }, [path]);
176
219
 
177
220
  // Record a buffer change: the FIRST divergence from the baseline is the edit
178
221
  // intent that warms the box (idempotent — latched per opened file).
179
222
  const noteEdit = useCallback(
180
223
  (next: string) => {
181
- setValue(next);
224
+ setBuffer((current) => ({ ...current, value: next }));
182
225
  setSavedTick(false);
183
226
  if (!editIntentFiredRef.current && next !== baseline) {
184
227
  editIntentFiredRef.current = true;
@@ -246,40 +289,46 @@ export function CodeEditor({
246
289
  const saveRef = useRef<() => void>(() => {});
247
290
 
248
291
  const save = useCallback(async () => {
249
- if (readOnly) return;
292
+ if (readOnly || saveInFlightRef.current) return;
250
293
  // Snapshot the buffer at call time so an in-flight edit can't race the write.
251
294
  const snapshot = value;
252
295
  if (snapshot === baseline) return; // nothing to persist
296
+ saveInFlightRef.current = true;
253
297
  setSaving(true);
254
298
  setSaveError(null);
255
299
  setSavedTick(false);
256
300
  try {
257
- await onSave(snapshot);
258
- setBaseline(snapshot);
259
- lastSeed.current = { path, contents: snapshot };
301
+ await onSave(snapshot, baseline);
302
+ setBuffer((current) =>
303
+ current.path === path ? { ...current, baseline: snapshot } : current,
304
+ );
260
305
  setSavedTick(true);
261
306
  } catch (cause) {
262
307
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
263
308
  } finally {
309
+ saveInFlightRef.current = false;
264
310
  setSaving(false);
265
311
  }
266
312
  }, [readOnly, value, baseline, onSave, path]);
267
313
 
268
314
  const overwrite = useCallback(async () => {
269
- if (readOnly || !onOverwrite) return;
315
+ if (readOnly || !onOverwrite || saveInFlightRef.current) return;
270
316
  const snapshot = value;
271
317
  if (snapshot === baseline) return;
318
+ saveInFlightRef.current = true;
272
319
  setSaving(true);
273
320
  setSaveError(null);
274
321
  setSavedTick(false);
275
322
  try {
276
323
  await onOverwrite(snapshot);
277
- setBaseline(snapshot);
278
- lastSeed.current = { path, contents: snapshot };
324
+ setBuffer((current) =>
325
+ current.path === path ? { ...current, baseline: snapshot } : current,
326
+ );
279
327
  setSavedTick(true);
280
328
  } catch (cause) {
281
329
  setSaveError(cause instanceof Error ? cause : new Error(String(cause)));
282
330
  } finally {
331
+ saveInFlightRef.current = false;
283
332
  setSaving(false);
284
333
  }
285
334
  }, [readOnly, onOverwrite, value, baseline, path]);
@@ -306,10 +355,19 @@ export function CodeEditor({
306
355
 
307
356
  const Editor = bundle?.Editor;
308
357
 
358
+ const reload = useCallback(() => {
359
+ setBuffer({ path, value: initialContents, baseline: initialContents });
360
+ editIntentFiredRef.current = false;
361
+ setSaveError(null);
362
+ setSavedTick(false);
363
+ onReload?.();
364
+ }, [initialContents, onReload, path]);
365
+
309
366
  return (
310
367
  <div
311
368
  className={cn("flex h-full min-h-0 flex-col", className)}
312
369
  data-opengeni-code-editor
370
+ data-opengeni-editor-dirty={dirty ? "true" : "false"}
313
371
  style={editorVars}
314
372
  >
315
373
  {/* Save bar: dirty indicator + status + the explicit Save button. */}
@@ -334,7 +392,9 @@ export function CodeEditor({
334
392
  {saveError.message}
335
393
  </span>
336
394
  )}
337
- {!saveError && savedTick && <span className="text-og-xs text-og-status-idle">Saved</span>}
395
+ {!saveError && savedTick && !dirty && (
396
+ <span className="text-og-xs text-og-status-idle">Saved</span>
397
+ )}
338
398
  {readOnly ? (
339
399
  <span className="text-og-xs uppercase tracking-wide text-og-fg-subtle">Read-only</span>
340
400
  ) : !saveConflict ? (
@@ -375,8 +435,7 @@ export function CodeEditor({
375
435
  <button
376
436
  type="button"
377
437
  onClick={() => {
378
- setSaveError(null);
379
- onReload();
438
+ reload();
380
439
  }}
381
440
  className="min-h-8 rounded-og-sm border border-og-border bg-og-surface-1 px-2 text-og-xs font-medium text-og-fg hover:bg-og-surface-2 pointer-coarse:min-h-11"
382
441
  >
@@ -458,7 +517,7 @@ function PlainTextarea({
458
517
  }
459
518
  }}
460
519
  aria-label="Plain text file editor"
461
- className="h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-none"
520
+ className="h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-hidden"
462
521
  />
463
522
  );
464
523
  }
@@ -101,7 +101,7 @@ export function CommandPalette({
101
101
  {dangerLabel}
102
102
  </span>
103
103
  ) : null}
104
- <span className="truncate text-[12px] text-og-fg-subtle max-sm:hidden">
104
+ <span className="og-command-description truncate text-[12px] text-og-fg-subtle max-sm:hidden">
105
105
  {command.description}
106
106
  </span>
107
107
  </span>
@@ -35,10 +35,12 @@ export type ComposerTranscriptionMessages = {
35
35
  stop: string;
36
36
  cancel: string;
37
37
  retry: string;
38
+ pauseRetrying: string;
38
39
  requestingPermission: string;
39
40
  recording: string;
40
41
  saving: string;
41
42
  transcribing: string;
43
+ retrying: string;
42
44
  recovered: string;
43
45
  recoveredTranscript: string;
44
46
  insertRecoveredTranscript: string;
@@ -61,10 +63,12 @@ const defaultMessages: ComposerTranscriptionMessages = {
61
63
  stop: "Stop and transcribe",
62
64
  cancel: "Cancel recording",
63
65
  retry: "Retry voice input",
66
+ pauseRetrying: "Pause automatic retry",
64
67
  requestingPermission: "Requesting microphone…",
65
68
  recording: "Recording. Press Escape to cancel.",
66
69
  saving: "Saving audio locally…",
67
70
  transcribing: "Transcribing…",
71
+ retrying: "Recording saved. Retrying automatically…",
68
72
  recovered: "Recording recovered and saved locally.",
69
73
  recoveredTranscript: "Transcript saved locally. Check your draft before inserting.",
70
74
  insertRecoveredTranscript: "Insert saved transcript",
@@ -138,6 +142,8 @@ export function ComposerTranscriptionControl({
138
142
  createOwnerId,
139
143
  });
140
144
  const { status } = transcription;
145
+ const retrying = status === "retrying";
146
+ const capturing = status === "requesting-permission" || status === "recording";
141
147
 
142
148
  const cancelTranscription = transcription.cancel;
143
149
  useEffect(() => {
@@ -146,14 +152,10 @@ export function ComposerTranscriptionControl({
146
152
  cancelTranscription();
147
153
  }
148
154
  }, [cancelTranscription, status, suppressed]);
149
- const active =
150
- status === "requesting-permission" ||
151
- status === "recording" ||
152
- status === "saving" ||
153
- status === "transcribing";
155
+ const active = capturing || status === "saving" || status === "transcribing";
154
156
  const recoverable =
155
157
  transcription.hasRecoverableRecording &&
156
- (status === "recovered" || status === "transcript-ready" || status === "error");
158
+ (retrying || status === "recovered" || status === "transcript-ready" || status === "error");
157
159
  const savedTranscript = status === "transcript-ready" && transcription.savedTranscript !== null;
158
160
  const unavailableMessage = composer.disabled
159
161
  ? messages.unavailableDisabled
@@ -175,13 +177,15 @@ export function ComposerTranscriptionControl({
175
177
  ? messages.saving
176
178
  : status === "transcribing"
177
179
  ? messages.transcribing
178
- : status === "recovered"
179
- ? messages.recovered
180
- : status === "transcript-ready"
181
- ? messages.recoveredTranscript
182
- : status === "error"
183
- ? (errorMessage ?? messages.errorUnknown)
184
- : unavailableMessage;
180
+ : retrying
181
+ ? messages.retrying
182
+ : status === "recovered"
183
+ ? messages.recovered
184
+ : status === "transcript-ready"
185
+ ? messages.recoveredTranscript
186
+ : status === "error"
187
+ ? (errorMessage ?? messages.errorUnknown)
188
+ : unavailableMessage;
185
189
 
186
190
  function start(event: MouseEvent<HTMLButtonElement>) {
187
191
  if (unavailableMessage) {
@@ -200,8 +204,15 @@ export function ComposerTranscriptionControl({
200
204
  animate={{ opacity: 1, width: "auto" }}
201
205
  exit={{ opacity: 0, width: 0 }}
202
206
  transition={{ duration: 0.36, ease: [0.22, 1, 0.36, 1] }}
203
- className={cn("inline-flex min-w-0 overflow-hidden", className)}
207
+ className={cn(
208
+ "inline-flex min-w-0 overflow-hidden",
209
+ // Mobile capture mode reserves the footer for this control. Keep the
210
+ // shell itself from shrinking and clipping its trailing actions.
211
+ capturing && "shrink-0",
212
+ className,
213
+ )}
204
214
  data-transcription-status={status}
215
+ data-transcription-capturing={capturing ? "" : undefined}
205
216
  >
206
217
  <span className="inline-flex min-w-0 items-center gap-1.5">
207
218
  <AnimatePresence mode="popLayout" initial={false}>
@@ -217,23 +228,39 @@ export function ComposerTranscriptionControl({
217
228
  "bg-og-surface-2/70 pl-2 pr-1 pointer-coarse:h-11",
218
229
  )}
219
230
  >
220
- <span className="max-w-44 truncate text-og-xs text-og-fg-muted max-sm:max-w-28">
231
+ <span className="og-transcription-recovered-label max-w-44 truncate text-og-xs text-og-fg-muted max-sm:max-w-28">
221
232
  {savedTranscript
222
233
  ? (errorMessage ?? messages.recoveredTranscript)
223
- : status === "error"
224
- ? (errorMessage ?? messages.errorRetryable)
225
- : messages.recovered}
234
+ : retrying
235
+ ? messages.retrying
236
+ : status === "error"
237
+ ? (errorMessage ?? messages.errorRetryable)
238
+ : messages.recovered}
226
239
  </span>
227
- <Tip tip={savedTranscript ? messages.insertRecoveredTranscript : messages.retry}>
240
+ <Tip
241
+ tip={
242
+ savedTranscript
243
+ ? messages.insertRecoveredTranscript
244
+ : retrying
245
+ ? messages.pauseRetrying
246
+ : messages.retry
247
+ }
248
+ >
228
249
  <button
229
250
  type="button"
230
251
  onClick={() =>
231
252
  savedTranscript
232
253
  ? void transcription.insertSavedTranscript()
233
- : transcription.retry()
254
+ : retrying
255
+ ? transcription.cancel()
256
+ : transcription.retry()
234
257
  }
235
258
  aria-label={
236
- savedTranscript ? messages.insertRecoveredTranscript : messages.retry
259
+ savedTranscript
260
+ ? messages.insertRecoveredTranscript
261
+ : retrying
262
+ ? messages.pauseRetrying
263
+ : messages.retry
237
264
  }
238
265
  className={cn(
239
266
  "inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
@@ -243,6 +270,8 @@ export function ComposerTranscriptionControl({
243
270
  >
244
271
  {savedTranscript ? (
245
272
  <ClipboardPasteIcon className="size-3.5" />
273
+ ) : retrying ? (
274
+ <SquareIcon className="size-3.5" />
246
275
  ) : (
247
276
  <RefreshCwIcon className="size-3.5" />
248
277
  )}
@@ -366,7 +395,7 @@ export function ComposerTranscriptionControl({
366
395
  <Tip tip={errorMessage}>
367
396
  <span
368
397
  aria-hidden="true"
369
- className="max-w-40 truncate text-og-xs text-og-status-failed max-sm:max-w-24"
398
+ className="og-transcription-error-label max-w-40 truncate text-og-xs text-og-status-failed max-sm:max-w-24"
370
399
  >
371
400
  {errorMessage}
372
401
  </span>
@@ -45,6 +45,10 @@ import {
45
45
  type SlashCommandContext,
46
46
  } from "../hooks/use-slash-commands";
47
47
  import { cn } from "../lib/cn";
48
+ import {
49
+ ComposerResponsiveContext,
50
+ type ResponsiveBasis,
51
+ } from "../lib/composer-responsive-context";
48
52
  import { composerSubmissionErrorMessage, formatBytes, formatRelativeTime } from "../lib/format";
49
53
  import type { PickerModelRow } from "../model-policy";
50
54
  import { OPEN_WORKSTREAM_CONTROL_EVENT } from "../workstream-control-event";
@@ -53,6 +57,7 @@ import { ModelPicker as ModelPickerView } from "./model-picker";
53
57
  import { TooltipProvider } from "./tooltip";
54
58
 
55
59
  export { OPEN_WORKSTREAM_CONTROL_EVENT };
60
+ export type { ResponsiveBasis } from "../lib/composer-responsive-context";
56
61
 
57
62
  function ComposerTip({
58
63
  tip,
@@ -743,29 +748,43 @@ function isRecord(value: unknown): value is Record<string, unknown> {
743
748
  export type ComposerRootProps = Omit<ComponentPropsWithoutRef<"div">, "children"> & {
744
749
  controller: ChatComposerController;
745
750
  children: ReactNode;
751
+ /**
752
+ * Measurement surface for responsive composer chrome. `viewport` preserves
753
+ * the existing breakpoint behavior; `container` responds to this root's
754
+ * inline size and also bounds composer-owned portalled menus to it.
755
+ */
756
+ responsiveBasis?: ResponsiveBasis | undefined;
746
757
  };
747
758
 
748
759
  export const Root = forwardRef<HTMLDivElement, ComposerRootProps>(function ComposerRoot(
749
- { controller, children, className, style, ...props },
760
+ { controller, children, className, style, responsiveBasis = "viewport", ...props },
750
761
  forwardedRef,
751
762
  ) {
763
+ const responsiveRootRef = useRef<HTMLDivElement | null>(null);
764
+ const responsiveContext = useMemo(
765
+ () => ({ responsiveBasis, rootRef: responsiveRootRef }),
766
+ [responsiveBasis],
767
+ );
752
768
  // Provider stays on Root so optional Radix tip surfaces (voice input) work.
753
769
  // Ordinary composer actions use native `title` via ComposerTip to avoid
754
770
  // pulling Popper into every tip hotspot.
755
771
  return (
756
772
  <ComposerContext.Provider value={controller}>
757
- <TooltipProvider delayDuration={300}>
758
- <div
759
- {...props}
760
- ref={mergeRefs(controller.rootRef, forwardedRef)}
761
- data-og-composer-id={controller.id}
762
- className={cn("og-root", className)}
763
- style={{ paddingBottom: "env(safe-area-inset-bottom)", ...style }}
764
- >
765
- {children}
766
- <ComposerAnnouncements />
767
- </div>
768
- </TooltipProvider>
773
+ <ComposerResponsiveContext.Provider value={responsiveContext}>
774
+ <TooltipProvider delayDuration={300}>
775
+ <div
776
+ {...props}
777
+ ref={mergeRefs(controller.rootRef, responsiveRootRef, forwardedRef)}
778
+ data-og-composer-id={controller.id}
779
+ data-og-responsive-basis={responsiveBasis}
780
+ className={cn("og-root og-composer", className)}
781
+ style={{ paddingBottom: "env(safe-area-inset-bottom)", ...style }}
782
+ >
783
+ {children}
784
+ <ComposerAnnouncements />
785
+ </div>
786
+ </TooltipProvider>
787
+ </ComposerResponsiveContext.Provider>
769
788
  </ComposerContext.Provider>
770
789
  );
771
790
  });
@@ -957,8 +976,8 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
957
976
  paletteOpen ? `${controller.listboxId}-option-${controller.palette.highlight}` : undefined
958
977
  }
959
978
  className={cn(
960
- "block w-full resize-none bg-transparent px-3.5 pt-3 pb-1 text-og-composer md:px-4 md:text-og-composer-wide pointer-coarse:min-h-11",
961
- "text-og-fg placeholder:text-og-fg-subtle focus:outline-none focus-visible:outline-none",
979
+ "og-composer-input block w-full resize-none bg-transparent px-3.5 pt-3 pb-1 text-og-composer md:px-4 md:text-og-composer-wide pointer-coarse:min-h-11",
980
+ "text-og-fg placeholder:text-og-fg-subtle focus:outline-hidden focus-visible:outline-hidden",
962
981
  "disabled:cursor-not-allowed disabled:opacity-60",
963
982
  className,
964
983
  )}
@@ -992,7 +1011,7 @@ export const Footer = forwardRef<HTMLDivElement, ComposerFooterProps>(function C
992
1011
  {...props}
993
1012
  ref={ref}
994
1013
  className={cn(
995
- "flex items-end gap-1.5 px-2 pb-2 pt-0.5 sm:px-2.5 sm:pb-2.5",
1014
+ "og-composer-footer flex items-end gap-1.5 px-2 pb-2 pt-0.5 sm:px-2.5 sm:pb-2.5",
996
1015
  // Mobile: one control row — never wrap into a second toolbar line.
997
1016
  "max-sm:flex-nowrap max-sm:items-center max-sm:gap-1",
998
1017
  className,
@@ -1010,7 +1029,7 @@ export const Controls = forwardRef<HTMLSpanElement, ComposerControlsProps>(
1010
1029
  {...props}
1011
1030
  ref={ref}
1012
1031
  className={cn(
1013
- "flex min-w-0 flex-1 flex-wrap items-center gap-1.5",
1032
+ "og-composer-controls flex min-w-0 flex-1 flex-wrap items-center gap-1.5",
1014
1033
  "max-sm:flex-nowrap max-sm:gap-1",
1015
1034
  className,
1016
1035
  )}
@@ -1030,7 +1049,10 @@ export const Hint = forwardRef<HTMLSpanElement, ComposerHintProps>(function Comp
1030
1049
  <span
1031
1050
  {...props}
1032
1051
  ref={ref}
1033
- className={cn("min-w-0 flex-1 px-1.5 text-og-xs text-og-fg-subtle max-sm:hidden", className)}
1052
+ className={cn(
1053
+ "og-composer-hint min-w-0 flex-1 px-1.5 text-og-xs text-og-fg-subtle max-sm:hidden",
1054
+ className,
1055
+ )}
1034
1056
  >
1035
1057
  {children ?? controller.messages.keyboardHint}
1036
1058
  </span>
@@ -1048,7 +1070,7 @@ export const Actions = forwardRef<HTMLSpanElement, ComposerActionsProps>(functio
1048
1070
  {...props}
1049
1071
  ref={ref}
1050
1072
  className={cn(
1051
- "ml-auto flex shrink-0 items-center gap-1.5",
1073
+ "og-composer-actions ml-auto flex shrink-0 items-center gap-1.5",
1052
1074
  "max-sm:flex-nowrap max-sm:gap-1",
1053
1075
  className,
1054
1076
  )}
@@ -1386,8 +1408,12 @@ function WorkstreamPausedStrip({
1386
1408
  ) : (
1387
1409
  <PlayIcon className="size-3.5 fill-current" />
1388
1410
  )}
1389
- <span className="hidden min-[400px]:inline">{messages.resumeThisWorkstream}</span>
1390
- <span className="min-[400px]:hidden">{messages.resumeShort}</span>
1411
+ <span className="og-composer-resume-label-long hidden min-[400px]:inline">
1412
+ {messages.resumeThisWorkstream}
1413
+ </span>
1414
+ <span className="og-composer-resume-label-short min-[400px]:hidden">
1415
+ {messages.resumeShort}
1416
+ </span>
1391
1417
  </button>
1392
1418
  <button
1393
1419
  type="button"
@@ -1412,7 +1438,7 @@ function WorkstreamPausedStrip({
1412
1438
  {blockerHref(entry, controlLinks) ? (
1413
1439
  <a
1414
1440
  href={blockerHref(entry, controlLinks)}
1415
- className="font-medium text-og-fg underline decoration-og-border-strong underline-offset-2 hover:text-og-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-ring/40"
1441
+ className="font-medium text-og-fg underline decoration-og-border-strong underline-offset-2 hover:text-og-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-ring/40"
1416
1442
  >
1417
1443
  {entry.displayName}
1418
1444
  </a>
@@ -1582,7 +1608,7 @@ function AttachmentChips({
1582
1608
  onRetry?: ((id: string) => void) | undefined;
1583
1609
  }) {
1584
1610
  return (
1585
- <div className="flex flex-wrap gap-2 border-b border-og-border px-3 py-2">
1611
+ <div className="flex flex-wrap gap-2 px-3 py-2">
1586
1612
  {attachments.map((attachment) => {
1587
1613
  const failed = attachment.status === "failed";
1588
1614
  const statusText =