@opengeni/react 5.1.0-canary.0 → 5.2.0-canary.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 (90) hide show
  1. package/README.md +12 -0
  2. package/dist/artifacts.d.ts +2 -0
  3. package/dist/artifacts.js +1391 -2
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/chunk-2TKI3WEG.js +73 -0
  6. package/dist/chunk-2TKI3WEG.js.map +1 -0
  7. package/dist/{chunk-M2HNWZYR.js → chunk-IHQII5WO.js} +69 -18
  8. package/dist/chunk-IHQII5WO.js.map +1 -0
  9. package/dist/{chunk-5PQJOCX5.js → chunk-KANFU34B.js} +6 -4
  10. package/dist/chunk-KANFU34B.js.map +1 -0
  11. package/dist/{chunk-WS6D5BHH.js → chunk-O4NQNMPG.js} +2393 -2123
  12. package/dist/chunk-O4NQNMPG.js.map +1 -0
  13. package/dist/{chunk-HUMIRRYI.js → chunk-PZ27MCZP.js} +54 -19
  14. package/dist/chunk-PZ27MCZP.js.map +1 -0
  15. package/dist/{chunk-BRFMGY4L.js → chunk-UGMKPUEM.js} +28 -3
  16. package/dist/chunk-UGMKPUEM.js.map +1 -0
  17. package/dist/{chunk-BV5A4OTE.js → chunk-YS24CFDI.js} +44 -10
  18. package/dist/chunk-YS24CFDI.js.map +1 -0
  19. package/dist/components/artifacts/inline-html-document.d.ts +2 -0
  20. package/dist/components/artifacts/published-html-artifact-frame.d.ts +2 -0
  21. package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
  22. package/dist/components/markdown.d.ts +13 -1
  23. package/dist/components/session-chrome.d.ts +6 -1
  24. package/dist/{computer-viewer-7TEZC6Y2.js → computer-viewer-CTFCJVS7.js} +2 -2
  25. package/dist/connect.js +1 -1
  26. package/dist/hooks/use-file-attachments.d.ts +2 -0
  27. package/dist/index.d.ts +6 -1
  28. package/dist/index.js +16 -5
  29. package/dist/index.js.map +1 -1
  30. package/dist/interaction.js +1 -1
  31. package/dist/{platform-activity-row-27LKAWZD.js → platform-activity-row-DUE2W74J.js} +6 -3
  32. package/dist/platform-activity-row-DUE2W74J.js.map +1 -0
  33. package/dist/session-ui.d.ts +2 -0
  34. package/dist/session-ui.js +7 -107
  35. package/dist/session-ui.js.map +1 -1
  36. package/dist/session.js +2 -2
  37. package/dist/sites-ui.d.ts +2 -1
  38. package/dist/sites.js +12 -4
  39. package/dist/sites.js.map +1 -1
  40. package/dist/timeline/activity-rail.d.ts +1 -0
  41. package/dist/timeline/genie-loading.d.ts +9 -1
  42. package/dist/timeline/index.d.ts +3 -1
  43. package/dist/timeline/knowledge-receipt.d.ts +17 -0
  44. package/dist/timeline/platform-activity-row.d.ts +1 -2
  45. package/dist/timeline/retained-image.d.ts +16 -0
  46. package/dist/timeline/rolling-activity.d.ts +9 -0
  47. package/dist/timeline/shared.d.ts +4 -1
  48. package/dist/timeline/turn-summary.d.ts +4 -1
  49. package/dist/timeline/types.d.ts +13 -2
  50. package/package.json +4 -4
  51. package/src/artifacts.ts +2 -0
  52. package/src/components/artifacts/inline-html-document.ts +24 -0
  53. package/src/components/artifacts/published-html-artifact-frame.tsx +32 -2
  54. package/src/components/artifacts/visualization-assets/base.css +1015 -0
  55. package/src/components/artifacts/visualization-assets/helpers.html +349 -0
  56. package/src/components/artifacts/visualization-assets.gen.ts +3 -0
  57. package/src/components/computer-viewer.tsx +11 -3
  58. package/src/components/copy-button.tsx +7 -2
  59. package/src/components/markdown.tsx +111 -34
  60. package/src/components/message-timeline.tsx +48 -3
  61. package/src/components/session-chrome.tsx +14 -2
  62. package/src/hooks/use-computer-frame-stream.ts +17 -5
  63. package/src/hooks/use-computer-session.ts +36 -1
  64. package/src/hooks/use-file-attachments.ts +4 -1
  65. package/src/hooks/use-goal.ts +3 -1
  66. package/src/index.ts +12 -1
  67. package/src/session-ui.ts +3 -0
  68. package/src/sites-ui.tsx +23 -6
  69. package/src/timeline/activity-rail.tsx +31 -4
  70. package/src/timeline/genie-loading.tsx +18 -4
  71. package/src/timeline/index.ts +4 -0
  72. package/src/timeline/knowledge-receipt.tsx +100 -0
  73. package/src/timeline/platform-activity-row.tsx +5 -3
  74. package/src/timeline/projection.ts +40 -0
  75. package/src/timeline/retained-image.ts +91 -0
  76. package/src/timeline/rolling-activity.tsx +83 -0
  77. package/src/timeline/shared.tsx +33 -12
  78. package/src/timeline/tool-renderers.tsx +55 -91
  79. package/src/timeline/turn-summary.tsx +21 -15
  80. package/src/timeline/types.ts +14 -0
  81. package/styles/compiled.css +122 -0
  82. package/styles/index.css +49 -0
  83. package/dist/chunk-5PQJOCX5.js.map +0 -1
  84. package/dist/chunk-BRFMGY4L.js.map +0 -1
  85. package/dist/chunk-BV5A4OTE.js.map +0 -1
  86. package/dist/chunk-HUMIRRYI.js.map +0 -1
  87. package/dist/chunk-M2HNWZYR.js.map +0 -1
  88. package/dist/chunk-WS6D5BHH.js.map +0 -1
  89. package/dist/platform-activity-row-27LKAWZD.js.map +0 -1
  90. /package/dist/{computer-viewer-7TEZC6Y2.js.map → computer-viewer-CTFCJVS7.js.map} +0 -0
@@ -0,0 +1,349 @@
1
+ <!--__INLINE_VISUALIZATION_FRAGMENT__-->
2
+ <script src="https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js"></script>
3
+ <script src="https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js"></script>
4
+ <script>
5
+ (() => {
6
+ if (window.FloatingUIDOM == null) {
7
+ return;
8
+ }
9
+ const { autoUpdate, computePosition, flip, offset, shift, size } = window.FloatingUIDOM;
10
+ const tooltipPlacements = new Set(["top", "right", "bottom", "left"]);
11
+ const tooltipId = `codex-visualization-tooltip-${
12
+ window.crypto?.randomUUID?.() ?? Date.now().toString(36)
13
+ }`;
14
+ const tooltipGap = 5;
15
+ const hoverDelay = 700;
16
+ const skipDelay = 300;
17
+ let activeTrigger = null;
18
+ let pendingTrigger = null;
19
+ let tooltip = null;
20
+ let openTimeout = null;
21
+ let autoUpdateCleanup = null;
22
+ let positionRequest = 0;
23
+ let skipDelayUntil = 0;
24
+ let lastPointerWasTouch = false;
25
+ let pinnedByTouch = false;
26
+
27
+ const getTrigger = (target) =>
28
+ target?.nodeType === Node.ELEMENT_NODE ? target.closest("[data-tooltip]") : null;
29
+ const containsTarget = (trigger, target) =>
30
+ target?.nodeType != null && trigger.contains(target);
31
+ const getContent = (trigger) => trigger.getAttribute("data-tooltip")?.trim() ?? "";
32
+ const getPlacement = (trigger) => {
33
+ const placement = trigger.getAttribute("data-tooltip-placement");
34
+ return tooltipPlacements.has(placement) ? placement : "top";
35
+ };
36
+ const getTooltip = () => {
37
+ if (tooltip == null) {
38
+ tooltip = document.createElement("div");
39
+ tooltip.id = tooltipId;
40
+ tooltip.className = "tooltip";
41
+ tooltip.setAttribute("role", "tooltip");
42
+ }
43
+ return tooltip;
44
+ };
45
+ const setDescribedBy = (trigger, described) => {
46
+ const ids = new Set(
47
+ (trigger.getAttribute("aria-describedby") ?? "").split(/\s+/).filter(Boolean),
48
+ );
49
+ if (described) {
50
+ ids.add(tooltipId);
51
+ } else {
52
+ ids.delete(tooltipId);
53
+ }
54
+ if (ids.size === 0) {
55
+ trigger.removeAttribute("aria-describedby");
56
+ } else {
57
+ trigger.setAttribute("aria-describedby", [...ids].join(" "));
58
+ }
59
+ };
60
+ const clearOpenTimeout = () => {
61
+ if (openTimeout != null) {
62
+ window.clearTimeout(openTimeout);
63
+ openTimeout = null;
64
+ }
65
+ pendingTrigger = null;
66
+ };
67
+ const closeTooltip = (activateSkipDelay = true) => {
68
+ clearOpenTimeout();
69
+ pinnedByTouch = false;
70
+ positionRequest += 1;
71
+ autoUpdateCleanup?.();
72
+ autoUpdateCleanup = null;
73
+ if (activeTrigger == null) {
74
+ return;
75
+ }
76
+ setDescribedBy(activeTrigger, false);
77
+ activeTrigger = null;
78
+ tooltip?.remove();
79
+ if (activateSkipDelay) {
80
+ skipDelayUntil = Date.now() + skipDelay;
81
+ }
82
+ };
83
+ const updatePosition = (trigger, floating) => {
84
+ const request = ++positionRequest;
85
+ void computePosition(trigger, floating, {
86
+ middleware: [
87
+ offset(tooltipGap),
88
+ flip({ padding: tooltipGap }),
89
+ shift({ padding: tooltipGap }),
90
+ size({
91
+ padding: tooltipGap,
92
+ apply({ availableHeight, availableWidth, elements }) {
93
+ elements.floating.style.setProperty(
94
+ "--tooltip-available-width",
95
+ `${Math.max(0, availableWidth)}px`,
96
+ );
97
+ elements.floating.style.setProperty(
98
+ "--tooltip-available-height",
99
+ `${Math.max(0, availableHeight)}px`,
100
+ );
101
+ },
102
+ }),
103
+ ],
104
+ placement: getPlacement(trigger),
105
+ strategy: "fixed",
106
+ })
107
+ .then(({ x, y }) => {
108
+ if (activeTrigger !== trigger || request !== positionRequest) {
109
+ return;
110
+ }
111
+ const scale = window.devicePixelRatio || 1;
112
+ floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;
113
+ floating.style.visibility = "visible";
114
+ })
115
+ .catch(() => {
116
+ if (activeTrigger === trigger && request === positionRequest) {
117
+ closeTooltip(false);
118
+ }
119
+ });
120
+ };
121
+ const openTooltip = (trigger) => {
122
+ const content = getContent(trigger);
123
+ if (!trigger.isConnected || content.length === 0) {
124
+ return;
125
+ }
126
+ clearOpenTimeout();
127
+ if (activeTrigger === trigger) {
128
+ return;
129
+ }
130
+ closeTooltip();
131
+ const floating = getTooltip();
132
+ floating.textContent = content;
133
+ floating.style.visibility = "hidden";
134
+ floating.style.transform = "translate(0, 0)";
135
+ document.body.appendChild(floating);
136
+ activeTrigger = trigger;
137
+ setDescribedBy(trigger, true);
138
+ autoUpdateCleanup = autoUpdate(trigger, floating, () => {
139
+ updatePosition(trigger, floating);
140
+ });
141
+ };
142
+ const requestOpen = (trigger, immediate = false) => {
143
+ if (
144
+ getContent(trigger).length === 0 ||
145
+ activeTrigger === trigger ||
146
+ pendingTrigger === trigger
147
+ ) {
148
+ return;
149
+ }
150
+ clearOpenTimeout();
151
+ pendingTrigger = trigger;
152
+ const delay =
153
+ immediate || activeTrigger != null || Date.now() < skipDelayUntil ? 0 : hoverDelay;
154
+ if (delay === 0) {
155
+ openTooltip(trigger);
156
+ return;
157
+ }
158
+ openTimeout = window.setTimeout(() => {
159
+ openTimeout = null;
160
+ pendingTrigger = null;
161
+ openTooltip(trigger);
162
+ }, delay);
163
+ };
164
+ const handlePointerOpen = (event) => {
165
+ if (event.pointerType === "touch") {
166
+ return;
167
+ }
168
+ const trigger = getTrigger(event.target);
169
+ if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
170
+ return;
171
+ }
172
+ requestOpen(trigger);
173
+ };
174
+ const handlePointer = (event) => {
175
+ lastPointerWasTouch = event.type === "pointerdown" && event.pointerType === "touch";
176
+ };
177
+ const handleTouchClick = (event) => {
178
+ const isTouch = event.pointerType === "touch" || lastPointerWasTouch;
179
+ lastPointerWasTouch = false;
180
+ if (!isTouch || event.detail === 0) {
181
+ return;
182
+ }
183
+ const trigger = getTrigger(event.target);
184
+ if (
185
+ trigger == null ||
186
+ !trigger.isConnected ||
187
+ getContent(trigger).length === 0 ||
188
+ (pinnedByTouch && activeTrigger === trigger)
189
+ ) {
190
+ if (pinnedByTouch) {
191
+ closeTooltip();
192
+ }
193
+ return;
194
+ }
195
+ openTooltip(trigger);
196
+ pinnedByTouch = activeTrigger === trigger;
197
+ window.setTimeout(() => {
198
+ if (activeTrigger === trigger && !trigger.isConnected) {
199
+ closeTooltip(false);
200
+ }
201
+ }, 0);
202
+ };
203
+ const handleLeave = (event) => {
204
+ if (pinnedByTouch) {
205
+ return;
206
+ }
207
+ const trigger = getTrigger(event.target);
208
+ if (trigger == null || containsTarget(trigger, event.relatedTarget)) {
209
+ return;
210
+ }
211
+ if (activeTrigger === trigger || pendingTrigger === trigger) {
212
+ closeTooltip();
213
+ }
214
+ };
215
+ const handleFocusIn = (event) => {
216
+ const trigger = getTrigger(event.target);
217
+ if (trigger?.matches(":focus-visible")) {
218
+ requestOpen(trigger, true);
219
+ }
220
+ };
221
+ const handleKeyDown = (event) => {
222
+ if (event.key === "Escape") {
223
+ closeTooltip();
224
+ }
225
+ };
226
+ const handleContextMenu = (event) => {
227
+ lastPointerWasTouch = false;
228
+ const trigger = getTrigger(event.target);
229
+ if (activeTrigger === trigger || pendingTrigger === trigger) {
230
+ closeTooltip();
231
+ }
232
+ };
233
+ const destroy = () => {
234
+ closeTooltip(false);
235
+ lastPointerWasTouch = false;
236
+ document.removeEventListener("pointerover", handlePointerOpen);
237
+ document.removeEventListener("pointerdown", handlePointer);
238
+ document.removeEventListener("pointercancel", handlePointer);
239
+ document.removeEventListener("click", handleTouchClick);
240
+ document.removeEventListener("pointerout", handleLeave);
241
+ document.removeEventListener("focusin", handleFocusIn);
242
+ document.removeEventListener("focusout", handleLeave);
243
+ document.removeEventListener("keydown", handleKeyDown);
244
+ document.removeEventListener("contextmenu", handleContextMenu);
245
+ tooltip = null;
246
+ };
247
+
248
+ document.addEventListener("pointerover", handlePointerOpen);
249
+ document.addEventListener("pointerdown", handlePointer);
250
+ document.addEventListener("pointercancel", handlePointer);
251
+ document.addEventListener("click", handleTouchClick);
252
+ document.addEventListener("pointerout", handleLeave);
253
+ document.addEventListener("focusin", handleFocusIn);
254
+ document.addEventListener("focusout", handleLeave);
255
+ document.addEventListener("keydown", handleKeyDown);
256
+ document.addEventListener("contextmenu", handleContextMenu);
257
+ window.addEventListener("pagehide", destroy, { once: true });
258
+ })();
259
+ </script>
260
+ <script id="codex-visualization-tabs">
261
+ (() => {
262
+ const selector = '.nav[role="tablist"] [role="tab"]';
263
+ const disabled = ':disabled,[aria-disabled="true"]';
264
+ const selectTab = (selected) => {
265
+ const panelId = selected.getAttribute("aria-controls");
266
+ for (const tab of selected.closest('[role="tablist"]').querySelectorAll('[role="tab"]')) {
267
+ const active = tab === selected;
268
+ tab.classList.toggle("active", active);
269
+ tab.setAttribute("aria-selected", String(active));
270
+ const id = tab.getAttribute("aria-controls");
271
+ const panel = id && document.getElementById(id);
272
+ if (panel != null) {
273
+ panel.hidden = id !== panelId;
274
+ if (!panel.hidden && selected.id) {
275
+ panel.setAttribute("aria-labelledby", selected.id);
276
+ }
277
+ }
278
+ }
279
+ };
280
+ const interact = (event) => {
281
+ const tab = event.target?.closest?.(selector);
282
+ if (event.defaultPrevented || tab == null || tab.matches(disabled)) {
283
+ return;
284
+ }
285
+ if (event.type === "click") {
286
+ selectTab(tab);
287
+ return;
288
+ }
289
+ const tabs = Array.from(
290
+ tab.closest('[role="tablist"]').querySelectorAll('[role="tab"]'),
291
+ ).filter((candidate) => !candidate.matches(disabled));
292
+ const current = tabs.indexOf(tab);
293
+ let next;
294
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") {
295
+ next = (current + 1) % tabs.length;
296
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
297
+ next = (current + tabs.length - 1) % tabs.length;
298
+ } else if (event.key === "Home") {
299
+ next = 0;
300
+ } else if (event.key === "End") {
301
+ next = tabs.length - 1;
302
+ } else {
303
+ return;
304
+ }
305
+ event.preventDefault();
306
+ tabs[next].focus();
307
+ selectTab(tabs[next]);
308
+ };
309
+
310
+ for (const list of document.querySelectorAll('.nav[role="tablist"]')) {
311
+ const selected =
312
+ list.querySelector('[role="tab"][aria-selected="true"]') ??
313
+ list.querySelector('[role="tab"].active') ??
314
+ list.querySelector('[role="tab"]');
315
+ if (selected != null) {
316
+ selectTab(selected);
317
+ }
318
+ }
319
+ document.addEventListener("click", interact);
320
+ document.addEventListener("keydown", interact);
321
+ window.addEventListener(
322
+ "pagehide",
323
+ () => {
324
+ document.removeEventListener("click", interact);
325
+ document.removeEventListener("keydown", interact);
326
+ },
327
+ { once: true },
328
+ );
329
+ })();
330
+ </script>
331
+ <script
332
+ id="codex-visualization-lucide"
333
+ async
334
+ src="https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js"
335
+ ></script>
336
+ <script>
337
+ (() => {
338
+ const initialize = () => {
339
+ globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });
340
+ };
341
+ if (globalThis.lucide != null) {
342
+ initialize();
343
+ return;
344
+ }
345
+ document
346
+ .getElementById("codex-visualization-lucide")
347
+ ?.addEventListener("load", initialize, { once: true });
348
+ })();
349
+ </script>
@@ -0,0 +1,3 @@
1
+ // Generated by scripts/generate-visualization-assets.ts.
2
+ export const VISUALIZATION_CSS = "/* Adapted from Codex visualize 1.0.32; shared with the OpenGeni visualization skill. */\n:root {\n color-scheme: light dark;\n background-color: var(\n --background,\n var(--color-background-primary, light-dark(rgb(255 255 255), rgb(24 24 24)))\n ) !important;\n\n /* Agent-facing contract; keep in sync with SKILL.md. */\n --background: var(\n --color-background-primary,\n light-dark(rgb(255 255 255), rgb(24 24 24))\n );\n --foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --card: color-mix(in oklab, var(--foreground) 5%, var(--background));\n --card-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --popover: var(\n --color-background-secondary,\n light-dark(rgb(255 255 255), rgb(45 45 45))\n );\n --popover-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --primary: var(\n --color-text-info,\n light-dark(rgb(51 156 255), rgb(131 195 255))\n );\n --primary-foreground: var(\n --color-text-inverse,\n light-dark(rgb(255 255 255), rgb(13 13 13))\n );\n --secondary: var(\n --color-background-secondary,\n light-dark(rgb(255 255 255 / 96%), rgb(54 54 54 / 96%))\n );\n --secondary-foreground: var(\n --color-text-primary,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --muted: color-mix(in srgb, var(--foreground) 10%, transparent);\n --muted-foreground: var(\n --color-text-secondary,\n light-dark(rgb(26 28 31 / 49.4%), rgb(255 255 255 / 49.8%))\n );\n --accent: var(\n --color-background-info,\n light-dark(rgb(229 242 255), rgb(13 39 63))\n );\n --accent-foreground: var(\n --color-text-info,\n light-dark(rgb(51 156 255), rgb(131 195 255))\n );\n --destructive: var(\n --color-text-warning,\n light-dark(rgb(226 85 7), rgb(255 133 73))\n );\n --border: var(\n --color-border-secondary,\n light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))\n );\n --input: var(\n --color-border-primary,\n light-dark(\n rgb(26 28 31 / 11.8%),\n color-mix(in oklab, rgb(0 0 0) 10%, transparent)\n )\n );\n --ring: var(\n --color-ring-primary,\n light-dark(rgb(51 156 255), rgb(131 195 255 / 76%))\n );\n --font-size-base: var(--font-text-md-size, 14px);\n --blue: light-dark(rgb(51 156 255), rgb(51 156 255));\n --orange: light-dark(rgb(226 85 7), rgb(251 106 34));\n --green: light-dark(rgb(0 162 64), rgb(64 201 119));\n --red: light-dark(rgb(224 46 42), rgb(255 103 100));\n --purple: light-dark(rgb(146 79 247), rgb(173 123 249));\n --yellow: light-dark(rgb(255 195 0), rgb(255 210 64));\n --viz-series-1: var(--primary);\n --viz-series-2: light-dark(rgb(243 136 59), rgb(245 154 86));\n --viz-series-3: light-dark(rgb(93 201 119), rgb(116 213 139));\n --viz-series-4: light-dark(rgb(235 119 177), rgb(240 143 192));\n --viz-series-5: light-dark(rgb(155 121 236), rgb(170 145 239));\n --viz-series-6: light-dark(rgb(58 185 177), rgb(90 203 194));\n\n /* Internal implementation variables; not part of the agent contract. */\n --font-sans: -apple-system, system-ui, \"Segoe UI\", sans-serif;\n --font-mono:\n ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\",\n monospace;\n --font-size-normal: max(11px, var(--font-size-base));\n --font-size-tooltip: calc(var(--font-size-base) - 1px);\n --font-size-small: max(11px, calc(var(--font-size-base) - 2px));\n --font-size-h1: calc(var(--font-size-normal) * 1.7142857143);\n --font-size-h2: calc(var(--font-size-normal) * 1.4285714286);\n --font-size-h3: calc(var(--font-size-normal) * 1.2857142857);\n --font-weight-normal: 430;\n --font-weight-medium: 500;\n --icon-size: 16px;\n --line-height-normal: calc(var(--font-size-normal) * 1.5);\n --line-height-tooltip: calc(var(--font-size-tooltip) * 1.4285714286);\n --line-height-small: calc(var(--font-size-small) + 4px);\n --corner-shape: round;\n --radius: var(--border-radius-lg, 10px);\n --radius-sm: calc(var(--radius) * 0.6);\n --radius-md: calc(var(--radius) * 0.8);\n --radius-lg: var(--radius);\n --radius-2xl: calc(var(--radius) * 1.6);\n --radius-full: 9999px;\n --shadow-sm: 0 1px 2px -1px rgb(0 0 0 / 8%);\n --checkmark-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 17 17'%3E%3Cpath d='M 12.8961 3.64101C13.1297 3.41418 13.4984 3.37523 13.7779 3.56581C14.0571 3.75635 14.1554 4.11331 14.0299 4.41347L13.9615 4.53847L7.71151 13.7045C7.59411 13.8767 7.4063 13.9877 7.19881 14.0072C6.99136 14.0267 6.78564 13.9533 6.63826 13.806L2.88826 10.056L2.79842 9.9457C2.6192 9.67407 2.64927 9.30496 2.88826 9.06581C3.12738 8.82669 3.49647 8.79676 3.76815 8.97597L3.8785 9.06581L7.03084 12.2182L12.8053 3.74941L12.8961 3.64101Z'/%3E%3C/svg%3E\");\n\n /* Legacy aliases; not part of the current agent contract. */\n --viz-bg: transparent;\n --viz-panel: var(--card);\n --viz-border: var(--border);\n --viz-text: var(--foreground);\n --viz-muted: var(--muted-foreground);\n --viz-accent: var(--primary);\n --viz-accent-text: var(--primary-foreground);\n --viz-accent-bg: var(--accent);\n --viz-font-size: var(--font-size-base);\n --viz-warning: var(--destructive);\n}\n\n:root[data-theme=\"light\"] {\n color-scheme: light;\n}\n\n:root[data-theme=\"dark\"] {\n color-scheme: dark;\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml > body {\n /* Preserve MCP aliases for older inline renderers without creating :root cycles. */\n --color-background-primary: var(\n --background,\n light-dark(rgb(255 255 255), rgb(24 24 24))\n );\n --color-text-primary: var(\n --foreground,\n light-dark(rgb(26 28 31), rgb(255 255 255))\n );\n --color-border-secondary: var(\n --border,\n light-dark(rgb(26 28 31 / 8%), rgb(255 255 255 / 8.2%))\n );\n\n margin: 0;\n padding: var(--visualize-paint-gutter, 16px);\n color: var(--foreground);\n background: transparent !important;\n font-family: var(--font-sans) !important;\n font-size: var(--font-size-normal);\n font-weight: var(--font-weight-normal);\n line-height: var(--line-height-normal);\n}\n\na {\n color: color-mix(in srgb, var(--viz-accent) 80%, var(--viz-text) 20%);\n cursor: pointer;\n font-weight: var(--font-weight-medium, 500);\n text-decoration: none;\n text-underline-offset: 2px;\n}\n\na:is(:hover, :focus-visible) {\n text-decoration-line: underline;\n text-decoration-style: dashed;\n text-decoration-thickness: 0.5px;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np {\n margin-block: 0;\n}\n\nh1 {\n font-size: var(--font-size-h1);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\nh2 {\n font-size: var(--font-size-h2);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\nh3,\nh4,\nh5,\nh6 {\n font-size: var(--font-size-h3);\n font-weight: var(--font-weight-medium);\n line-height: 1.3;\n}\n\nb,\nstrong,\nth {\n font-weight: var(--font-weight-medium);\n}\n\ncode:not(pre code) {\n display: inline;\n padding: 1px 6px;\n border-radius: var(--radius-sm);\n corner-shape: var(--corner-shape);\n background: var(--muted);\n font-family: var(--font-mono);\n font-size: 0.92em;\n overflow-wrap: anywhere;\n -webkit-box-decoration-break: clone;\n box-decoration-break: clone;\n word-break: break-word;\n}\n\n.table-responsive {\n width: 100%;\n overflow-x: auto;\n scrollbar-width: thin;\n}\n\n.table {\n width: 100%;\n border-collapse: collapse;\n color: var(--foreground);\n font: inherit;\n text-align: start;\n}\n\n.table :is(th, td) {\n padding-block: 10px;\n padding-inline: 0 24px;\n overflow-wrap: anywhere;\n border-bottom: 1px solid var(--border);\n text-align: start;\n vertical-align: top;\n}\n\n.table-responsive > .table :is(th, td) {\n overflow-wrap: break-word;\n}\n\n.table :is(th, td):last-child {\n padding-inline-end: 0;\n}\n\n.table :is(caption, thead th) {\n font-weight: 600;\n}\n\n.table thead th {\n padding-block: 8px;\n border-bottom-color: color-mix(in srgb, var(--foreground) 16%, transparent);\n}\n\n.table tbody tr:last-child :is(th, td) {\n border-bottom: 0;\n}\n\n.table.table-sm :is(th, td) {\n padding-block: 6px;\n}\n\n.table.table-sm :is(th, td):not(:last-child) {\n padding-inline-end: 16px;\n}\n\n.table :is(.text-end, [align=\"right\"]) {\n text-align: end;\n font-variant-numeric: tabular-nums;\n}\n\n.table :is(.text-center, [align=\"center\"]) {\n text-align: center;\n}\n\n.table .text-nowrap {\n white-space: nowrap;\n}\n\n#widget {\n display: flex;\n flex-direction: column;\n gap: 12px;\n width: 100%;\n padding: 0;\n background: transparent !important;\n}\n\n.card {\n min-width: 0;\n padding: 12px;\n overflow: hidden;\n overflow-wrap: break-word;\n border-radius: var(--radius-2xl);\n corner-shape: var(--corner-shape);\n color: var(--card-foreground);\n background: var(--card);\n}\n\n#widget > :not(.card, .progress) {\n width: 100% !important;\n max-width: none !important;\n margin: 0 !important;\n padding: 0 !important;\n border: 0 !important;\n border-radius: 0 !important;\n background: transparent !important;\n box-shadow: none !important;\n}\n\n.tooltip {\n position: fixed;\n z-index: 50;\n top: 0;\n left: 0;\n width: max-content;\n max-width: min(\n 20rem,\n var(--tooltip-available-width, calc(100vw - 10px)),\n calc(100vw - 10px)\n );\n max-height: min(\n var(--tooltip-available-height, calc(100vh - 10px)),\n calc(100vh - 10px)\n );\n padding: 4px 8px;\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--popover-foreground);\n background: var(--popover);\n box-shadow: none;\n font-size: var(--font-size-tooltip);\n line-height: var(--line-height-tooltip);\n overflow-wrap: break-word;\n white-space: normal;\n pointer-events: none;\n user-select: none;\n}\n\n.viz-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(max(180px, 24%), 1fr));\n gap: 10px;\n}\n\n.viz-stat {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.viz-stat-value {\n font-size: var(--font-size-h2);\n font-weight: var(--font-weight-medium);\n line-height: 1.25;\n}\n\n.viz-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n\nhr {\n width: 100%;\n height: 1px;\n margin-block: 6px;\n border: 0;\n background: var(--border);\n}\n\n.nav {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 4px;\n}\n\n.progress {\n display: flex;\n height: 8px;\n margin-block: calc((var(--line-height-normal, 1.5em) - 8px) / 2);\n overflow: hidden;\n border: 0;\n border-radius: var(--radius-full);\n background: var(--muted);\n}\n\n.progress-bar:only-child {\n border-radius: var(--radius-full);\n}\n\n.progress-bar {\n height: 100%;\n flex-shrink: 0;\n border: 0;\n background: var(--viz-series-1);\n}\n\n.viz-badge {\n padding: 3px 8px;\n border-radius: var(--radius-full);\n color: var(--accent-foreground);\n background: var(--accent);\n font-weight: var(--font-weight-medium);\n}\n\nsmall,\n.text-small,\n.viz-badge {\n font-size: var(--font-size-small);\n line-height: var(--line-height-small);\n}\n\n.text-muted {\n color: var(--muted-foreground);\n}\n\n.tabular-nums {\n font-variant-numeric: tabular-nums;\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.viz-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n\n.viz-controls > .form-label {\n display: grid;\n min-width: min(100%, 260px);\n flex: 1 1 280px;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: baseline;\n gap: 2px 12px;\n margin-bottom: 0;\n}\n\n.viz-controls > .form-label > :is(.form-control, .form-range, .form-select) {\n grid-column: 1 / -1;\n}\n\n.btn,\n.nav-link,\n.form-check-input,\n.form-control,\n.form-range,\n.form-select {\n font: inherit;\n}\n\n.btn,\n.nav-link {\n appearance: button;\n display: inline-flex;\n inline-size: fit-content;\n max-inline-size: 100%;\n min-height: 28px;\n align-items: center;\n justify-content: center;\n gap: 4px;\n margin: 0;\n padding: 0 8px;\n -webkit-app-region: no-drag;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--secondary-foreground);\n background: var(--secondary);\n cursor: var(--cursor-interaction, pointer);\n text-align: center;\n text-decoration: none;\n white-space: nowrap;\n user-select: none;\n -webkit-font-smoothing: antialiased;\n}\n\n.btn:is(.btn-block, .viz-tile) {\n inline-size: 100%;\n}\n\n.btn.viz-tile {\n min-width: 0;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\na.btn {\n cursor: pointer;\n text-decoration: none;\n}\n\n.btn:not(:disabled):hover,\n.nav-link:not(:disabled):not([aria-disabled=\"true\"]):hover {\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.btn-primary {\n border-color: transparent;\n color: var(--primary-foreground);\n background: var(--foreground);\n background-clip: padding-box;\n}\n\n.btn-primary .text-muted {\n color: color-mix(in srgb, var(--primary-foreground) 50%, transparent);\n}\n\n.btn-primary:not(:disabled):hover {\n background: color-mix(in srgb, var(--foreground) 80%, transparent);\n background-clip: padding-box;\n}\n\n.btn-ghost {\n border-color: transparent;\n color: var(--muted-foreground);\n background: transparent;\n}\n\n.btn-ghost:not(:disabled):hover {\n color: var(--foreground);\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.nav-pills .nav-link {\n min-width: 0;\n border-color: transparent;\n color: var(--muted-foreground);\n background: transparent;\n}\n\n.nav-justified .nav-link {\n flex: 1 1 0;\n overflow-wrap: anywhere;\n white-space: normal;\n}\n\n.nav-pills .nav-link.active {\n color: var(--foreground);\n background: var(--muted);\n}\n\n.btn:disabled,\n.nav-link:disabled,\n.nav-link[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n[data-lucide] {\n stroke-width: 1.6;\n}\n\n.form-label {\n display: block;\n margin-bottom: 6px;\n color: var(--foreground);\n}\n\n.form-control {\n display: block;\n width: 100%;\n min-height: 28px;\n padding: 0 8px;\n outline: none;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--foreground);\n background: var(--secondary);\n}\n\n.form-control::placeholder {\n color: var(--muted-foreground);\n}\n\n.form-control[type=\"file\"] {\n padding: 0;\n overflow: hidden;\n cursor: var(--cursor-interaction, pointer);\n}\n\n.form-control[type=\"file\"]::file-selector-button {\n min-height: 26px;\n margin-right: 8px;\n padding: 0 8px;\n border: 0;\n border-right: 1px solid var(--input);\n color: var(--secondary-foreground);\n background: var(--secondary);\n cursor: inherit;\n font: inherit;\n}\n\n.form-control[type=\"file\"]:not(:disabled):hover::file-selector-button {\n background: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.form-control-color[type=\"color\"] {\n width: 40px;\n height: 28px;\n padding: 3px;\n cursor: var(--cursor-interaction, pointer);\n}\n\n.form-control-color[type=\"color\"]::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n.form-control-color[type=\"color\"]::-webkit-color-swatch {\n border: 0;\n border-radius: calc(var(--radius-lg) - 4px);\n corner-shape: var(--corner-shape);\n}\n\ntextarea.form-control {\n height: auto;\n min-height: 72px;\n padding: 8px 10px;\n resize: vertical;\n}\n\n.form-control:focus-visible {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-control:disabled,\n.form-select:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.form-select {\n appearance: none;\n display: block;\n width: 100%;\n min-height: 28px;\n margin: 0;\n padding: 0 32px 0 8px;\n outline: none;\n border: 1px solid var(--input);\n border-radius: var(--radius-lg);\n corner-shape: var(--corner-shape);\n color: var(--foreground);\n background-color: var(--secondary);\n background-image:\n linear-gradient(45deg, transparent 50%, var(--muted-foreground) 50%),\n linear-gradient(135deg, var(--muted-foreground) 50%, transparent 50%);\n background-position:\n calc(100% - 14px) 50%,\n calc(100% - 10px) 50%;\n background-repeat: no-repeat;\n background-size: 4px 4px;\n cursor: var(--cursor-interaction, default);\n}\n\n.form-select:not(:disabled):hover {\n background-color: color-mix(in srgb, var(--foreground) 6%, var(--secondary));\n}\n\n.form-select:focus-visible {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-check {\n display: flex;\n min-height: 20px;\n align-items: center;\n gap: 6px;\n}\n\n.form-check-input {\n appearance: none;\n width: 14px;\n height: 14px;\n flex: 0 0 auto;\n margin: 0;\n border: 1px solid var(--input);\n color: var(--primary-foreground);\n background-color: transparent;\n cursor: var(--cursor-interaction, default);\n transition:\n background-color 150ms,\n border-color 150ms,\n box-shadow 150ms;\n}\n\n.form-check:not(.form-switch) .form-check-input[type=\"checkbox\"] {\n border-color: var(--input);\n border-radius: var(--radius-sm);\n corner-shape: var(--corner-shape);\n background-color: var(--secondary);\n box-shadow: var(--shadow-sm);\n}\n\n.form-check:not(.form-switch)\n .form-check-input:not(:disabled):not(:checked):hover {\n background-color: var(--card);\n}\n\n.form-check:not(.form-switch) .form-check-input[type=\"checkbox\"]:checked {\n border-color: var(--primary);\n background-color: var(--primary);\n}\n\n.form-check:not(.form-switch)\n .form-check-input[type=\"checkbox\"]:checked::before {\n display: block;\n width: 100%;\n height: 100%;\n background: var(--primary-foreground);\n content: \"\";\n mask: var(--checkmark-image) center / 12px 12px no-repeat;\n}\n\n.form-check-input[type=\"radio\"] {\n width: 14px;\n height: 14px;\n border-radius: var(--radius-full);\n}\n\n.form-check-input[type=\"radio\"]:checked {\n border: 2px solid var(--primary);\n background:\n radial-gradient(circle, var(--primary-foreground) 0 2.5px, transparent 3px),\n var(--primary);\n}\n\n.form-check:not(.form-switch) .form-check-input:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n\n.form-check:not(.form-switch) .form-check-input:disabled {\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.form-check:not(.form-switch) .form-check-input:disabled + .form-check-label {\n cursor: not-allowed;\n}\n\n.form-switch .form-check-input:disabled,\n.form-switch .form-check-input:disabled + .form-check-label {\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.form-check-label {\n color: var(--foreground);\n cursor: var(--cursor-interaction, default);\n}\n\n.form-switch .form-check-input {\n position: relative;\n width: 32px;\n height: 20px;\n border: 0;\n border-radius: var(--radius-full);\n background: var(--muted);\n box-shadow: none;\n transition: background-color 200ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.form-switch .form-check-input::before {\n position: absolute;\n top: 50%;\n left: 0;\n width: 16px;\n height: 16px;\n box-sizing: border-box;\n border: 1px solid light-dark(var(--primary-foreground), var(--foreground));\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n box-shadow: var(--shadow-sm);\n content: \"\";\n transform: translate(2px, -50%);\n transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.form-switch .form-check-input:checked {\n background: var(--primary);\n}\n\n.form-switch .form-check-input:checked::before {\n transform: translate(14px, -50%);\n}\n\n.form-switch .form-check-input:focus-visible {\n box-shadow: 0 0 0 2px var(--ring);\n}\n\n.form-range {\n appearance: none;\n display: block;\n width: 100%;\n height: 28px;\n flex: 1;\n margin: 0;\n padding: 0;\n outline: none;\n border: 0;\n accent-color: var(--primary);\n background: linear-gradient(\n color-mix(in srgb, var(--foreground) 7%, transparent),\n color-mix(in srgb, var(--foreground) 7%, transparent)\n )\n center / 100% 2px no-repeat;\n}\n\n.form-range::-webkit-slider-runnable-track {\n height: 28px;\n background: transparent;\n}\n\n.form-range::-webkit-slider-thumb {\n appearance: none;\n width: 20px;\n height: 20px;\n margin-top: 4px;\n border: 1px solid var(--border);\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n}\n\n.form-range:focus-visible::-webkit-slider-thumb {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-range::-moz-range-track {\n height: 28px;\n background: transparent;\n}\n\n.form-range::-moz-range-thumb {\n width: 20px;\n height: 20px;\n border: 1px solid var(--border);\n border-radius: var(--radius-full);\n background: light-dark(var(--primary-foreground), var(--foreground));\n}\n\n.form-range:focus-visible::-moz-range-thumb {\n border-color: var(--ring);\n box-shadow: inset 0 0 0 1px var(--ring);\n}\n\n.form-range:disabled {\n cursor: not-allowed;\n opacity: 0.4;\n}\n\n.btn:not(.btn-primary, .viz-tile):is(\n [aria-pressed=\"true\"],\n [aria-selected=\"true\"],\n .is-selected\n) {\n border-color: var(--primary);\n color: var(--primary-foreground);\n background: var(--primary);\n}\n\n.btn.viz-tile:is([aria-pressed=\"true\"], [aria-selected=\"true\"], .is-selected) {\n border-color: var(--primary);\n box-shadow: inset 0 0 0 1px var(--primary);\n}\n\n.btn:focus-visible,\n.nav-link:focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n\n@media (pointer: coarse) {\n .btn,\n .nav-link,\n .form-control,\n .form-select {\n min-width: 44px;\n min-height: 44px;\n }\n\n /* Keep small-screen WebKit from zooming when a text field receives focus. */\n .form-control,\n .form-select {\n font-size: max(16px, var(--font-size-normal));\n }\n\n .form-control[type=\"file\"]::file-selector-button {\n min-width: 44px;\n min-height: 44px;\n }\n\n .form-control-color[type=\"color\"] {\n width: 44px;\n height: 44px;\n }\n\n .form-control:is(\n [type=\"date\"],\n [type=\"datetime-local\"],\n [type=\"month\"],\n [type=\"time\"],\n [type=\"week\"]\n ) {\n min-width: 0;\n max-width: 100%;\n padding-block: 8px;\n }\n\n .form-check {\n min-height: 44px;\n padding-inline: 15px;\n }\n\n .form-check-input {\n position: relative;\n }\n\n .form-check-input::after {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 44px;\n height: 44px;\n content: \"\";\n transform: translate(-50%, -50%);\n }\n\n .form-check-label {\n display: inline-flex;\n min-width: 44px;\n min-height: 44px;\n align-items: center;\n }\n\n .form-range {\n height: 44px;\n touch-action: pan-y;\n }\n\n .form-range::-webkit-slider-runnable-track {\n height: 44px;\n }\n\n .form-range::-moz-range-track {\n height: 44px;\n }\n\n .form-range::-webkit-slider-thumb {\n margin-top: 12px;\n }\n}\n\nsvg {\n display: block;\n max-width: 100%;\n height: auto;\n}\n\n#widget > svg {\n width: 100%;\n}\n\n/* Model-authored chart rules can otherwise stretch icons after these styles load. */\nsvg.lucide {\n display: block;\n width: var(--icon-size) !important;\n height: var(--icon-size) !important;\n flex: none;\n margin: 0 !important;\n stroke-width: 1.6;\n}\n\n.text-warning,\n.text-destructive {\n color: var(--destructive);\n}\n";
3
+ export const VISUALIZATION_HELPERS = "\n<script src=\"https://unpkg.com/@floating-ui/core@1.7.3/dist/floating-ui.core.umd.min.js\"></script>\n<script src=\"https://unpkg.com/@floating-ui/dom@1.7.4/dist/floating-ui.dom.umd.min.js\"></script>\n<script>\n (() => {\n if (window.FloatingUIDOM == null) {\n return;\n }\n const { autoUpdate, computePosition, flip, offset, shift, size } = window.FloatingUIDOM;\n const tooltipPlacements = new Set([\"top\", \"right\", \"bottom\", \"left\"]);\n const tooltipId = `codex-visualization-tooltip-${\n window.crypto?.randomUUID?.() ?? Date.now().toString(36)\n }`;\n const tooltipGap = 5;\n const hoverDelay = 700;\n const skipDelay = 300;\n let activeTrigger = null;\n let pendingTrigger = null;\n let tooltip = null;\n let openTimeout = null;\n let autoUpdateCleanup = null;\n let positionRequest = 0;\n let skipDelayUntil = 0;\n let lastPointerWasTouch = false;\n let pinnedByTouch = false;\n\n const getTrigger = (target) =>\n target?.nodeType === Node.ELEMENT_NODE ? target.closest(\"[data-tooltip]\") : null;\n const containsTarget = (trigger, target) =>\n target?.nodeType != null && trigger.contains(target);\n const getContent = (trigger) => trigger.getAttribute(\"data-tooltip\")?.trim() ?? \"\";\n const getPlacement = (trigger) => {\n const placement = trigger.getAttribute(\"data-tooltip-placement\");\n return tooltipPlacements.has(placement) ? placement : \"top\";\n };\n const getTooltip = () => {\n if (tooltip == null) {\n tooltip = document.createElement(\"div\");\n tooltip.id = tooltipId;\n tooltip.className = \"tooltip\";\n tooltip.setAttribute(\"role\", \"tooltip\");\n }\n return tooltip;\n };\n const setDescribedBy = (trigger, described) => {\n const ids = new Set(\n (trigger.getAttribute(\"aria-describedby\") ?? \"\").split(/\\s+/).filter(Boolean),\n );\n if (described) {\n ids.add(tooltipId);\n } else {\n ids.delete(tooltipId);\n }\n if (ids.size === 0) {\n trigger.removeAttribute(\"aria-describedby\");\n } else {\n trigger.setAttribute(\"aria-describedby\", [...ids].join(\" \"));\n }\n };\n const clearOpenTimeout = () => {\n if (openTimeout != null) {\n window.clearTimeout(openTimeout);\n openTimeout = null;\n }\n pendingTrigger = null;\n };\n const closeTooltip = (activateSkipDelay = true) => {\n clearOpenTimeout();\n pinnedByTouch = false;\n positionRequest += 1;\n autoUpdateCleanup?.();\n autoUpdateCleanup = null;\n if (activeTrigger == null) {\n return;\n }\n setDescribedBy(activeTrigger, false);\n activeTrigger = null;\n tooltip?.remove();\n if (activateSkipDelay) {\n skipDelayUntil = Date.now() + skipDelay;\n }\n };\n const updatePosition = (trigger, floating) => {\n const request = ++positionRequest;\n void computePosition(trigger, floating, {\n middleware: [\n offset(tooltipGap),\n flip({ padding: tooltipGap }),\n shift({ padding: tooltipGap }),\n size({\n padding: tooltipGap,\n apply({ availableHeight, availableWidth, elements }) {\n elements.floating.style.setProperty(\n \"--tooltip-available-width\",\n `${Math.max(0, availableWidth)}px`,\n );\n elements.floating.style.setProperty(\n \"--tooltip-available-height\",\n `${Math.max(0, availableHeight)}px`,\n );\n },\n }),\n ],\n placement: getPlacement(trigger),\n strategy: \"fixed\",\n })\n .then(({ x, y }) => {\n if (activeTrigger !== trigger || request !== positionRequest) {\n return;\n }\n const scale = window.devicePixelRatio || 1;\n floating.style.transform = `translate(${Math.round(x * scale) / scale}px, ${Math.round(y * scale) / scale}px)`;\n floating.style.visibility = \"visible\";\n })\n .catch(() => {\n if (activeTrigger === trigger && request === positionRequest) {\n closeTooltip(false);\n }\n });\n };\n const openTooltip = (trigger) => {\n const content = getContent(trigger);\n if (!trigger.isConnected || content.length === 0) {\n return;\n }\n clearOpenTimeout();\n if (activeTrigger === trigger) {\n return;\n }\n closeTooltip();\n const floating = getTooltip();\n floating.textContent = content;\n floating.style.visibility = \"hidden\";\n floating.style.transform = \"translate(0, 0)\";\n document.body.appendChild(floating);\n activeTrigger = trigger;\n setDescribedBy(trigger, true);\n autoUpdateCleanup = autoUpdate(trigger, floating, () => {\n updatePosition(trigger, floating);\n });\n };\n const requestOpen = (trigger, immediate = false) => {\n if (\n getContent(trigger).length === 0 ||\n activeTrigger === trigger ||\n pendingTrigger === trigger\n ) {\n return;\n }\n clearOpenTimeout();\n pendingTrigger = trigger;\n const delay =\n immediate || activeTrigger != null || Date.now() < skipDelayUntil ? 0 : hoverDelay;\n if (delay === 0) {\n openTooltip(trigger);\n return;\n }\n openTimeout = window.setTimeout(() => {\n openTimeout = null;\n pendingTrigger = null;\n openTooltip(trigger);\n }, delay);\n };\n const handlePointerOpen = (event) => {\n if (event.pointerType === \"touch\") {\n return;\n }\n const trigger = getTrigger(event.target);\n if (trigger == null || containsTarget(trigger, event.relatedTarget)) {\n return;\n }\n requestOpen(trigger);\n };\n const handlePointer = (event) => {\n lastPointerWasTouch = event.type === \"pointerdown\" && event.pointerType === \"touch\";\n };\n const handleTouchClick = (event) => {\n const isTouch = event.pointerType === \"touch\" || lastPointerWasTouch;\n lastPointerWasTouch = false;\n if (!isTouch || event.detail === 0) {\n return;\n }\n const trigger = getTrigger(event.target);\n if (\n trigger == null ||\n !trigger.isConnected ||\n getContent(trigger).length === 0 ||\n (pinnedByTouch && activeTrigger === trigger)\n ) {\n if (pinnedByTouch) {\n closeTooltip();\n }\n return;\n }\n openTooltip(trigger);\n pinnedByTouch = activeTrigger === trigger;\n window.setTimeout(() => {\n if (activeTrigger === trigger && !trigger.isConnected) {\n closeTooltip(false);\n }\n }, 0);\n };\n const handleLeave = (event) => {\n if (pinnedByTouch) {\n return;\n }\n const trigger = getTrigger(event.target);\n if (trigger == null || containsTarget(trigger, event.relatedTarget)) {\n return;\n }\n if (activeTrigger === trigger || pendingTrigger === trigger) {\n closeTooltip();\n }\n };\n const handleFocusIn = (event) => {\n const trigger = getTrigger(event.target);\n if (trigger?.matches(\":focus-visible\")) {\n requestOpen(trigger, true);\n }\n };\n const handleKeyDown = (event) => {\n if (event.key === \"Escape\") {\n closeTooltip();\n }\n };\n const handleContextMenu = (event) => {\n lastPointerWasTouch = false;\n const trigger = getTrigger(event.target);\n if (activeTrigger === trigger || pendingTrigger === trigger) {\n closeTooltip();\n }\n };\n const destroy = () => {\n closeTooltip(false);\n lastPointerWasTouch = false;\n document.removeEventListener(\"pointerover\", handlePointerOpen);\n document.removeEventListener(\"pointerdown\", handlePointer);\n document.removeEventListener(\"pointercancel\", handlePointer);\n document.removeEventListener(\"click\", handleTouchClick);\n document.removeEventListener(\"pointerout\", handleLeave);\n document.removeEventListener(\"focusin\", handleFocusIn);\n document.removeEventListener(\"focusout\", handleLeave);\n document.removeEventListener(\"keydown\", handleKeyDown);\n document.removeEventListener(\"contextmenu\", handleContextMenu);\n tooltip = null;\n };\n\n document.addEventListener(\"pointerover\", handlePointerOpen);\n document.addEventListener(\"pointerdown\", handlePointer);\n document.addEventListener(\"pointercancel\", handlePointer);\n document.addEventListener(\"click\", handleTouchClick);\n document.addEventListener(\"pointerout\", handleLeave);\n document.addEventListener(\"focusin\", handleFocusIn);\n document.addEventListener(\"focusout\", handleLeave);\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"contextmenu\", handleContextMenu);\n window.addEventListener(\"pagehide\", destroy, { once: true });\n })();\n</script>\n<script id=\"codex-visualization-tabs\">\n (() => {\n const selector = '.nav[role=\"tablist\"] [role=\"tab\"]';\n const disabled = ':disabled,[aria-disabled=\"true\"]';\n const selectTab = (selected) => {\n const panelId = selected.getAttribute(\"aria-controls\");\n for (const tab of selected.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]')) {\n const active = tab === selected;\n tab.classList.toggle(\"active\", active);\n tab.setAttribute(\"aria-selected\", String(active));\n const id = tab.getAttribute(\"aria-controls\");\n const panel = id && document.getElementById(id);\n if (panel != null) {\n panel.hidden = id !== panelId;\n if (!panel.hidden && selected.id) {\n panel.setAttribute(\"aria-labelledby\", selected.id);\n }\n }\n }\n };\n const interact = (event) => {\n const tab = event.target?.closest?.(selector);\n if (event.defaultPrevented || tab == null || tab.matches(disabled)) {\n return;\n }\n if (event.type === \"click\") {\n selectTab(tab);\n return;\n }\n const tabs = Array.from(\n tab.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]'),\n ).filter((candidate) => !candidate.matches(disabled));\n const current = tabs.indexOf(tab);\n let next;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = (current + 1) % tabs.length;\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = (current + tabs.length - 1) % tabs.length;\n } else if (event.key === \"Home\") {\n next = 0;\n } else if (event.key === \"End\") {\n next = tabs.length - 1;\n } else {\n return;\n }\n event.preventDefault();\n tabs[next].focus();\n selectTab(tabs[next]);\n };\n\n for (const list of document.querySelectorAll('.nav[role=\"tablist\"]')) {\n const selected =\n list.querySelector('[role=\"tab\"][aria-selected=\"true\"]') ??\n list.querySelector('[role=\"tab\"].active') ??\n list.querySelector('[role=\"tab\"]');\n if (selected != null) {\n selectTab(selected);\n }\n }\n document.addEventListener(\"click\", interact);\n document.addEventListener(\"keydown\", interact);\n window.addEventListener(\n \"pagehide\",\n () => {\n document.removeEventListener(\"click\", interact);\n document.removeEventListener(\"keydown\", interact);\n },\n { once: true },\n );\n })();\n</script>\n<script\n id=\"codex-visualization-lucide\"\n async\n src=\"https://unpkg.com/lucide@1.17.0/dist/umd/lucide.js\"\n></script>\n<script>\n (() => {\n const initialize = () => {\n globalThis.lucide?.createIcons({ attrs: { width: 16, height: 16 } });\n };\n if (globalThis.lucide != null) {\n initialize();\n return;\n }\n document\n .getElementById(\"codex-visualization-lucide\")\n ?.addEventListener(\"load\", initialize, { once: true });\n })();\n</script>\n";
@@ -567,7 +567,12 @@ export function ComputerViewer({
567
567
  if (relevant[0]) selectComputerSession({ sessionId: relevant[0].id, pinned: false });
568
568
  }}
569
569
  onCreate={hideGenericCreate ? undefined : createComputer}
570
- onRefresh={() => void Promise.all([refreshRegistry(), refreshComputer()])}
570
+ onRefresh={() => {
571
+ if (frames.state === "error" && !generationLossSelected && !generationLossFrames) {
572
+ frames.reconnect();
573
+ }
574
+ void Promise.all([refreshRegistry(), refreshComputer()]);
575
+ }}
571
576
  />
572
577
  <InteractionInterventionBanner
573
578
  interventions={selectedInterventions}
@@ -1031,8 +1036,11 @@ function ComputerViewport(props: {
1031
1036
  actionRef.current = props.onAction;
1032
1037
  readClipboardRef.current = props.onReadClipboard;
1033
1038
  errorRef.current = props.onError;
1039
+ const streamFailed = props.connectionState === "error";
1034
1040
  const rawInputEnabled =
1035
- !props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true;
1041
+ !streamFailed &&
1042
+ !props.machineLocked &&
1043
+ (!props.backgroundActions || props.target?.kind === "screen" || props.target?.focused === true);
1036
1044
 
1037
1045
  const paintQueuedFrames = useCallback(() => {
1038
1046
  if (decodingFrameRef.current) return;
@@ -1346,7 +1354,7 @@ function ComputerViewport(props: {
1346
1354
  if (inputRef.current) inputRef.current.value = "";
1347
1355
  };
1348
1356
 
1349
- const showCanvas = props.frame !== null && !props.machineLocked;
1357
+ const showCanvas = props.frame !== null && !props.machineLocked && !streamFailed;
1350
1358
  return (
1351
1359
  <div className="relative min-h-0 flex-1 overflow-hidden bg-black">
1352
1360
  <canvas
@@ -142,11 +142,16 @@ export function CopyHoverFrame({
142
142
  <div
143
143
  data-og-annotation-chrome=""
144
144
  className={cn(
145
- "mt-1 flex h-7 items-center gap-1.5",
145
+ "flex h-6 items-center gap-1.5 pointer-coarse:h-11",
146
146
  align === "end" ? "justify-end" : "justify-start",
147
147
  )}
148
148
  >
149
- <CopyButton text={copyText} label={label} reveal="group-hover" />
149
+ <CopyButton
150
+ text={copyText}
151
+ label={label}
152
+ reveal="group-hover"
153
+ className="size-6 pointer-coarse:size-11"
154
+ />
150
155
  {trailing}
151
156
  </div>
152
157
  </div>