@opengeni/react 7.4.0-canary.36844267522001 → 7.4.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 (113) hide show
  1. package/README.md +0 -10
  2. package/dist/artifacts-document.js +2 -3
  3. package/dist/artifacts-presentation.js +2 -3
  4. package/dist/artifacts-spreadsheet.js +2 -2
  5. package/dist/artifacts.d.ts +0 -1
  6. package/dist/artifacts.js +1402 -505
  7. package/dist/artifacts.js.map +1 -1
  8. package/dist/{browser-viewer-AJO7LMVE.js → browser-viewer-2E6R2Y4W.js} +2 -2
  9. package/dist/{chunk-WUISZ3WU.js → chunk-3E2AITTZ.js} +28 -32
  10. package/dist/{chunk-WUISZ3WU.js.map → chunk-3E2AITTZ.js.map} +1 -1
  11. package/dist/{chunk-Y2PRMYLC.js → chunk-DUEBTL3N.js} +3 -4
  12. package/dist/{chunk-Y2PRMYLC.js.map → chunk-DUEBTL3N.js.map} +1 -1
  13. package/dist/{chunk-7UAMYXJY.js → chunk-KNDIXZN6.js} +18 -25
  14. package/dist/{chunk-7UAMYXJY.js.map → chunk-KNDIXZN6.js.map} +1 -1
  15. package/dist/{chunk-XVYRQ4PK.js → chunk-L4OSXQ6H.js} +116 -277
  16. package/dist/chunk-L4OSXQ6H.js.map +1 -0
  17. package/dist/{chunk-7B44WVGH.js → chunk-S5JQ4R22.js} +12 -31
  18. package/dist/chunk-S5JQ4R22.js.map +1 -0
  19. package/dist/{chunk-A4UJB2MM.js → chunk-SEEVCT36.js} +2 -2
  20. package/dist/chunk-UGMKPUEM.js +299 -0
  21. package/dist/chunk-UGMKPUEM.js.map +1 -0
  22. package/dist/{chunk-OQVBTOBJ.js → chunk-USXVQACU.js} +3 -3
  23. package/dist/{chunk-ASCSKOWU.js → chunk-V2ABIVYJ.js} +3 -50
  24. package/dist/chunk-V2ABIVYJ.js.map +1 -0
  25. package/dist/{chunk-UQUR5WG5.js → chunk-X4HIN6TV.js} +157 -66
  26. package/dist/chunk-X4HIN6TV.js.map +1 -0
  27. package/dist/{chunk-QLG6W33X.js → chunk-XEJEOAOV.js} +35 -46
  28. package/dist/chunk-XEJEOAOV.js.map +1 -0
  29. package/dist/components/artifacts/editable-artifact-ui.d.ts +1 -2
  30. package/dist/components/artifacts/index.d.ts +0 -7
  31. package/dist/components/message-timeline.d.ts +1 -7
  32. package/dist/components/open-geni-links.d.ts +0 -16
  33. package/dist/components/session-conversation.d.ts +1 -13
  34. package/dist/{computer-viewer-NB6O6BTV.js → computer-viewer-WMJE6OA3.js} +2 -2
  35. package/dist/connect.js +1 -2
  36. package/dist/connect.js.map +1 -1
  37. package/dist/hooks/latest-question.d.ts +1 -1
  38. package/dist/index.d.ts +2 -3
  39. package/dist/index.js +12 -22
  40. package/dist/index.js.map +1 -1
  41. package/dist/interaction.js +2 -2
  42. package/dist/{latest-question-3HGQUI2E.js → latest-question-QB4EUCW5.js} +2 -2
  43. package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-PFUEZ2U4.js} +5 -37
  44. package/dist/markdown-table-layout-PFUEZ2U4.js.map +1 -0
  45. package/dist/session-ui.d.ts +2 -3
  46. package/dist/session-ui.js +9 -18
  47. package/dist/session-ui.js.map +1 -1
  48. package/dist/session.js +2 -2
  49. package/dist/sites.js +289 -4
  50. package/dist/sites.js.map +1 -1
  51. package/package.json +3 -3
  52. package/src/artifacts.ts +0 -37
  53. package/src/components/artifacts/editable-artifact-ui.tsx +1 -3
  54. package/src/components/artifacts/editable-artifact-workbench.tsx +2 -5
  55. package/src/components/artifacts/editable-document.tsx +36 -34
  56. package/src/components/artifacts/editable-presentation.tsx +16 -21
  57. package/src/components/artifacts/index.ts +0 -41
  58. package/src/components/browser-viewer.tsx +20 -59
  59. package/src/components/computer-viewer.tsx +23 -51
  60. package/src/components/markdown-table-layout.ts +5 -49
  61. package/src/components/markdown.tsx +2 -5
  62. package/src/components/message-timeline.tsx +7 -59
  63. package/src/components/open-geni-links.tsx +0 -24
  64. package/src/components/session-conversation.tsx +20 -120
  65. package/src/index.ts +0 -7
  66. package/src/session-ui.ts +0 -7
  67. package/src/timeline/projection.ts +4 -54
  68. package/styles/compiled.css +0 -220
  69. package/dist/chat-interactive-block-VAGABN7J.js +0 -11
  70. package/dist/chunk-5CVXZA3Y.js +0 -292
  71. package/dist/chunk-5CVXZA3Y.js.map +0 -1
  72. package/dist/chunk-7B44WVGH.js.map +0 -1
  73. package/dist/chunk-ASCSKOWU.js.map +0 -1
  74. package/dist/chunk-QLG6W33X.js.map +0 -1
  75. package/dist/chunk-RAIQYITC.js +0 -1772
  76. package/dist/chunk-RAIQYITC.js.map +0 -1
  77. package/dist/chunk-SCOTPE2I.js +0 -589
  78. package/dist/chunk-SCOTPE2I.js.map +0 -1
  79. package/dist/chunk-UQUR5WG5.js.map +0 -1
  80. package/dist/chunk-WEHMPOXK.js +0 -39
  81. package/dist/chunk-WEHMPOXK.js.map +0 -1
  82. package/dist/chunk-XCHWDJ22.js +0 -107
  83. package/dist/chunk-XCHWDJ22.js.map +0 -1
  84. package/dist/chunk-XVYRQ4PK.js.map +0 -1
  85. package/dist/components/artifacts/artifact-chrome.d.ts +0 -120
  86. package/dist/components/artifacts/artifact-sandbox.d.ts +0 -27
  87. package/dist/components/artifacts/chat-interactive-block.d.ts +0 -29
  88. package/dist/components/artifacts/deferred-chat-media.d.ts +0 -15
  89. package/dist/components/artifacts/editable-artifact-view.d.ts +0 -46
  90. package/dist/components/artifacts/latest-task-runner.d.ts +0 -15
  91. package/dist/components/artifacts/session-artifact-viewer.d.ts +0 -39
  92. package/dist/components/artifacts/site-view.d.ts +0 -23
  93. package/dist/components/timeline-pill-geometry.d.ts +0 -6
  94. package/dist/components/timeline-pill-placement.d.ts +0 -18
  95. package/dist/computer-viewer-NB6O6BTV.js.map +0 -1
  96. package/dist/markdown-table-layout-QXSLK4BY.js.map +0 -1
  97. package/dist/timeline-pill-geometry-2G6EK47V.js +0 -73
  98. package/dist/timeline-pill-geometry-2G6EK47V.js.map +0 -1
  99. package/src/components/artifacts/artifact-chrome.tsx +0 -413
  100. package/src/components/artifacts/artifact-sandbox.tsx +0 -185
  101. package/src/components/artifacts/chat-interactive-block.tsx +0 -260
  102. package/src/components/artifacts/deferred-chat-media.tsx +0 -67
  103. package/src/components/artifacts/editable-artifact-view.tsx +0 -187
  104. package/src/components/artifacts/latest-task-runner.ts +0 -49
  105. package/src/components/artifacts/session-artifact-viewer.tsx +0 -376
  106. package/src/components/artifacts/site-view.tsx +0 -116
  107. package/src/components/timeline-pill-geometry.ts +0 -112
  108. package/src/components/timeline-pill-placement.ts +0 -81
  109. /package/dist/{browser-viewer-AJO7LMVE.js.map → browser-viewer-2E6R2Y4W.js.map} +0 -0
  110. /package/dist/{chunk-A4UJB2MM.js.map → chunk-SEEVCT36.js.map} +0 -0
  111. /package/dist/{chunk-OQVBTOBJ.js.map → chunk-USXVQACU.js.map} +0 -0
  112. /package/dist/{chat-interactive-block-VAGABN7J.js.map → computer-viewer-WMJE6OA3.js.map} +0 -0
  113. /package/dist/{latest-question-3HGQUI2E.js.map → latest-question-QB4EUCW5.js.map} +0 -0
@@ -60,12 +60,10 @@ export function EditableArtifactMessage({
60
60
  title,
61
61
  detail,
62
62
  retry,
63
- retryLabel = "Retry",
64
63
  }: {
65
64
  title: string;
66
65
  detail: string;
67
66
  retry?: (() => void) | undefined;
68
- retryLabel?: string | undefined;
69
67
  }) {
70
68
  return (
71
69
  <div className="grid h-full min-h-56 place-items-center bg-og-bg p-6 text-center">
@@ -78,7 +76,7 @@ export function EditableArtifactMessage({
78
76
  onClick={retry}
79
77
  className="mt-3 rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-3 py-1.5 text-og-sm font-medium hover:bg-og-primary-hover outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent"
80
78
  >
81
- {retryLabel}
79
+ Retry
82
80
  </button>
83
81
  ) : null}
84
82
  </div>
@@ -6,7 +6,6 @@ import {
6
6
  } from "@opengeni/sdk/editable-artifacts";
7
7
  import { useCallback, useEffect, useRef, useState } from "react";
8
8
 
9
- import { useArtifactLabels } from "./artifact-chrome";
10
9
  import { EditableArtifactMessage } from "./editable-artifact-ui";
11
10
  import {
12
11
  EditableDocumentArtifactSurface,
@@ -134,7 +133,6 @@ export function EditableArtifactWorkbenchHost({
134
133
  }, [retryEpoch, sessionKey]);
135
134
 
136
135
  const retry = useCallback(() => setRetryEpoch((value) => value + 1), []);
137
- const labels = useArtifactLabels();
138
136
  const currentError = state.key === sessionKey ? state.error : null;
139
137
  if (state.key === sessionKey && state.session) {
140
138
  return (
@@ -148,10 +146,9 @@ export function EditableArtifactWorkbenchHost({
148
146
  }
149
147
  return (
150
148
  <EditableArtifactMessage
151
- title={currentError ? labels.editableErrors.transient.title : labels.opening}
152
- detail={currentError?.message ?? labels.startingEditor}
149
+ title={currentError ? "Could not open this artifact" : "Opening artifact"}
150
+ detail={currentError?.message ?? "Starting the secure editing session…"}
153
151
  retry={currentError ? retry : undefined}
154
- retryLabel={labels.retry}
155
152
  />
156
153
  );
157
154
  }
@@ -9,7 +9,6 @@ import {
9
9
  type EditableArtifactSession,
10
10
  } from "@opengeni/sdk/editable-artifacts";
11
11
  import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
12
- import { useLatestTaskRunner } from "./latest-task-runner";
13
12
 
14
13
  import {
15
14
  DocumentProjectionArtifactSurface,
@@ -88,7 +87,6 @@ export function EditableDocumentArtifactSurface({
88
87
  error: null,
89
88
  });
90
89
  const loadGeneration = useRef(0);
91
- const scheduleLoad = useLatestTaskRunner(session);
92
90
  const commandStateRef = useRef<DocumentCommandState | null>(null);
93
91
  if (commandStateRef.current?.session !== session) {
94
92
  commandStateRef.current = {
@@ -109,41 +107,45 @@ export function EditableDocumentArtifactSurface({
109
107
  ? { ...current, loading: true, error: null }
110
108
  : { session, projection: null, loading: true, error: null },
111
109
  );
112
- const superseded = () =>
113
- cancelled ||
114
- generation !== loadGeneration.current ||
115
- commandStateRef.current !== commandState;
116
- scheduleLoad(async () => {
117
- if (superseded()) return;
118
- await composeDocumentEditorProjection(session).then(
119
- (projection) => {
120
- if (superseded()) return;
121
- const nextParagraphText = paragraphTextById(projection);
122
- for (const [localId, canonicalId] of commandState.optimisticParagraphIds) {
123
- if (nextParagraphText.has(canonicalId)) continue;
124
- const optimisticText =
125
- commandState.paragraphText.get(canonicalId) ??
126
- commandState.paragraphText.get(localId);
127
- if (optimisticText !== undefined) nextParagraphText.set(canonicalId, optimisticText);
128
- }
129
- commandState.paragraphText = nextParagraphText;
130
- setState({ session, projection, loading: false, error: null });
131
- },
132
- (cause) => {
133
- if (superseded()) return;
134
- setState({
135
- session,
136
- projection: null,
137
- loading: false,
138
- error: asEditableArtifactError(cause, "Could not open this document"),
139
- });
140
- },
141
- );
142
- });
110
+ void composeDocumentEditorProjection(session).then(
111
+ (projection) => {
112
+ if (
113
+ cancelled ||
114
+ generation !== loadGeneration.current ||
115
+ commandStateRef.current !== commandState
116
+ ) {
117
+ return;
118
+ }
119
+ const nextParagraphText = paragraphTextById(projection);
120
+ for (const [localId, canonicalId] of commandState.optimisticParagraphIds) {
121
+ if (nextParagraphText.has(canonicalId)) continue;
122
+ const optimisticText =
123
+ commandState.paragraphText.get(canonicalId) ?? commandState.paragraphText.get(localId);
124
+ if (optimisticText !== undefined) nextParagraphText.set(canonicalId, optimisticText);
125
+ }
126
+ commandState.paragraphText = nextParagraphText;
127
+ setState({ session, projection, loading: false, error: null });
128
+ },
129
+ (cause) => {
130
+ if (
131
+ cancelled ||
132
+ generation !== loadGeneration.current ||
133
+ commandStateRef.current !== commandState
134
+ ) {
135
+ return;
136
+ }
137
+ setState({
138
+ session,
139
+ projection: null,
140
+ loading: false,
141
+ error: asEditableArtifactError(cause, "Could not open this document"),
142
+ });
143
+ },
144
+ );
143
145
  return () => {
144
146
  cancelled = true;
145
147
  };
146
- }, [commandState, invalidator, retryEpoch, scheduleLoad, session]);
148
+ }, [commandState, invalidator, retryEpoch, session]);
147
149
 
148
150
  const refresh = useCallback(() => setRetryEpoch((value) => value + 1), []);
149
151
  const writable = !readOnly && view.writable;
@@ -33,7 +33,6 @@ import {
33
33
  type PresentationSlideProjection,
34
34
  } from "./presentation-editor";
35
35
  import { ArtifactSurface } from "./artifact-surface";
36
- import { useLatestTaskRunner } from "./latest-task-runner";
37
36
  import {
38
37
  asEditableArtifactError,
39
38
  editableArtifactAccessRevoked,
@@ -106,35 +105,31 @@ export function EditablePresentationArtifactSurface({
106
105
  error: null,
107
106
  });
108
107
  const loadGeneration = useRef(0);
109
- const scheduleLoad = useLatestTaskRunner(session);
110
108
  const sceneNodes = useRef(new Map<string, PresentationArtifactEditorSceneNode>());
111
109
 
112
110
  useEffect(() => {
113
111
  const generation = ++loadGeneration.current;
114
112
  let cancelled = false;
115
113
  setState((current) => ({ ...current, loading: true, error: null }));
116
- scheduleLoad(async () => {
117
- if (cancelled || generation !== loadGeneration.current) return;
118
- await composePresentationEditorProjection(session).then(
119
- (composed) => {
120
- if (cancelled || generation !== loadGeneration.current) return;
121
- sceneNodes.current = new Map(composed.sceneNodes);
122
- setState({ composed, loading: false, error: null });
123
- },
124
- (cause) => {
125
- if (cancelled || generation !== loadGeneration.current) return;
126
- setState({
127
- composed: null,
128
- loading: false,
129
- error: asEditableArtifactError(cause, "Could not open this presentation"),
130
- });
131
- },
132
- );
133
- });
114
+ void composePresentationEditorProjection(session).then(
115
+ (composed) => {
116
+ if (cancelled || generation !== loadGeneration.current) return;
117
+ sceneNodes.current = new Map(composed.sceneNodes);
118
+ setState({ composed, loading: false, error: null });
119
+ },
120
+ (cause) => {
121
+ if (cancelled || generation !== loadGeneration.current) return;
122
+ setState({
123
+ composed: null,
124
+ loading: false,
125
+ error: asEditableArtifactError(cause, "Could not open this presentation"),
126
+ });
127
+ },
128
+ );
134
129
  return () => {
135
130
  cancelled = true;
136
131
  };
137
- }, [invalidator, retryEpoch, scheduleLoad, session]);
132
+ }, [invalidator, retryEpoch, session]);
138
133
 
139
134
  const refresh = useCallback(() => setRetryEpoch((value) => value + 1), []);
140
135
  const writable = !readOnly && view.writable;
@@ -1,44 +1,3 @@
1
- export {
2
- ArtifactBadge,
3
- ArtifactLabelsProvider,
4
- DEFAULT_ARTIFACT_LABELS,
5
- useArtifactLabels,
6
- type ArtifactLabels,
7
- ArtifactButton,
8
- ArtifactLoading,
9
- ArtifactProblem,
10
- ArtifactSelect,
11
- ArtifactViewerHeader,
12
- artifactKindIcon,
13
- artifactKindSubtitle,
14
- artifactLoadErrorMessage,
15
- artifactLoadErrorView,
16
- type ArtifactKind,
17
- type ArtifactLoadErrorKind,
18
- type ArtifactLoadErrorView,
19
- } from "./artifact-chrome";
20
- export { ArtifactSandbox, type ArtifactSandboxProps } from "./artifact-sandbox";
21
- export { DeferredChatMedia } from "./deferred-chat-media";
22
- export {
23
- ChatInteractiveBlock,
24
- type ChatInteractiveBlockProps,
25
- type SiteSnapshotClient,
26
- type SiteToolBridgeFactory,
27
- type SiteToolScope,
28
- } from "./chat-interactive-block";
29
- export { SiteView, type SiteViewProps } from "./site-view";
30
- export {
31
- EditableArtifactView,
32
- type EditableArtifactRuntimes,
33
- type EditableArtifactViewProps,
34
- type OpenedEditableArtifact,
35
- } from "./editable-artifact-view";
36
- export {
37
- SessionArtifactViewer,
38
- type SessionArtifactTarget,
39
- type SessionArtifactViewerLabels,
40
- type SessionArtifactViewerProps,
41
- } from "./session-artifact-viewer";
42
1
  export {
43
2
  ArtifactSurface,
44
3
  type ArtifactModality,
@@ -208,11 +208,6 @@ export function BrowserViewer({
208
208
  () => registry.relevantSessions.filter((session) => isLiveBrowser(session)),
209
209
  [registry.relevantSessions],
210
210
  );
211
- const latestRelevantBrowser = registry.relevantSessions[0];
212
- const unavailableBrowser =
213
- latestRelevantBrowser?.lifecycle === "lost" || latestRelevantBrowser?.lifecycle === "failed"
214
- ? latestRelevantBrowser
215
- : undefined;
216
211
  const [selection, setSelection] = useState<BrowserSelection>(() =>
217
212
  initialBrowserSessionId ? { sessionId: initialBrowserSessionId, pinned: true } : null,
218
213
  );
@@ -774,8 +769,7 @@ export function BrowserViewer({
774
769
  );
775
770
  }
776
771
  if (liveSessions.length === 0) {
777
- if (renderEmpty && !unavailableBrowser)
778
- return <>{renderEmpty(() => createBrowser(), creating)}</>;
772
+ if (renderEmpty) return <>{renderEmpty(() => createBrowser(), creating)}</>;
779
773
  return (
780
774
  <div
781
775
  className={cn("flex h-full min-h-0 items-center justify-center bg-og-bg p-6", className)}
@@ -784,22 +778,16 @@ export function BrowserViewer({
784
778
  <span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted">
785
779
  <Globe2Icon className="size-4.5" />
786
780
  </span>
787
- {unavailableBrowser && !attachedGenerationLoss ? (
788
- <BrowserUnavailableNotice session={unavailableBrowser} />
789
- ) : (
790
- <>
791
- <p className="mt-3 text-og-menu font-medium text-og-fg">
792
- {attachedGenerationLoss
793
- ? "Chrome reconnected—open a fresh browser/desktop."
794
- : "No browser open"}
795
- </p>
796
- <p className="mt-1 text-og-control leading-5 text-og-fg-muted">
797
- {attachedGenerationLoss
798
- ? "This Chrome profile is live again, but the previous browser cannot move to the new connection. Use Browser → New browser → Connected Chrome."
799
- : "A browser appears here when this agent—or another agent in the workspace—opens one."}
800
- </p>
801
- </>
802
- )}
781
+ <p className="mt-3 text-og-menu font-medium text-og-fg">
782
+ {attachedGenerationLoss
783
+ ? "Chrome reconnected—open a fresh browser/desktop."
784
+ : "No browser open"}
785
+ </p>
786
+ <p className="mt-1 text-og-control leading-5 text-og-fg-muted">
787
+ {attachedGenerationLoss
788
+ ? "This Chrome profile is live again, but the previous browser cannot move to the new connection. Use Browser → New browser → Connected Chrome."
789
+ : "A browser appears here when this agent—or another agent in the workspace—opens one."}
790
+ </p>
803
791
  <BrowserLaunchMenu
804
792
  attachedBridges={onlineAttachedBridges}
805
793
  attachedDevices={attached.devices}
@@ -867,7 +855,6 @@ export function BrowserViewer({
867
855
  />
868
856
  {!selectedRegistrySession ? (
869
857
  <BrowserUnselectedPanel
870
- unavailableBrowser={unavailableBrowser}
871
858
  peerCount={liveSessions.length}
872
859
  creating={creating}
873
860
  onCreate={() => createBrowser()}
@@ -1199,7 +1186,6 @@ function BrowserToolbar(props: {
1199
1186
  }
1200
1187
 
1201
1188
  function BrowserUnselectedPanel(props: {
1202
- unavailableBrowser: BrowserSession | undefined;
1203
1189
  peerCount: number;
1204
1190
  creating: boolean;
1205
1191
  onCreate: () => void;
@@ -1210,11 +1196,7 @@ function BrowserUnselectedPanel(props: {
1210
1196
  <span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted">
1211
1197
  <Globe2Icon className="size-4.5" />
1212
1198
  </span>
1213
- {props.unavailableBrowser ? (
1214
- <BrowserUnavailableNotice session={props.unavailableBrowser} />
1215
- ) : (
1216
- <p className="mt-3 text-og-menu font-medium text-og-fg">No browser for this agent</p>
1217
- )}
1199
+ <p className="mt-3 text-og-menu font-medium text-og-fg">No browser for this agent</p>
1218
1200
  <p className="mt-1 text-og-control leading-5 text-og-fg-muted">
1219
1201
  {props.peerCount === 1
1220
1202
  ? "One workspace browser is available from the browser menu."
@@ -1234,25 +1216,6 @@ function BrowserUnselectedPanel(props: {
1234
1216
  );
1235
1217
  }
1236
1218
 
1237
- function BrowserUnavailableNotice({ session }: { session: BrowserSession }) {
1238
- const reason =
1239
- session.failureCode === "provider_deadline_rotation"
1240
- ? "The computer running it reached its time limit."
1241
- : session.failureCode === "source_placement_changed"
1242
- ? "This chat moved to another computer."
1243
- : session.lifecycle === "failed"
1244
- ? "It stopped unexpectedly."
1245
- : "The connection to it was lost.";
1246
- return (
1247
- <div role="status">
1248
- <p className="mt-3 text-og-menu font-medium text-og-fg">Browser unavailable</p>
1249
- <p className="mt-1 break-words text-og-control leading-5 text-og-fg-muted">
1250
- {session.name}. {reason} Open a new browser to continue.
1251
- </p>
1252
- </div>
1253
- );
1254
- }
1255
-
1256
1219
  function BrowserSessionGroup(props: {
1257
1220
  label: string;
1258
1221
  sessions: BrowserSession[];
@@ -1756,8 +1719,7 @@ function BrowserProfileMenu(props: {
1756
1719
  ) : null}
1757
1720
  {canSave ? (
1758
1721
  <p className="mt-2 text-[10px] leading-4 text-og-fg-subtle">
1759
- Saving restarts this browser. Save unfinished work on its pages first; unsaved form
1760
- entries may be lost. Other open browsers are unchanged.
1722
+ Saving briefly restarts this browser. Other open browsers are unchanged.
1761
1723
  </p>
1762
1724
  ) : null}
1763
1725
  </div>
@@ -2778,14 +2740,13 @@ function BrowserDiagnosticsDrawer(props: {
2778
2740
  id="browser-page-title"
2779
2741
  className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
2780
2742
  >
2781
- Tab diagnostics
2743
+ Current page
2782
2744
  </h3>
2783
- <p className="mt-1 text-og-xs text-og-fg-subtle">Includes earlier pages in this tab.</p>
2784
2745
  <dl className="mt-2 grid grid-cols-2 gap-2">
2785
- <DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount} />
2786
- <DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount} />
2787
- <DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount} />
2788
- <DiagnosticCount label="Downloads" value={diagnostics?.downloadCount} />
2746
+ <DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount ?? 0} />
2747
+ <DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount ?? 0} />
2748
+ <DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount ?? 0} />
2749
+ <DiagnosticCount label="Downloads" value={diagnostics?.downloadCount ?? 0} />
2789
2750
  </dl>
2790
2751
  {session?.failureCode ? (
2791
2752
  <p className="mt-2 rounded-og-sm border border-og-status-error/30 bg-og-status-error/5 px-2.5 py-2 text-og-xs text-og-danger">
@@ -3083,7 +3044,7 @@ function DiagnosticFact(props: { label: string; value: string }) {
3083
3044
  );
3084
3045
  }
3085
3046
 
3086
- function DiagnosticCount(props: { label: string; value: number | undefined }) {
3047
+ function DiagnosticCount(props: { label: string; value: number }) {
3087
3048
  return (
3088
3049
  <div className="rounded-og-sm border border-og-border bg-og-bg px-2.5 py-2">
3089
3050
  <dt className="text-[10px] text-og-fg-subtle">{props.label}</dt>
@@ -3093,7 +3054,7 @@ function DiagnosticCount(props: { label: string; value: number | undefined }) {
3093
3054
  props.value ? "text-og-fg" : "text-og-fg-muted",
3094
3055
  )}
3095
3056
  >
3096
- {props.value ?? "Unavailable"}
3057
+ {props.value}
3097
3058
  </dd>
3098
3059
  </div>
3099
3060
  );
@@ -289,19 +289,13 @@ export function ComputerViewer({
289
289
  : null;
290
290
  const rfbStream =
291
291
  frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
292
- const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
293
- // RFB has one combined input switch; partial native input stays view-only.
294
- const rfbInputEnabled =
295
- !machineLocked &&
296
- computer.session?.capabilities?.pointerInput === true &&
297
- computer.session?.capabilities?.keyboardInput === true;
298
292
  const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
299
293
  if (!rfbStream || !frames.attachment) return null;
300
294
  const bounds = computer.selectedTarget?.bounds;
301
295
  return {
302
296
  transport: "vnc-ws",
303
297
  client: "novnc",
304
- mode: rfbInputEnabled ? "interactive" : "read-only",
298
+ mode: "interactive",
305
299
  url: rfbStream.url,
306
300
  token: null,
307
301
  expiresAt: frames.attachment.expiresAt,
@@ -313,7 +307,8 @@ export function ComputerViewer({
313
307
  sharedSessionIds: [],
314
308
  reason: null,
315
309
  };
316
- }, [computer.selectedTarget?.bounds, frames.attachment, rfbInputEnabled, rfbStream]);
310
+ }, [computer.selectedTarget?.bounds, frames.attachment, rfbStream]);
311
+ const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
317
312
  const currentIds = useMemo(() => new Set(relevant.map((session) => session.id)), [relevant]);
318
313
 
319
314
  useEffect(() => {
@@ -457,8 +452,7 @@ export function ComputerViewer({
457
452
 
458
453
  const copyFromRfb = useCallback(
459
454
  (event: ClipboardEvent<HTMLDivElement>) => {
460
- if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
461
- return;
455
+ if (!rfbStream || computer.session?.capabilities?.clipboard !== true) return;
462
456
  event.preventDefault();
463
457
  event.stopPropagation();
464
458
  void perform({ type: "clipboard", operation: "copy" }, null)
@@ -470,13 +464,12 @@ export function ComputerViewer({
470
464
  })
471
465
  .catch((cause) => notifyError(cause, "Could not copy from the desktop."));
472
466
  },
473
- [computer, notifyError, perform, rfbInputEnabled, rfbStream],
467
+ [computer, notifyError, perform, rfbStream],
474
468
  );
475
469
 
476
470
  const pasteIntoRfb = useCallback(
477
471
  (text: string): boolean => {
478
- if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
479
- return false;
472
+ if (!rfbStream || computer.session?.capabilities?.clipboard !== true) return false;
480
473
  // Keep paste on the canonical ComputerSession action path. RFB
481
474
  // ClientCutText synchronization varies by server and can acknowledge a
482
475
  // local paste without ever updating the remote graphical seat. These two
@@ -491,7 +484,7 @@ export function ComputerViewer({
491
484
  })().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
492
485
  return true;
493
486
  },
494
- [computer, notifyError, perform, rfbInputEnabled, rfbStream],
487
+ [computer, notifyError, perform, rfbStream],
495
488
  );
496
489
 
497
490
  const hideGenericCreate = suppressGenericCreate && liveRelevant.length === 0;
@@ -630,18 +623,13 @@ export function ComputerViewer({
630
623
  <div className="relative min-h-0 flex-1 bg-black" onCopyCapture={copyFromRfb}>
631
624
  <DesktopViewer
632
625
  capability={rfbCapability}
633
- interactive={rfbInputEnabled}
626
+ interactive
634
627
  showControlToggle={false}
635
628
  webSocketProtocols={rfbStream.protocols}
636
629
  onPasteText={pasteIntoRfb}
637
630
  targetPlatform={computer.session?.platform ?? null}
638
631
  className="h-full"
639
632
  />
640
- {!rfbInputEnabled ? (
641
- <div className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-white/10 bg-black/70 px-3 py-1 text-[11px] text-white/80 backdrop-blur">
642
- View only · direct input unavailable
643
- </div>
644
- ) : null}
645
633
  </div>
646
634
  ) : (
647
635
  <ComputerViewport
@@ -660,8 +648,6 @@ export function ComputerViewer({
660
648
  mutating={computer.mutating}
661
649
  backgroundActions={computer.session?.capabilities?.backgroundActions === true}
662
650
  clipboardEnabled={computer.session?.capabilities?.clipboard === true}
663
- pointerInput={computer.session?.capabilities?.pointerInput === true}
664
- keyboardInput={computer.session?.capabilities?.keyboardInput === true}
665
651
  onAction={perform}
666
652
  onReadClipboard={computer.readClipboard}
667
653
  onReconnect={
@@ -1025,8 +1011,6 @@ function ComputerViewport(props: {
1025
1011
  mutating: boolean;
1026
1012
  backgroundActions: boolean;
1027
1013
  clipboardEnabled: boolean;
1028
- pointerInput: boolean;
1029
- keyboardInput: boolean;
1030
1014
  onAction: (action: ComputerAction, frame: ComputerFrame | null) => Promise<void>;
1031
1015
  onReadClipboard: () => Promise<ComputerClipboard>;
1032
1016
  onReconnect: () => void;
@@ -1086,9 +1070,6 @@ function ComputerViewport(props: {
1086
1070
  if (inputRef.current) inputRef.current.value = "";
1087
1071
  }, []);
1088
1072
 
1089
- const pointerInputEnabled = rawInputEnabled && props.pointerInput;
1090
- const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
1091
-
1092
1073
  const paintQueuedFrames = useCallback(() => {
1093
1074
  if (decodingFrameRef.current) return;
1094
1075
  decodingFrameRef.current = true;
@@ -1178,11 +1159,11 @@ function ComputerViewport(props: {
1178
1159
  }, [clearBufferedInput, paintQueuedFrames, props.frame]);
1179
1160
 
1180
1161
  useLayoutEffect(() => {
1181
- if (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) {
1162
+ if (!rawInputEnabled) {
1182
1163
  actionQueueEpochRef.current += 1;
1183
1164
  clearBufferedInput();
1184
1165
  }
1185
- }, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
1166
+ }, [clearBufferedInput, rawInputEnabled]);
1186
1167
 
1187
1168
  const enqueue = useCallback(
1188
1169
  (
@@ -1267,8 +1248,6 @@ function ComputerViewport(props: {
1267
1248
  if (!frame || props.mutating || !rawInputEnabled || event.button !== 0) return;
1268
1249
  // Preserve the keyboard sink's focus through the canvas pointer default action.
1269
1250
  event.preventDefault();
1270
- if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
1271
- if (!pointerInputEnabled) return;
1272
1251
  flushPendingWheel();
1273
1252
  flushPendingText();
1274
1253
  pointerStartRef.current = {
@@ -1278,10 +1257,11 @@ function ComputerViewport(props: {
1278
1257
  frame,
1279
1258
  };
1280
1259
  event.currentTarget.setPointerCapture?.(event.pointerId);
1260
+ inputRef.current?.focus({ preventScroll: true });
1281
1261
  };
1282
1262
 
1283
1263
  const pointerUp = (event: PointerEvent<HTMLCanvasElement>) => {
1284
- if (!pointerInputEnabled) return;
1264
+ if (!rawInputEnabled) return;
1285
1265
  const start = pointerStartRef.current;
1286
1266
  pointerStartRef.current = null;
1287
1267
  if (!start || start.pointerId !== event.pointerId) return;
@@ -1346,7 +1326,7 @@ function ComputerViewport(props: {
1346
1326
 
1347
1327
  const contextMenu = (event: MouseEvent<HTMLCanvasElement>) => {
1348
1328
  event.preventDefault();
1349
- if (!pointerInputEnabled) return;
1329
+ if (!rawInputEnabled) return;
1350
1330
  const frame = paintedFrameRef.current;
1351
1331
  if (!frame) return;
1352
1332
  flushPendingWheel();
@@ -1369,7 +1349,7 @@ function ComputerViewport(props: {
1369
1349
  };
1370
1350
 
1371
1351
  const wheel = (event: WheelEvent<HTMLCanvasElement>) => {
1372
- if (!pointerInputEnabled) return;
1352
+ if (!rawInputEnabled) return;
1373
1353
  const frame = paintedFrameRef.current;
1374
1354
  if (!frame) return;
1375
1355
  const at = point(frame, event.clientX, event.clientY);
@@ -1397,7 +1377,7 @@ function ComputerViewport(props: {
1397
1377
  };
1398
1378
 
1399
1379
  const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
1400
- if (!keyboardInputEnabled) return;
1380
+ if (!rawInputEnabled) return;
1401
1381
  flushPendingWheel();
1402
1382
  const command = event.metaKey || event.ctrlKey;
1403
1383
  if (
@@ -1418,7 +1398,7 @@ function ComputerViewport(props: {
1418
1398
  };
1419
1399
 
1420
1400
  const copy = (event: ClipboardEvent<HTMLTextAreaElement>) => {
1421
- if (!props.clipboardEnabled || !keyboardInputEnabled) return;
1401
+ if (!props.clipboardEnabled || !rawInputEnabled) return;
1422
1402
  event.preventDefault();
1423
1403
  flushPendingWheel();
1424
1404
  flushPendingText();
@@ -1435,7 +1415,7 @@ function ComputerViewport(props: {
1435
1415
  };
1436
1416
 
1437
1417
  const paste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
1438
- if (!props.clipboardEnabled || !keyboardInputEnabled) return;
1418
+ if (!props.clipboardEnabled || !rawInputEnabled) return;
1439
1419
  event.preventDefault();
1440
1420
  flushPendingWheel();
1441
1421
  flushPendingText();
@@ -1452,7 +1432,7 @@ function ComputerViewport(props: {
1452
1432
  };
1453
1433
 
1454
1434
  const input = (value: string) => {
1455
- if (!value || !keyboardInputEnabled) return;
1435
+ if (!value || !rawInputEnabled) return;
1456
1436
  flushPendingWheel();
1457
1437
  flushPendingClick();
1458
1438
  const pending = pendingTextRef.current;
@@ -1482,7 +1462,7 @@ function ComputerViewport(props: {
1482
1462
  className={cn(
1483
1463
  "absolute inset-0 m-auto max-h-full max-w-full touch-none focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
1484
1464
  !showCanvas && "invisible",
1485
- !pointerInputEnabled && "cursor-default",
1465
+ !rawInputEnabled && "cursor-default",
1486
1466
  )}
1487
1467
  onPointerDown={pointerDown}
1488
1468
  onPointerUp={pointerUp}
@@ -1505,7 +1485,7 @@ function ComputerViewport(props: {
1505
1485
  autoCapitalize="off"
1506
1486
  autoCorrect="off"
1507
1487
  spellCheck={false}
1508
- disabled={!keyboardInputEnabled}
1488
+ disabled={!rawInputEnabled}
1509
1489
  />
1510
1490
  {!showCanvas ? (
1511
1491
  <ComputerViewportFallback
@@ -1522,18 +1502,10 @@ function ComputerViewport(props: {
1522
1502
  <LoaderCircleIcon className="size-3 animate-spin" /> Acting
1523
1503
  </div>
1524
1504
  ) : null}
1525
- {showCanvas && (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) ? (
1505
+ {showCanvas && !rawInputEnabled ? (
1526
1506
  <div className="absolute bottom-3 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-full border border-white/10 bg-black/70 py-1 pl-3 pr-1 text-[11px] text-white/80 backdrop-blur">
1527
- <span>
1528
- {!rawInputEnabled && (props.pointerInput || props.keyboardInput)
1529
- ? "Background view · use native controls"
1530
- : !props.pointerInput && !props.keyboardInput
1531
- ? "View only · mouse and keyboard unavailable"
1532
- : props.keyboardInput
1533
- ? "Keyboard only · mouse unavailable"
1534
- : "Mouse only · keyboard unavailable"}
1535
- </span>
1536
- {!rawInputEnabled && props.target && (props.pointerInput || props.keyboardInput) ? (
1507
+ <span>Background view · use native controls</span>
1508
+ {props.target ? (
1537
1509
  <button
1538
1510
  type="button"
1539
1511
  disabled={props.mutating}