@opengeni/react 6.1.0 → 7.0.0-canary.35625901699001

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 (214) hide show
  1. package/README.md +83 -2
  2. package/dist/approvals.d.ts +2 -1
  3. package/dist/artifacts-document.js +1 -1
  4. package/dist/artifacts-presentation.js +1 -1
  5. package/dist/artifacts-spreadsheet.js +1 -1
  6. package/dist/artifacts.js +3 -3
  7. package/dist/{browser-viewer-NTD6UIPR.js → browser-viewer-LGH4IP47.js} +3 -3
  8. package/dist/capability-catalog-row.d.ts +20 -0
  9. package/dist/capability-logo-fallback.d.ts +3 -0
  10. package/dist/{chunk-NOSYBS2D.js → chunk-2VHARTBL.js} +2 -1
  11. package/dist/{chunk-VRBL3F5E.js → chunk-5GAL23FF.js} +10 -2
  12. package/dist/{chunk-VRBL3F5E.js.map → chunk-5GAL23FF.js.map} +1 -1
  13. package/dist/chunk-5UONOQ3Y.js +635 -0
  14. package/dist/chunk-5UONOQ3Y.js.map +1 -0
  15. package/dist/{chunk-NYESIADM.js → chunk-AKOMC3VO.js} +3 -3
  16. package/dist/chunk-AKOMC3VO.js.map +1 -0
  17. package/dist/chunk-BRM6FVZA.js +302 -0
  18. package/dist/chunk-BRM6FVZA.js.map +1 -0
  19. package/dist/{chunk-KANFU34B.js → chunk-DBPVUGQT.js} +11 -2
  20. package/dist/chunk-DBPVUGQT.js.map +1 -0
  21. package/dist/chunk-FHIGTPME.js +1589 -0
  22. package/dist/chunk-FHIGTPME.js.map +1 -0
  23. package/dist/{chunk-5M6VTFJ5.js → chunk-GBPCZH3Y.js} +2 -2
  24. package/dist/{chunk-YBM6CQRU.js → chunk-GEN54DU5.js} +4 -4
  25. package/dist/{chunk-UMECRJRV.js → chunk-HJ5VJ4JB.js} +21 -12
  26. package/dist/chunk-HJ5VJ4JB.js.map +1 -0
  27. package/dist/{chunk-R4KRSFGA.js → chunk-IP4BUBKH.js} +3 -3
  28. package/dist/{chunk-NLTS7JHI.js → chunk-JPHDWKZE.js} +5 -5
  29. package/dist/{chunk-TDSEILUH.js → chunk-MI6ALLLD.js} +1297 -1535
  30. package/dist/chunk-MI6ALLLD.js.map +1 -0
  31. package/dist/{chunk-ICA6R6TW.js → chunk-OWHPKAOV.js} +28 -20
  32. package/dist/chunk-OWHPKAOV.js.map +1 -0
  33. package/dist/{chunk-24M4YBCL.js → chunk-PNAVEK2U.js} +4 -4
  34. package/dist/{chunk-L2NBTKRX.js → chunk-TXCBE32L.js} +203 -19
  35. package/dist/chunk-TXCBE32L.js.map +1 -0
  36. package/dist/{chunk-45XEUVTK.js → chunk-UXE5SSNN.js} +293 -463
  37. package/dist/chunk-UXE5SSNN.js.map +1 -0
  38. package/dist/{chunk-PEI6RANI.js → chunk-VR4Z73EU.js} +203 -9
  39. package/dist/chunk-VR4Z73EU.js.map +1 -0
  40. package/dist/{chunk-HUIKIDM5.js → chunk-XMXRI5JK.js} +65 -18
  41. package/dist/chunk-XMXRI5JK.js.map +1 -0
  42. package/dist/client.d.ts +1 -1
  43. package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
  44. package/dist/components/chat-composer.d.ts +13 -3
  45. package/dist/components/markdown.d.ts +6 -3
  46. package/dist/components/message-timeline.d.ts +7 -2
  47. package/dist/components/preview-loading.d.ts +2 -0
  48. package/dist/components/session-capability-frame.d.ts +24 -0
  49. package/dist/components/session-chrome.d.ts +1 -2
  50. package/dist/components/session-commands.d.ts +6 -0
  51. package/dist/components/session-connection-request.d.ts +8 -0
  52. package/dist/components/session-conversation.d.ts +3 -0
  53. package/dist/components/session-mcp-capability-card.d.ts +22 -0
  54. package/dist/components/timeline-search.d.ts +34 -0
  55. package/dist/composer.js +7 -7
  56. package/dist/connect-account-identity.d.ts +8 -0
  57. package/dist/connect-accounts.d.ts +5 -0
  58. package/dist/connect-chooser.d.ts +6 -0
  59. package/dist/connect-panel.d.ts +9 -2
  60. package/dist/connect-setup.d.ts +5 -1
  61. package/dist/connect.d.ts +5 -21
  62. package/dist/connect.js +1268 -584
  63. package/dist/connect.js.map +1 -1
  64. package/dist/connection-catalog.d.ts +6 -1
  65. package/dist/connection-discovery.d.ts +12 -0
  66. package/dist/connection-installed.d.ts +1 -0
  67. package/dist/connection-logo.d.ts +5 -2
  68. package/dist/connection-service-logo.d.ts +8 -0
  69. package/dist/connection-service-presentation.d.ts +13 -0
  70. package/dist/connection-skeleton.d.ts +5 -0
  71. package/dist/discovery-cache.d.ts +5 -0
  72. package/dist/hooks/use-codex-accounts.d.ts +11 -5
  73. package/dist/hooks/use-connect.d.ts +22 -0
  74. package/dist/hooks/use-session-events.d.ts +5 -0
  75. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  76. package/dist/hooks/use-workspace-sessions.d.ts +2 -0
  77. package/dist/index.d.ts +8 -2
  78. package/dist/index.js +771 -793
  79. package/dist/index.js.map +1 -1
  80. package/dist/interaction.js +2 -2
  81. package/dist/machines.js +2 -2
  82. package/dist/mcp-connection-status.d.ts +14 -0
  83. package/dist/{model-policy-picker-menu-3R336DP2.js → model-policy-picker-menu-RVOQ4ZZG.js} +4 -4
  84. package/dist/model-policy.d.ts +6 -2
  85. package/dist/model-policy.js +1 -1
  86. package/dist/{platform-activity-row-DUE2W74J.js → platform-activity-row-HLOH7FW4.js} +9 -4
  87. package/dist/platform-activity-row-HLOH7FW4.js.map +1 -0
  88. package/dist/plugin-discovery.d.ts +6 -0
  89. package/dist/{queue-surface-implementation-LK3S4BIW.js → queue-surface-implementation-3RIFK2BS.js} +4 -4
  90. package/dist/realtime.js +22 -22
  91. package/dist/realtime.js.map +1 -1
  92. package/dist/session-auth-recommendation.d.ts +5 -0
  93. package/dist/session-capability-policy.d.ts +12 -0
  94. package/dist/session-ui.d.ts +2 -0
  95. package/dist/session-ui.js +14 -12
  96. package/dist/session.d.ts +1 -0
  97. package/dist/session.js +6 -4
  98. package/dist/skill-discovery.d.ts +11 -0
  99. package/dist/timeline/compaction-copy.d.ts +10 -0
  100. package/dist/timeline/projection.d.ts +3 -1
  101. package/dist/timeline/tool-display-name.d.ts +2 -1
  102. package/dist/timeline/turn-summary.d.ts +2 -2
  103. package/dist/timeline/types.d.ts +24 -2
  104. package/dist/{timeline-annotation-markers-DRSTLEM6.js → timeline-annotation-markers-OY6OWR3V.js} +2 -2
  105. package/dist/{timeline-annotation-selection-IHLUGVND.js → timeline-annotation-selection-BOXA2TIN.js} +2 -2
  106. package/dist/{timeline-annotations-dialog-6Q53N5FG.js → timeline-annotations-dialog-RGY3MHL3.js} +3 -3
  107. package/package.json +3 -3
  108. package/src/approvals.ts +8 -1
  109. package/src/assets/README.md +8 -0
  110. package/src/assets/gmail.ico +0 -0
  111. package/src/assets/outlook.ico +0 -0
  112. package/src/capability-catalog-row.tsx +94 -0
  113. package/src/capability-logo-fallback.ts +22 -0
  114. package/src/client.ts +0 -9
  115. package/src/components/approval-surface.tsx +12 -2
  116. package/src/components/artifacts/document-editor.tsx +63 -6
  117. package/src/components/artifacts/presentation-editor.tsx +214 -9
  118. package/src/components/artifacts/spreadsheet-canvas.ts +3 -8
  119. package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
  120. package/src/components/artifacts/spreadsheet-grid.tsx +10 -11
  121. package/src/components/chat-composer.tsx +26 -3
  122. package/src/components/composer.tsx +10 -4
  123. package/src/components/human-input-form.tsx +12 -2
  124. package/src/components/markdown.tsx +133 -9
  125. package/src/components/message-timeline.tsx +104 -36
  126. package/src/components/model-picker.tsx +11 -3
  127. package/src/components/model-policy-picker-menu.tsx +10 -3
  128. package/src/components/preview-loading.tsx +154 -0
  129. package/src/components/sandbox-workspace.tsx +20 -36
  130. package/src/components/session-capability-frame.tsx +163 -0
  131. package/src/components/session-chrome.tsx +46 -77
  132. package/src/components/session-commands.tsx +18 -0
  133. package/src/components/session-connection-request.tsx +97 -0
  134. package/src/components/session-conversation.tsx +10 -5
  135. package/src/components/session-mcp-capability-card.tsx +532 -0
  136. package/src/components/session-status.tsx +2 -2
  137. package/src/components/timeline-anchor.tsx +9 -8
  138. package/src/components/timeline-search.tsx +168 -0
  139. package/src/components/user-message-body.tsx +7 -0
  140. package/src/connect-account-identity.tsx +34 -0
  141. package/src/connect-accounts.tsx +114 -52
  142. package/src/connect-chooser.tsx +171 -39
  143. package/src/connect-panel.tsx +73 -9
  144. package/src/connect-setup.tsx +72 -25
  145. package/src/connect.ts +13 -22
  146. package/src/connection-assets.d.ts +4 -0
  147. package/src/connection-catalog.tsx +61 -39
  148. package/src/connection-discovery.tsx +235 -0
  149. package/src/connection-installed.tsx +2 -0
  150. package/src/connection-logo.tsx +18 -10
  151. package/src/connection-service-logo.tsx +55 -0
  152. package/src/connection-service-presentation.ts +46 -0
  153. package/src/connection-skeleton.tsx +16 -0
  154. package/src/conversation-timeline.ts +1 -0
  155. package/src/discovery-cache.ts +42 -0
  156. package/src/hooks/use-codex-accounts.ts +33 -14
  157. package/src/hooks/use-composer.ts +4 -5
  158. package/src/hooks/use-connect.ts +23 -0
  159. package/src/hooks/use-session-events.ts +114 -1
  160. package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
  161. package/src/hooks/use-workspace-sessions.ts +10 -0
  162. package/src/index.ts +22 -2
  163. package/src/mcp-connection-status.ts +35 -0
  164. package/src/model-policy.ts +21 -0
  165. package/src/plugin-details.tsx +8 -5
  166. package/src/plugin-discovery.tsx +92 -72
  167. package/src/realtime/realtime-control.tsx +6 -8
  168. package/src/session-auth-recommendation.ts +48 -0
  169. package/src/session-capability-policy.ts +83 -0
  170. package/src/session-ui.ts +2 -0
  171. package/src/session.ts +1 -0
  172. package/src/skill-discovery.tsx +101 -56
  173. package/src/timeline/compaction-copy.ts +40 -0
  174. package/src/timeline/platform-activity-row.tsx +8 -3
  175. package/src/timeline/projection.ts +78 -2
  176. package/src/timeline/rolling-activity.tsx +4 -2
  177. package/src/timeline/shared.tsx +6 -1
  178. package/src/timeline/startup-timings.tsx +1 -1
  179. package/src/timeline/tool-display-name.ts +8 -6
  180. package/src/timeline/tool-renderers.tsx +5 -3
  181. package/src/timeline/turn-summary.tsx +17 -3
  182. package/src/timeline/types.ts +24 -2
  183. package/styles/compiled.css +325 -27
  184. package/styles/connect.css +90 -15
  185. package/styles/index.css +46 -0
  186. package/styles/session-capability.css +42 -0
  187. package/dist/chunk-45XEUVTK.js.map +0 -1
  188. package/dist/chunk-HQV2I5HV.js +0 -124
  189. package/dist/chunk-HQV2I5HV.js.map +0 -1
  190. package/dist/chunk-HUIKIDM5.js.map +0 -1
  191. package/dist/chunk-ICA6R6TW.js.map +0 -1
  192. package/dist/chunk-IHQII5WO.js +0 -886
  193. package/dist/chunk-IHQII5WO.js.map +0 -1
  194. package/dist/chunk-KANFU34B.js.map +0 -1
  195. package/dist/chunk-L2NBTKRX.js.map +0 -1
  196. package/dist/chunk-NYESIADM.js.map +0 -1
  197. package/dist/chunk-PEI6RANI.js.map +0 -1
  198. package/dist/chunk-TDSEILUH.js.map +0 -1
  199. package/dist/chunk-UMECRJRV.js.map +0 -1
  200. package/dist/hooks/use-packs.d.ts +0 -32
  201. package/dist/platform-activity-row-DUE2W74J.js.map +0 -1
  202. package/src/hooks/use-packs.ts +0 -157
  203. /package/dist/{browser-viewer-NTD6UIPR.js.map → browser-viewer-LGH4IP47.js.map} +0 -0
  204. /package/dist/{chunk-NOSYBS2D.js.map → chunk-2VHARTBL.js.map} +0 -0
  205. /package/dist/{chunk-5M6VTFJ5.js.map → chunk-GBPCZH3Y.js.map} +0 -0
  206. /package/dist/{chunk-YBM6CQRU.js.map → chunk-GEN54DU5.js.map} +0 -0
  207. /package/dist/{chunk-R4KRSFGA.js.map → chunk-IP4BUBKH.js.map} +0 -0
  208. /package/dist/{chunk-NLTS7JHI.js.map → chunk-JPHDWKZE.js.map} +0 -0
  209. /package/dist/{chunk-24M4YBCL.js.map → chunk-PNAVEK2U.js.map} +0 -0
  210. /package/dist/{model-policy-picker-menu-3R336DP2.js.map → model-policy-picker-menu-RVOQ4ZZG.js.map} +0 -0
  211. /package/dist/{queue-surface-implementation-LK3S4BIW.js.map → queue-surface-implementation-3RIFK2BS.js.map} +0 -0
  212. /package/dist/{timeline-annotation-markers-DRSTLEM6.js.map → timeline-annotation-markers-OY6OWR3V.js.map} +0 -0
  213. /package/dist/{timeline-annotation-selection-IHLUGVND.js.map → timeline-annotation-selection-BOXA2TIN.js.map} +0 -0
  214. /package/dist/{timeline-annotations-dialog-6Q53N5FG.js.map → timeline-annotations-dialog-RGY3MHL3.js.map} +0 -0
@@ -0,0 +1,154 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ /** Private loading surface: never receives or executes the unfinished fence. */
4
+ export function PreviewLoading() {
5
+ const ref = useRef<HTMLCanvasElement>(null);
6
+ useEffect(() => {
7
+ const canvas = ref.current;
8
+ if (!canvas) return;
9
+ let ctx: CanvasRenderingContext2D | null;
10
+ try {
11
+ ctx = canvas.getContext("2d");
12
+ } catch {
13
+ return; // The CSS grid and status remain when canvas is unavailable.
14
+ }
15
+ if (!ctx) return;
16
+ const context = ctx;
17
+ const motion = window.matchMedia?.("(prefers-reduced-motion: reduce)");
18
+ let frame: number | undefined;
19
+ let time = 2;
20
+ let last: number | undefined;
21
+ let disposed = false;
22
+ // Without visibility observation, keep a static surface rather than doing
23
+ // unbounded work for previews that may be outside the viewport.
24
+ let visible = false;
25
+ const canAnimate = () => !disposed && visible && !document.hidden && motion?.matches === false;
26
+
27
+ function draw() {
28
+ const w = canvas!.clientWidth;
29
+ const h = canvas!.clientHeight;
30
+ if (!w || !h || document.hidden || disposed) return;
31
+ const d = Math.min(window.devicePixelRatio || 1, 2);
32
+ if (canvas!.width !== Math.round(w * d) || canvas!.height !== Math.round(h * d)) {
33
+ canvas!.width = Math.round(w * d);
34
+ canvas!.height = Math.round(h * d);
35
+ }
36
+ const style = getComputedStyle(canvas!);
37
+ const ink = style.getPropertyValue("--_og-preview-ink").trim() || "#bca3ef";
38
+ const glowColor = style.getPropertyValue("--_og-preview-glow").trim() || "#8f6ccc";
39
+ context.setTransform(d, 0, 0, d, 0, 0);
40
+ context.clearRect(0, 0, w, h);
41
+ const cx = w * (0.5 + 0.13 * Math.sin(time * 0.21));
42
+ const cy = h * (0.5 + 0.12 * Math.cos(time * 0.27));
43
+ const glow = context.createRadialGradient(cx, cy, 0, cx, cy, Math.max(w, h) * 0.65);
44
+ glow.addColorStop(0, glowColor);
45
+ glow.addColorStop(1, "transparent");
46
+ context.globalAlpha = 0.09;
47
+ context.fillStyle = glow;
48
+ context.fillRect(0, 0, w, h);
49
+ const rows = Math.ceil(h / 18) + 7;
50
+ const cols = Math.ceil(w / 18) + 7;
51
+ const points = Array.from({ length: rows }, (_row, r) =>
52
+ Array.from({ length: cols }, (_col, c) => {
53
+ const x = (c - 3) * 18;
54
+ const y = (r - 3) * 18;
55
+ const dist = Math.hypot((x - cx) * 0.8, y - cy);
56
+ const wave = Math.sin(dist * 0.032 - time * 0.95);
57
+ return {
58
+ x: x + ((x - cx) / Math.max(dist, 1)) * wave * 4,
59
+ y: y + ((y - cy) / Math.max(dist, 1)) * wave * 4,
60
+ l: Math.pow((wave + 1) / 2, 5),
61
+ };
62
+ }),
63
+ );
64
+ context.strokeStyle = ink;
65
+ context.fillStyle = ink;
66
+ context.lineWidth = 0.65;
67
+ points.forEach((row, r) =>
68
+ row.forEach((p, c) => {
69
+ context.globalAlpha = 0.035 + p.l * 0.055;
70
+ context.beginPath();
71
+ if (c + 1 < cols) {
72
+ context.moveTo(p.x, p.y);
73
+ context.lineTo(row[c + 1]!.x, row[c + 1]!.y);
74
+ }
75
+ if (r + 1 < rows) {
76
+ context.moveTo(p.x, p.y);
77
+ context.lineTo(points[r + 1]![c]!.x, points[r + 1]![c]!.y);
78
+ }
79
+ context.stroke();
80
+ context.globalAlpha = 0.15 + p.l * 0.55;
81
+ context.beginPath();
82
+ context.arc(p.x, p.y, 0.85 + p.l * 0.65, 0, Math.PI * 2);
83
+ context.fill();
84
+ }),
85
+ );
86
+ context.globalAlpha = 1;
87
+ canvas!.dataset.painted = "true";
88
+ }
89
+
90
+ function stop() {
91
+ if (frame !== undefined) window.cancelAnimationFrame(frame);
92
+ frame = undefined;
93
+ last = undefined;
94
+ }
95
+ function tick(stamp: number) {
96
+ frame = undefined;
97
+ if (!canAnimate()) return;
98
+ if (last !== undefined) time += Math.min((stamp - last) / 1000, 0.05);
99
+ last = stamp;
100
+ draw();
101
+ frame = window.requestAnimationFrame(tick);
102
+ }
103
+ function refresh() {
104
+ stop();
105
+ if (disposed || document.hidden || (!visible && intersection)) return;
106
+ draw();
107
+ if (canAnimate() && typeof window.requestAnimationFrame === "function") {
108
+ frame = window.requestAnimationFrame(tick);
109
+ }
110
+ }
111
+ const intersection =
112
+ typeof IntersectionObserver === "function"
113
+ ? new IntersectionObserver(([entry]) => {
114
+ visible = entry?.isIntersecting === true;
115
+ refresh();
116
+ })
117
+ : undefined;
118
+ intersection?.observe(canvas);
119
+ const resize = typeof ResizeObserver === "function" ? new ResizeObserver(refresh) : undefined;
120
+ resize?.observe(canvas);
121
+ // Observe only ancestors, not the document subtree. This also updates the
122
+ // static reduced-motion canvas when a host changes its local theme.
123
+ const theme =
124
+ typeof MutationObserver === "function" ? new MutationObserver(refresh) : undefined;
125
+ for (let ancestor = canvas.parentElement; ancestor; ancestor = ancestor.parentElement) {
126
+ theme?.observe(ancestor, {
127
+ attributes: true,
128
+ attributeFilter: ["class", "style", "data-og-theme"],
129
+ });
130
+ }
131
+ motion?.addEventListener?.("change", refresh);
132
+ document.addEventListener("visibilitychange", refresh);
133
+ window.addEventListener("resize", refresh);
134
+ // Old hosts get one still frame; observed surfaces wait until onscreen.
135
+ if (!intersection) draw();
136
+ return () => {
137
+ disposed = true;
138
+ stop();
139
+ intersection?.disconnect();
140
+ resize?.disconnect();
141
+ theme?.disconnect();
142
+ motion?.removeEventListener?.("change", refresh);
143
+ document.removeEventListener("visibilitychange", refresh);
144
+ window.removeEventListener("resize", refresh);
145
+ };
146
+ }, []);
147
+
148
+ return (
149
+ <div className="og-preview-loading">
150
+ <canvas ref={ref} aria-hidden="true" />
151
+ <span className="og-preview-loading-label">Preparing preview…</span>
152
+ </div>
153
+ );
154
+ }
@@ -119,19 +119,6 @@ function sourceDrivenDefaultTab(
119
119
  return null;
120
120
  }
121
121
 
122
- function captureDegradedMessage(reason: string): string {
123
- switch (reason) {
124
- case "repository_discovery_timed_out":
125
- return "Workspace capture is incomplete because repository discovery timed out. Live files remain authoritative.";
126
- case "repository_discovery_result_limit_exceeded":
127
- return "Workspace capture is incomplete because the repository limit was exceeded. Live files remain authoritative.";
128
- case "repository_read_unavailable":
129
- return "Workspace capture is incomplete because repository changes could not be read. Live files remain authoritative.";
130
- default:
131
- return "Workspace capture is incomplete because repository discovery failed. Live files remain authoritative.";
132
- }
133
- }
134
-
135
122
  function WorkbenchSurfaceLoading({ name }: { name: "Browser" | "Desktop" }) {
136
123
  return (
137
124
  <CenteredState
@@ -555,23 +542,6 @@ export function useSandboxWorkspaceTabs(
555
542
  if (advertised.length > 0) return advertised;
556
543
  return captureState.capture?.repos.map((repo) => repo.root) ?? [];
557
544
  }, [capabilities?.Git.repos, captureState.capture]);
558
- const notifiedCaptureDegradedReason = useRef<{
559
- sessionId: string;
560
- reason: string | null;
561
- }>({
562
- sessionId,
563
- reason: null,
564
- });
565
- if (notifiedCaptureDegradedReason.current.sessionId !== sessionId) {
566
- notifiedCaptureDegradedReason.current = { sessionId, reason: null };
567
- }
568
- useEffect(() => {
569
- const reason = captureState.degradedReason;
570
- if (!reason || notifiedCaptureDegradedReason.current.reason === reason) return;
571
- notifiedCaptureDegradedReason.current = { sessionId, reason };
572
- onNotify?.({ kind: "error", message: captureDegradedMessage(reason) });
573
- }, [sessionId, captureState.degradedReason, onNotify]);
574
-
575
545
  const files = useSandboxFiles(sessionId, {
576
546
  events,
577
547
  // No passive Channel-A reads while cold, even after a conclusive capture
@@ -743,6 +713,7 @@ export function useSandboxWorkspaceTabs(
743
713
  comparison={changesComparison}
744
714
  onComparisonChange={setChangesComparison}
745
715
  captureAvailable={captureAvailable}
716
+ captureDegraded={captureState.degradedReason !== null}
746
717
  captureRevision={captureState.revision}
747
718
  capturePending={capturePending}
748
719
  liveWorkspaceExpected={liveWorkspaceExpected}
@@ -928,6 +899,7 @@ export function useSandboxWorkspaceTabs(
928
899
  browserEnabled,
929
900
  desktopEnabled,
930
901
  captureAvailable,
902
+ captureState.degradedReason,
931
903
  dirtyCount,
932
904
  warmTerminal,
933
905
  requestedFilePath,
@@ -1354,6 +1326,7 @@ function ChangesTabContent({
1354
1326
  git,
1355
1327
  comparison,
1356
1328
  captureAvailable,
1329
+ captureDegraded,
1357
1330
  captureRevision,
1358
1331
  capturePending,
1359
1332
  liveWorkspaceExpected,
@@ -1367,6 +1340,7 @@ function ChangesTabContent({
1367
1340
  git: UseSandboxGitResult;
1368
1341
  comparison: SandboxGitComparison;
1369
1342
  captureAvailable: boolean;
1343
+ captureDegraded: boolean;
1370
1344
  captureRevision: number | null;
1371
1345
  capturePending: boolean;
1372
1346
  liveWorkspaceExpected: boolean;
@@ -1460,6 +1434,8 @@ function ChangesTabContent({
1460
1434
  }
1461
1435
 
1462
1436
  if (!liveWorkspaceExpected && (comparison !== "working" || !captureAvailable)) {
1437
+ // Staged changes are live-only; a failed saved preview is not their blocker.
1438
+ const previewUnavailable = captureDegraded && !captureAvailable && comparison !== "staged";
1463
1439
  return (
1464
1440
  <CenteredState
1465
1441
  icon={
@@ -1468,22 +1444,30 @@ function ChangesTabContent({
1468
1444
  className="size-5 animate-spin motion-reduce:animate-none"
1469
1445
  aria-hidden
1470
1446
  />
1447
+ ) : previewUnavailable ? (
1448
+ <TriangleAlertIcon className="size-5" aria-hidden />
1471
1449
  ) : (
1472
1450
  <CpuIcon className="size-5" aria-hidden />
1473
1451
  )
1474
1452
  }
1475
1453
  >
1476
1454
  <p className="text-og-sm font-medium text-og-fg">
1477
- {workspaceWaking ? "Waking workspace" : "Workspace is resting"}
1455
+ {workspaceWaking
1456
+ ? "Waking workspace"
1457
+ : previewUnavailable
1458
+ ? "Saved changes preview is unavailable"
1459
+ : "Workspace is resting"}
1478
1460
  </p>
1479
1461
  <p className="text-og-sm leading-5 text-og-fg-subtle">
1480
1462
  {workspaceWaking
1481
1463
  ? "Connecting to the live working tree…"
1482
- : comparison === "branch"
1483
- ? "Wake the sandbox to compare this branch with the remote default branch."
1484
- : comparison === "staged"
1485
- ? "Wake the sandbox to inspect staged changes."
1486
- : "No captured revision is available yet. Wake the sandbox to inspect uncommitted changes."}
1464
+ : previewUnavailable
1465
+ ? "Open the live workspace to view current changes."
1466
+ : comparison === "branch"
1467
+ ? "Wake the sandbox to compare this branch with the remote default branch."
1468
+ : comparison === "staged"
1469
+ ? "Wake the sandbox to inspect staged changes."
1470
+ : "No captured revision is available yet. Wake the sandbox to inspect uncommitted changes."}
1487
1471
  </p>
1488
1472
  {!workspaceWaking ? (
1489
1473
  <DockActionButton onClick={onWake}>
@@ -0,0 +1,163 @@
1
+ import { CheckIcon, ShieldCheckIcon, XIcon } from "lucide-react";
2
+ import { Dialog } from "radix-ui";
3
+ import { useRef, useState, type ReactNode, type RefObject } from "react";
4
+ import { usePortalTokenStyle } from "../lib/use-portal-token-style";
5
+
6
+ export type SessionCapabilityFrameProps = {
7
+ name: string;
8
+ subtitle: string;
9
+ logo: string | null;
10
+ typeLabel: string;
11
+ description: string;
12
+ skill: boolean;
13
+ expanded: boolean;
14
+ complete: boolean;
15
+ actionLabel: string;
16
+ note: string;
17
+ onOpen(): void;
18
+ onClose(): void;
19
+ busy?: boolean;
20
+ opener?: RefObject<HTMLButtonElement | null>;
21
+ cardRef?: RefObject<HTMLElement | null>;
22
+ children?: ReactNode;
23
+ /** A catalogue row can own the opener while reusing the same details dialog. */
24
+ dialogOnly?: boolean;
25
+ };
26
+
27
+ function Mark({ src, name }: { src: string | null; name: string }) {
28
+ const [failed, setFailed] = useState<string | null>(null);
29
+ return (
30
+ <span className="og-session-capability-logo" aria-hidden>
31
+ {src && failed !== src ? (
32
+ <img src={src} alt="" loading="lazy" decoding="async" onError={() => setFailed(src)} />
33
+ ) : (
34
+ name.trim().slice(0, 2).toUpperCase()
35
+ )}
36
+ </span>
37
+ );
38
+ }
39
+
40
+ /** The same conversation card in the console and embedded clients. Setup
41
+ * content owns the provider flow; this frame owns identity and protected focus. */
42
+ export function SessionCapabilityFrame({
43
+ name,
44
+ subtitle,
45
+ logo,
46
+ typeLabel,
47
+ description,
48
+ skill,
49
+ expanded,
50
+ complete,
51
+ actionLabel,
52
+ note,
53
+ onOpen,
54
+ onClose,
55
+ busy = false,
56
+ opener,
57
+ cardRef,
58
+ children,
59
+ dialogOnly = false,
60
+ }: SessionCapabilityFrameProps) {
61
+ const localOpener = useRef<HTMLButtonElement>(null);
62
+ const localCard = useRef<HTMLElement>(null);
63
+ const buttonRef = opener ?? localOpener;
64
+ const sectionRef = cardRef ?? localCard;
65
+ const portalStyle = usePortalTokenStyle(sectionRef.current);
66
+ const identity = (
67
+ <>
68
+ <Mark src={logo} name={name} />
69
+ <div className="og-session-capability-identity">
70
+ <h3>{name}</h3>
71
+ <span>{subtitle}</span>
72
+ </div>
73
+ <span className="og-session-capability-type">{typeLabel}</span>
74
+ </>
75
+ );
76
+ return (
77
+ <div className="og-session-capability">
78
+ <section
79
+ hidden={dialogOnly}
80
+ style={dialogOnly ? { display: "none" } : undefined}
81
+ ref={sectionRef}
82
+ tabIndex={-1}
83
+ aria-label={`${name} setup`}
84
+ data-state={complete ? "complete" : expanded ? "setup" : "suggested"}
85
+ className="og-session-capability-shell"
86
+ >
87
+ <div className="og-session-capability-header">
88
+ {identity}
89
+ {complete ? <CheckIcon size={16} aria-hidden /> : null}
90
+ </div>
91
+ {complete ? (
92
+ <p role="status" className="og-session-capability-copy">
93
+ {skill ? "Installed · Workspace" : "Connected · Available in this conversation"}
94
+ </p>
95
+ ) : (
96
+ <>
97
+ <p className="og-session-capability-copy">{description}</p>
98
+ <div className="og-session-capability-actions">
99
+ <span>
100
+ <ShieldCheckIcon size={13} aria-hidden />
101
+ {skill ? "Guidance only · no account access" : "You choose what to authorize"}
102
+ </span>
103
+ <button
104
+ ref={buttonRef}
105
+ type="button"
106
+ onClick={onOpen}
107
+ aria-haspopup="dialog"
108
+ aria-expanded={expanded}
109
+ >
110
+ {actionLabel}
111
+ </button>
112
+ </div>
113
+ </>
114
+ )}
115
+ </section>
116
+ {!complete && !dialogOnly ? <p className="og-session-capability-note">{note}</p> : null}
117
+ <Dialog.Root
118
+ open={expanded && !complete}
119
+ onOpenChange={(open) => {
120
+ if (!open && !busy) onClose();
121
+ }}
122
+ >
123
+ <Dialog.Portal>
124
+ <Dialog.Overlay className="og-root og-session-capability-overlay" style={portalStyle} />
125
+ <Dialog.Content
126
+ className="og-root og-session-capability-dialog"
127
+ style={portalStyle}
128
+ onEscapeKeyDown={(event) => {
129
+ if (busy) event.preventDefault();
130
+ }}
131
+ onInteractOutside={(event) => {
132
+ if (busy) event.preventDefault();
133
+ }}
134
+ onCloseAutoFocus={(event) => {
135
+ event.preventDefault();
136
+ const target = buttonRef.current?.isConnected
137
+ ? buttonRef.current
138
+ : sectionRef.current;
139
+ if (target?.isConnected) target.focus();
140
+ }}
141
+ >
142
+ <div className="og-session-capability-dialog-header">
143
+ <Mark src={logo} name={name} />
144
+ <div>
145
+ <Dialog.Title>{name}</Dialog.Title>
146
+ <Dialog.Description>
147
+ {typeLabel}
148
+ {subtitle ? ` · ${subtitle}` : ""}
149
+ </Dialog.Description>
150
+ </div>
151
+ {!busy ? (
152
+ <Dialog.Close aria-label="Close connection setup">
153
+ <XIcon size={18} />
154
+ </Dialog.Close>
155
+ ) : null}
156
+ </div>
157
+ <div className="og-session-capability-dialog-body">{children}</div>
158
+ </Dialog.Content>
159
+ </Dialog.Portal>
160
+ </Dialog.Root>
161
+ </div>
162
+ );
163
+ }