@opengeni/react 0.45.0 → 0.48.0

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 (141) hide show
  1. package/README.md +68 -12
  2. package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  3. package/dist/chunk-FK6VG4LL.js.map +1 -0
  4. package/dist/{chunk-HFO4ERGQ.js → chunk-FWXGSNXT.js} +2 -2
  5. package/dist/{chunk-SRFUT2ZU.js → chunk-I4MPFDRP.js} +119 -63
  6. package/dist/chunk-I4MPFDRP.js.map +1 -0
  7. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  8. package/dist/chunk-KZ73RL76.js.map +1 -0
  9. package/dist/{chunk-LWR4MXSS.js → chunk-LHLYWG5R.js} +10 -1
  10. package/dist/chunk-LHLYWG5R.js.map +1 -0
  11. package/dist/{chunk-L3EWO3UK.js → chunk-NJ7M2B6Z.js} +445 -84
  12. package/dist/chunk-NJ7M2B6Z.js.map +1 -0
  13. package/dist/{chunk-VCBFUSUZ.js → chunk-O4VXOBGF.js} +3 -3
  14. package/dist/{chunk-VCBFUSUZ.js.map → chunk-O4VXOBGF.js.map} +1 -1
  15. package/dist/{chunk-6EDV6YFE.js → chunk-OL6QVXGF.js} +121 -29
  16. package/dist/chunk-OL6QVXGF.js.map +1 -0
  17. package/dist/chunk-VZTQ73XT.js +86 -0
  18. package/dist/chunk-VZTQ73XT.js.map +1 -0
  19. package/dist/{chunk-EHSYZ4KP.js → chunk-WYA65Y3F.js} +16 -16
  20. package/dist/chunk-WYA65Y3F.js.map +1 -0
  21. package/dist/{chunk-FD72GJMK.js → chunk-XCNTXWBB.js} +274 -125
  22. package/dist/chunk-XCNTXWBB.js.map +1 -0
  23. package/dist/client.d.ts +1 -1
  24. package/dist/components/chat-composer.d.ts +7 -2
  25. package/dist/components/composer-transcription-control.d.ts +2 -0
  26. package/dist/components/composer.d.ts +14 -0
  27. package/dist/components/machines/machine-detail.d.ts +3 -1
  28. package/dist/components/machines-dashboard.d.ts +3 -3
  29. package/dist/components/message-timeline.d.ts +12 -2
  30. package/dist/components/sandbox-files.d.ts +2 -2
  31. package/dist/components/sandbox-terminal.d.ts +29 -0
  32. package/dist/components/sandbox-workspace.d.ts +5 -1
  33. package/dist/components/workspace-dock.d.ts +3 -1
  34. package/dist/composer.d.ts +2 -2
  35. package/dist/composer.js +6 -6
  36. package/dist/hooks/internal.d.ts +6 -0
  37. package/dist/hooks/use-codex-accounts.d.ts +1 -1
  38. package/dist/hooks/use-environments.d.ts +10 -4
  39. package/dist/hooks/use-machines.d.ts +7 -0
  40. package/dist/hooks/use-sandbox-files.d.ts +19 -0
  41. package/dist/hooks/use-sandbox-git.d.ts +7 -0
  42. package/dist/hooks/use-transcription.d.ts +2 -2
  43. package/dist/hooks/use-voice-input.d.ts +13 -4
  44. package/dist/index.d.ts +6 -5
  45. package/dist/index.js +904 -538
  46. package/dist/index.js.map +1 -1
  47. package/dist/lib/composer-responsive-context.d.ts +9 -0
  48. package/dist/lib/use-portal-token-style.d.ts +14 -3
  49. package/dist/machines.js +2 -2
  50. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-U5JFG7HT.js} +19 -18
  51. package/dist/queue-surface-implementation-U5JFG7HT.js.map +1 -0
  52. package/dist/realtime/realtime-control.d.ts +9 -0
  53. package/dist/realtime.js +29 -32
  54. package/dist/realtime.js.map +1 -1
  55. package/dist/session-ui.js +5 -5
  56. package/dist/session.js +4 -4
  57. package/dist/timeline/activity-rail.d.ts +3 -2
  58. package/dist/timeline/index.d.ts +2 -2
  59. package/dist/timeline/parsers.d.ts +3 -3
  60. package/dist/timeline/registry.d.ts +3 -0
  61. package/dist/timeline/screenshot-lightbox.d.ts +1 -1
  62. package/dist/timeline/types.d.ts +4 -0
  63. package/dist/voice-recording-store.d.ts +8 -2
  64. package/package.json +20 -4
  65. package/src/client.ts +3 -0
  66. package/src/components/chat-composer.tsx +12 -2
  67. package/src/components/code-editor.tsx +1 -1
  68. package/src/components/command-palette.tsx +1 -1
  69. package/src/components/composer-transcription-control.tsx +41 -16
  70. package/src/components/composer.tsx +52 -26
  71. package/src/components/copy-button.tsx +2 -2
  72. package/src/components/desktop-viewer.tsx +2 -2
  73. package/src/components/file-browser.tsx +39 -17
  74. package/src/components/human-input-form.tsx +2 -2
  75. package/src/components/machine-card.tsx +1 -1
  76. package/src/components/machines/machine-detail.tsx +39 -15
  77. package/src/components/machines-dashboard.tsx +7 -6
  78. package/src/components/message-timeline.tsx +44 -11
  79. package/src/components/model-picker.tsx +2 -2
  80. package/src/components/model-policy-picker.tsx +16 -15
  81. package/src/components/queue-surface-implementation.tsx +20 -16
  82. package/src/components/queue-surface-state.tsx +2 -2
  83. package/src/components/sandbox-files.tsx +140 -60
  84. package/src/components/sandbox-terminal.tsx +24 -5
  85. package/src/components/sandbox-workspace.tsx +180 -142
  86. package/src/components/session-chrome.tsx +14 -14
  87. package/src/components/tooltip.tsx +13 -16
  88. package/src/components/user-message-body.tsx +1 -1
  89. package/src/components/workbench-changes.tsx +4 -4
  90. package/src/components/workspace-dock.tsx +13 -5
  91. package/src/composer.ts +3 -0
  92. package/src/hooks/internal.ts +132 -38
  93. package/src/hooks/use-codex-accounts.ts +4 -4
  94. package/src/hooks/use-environments.ts +24 -5
  95. package/src/hooks/use-file-attachments.ts +70 -32
  96. package/src/hooks/use-goal.ts +17 -5
  97. package/src/hooks/use-machines.ts +47 -1
  98. package/src/hooks/use-sandbox-files.ts +310 -59
  99. package/src/hooks/use-sandbox-git.ts +148 -41
  100. package/src/hooks/use-session-capabilities.ts +35 -24
  101. package/src/hooks/use-session-events.ts +18 -4
  102. package/src/hooks/use-transcription.ts +4 -14
  103. package/src/hooks/use-turn-queue.ts +17 -5
  104. package/src/hooks/use-voice-input.ts +481 -36
  105. package/src/hooks/use-windowed-sections.ts +9 -7
  106. package/src/index.ts +11 -6
  107. package/src/lib/composer-responsive-context.ts +15 -0
  108. package/src/lib/use-portal-token-style.ts +60 -15
  109. package/src/provider.tsx +52 -7
  110. package/src/realtime/dropdown-menu.tsx +15 -19
  111. package/src/realtime/realtime-control.tsx +18 -7
  112. package/src/timeline/activity-rail.tsx +15 -6
  113. package/src/timeline/index.ts +1 -1
  114. package/src/timeline/parsers.ts +33 -20
  115. package/src/timeline/projection.ts +23 -0
  116. package/src/timeline/registry.ts +7 -0
  117. package/src/timeline/screenshot-lightbox.tsx +15 -4
  118. package/src/timeline/shared.tsx +8 -8
  119. package/src/timeline/tool-renderers.tsx +166 -22
  120. package/src/timeline/turn-summary.tsx +6 -6
  121. package/src/timeline/types.ts +4 -0
  122. package/src/voice-recording-store.ts +15 -0
  123. package/styles/compiled.css +5065 -0
  124. package/styles/compiled.d.ts +2 -0
  125. package/styles/effective-tokens.css +27 -0
  126. package/styles/index.css +3 -0
  127. package/styles/responsive.css +196 -0
  128. package/styles/responsive.d.ts +2 -0
  129. package/styles/tokens.css +50 -17
  130. package/dist/chunk-6EDV6YFE.js.map +0 -1
  131. package/dist/chunk-EB67J347.js.map +0 -1
  132. package/dist/chunk-EHSYZ4KP.js.map +0 -1
  133. package/dist/chunk-FD72GJMK.js.map +0 -1
  134. package/dist/chunk-JB5725BI.js +0 -48
  135. package/dist/chunk-JB5725BI.js.map +0 -1
  136. package/dist/chunk-L3EWO3UK.js.map +0 -1
  137. package/dist/chunk-LWR4MXSS.js.map +0 -1
  138. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  139. package/dist/chunk-U3CSCMWA.js.map +0 -1
  140. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
  141. /package/dist/{chunk-HFO4ERGQ.js.map → chunk-FWXGSNXT.js.map} +0 -0
@@ -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,7 +6,14 @@
6
6
  // how have they moved over the chosen window. Built entirely on the existing
7
7
  // `og-*` token system — no new visual language, just the missing depth.
8
8
  // ----------------------------------------------------------------------------
9
- import { ArrowLeftIcon, CpuIcon, LaptopIcon, RadioIcon, ServerIcon } from "lucide-react";
9
+ import {
10
+ ArrowLeftIcon,
11
+ CpuIcon,
12
+ LaptopIcon,
13
+ RadioIcon,
14
+ ServerIcon,
15
+ Trash2Icon,
16
+ } from "lucide-react";
10
17
  import { cn } from "../../lib/cn";
11
18
  import { formatRelativeTime } from "../../lib/format";
12
19
  import type { MachineView, MetricSample } from "../../types/machines";
@@ -23,6 +30,8 @@ export type MachineDetailProps = {
23
30
  onWindowChange: (w: MetricWindow) => void;
24
31
  loadingSeries?: boolean | undefined;
25
32
  onBack?: (() => void) | undefined;
33
+ /** Open the workspace-admin removal confirmation for a self-hosted machine. */
34
+ onRemove?: ((machine: MachineView) => void) | undefined;
26
35
  now?: number | undefined;
27
36
  className?: string | undefined;
28
37
  };
@@ -40,6 +49,7 @@ export function MachineDetail({
40
49
  onWindowChange,
41
50
  loadingSeries,
42
51
  onBack,
52
+ onRemove,
43
53
  now = Date.now(),
44
54
  className,
45
55
  }: MachineDetailProps) {
@@ -78,24 +88,38 @@ export function MachineDetail({
78
88
  </span>
79
89
  </div>
80
90
 
81
- {/* range selector */}
82
- <div className="flex shrink-0 items-center gap-0.5 rounded-og-md border border-og-border bg-og-surface-1 p-0.5">
83
- {METRIC_WINDOWS.map((w) => (
91
+ <div className="flex shrink-0 items-center gap-2">
92
+ {onRemove && machine.enrollmentId && !machine.isSessionGroup ? (
84
93
  <button
85
- key={w}
86
94
  type="button"
87
- onClick={() => onWindowChange(w)}
88
- data-active={w === window}
89
- className={cn(
90
- "rounded-og-sm px-2 py-1 font-og-mono text-og-xs transition-colors",
91
- w === window
92
- ? "bg-og-surface-3 text-og-fg shadow-og-sm"
93
- : "text-og-fg-subtle hover:text-og-fg-muted",
94
- )}
95
+ data-remove-machine
96
+ aria-label={`Remove machine ${machine.name}`}
97
+ onClick={() => onRemove(machine)}
98
+ className="inline-flex min-h-9 items-center gap-1.5 rounded-og-sm border border-og-status-failed/40 px-2.5 py-1 font-medium text-og-xs text-og-status-failed transition-colors hover:bg-og-status-failed/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-status-failed/50 pointer-coarse:min-h-11"
95
99
  >
96
- {w}
100
+ <Trash2Icon className="size-3.5" aria-hidden />
101
+ <span className="hidden sm:inline">Remove machine</span>
97
102
  </button>
98
- ))}
103
+ ) : null}
104
+ {/* range selector */}
105
+ <div className="flex items-center gap-0.5 rounded-og-md border border-og-border bg-og-surface-1 p-0.5">
106
+ {METRIC_WINDOWS.map((w) => (
107
+ <button
108
+ key={w}
109
+ type="button"
110
+ onClick={() => onWindowChange(w)}
111
+ data-active={w === window}
112
+ className={cn(
113
+ "rounded-og-sm px-2 py-1 font-og-mono text-og-xs transition-colors",
114
+ w === window
115
+ ? "bg-og-surface-3 text-og-fg shadow-og-sm"
116
+ : "text-og-fg-subtle hover:text-og-fg-muted",
117
+ )}
118
+ >
119
+ {w}
120
+ </button>
121
+ ))}
122
+ </div>
99
123
  </div>
100
124
  </div>
101
125
 
@@ -20,7 +20,7 @@ export type MachinesDashboardProps = {
20
20
  onOpenDetail?: ((machine: MachineView) => void) | undefined;
21
21
  /** Shared clock so freshness/relative times render consistently across cards. */
22
22
  now?: number | undefined;
23
- /** Open the enrollment flow (the "Enroll a machine" CTA). */
23
+ /** Open the connection flow (the "Connect a machine" CTA). */
24
24
  onEnroll?: (() => void) | undefined;
25
25
  onRefresh?: (() => void) | undefined;
26
26
  className?: string | undefined;
@@ -38,7 +38,8 @@ function EmptyState({ onEnroll }: { onEnroll?: (() => void) | undefined }) {
38
38
  <div className="space-y-1">
39
39
  <p className="text-og-base font-medium text-og-fg">No machines yet</p>
40
40
  <p className="max-w-xs text-og-sm text-og-fg-muted">
41
- Enroll your own computer to run the agent on it — your files, your terminal, your desktop.
41
+ Connect your own computer to run the agent on it — your files, your terminal, your
42
+ desktop.
42
43
  </p>
43
44
  </div>
44
45
  {onEnroll ? (
@@ -49,7 +50,7 @@ function EmptyState({ onEnroll }: { onEnroll?: (() => void) | undefined }) {
49
50
  className="inline-flex items-center gap-1.5 rounded-og-sm bg-og-accent px-3 py-1.5 text-og-sm font-medium text-og-accent-fg transition-colors hover:bg-og-accent-strong pointer-coarse:min-h-11"
50
51
  >
51
52
  <PlusIcon className="size-3.5" aria-hidden />
52
- Enroll a machine
53
+ Connect a machine
53
54
  </button>
54
55
  ) : null}
55
56
  </div>
@@ -93,7 +94,7 @@ function Header({
93
94
  className="inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2.5 py-1 text-og-sm font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg pointer-coarse:min-h-11"
94
95
  >
95
96
  <PlusIcon className="size-3.5" aria-hidden />
96
- Enroll machine
97
+ Connect a machine
97
98
  </button>
98
99
  ) : null}
99
100
  </div>
@@ -102,10 +103,10 @@ function Header({
102
103
  }
103
104
 
104
105
  /**
105
- * The workspace Machines dashboard: the fleet of selfhosted enrollments + the
106
+ * The workspace Machines dashboard: the fleet of Connected Machines + the
106
107
  * session's managed sandbox, each with its connection-status pill, state badges,
107
108
  * latest metrics, and an attach/swap affordance. Renders the empty state (no
108
- * machines → enroll CTA), a load error, and the populated grid. The component
109
+ * machines → connect CTA), a load error, and the populated grid. The component
109
110
  * is purely presentational — feed it `MachinesResponse` data via `useMachines`.
110
111
  */
111
112
  export function MachinesDashboard({
@@ -68,6 +68,7 @@ import {
68
68
  type NoticeItem,
69
69
  type TimelineGroup,
70
70
  type TimelineItem,
71
+ type RetainedScreenshotLoader,
71
72
  type ToolRegistry,
72
73
  type TurnSummaryOptions,
73
74
  type UserMessageItem,
@@ -123,6 +124,12 @@ export type MessageTimelineProps = {
123
124
  * a Reconnect button without a handler to run it.
124
125
  */
125
126
  onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
127
+ /**
128
+ * Decide which durable authentication notices this timeline presents.
129
+ * Defaults to showing every notice. Embedded hosts can suppress notices for
130
+ * credentials they manage elsewhere without discarding the underlying event.
131
+ */
132
+ shouldRenderAuthNeeded?: ((item: AuthNeededItem) => boolean) | undefined;
126
133
  /**
127
134
  * Resolve a provider domain (from a reconnect card) to a logo URL the host
128
135
  * serves itself — the app maps it through its catalog + `catalogAssetUrl`.
@@ -137,6 +144,8 @@ export type MessageTimelineProps = {
137
144
  * `createDefaultToolRegistry({ entries })` to add custom tool renderers.
138
145
  */
139
146
  toolRegistry?: ToolRegistry | undefined;
147
+ /** Resolve opaque retained screenshot receipts through the authenticated host SDK. */
148
+ loadRetainedScreenshot?: RetainedScreenshotLoader | undefined;
140
149
  /**
141
150
  * Display name of the session's active compute target (Connected Machine or
142
151
  * cloud sandbox). When set, exec_command collapsed previews prefix `on {label}`.
@@ -167,6 +176,8 @@ export type MessageTimelineProps = {
167
176
  * scrolls the in-memory window.
168
177
  */
169
178
  onJumpToLatest?: (() => void | Promise<void>) | undefined;
179
+ /** Host-owned content appended after timeline groups, such as startup progress. */
180
+ trailingState?: ReactNode | undefined;
170
181
  emptyState?: ReactNode | undefined;
171
182
  className?: string | undefined;
172
183
  };
@@ -277,8 +288,10 @@ export function MessageTimeline({
277
288
  onOpenSession,
278
289
  onMemoryClick,
279
290
  onReconnect,
291
+ shouldRenderAuthNeeded,
280
292
  resolveProviderLogo,
281
293
  toolRegistry = defaultToolRegistry,
294
+ loadRetainedScreenshot,
282
295
  computeLabel = null,
283
296
  turnSummary,
284
297
  autoFollow = true,
@@ -291,10 +304,17 @@ export function MessageTimeline({
291
304
  loadingNewer = false,
292
305
  onLoadNewer,
293
306
  onJumpToLatest,
307
+ trailingState,
294
308
  emptyState,
295
309
  className,
296
310
  }: MessageTimelineProps) {
297
- const resolvedItems = useMemo(() => items ?? buildTimeline(events ?? []), [items, events]);
311
+ const resolvedItems = useMemo(() => {
312
+ const projected = items ?? buildTimeline(events ?? []);
313
+ if (!shouldRenderAuthNeeded) {
314
+ return projected;
315
+ }
316
+ return projected.filter((item) => item.kind !== "auth-needed" || shouldRenderAuthNeeded(item));
317
+ }, [items, events, shouldRenderAuthNeeded]);
298
318
  const allGroups = useMemo(() => groupTimeline(resolvedItems), [resolvedItems]);
299
319
  const groups = useStableTimelineGroupKeys(allGroups);
300
320
  const scrollRef = useRef<HTMLDivElement | null>(null);
@@ -1278,7 +1298,7 @@ export function MessageTimeline({
1278
1298
  className={cn(
1279
1299
  // tabIndex=-1 is programmatic only — never paint a focus ring on
1280
1300
  // the whole scroller (click + Shift used to flash a blue outline).
1281
- "min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-6 sm:px-6 outline-none",
1301
+ "min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-6 sm:px-6 outline-hidden",
1282
1302
  autoFollow && pinned && !hasNewer
1283
1303
  ? "[overflow-anchor:none]"
1284
1304
  : "[overflow-anchor:auto]",
@@ -1325,6 +1345,7 @@ export function MessageTimeline({
1325
1345
  onReconnect,
1326
1346
  resolveProviderLogo,
1327
1347
  toolRegistry,
1348
+ loadRetainedScreenshot,
1328
1349
  turnSummary,
1329
1350
  ]}
1330
1351
  >
@@ -1337,6 +1358,7 @@ export function MessageTimeline({
1337
1358
  onReconnect={onReconnect}
1338
1359
  resolveProviderLogo={resolveProviderLogo}
1339
1360
  toolRegistry={toolRegistry}
1361
+ loadRetainedScreenshot={loadRetainedScreenshot}
1340
1362
  turnSummary={turnSummary}
1341
1363
  foldLiveCluster={isAgentProgress(next)}
1342
1364
  trailingAgentText={trailingAgentTextAfterTurn(group, next)}
@@ -1349,6 +1371,9 @@ export function MessageTimeline({
1349
1371
  </div>
1350
1372
  );
1351
1373
  })}
1374
+ {groups.length > 0 && trailingState ? (
1375
+ <div data-og-timeline-trailing-state="">{trailingState}</div>
1376
+ ) : null}
1352
1377
  {hasNewer ? (
1353
1378
  <div
1354
1379
  ref={bottomSentinelRef}
@@ -1502,7 +1527,7 @@ export function MessageTimeline({
1502
1527
  }
1503
1528
  }}
1504
1529
  className={cn(
1505
- "absolute bottom-4 left-1/2 -translate-x-1/2",
1530
+ "absolute inset-x-0 bottom-4 mx-auto w-fit",
1506
1531
  "inline-flex items-center gap-1.5 rounded-full border border-og-border bg-og-surface-3/90 px-3 py-1.5",
1507
1532
  "text-og-control font-medium text-og-fg shadow-og-md backdrop-blur",
1508
1533
  "hover:border-og-border-strong",
@@ -1677,6 +1702,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1677
1702
  onReconnect,
1678
1703
  resolveProviderLogo,
1679
1704
  toolRegistry,
1705
+ loadRetainedScreenshot,
1680
1706
  turnSummary,
1681
1707
  insideTurn = false,
1682
1708
  nestClusterChips = false,
@@ -1693,6 +1719,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1693
1719
  onReconnect?: ((item: AuthNeededItem) => void | Promise<void>) | undefined;
1694
1720
  resolveProviderLogo?: ((providerDomain: string) => string | null | undefined) | undefined;
1695
1721
  toolRegistry: ToolRegistry;
1722
+ loadRetainedScreenshot?: RetainedScreenshotLoader | undefined;
1696
1723
  turnSummary?: TurnSummaryOptions | undefined;
1697
1724
  /** A completed cluster of a still-RUNNING turn (not the live tail) folds
1698
1725
  behind a neutral chip — the one place activity without an outcome still
@@ -1766,6 +1793,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1766
1793
  onOpenSession={onOpenSession}
1767
1794
  onMemoryClick={onMemoryClick}
1768
1795
  toolRegistry={toolRegistry}
1796
+ loadRetainedScreenshot={loadRetainedScreenshot}
1769
1797
  bare
1770
1798
  />
1771
1799
  );
@@ -1787,6 +1815,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1787
1815
  onOpenSession={onOpenSession}
1788
1816
  onMemoryClick={onMemoryClick}
1789
1817
  toolRegistry={toolRegistry}
1818
+ loadRetainedScreenshot={loadRetainedScreenshot}
1790
1819
  bare
1791
1820
  />
1792
1821
  </TurnSummary>
@@ -1812,6 +1841,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1812
1841
  onOpenSession={onOpenSession}
1813
1842
  onMemoryClick={onMemoryClick}
1814
1843
  toolRegistry={toolRegistry}
1844
+ loadRetainedScreenshot={loadRetainedScreenshot}
1815
1845
  bare
1816
1846
  />
1817
1847
  </TurnRailFrame>
@@ -1838,6 +1868,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1838
1868
  onReconnect,
1839
1869
  resolveProviderLogo,
1840
1870
  toolRegistry,
1871
+ loadRetainedScreenshot,
1841
1872
  turnSummary,
1842
1873
  ]}
1843
1874
  >
@@ -1849,6 +1880,7 @@ const TimelineGroupView = memo(function TimelineGroupView({
1849
1880
  onReconnect={onReconnect}
1850
1881
  resolveProviderLogo={resolveProviderLogo}
1851
1882
  toolRegistry={toolRegistry}
1883
+ loadRetainedScreenshot={loadRetainedScreenshot}
1852
1884
  turnSummary={turnSummary}
1853
1885
  insideTurn
1854
1886
  nestClusterChips={nestClusters}
@@ -2184,12 +2216,12 @@ function CompactionRow({ item }: { item: ContextCompactionItem }) {
2184
2216
  : null;
2185
2217
  const title =
2186
2218
  item.phase === "started"
2187
- ? "Compacting conversation memory…"
2219
+ ? "Compacting conversation history…"
2188
2220
  : item.phase === "compacted"
2189
2221
  ? before && after
2190
- ? `Conversation memory compacted · ~${before} → ~${after} tokens`
2191
- : "Conversation memory compacted"
2192
- : "Couldn’t compact conversation memory";
2222
+ ? `Conversation history compacted · ~${before} → ~${after} estimated history tokens`
2223
+ : "Conversation history compacted"
2224
+ : "Couldn’t compact conversation history";
2193
2225
  const subtitle =
2194
2226
  item.phase === "compacted"
2195
2227
  ? "Chat history above is unchanged"
@@ -2247,7 +2279,7 @@ function MessageFooterTime({ occurredAt }: { occurredAt: string }) {
2247
2279
  className={cn(
2248
2280
  "shrink-0 tabular-nums text-og-xs text-og-fg-subtle",
2249
2281
  "opacity-0 transition-opacity duration-150",
2250
- "group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 pointer-coarse:opacity-70",
2282
+ "group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 pointer-coarse:opacity-100",
2251
2283
  )}
2252
2284
  >
2253
2285
  {formatClockTime(occurredAt)}
@@ -2428,7 +2460,7 @@ function WorkerCompletionRow({
2428
2460
  onClick={() => onOpenSession(item.childSessionId)}
2429
2461
  className={cn(
2430
2462
  "-my-0.5 -mr-1 inline-flex shrink-0 items-center gap-1 rounded-og-sm px-2 py-1 text-og-sm font-medium text-og-fg-muted pointer-coarse:py-2",
2431
- "outline-none transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
2463
+ "outline-hidden transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
2432
2464
  "focus-visible:ring-2 focus-visible:ring-og-accent",
2433
2465
  )}
2434
2466
  >
@@ -2444,7 +2476,7 @@ function WorkerCompletionRow({
2444
2476
  type="button"
2445
2477
  className={cn(
2446
2478
  "group/wc -mx-1 inline-flex w-fit items-center gap-1 rounded-og-sm px-1 py-0.5 text-og-xs font-medium text-og-fg-subtle",
2447
- "outline-none transition-colors duration-150 hover:text-og-fg-muted focus-visible:ring-2 focus-visible:ring-og-accent",
2479
+ "outline-hidden transition-colors duration-150 hover:text-og-fg-muted focus-visible:ring-2 focus-visible:ring-og-accent",
2448
2480
  )}
2449
2481
  >
2450
2482
  <ChevronRightIcon className="size-3 transition-transform duration-150 ease-og-in-out group-data-[state=open]/wc:rotate-90" />
@@ -2693,7 +2725,8 @@ function AuthNeededRow({
2693
2725
  const enter = useEntranceAnimation();
2694
2726
  const [busy, setBusy] = useState(false);
2695
2727
  const [failed, setFailed] = useState(false);
2696
- const provider = providerLabel(item.providerDomain);
2728
+ const provider =
2729
+ item.serverId === "codex_apps" ? "Codex Apps" : providerLabel(item.providerDomain);
2697
2730
  const unavailable =
2698
2731
  item.reason === "personal_authority_unavailable" ||
2699
2732
  item.reason === "unsupported_auth" ||
@@ -100,10 +100,10 @@ export function ModelPicker({
100
100
  disabled={disabled === true}
101
101
  aria-label={label}
102
102
  className={cn(
103
- "h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent",
103
+ "og-model-picker-native h-8 max-w-[180px] cursor-pointer appearance-none truncate rounded-og-md bg-transparent pointer-coarse:h-11",
104
104
  "py-0 pl-2 pr-6 text-[13px] text-og-fg-muted",
105
105
  "transition-colors duration-150 hover:bg-og-surface-2 hover:text-og-fg",
106
- "focus:outline-none focus-visible:outline-none",
106
+ "focus:outline-hidden focus-visible:outline-hidden",
107
107
  "disabled:cursor-not-allowed disabled:opacity-50",
108
108
  )}
109
109
  >
@@ -12,14 +12,13 @@ import { DropdownMenu } from "radix-ui";
12
12
  import {
13
13
  useEffect,
14
14
  useMemo,
15
- useRef,
16
15
  useState,
17
16
  type CSSProperties,
18
17
  type ReactNode,
19
18
  type SVGProps,
20
19
  } from "react";
21
20
  import { cn } from "../lib/cn";
22
- import { usePortalTokenStyle } from "../lib/use-portal-token-style";
21
+ import { usePortalTokenSource, usePortalTokenStyle } from "../lib/use-portal-token-style";
23
22
  import {
24
23
  coerceReasoningEffortForModel,
25
24
  effortOptionsForModel,
@@ -243,7 +242,7 @@ export function PickerNavRow(props: {
243
242
  onClick={props.onClick}
244
243
  data-testid={props.testId}
245
244
  className={cn(
246
- "flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-[var(--og-model-picker-row-padding-x)] py-[var(--og-model-picker-row-padding-y)] text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
245
+ "flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-[var(--og-model-picker-row-padding-x)] py-[var(--og-model-picker-row-padding-y)] text-left text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
247
246
  props.disabled && "cursor-not-allowed opacity-50",
248
247
  )}
249
248
  >
@@ -278,7 +277,7 @@ export function PickerBackHeader(props: {
278
277
  type="button"
279
278
  onClick={props.onBack}
280
279
  data-testid="model-picker-back"
281
- className="flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40"
280
+ className="flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40"
282
281
  >
283
282
  <ChevronLeftIcon className="size-3.5 shrink-0 text-og-fg-subtle" />
284
283
  {props.icon}
@@ -455,7 +454,7 @@ export function ModelPolicyPickerMenu(
455
454
  );
456
455
  }}
457
456
  className={cn(
458
- "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
457
+ "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
459
458
  activeModel && props.latencyMode === "fast" && "text-og-fg",
460
459
  )}
461
460
  >
@@ -487,7 +486,7 @@ export function ModelPolicyPickerMenu(
487
486
  disabled={!focusModel.selectable}
488
487
  onClick={() => selectEffort(focusModel, effort)}
489
488
  className={cn(
490
- "flex h-[var(--og-model-picker-effort-height)] w-full cursor-pointer items-center rounded-og-sm px-[var(--og-model-picker-row-padding-x)] text-left text-og-menu text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
489
+ "flex h-[var(--og-model-picker-effort-height)] w-full cursor-pointer items-center rounded-og-sm px-[var(--og-model-picker-row-padding-x)] text-left text-og-menu text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
491
490
  selected && "bg-og-surface-2",
492
491
  )}
493
492
  >
@@ -542,8 +541,8 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
542
541
  if (props.open === undefined) setUncontrolledOpen(next);
543
542
  props.onOpenChange?.(next);
544
543
  };
545
- const triggerRef = useRef<HTMLButtonElement | null>(null);
546
- const portalStyle = usePortalTokenStyle(triggerRef);
544
+ const trigger = usePortalTokenSource<HTMLButtonElement>();
545
+ const portalStyle = usePortalTokenStyle(trigger.source);
547
546
  const messages = useMemo(
548
547
  () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),
549
548
  [props.messages],
@@ -556,7 +555,7 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
556
555
  return (
557
556
  <span
558
557
  className={cn(
559
- "inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2",
558
+ "og-root inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2",
560
559
  props.className,
561
560
  )}
562
561
  aria-label={messages.loading}
@@ -568,12 +567,12 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
568
567
  <DropdownMenu.Root open={open} onOpenChange={setOpen}>
569
568
  <DropdownMenu.Trigger asChild>
570
569
  <button
571
- ref={triggerRef}
570
+ ref={trigger.ref}
572
571
  type="button"
573
572
  disabled={props.disabled}
574
573
  aria-label={messages.label}
575
574
  className={cn(
576
- "og-root inline-flex h-[var(--og-model-picker-trigger-height)] min-w-0 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-og-control text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-[var(--og-model-picker-trigger-height-mobile)] max-sm:max-w-[7.5rem] max-sm:px-2",
575
+ "og-root og-model-policy-trigger inline-flex h-[var(--og-model-picker-trigger-height)] min-w-0 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-og-control text-og-fg-muted outline-hidden transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-11 max-sm:max-w-[7.5rem] max-sm:px-2",
577
576
  props.className,
578
577
  )}
579
578
  >
@@ -584,13 +583,15 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
584
583
  }
585
584
  className="text-og-fg"
586
585
  />
587
- <span className="min-w-0 truncate font-medium text-og-fg max-sm:hidden">
586
+ <span className="og-model-policy-label-full min-w-0 truncate font-medium text-og-fg max-sm:hidden">
588
587
  {selected?.label ?? props.model}
589
588
  </span>
590
- <span className="min-w-0 truncate font-medium text-og-fg sm:hidden">
589
+ <span className="og-model-policy-label-short min-w-0 truncate font-medium text-og-fg sm:hidden">
591
590
  {selected?.shortLabel ?? selected?.label ?? props.model}
592
591
  </span>
593
- <span className="max-sm:hidden">{labelReasoningEffort(props.effort)}</span>
592
+ <span className="og-model-policy-effort max-sm:hidden">
593
+ {labelReasoningEffort(props.effort)}
594
+ </span>
594
595
  {props.latencyMode === "fast" ? (
595
596
  <ZapIcon
596
597
  className="size-3.5 shrink-0 fill-current stroke-current text-og-fg"
@@ -609,7 +610,7 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
609
610
  collisionPadding={12}
610
611
  onCloseAutoFocus={(event) => event.preventDefault()}
611
612
  className={cn(
612
- "og-root z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-[var(--og-model-picker-menu-width)] max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-[var(--og-model-picker-menu-padding)] text-og-fg shadow-og-lg",
613
+ "og-root og-model-policy-menu z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-[var(--og-model-picker-menu-width)] max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-[var(--og-model-picker-menu-padding)] text-og-fg shadow-og-lg",
613
614
  props.contentClassName,
614
615
  )}
615
616
  style={{ ...portalStyle, ...props.contentStyle }}
@@ -32,7 +32,6 @@ import {
32
32
  useCallback,
33
33
  useId,
34
34
  useMemo,
35
- useRef,
36
35
  useState,
37
36
  type KeyboardEvent as ReactKeyboardEvent,
38
37
  } from "react";
@@ -40,7 +39,11 @@ import {
40
39
  import { DropdownMenu } from "radix-ui";
41
40
  import type { ComposerState } from "../hooks/use-composer";
42
41
  import type { QueueMutationKind, UseTurnQueueResult } from "../hooks/use-turn-queue";
43
- import { type PortalTokenStyle, usePortalTokenStyle } from "../lib/use-portal-token-style";
42
+ import {
43
+ type PortalTokenStyle,
44
+ usePortalTokenSource,
45
+ usePortalTokenStyle,
46
+ } from "../lib/use-portal-token-style";
44
47
  import { requestQueueDraftEdit } from "./queue-draft-policy";
45
48
  import { QueueErrorAlert, QueueStoppingStatus } from "./queue-surface-state";
46
49
 
@@ -80,8 +83,9 @@ export function QueueSurface({
80
83
  const [replaceDraftFor, setReplaceDraftFor] = useState<string | null>(null);
81
84
  const [announcement, setAnnouncement] = useState("");
82
85
  const [draggedTurnId, setDraggedTurnId] = useState<string | null>(null);
83
- const surfaceRef = useRef<HTMLDivElement | null>(null);
84
- const portalTokenStyle = usePortalTokenStyle(surfaceRef);
86
+ const surface = usePortalTokenSource<HTMLDivElement>();
87
+ const surfaceRef = surface.currentRef;
88
+ const portalTokenStyle = usePortalTokenStyle(surface.source);
85
89
  const [keyboardDrag, setKeyboardDrag] = useState<{
86
90
  turnId: string;
87
91
  projectedIndex: number;
@@ -129,7 +133,7 @@ export function QueueSurface({
129
133
  );
130
134
  if (moved) focusQueueTurn(surfaceRef.current, turnId);
131
135
  },
132
- [queue],
136
+ [queue, surfaceRef],
133
137
  );
134
138
 
135
139
  const onDragEnd = useCallback(
@@ -245,7 +249,7 @@ export function QueueSurface({
245
249
 
246
250
  return (
247
251
  <div
248
- ref={surfaceRef}
252
+ ref={surface.ref}
249
253
  className="og-root mx-auto mb-2 w-full max-w-3xl shrink-0 px-4 sm:px-6"
250
254
  data-testid="queue-surface"
251
255
  >
@@ -253,7 +257,7 @@ export function QueueSurface({
253
257
  {queue.stoppingPreviousAttempt ? <QueueStoppingStatus queue={queue} dividerAfter /> : null}
254
258
  <button
255
259
  type="button"
256
- className="flex w-full min-w-0 flex-wrap items-center gap-2 px-3 py-2 text-left outline-none transition-colors hover:bg-surface-2/60 focus-visible:bg-surface-2/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
260
+ className="flex w-full min-w-0 flex-wrap items-center gap-2 px-3 py-2 text-left outline-hidden transition-colors hover:bg-surface-2/60 focus-visible:bg-surface-2/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
257
261
  onClick={() => setOpen((value) => !value)}
258
262
  aria-description={!open && totalCount > 0 ? collapsedPreview.summary : undefined}
259
263
  aria-expanded={open}
@@ -800,7 +804,7 @@ function QueuePrompt({
800
804
  aria-controls={fullContentId}
801
805
  aria-expanded={expanded}
802
806
  aria-label={`${expanded ? "Hide" : "Show"} full content for queued prompt ${index + 1}`}
803
- className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-og-xs font-medium text-fg outline-none focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
807
+ className="mt-1 inline-flex min-h-7 min-w-0 max-w-full items-center gap-1 whitespace-normal rounded-md text-left text-og-xs font-medium text-fg outline-hidden focus-visible:ring-2 focus-visible:ring-ring/40 pointer-coarse:min-h-[44px]"
804
808
  data-testid={`queue-prompt-disclosure-${index + 1}`}
805
809
  onClick={() => {
806
810
  const next = !expanded;
@@ -963,7 +967,7 @@ function SortableQueueRow({
963
967
  onClick={onSteer}
964
968
  aria-label={`Steer queued prompt ${index + 1}`}
965
969
  title="Steer — interrupt the current turn and send this message now"
966
- className="inline-flex h-7 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-og-control font-medium text-fg outline-none transition-[background-color] hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:min-h-[44px] pointer-coarse:min-w-[44px]"
970
+ className="inline-flex h-7 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-og-control font-medium text-fg outline-hidden transition-[background-color] hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:min-h-[44px] pointer-coarse:min-w-[44px]"
967
971
  >
968
972
  {pending === "steer" ? (
969
973
  <Loader2Icon className="size-3.5 animate-spin" />
@@ -978,7 +982,7 @@ function SortableQueueRow({
978
982
  onClick={onDelete}
979
983
  aria-label={`Delete queued prompt ${index + 1}`}
980
984
  title="Delete this queued prompt"
981
- className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-surface-2 hover:text-status-failed focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-[44px]"
985
+ className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-hidden transition-colors hover:bg-surface-2 hover:text-status-failed focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-[44px]"
982
986
  >
983
987
  {pending === "delete" ? (
984
988
  <Loader2Icon className="size-3.5 animate-spin" />
@@ -992,7 +996,7 @@ function SortableQueueRow({
992
996
  type="button"
993
997
  disabled={pending !== null}
994
998
  aria-label={`More actions for queued prompt ${index + 1}`}
995
- className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-none transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-[44px]"
999
+ className="inline-flex size-7 shrink-0 items-center justify-center rounded-md outline-hidden transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 pointer-coarse:size-[44px]"
996
1000
  >
997
1001
  {pending && pending !== "steer" && pending !== "delete" ? (
998
1002
  <Loader2Icon className="size-3.5 animate-spin" />
@@ -1012,7 +1016,7 @@ function SortableQueueRow({
1012
1016
  {onEdit ? (
1013
1017
  <>
1014
1018
  <DropdownMenu.Item
1015
- className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 pointer-coarse:min-h-[44px]"
1019
+ className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-hidden focus:bg-surface-2 pointer-coarse:min-h-[44px]"
1016
1020
  onSelect={onEdit}
1017
1021
  >
1018
1022
  <PencilIcon className="size-3.5" /> Edit in composer
@@ -1021,28 +1025,28 @@ function SortableQueueRow({
1021
1025
  </>
1022
1026
  ) : null}
1023
1027
  <DropdownMenu.Item
1024
- className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1028
+ className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-hidden focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1025
1029
  disabled={index === 0}
1026
1030
  onSelect={() => onMove(0)}
1027
1031
  >
1028
1032
  <ArrowUpToLineIcon className="size-3.5" /> Move to top
1029
1033
  </DropdownMenu.Item>
1030
1034
  <DropdownMenu.Item
1031
- className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1035
+ className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-hidden focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1032
1036
  disabled={index === 0}
1033
1037
  onSelect={() => onMove(index - 1)}
1034
1038
  >
1035
1039
  Move up
1036
1040
  </DropdownMenu.Item>
1037
1041
  <DropdownMenu.Item
1038
- className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1042
+ className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-hidden focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1039
1043
  disabled={index === count - 1}
1040
1044
  onSelect={() => onMove(index + 1)}
1041
1045
  >
1042
1046
  Move down
1043
1047
  </DropdownMenu.Item>
1044
1048
  <DropdownMenu.Item
1045
- className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-none focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1049
+ className="flex min-w-0 cursor-default items-center gap-2 whitespace-normal break-words rounded-sm px-2 py-1.5 outline-hidden focus:bg-surface-2 data-[disabled]:opacity-50 pointer-coarse:min-h-[44px]"
1046
1050
  disabled={index === count - 1}
1047
1051
  onSelect={() => onMove(count - 1)}
1048
1052
  >
@@ -70,7 +70,7 @@ export function QueueErrorAlert({
70
70
  role="region"
71
71
  aria-label="Queue error details"
72
72
  tabIndex={0}
73
- className="max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-none [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40"
73
+ className="max-h-[min(5rem,20dvh)] min-w-0 max-w-full flex-[1_1_5rem] overflow-auto overscroll-contain whitespace-pre-wrap rounded-sm outline-hidden [overflow-wrap:anywhere] [unicode-bidi:plaintext] focus-visible:ring-2 focus-visible:ring-ring/40"
74
74
  data-testid="queue-error-message"
75
75
  dir="auto"
76
76
  >
@@ -84,7 +84,7 @@ export function QueueErrorAlert({
84
84
  }}
85
85
  aria-label="Dismiss queue error and retry"
86
86
  title="Retry loading the queue"
87
- className="ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-none transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]"
87
+ className="ms-auto inline-flex size-7 shrink-0 items-center justify-center self-start rounded-md outline-hidden transition-colors hover:bg-surface-3 focus-visible:ring-2 focus-visible:ring-ring/40 motion-reduce:transition-none pointer-coarse:size-[44px]"
88
88
  >
89
89
  <RotateCwIcon className="size-3.5" />
90
90
  </button>