@opengeni/react 7.4.0 → 7.5.0-canary.36927100590001
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.
- package/README.md +124 -27
- package/dist/artifacts-document.js +3 -2
- package/dist/artifacts-presentation.js +3 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +516 -1413
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-2E6R2Y4W.js → browser-viewer-Z7S7QEJL.js} +6 -5
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/{chunk-Y3QTZN2F.js → chunk-2VFNCX4E.js} +6 -6
- package/dist/{chunk-UDD6DLLT.js → chunk-3IJJEYW4.js} +15 -3
- package/dist/chunk-3IJJEYW4.js.map +1 -0
- package/dist/chunk-4RVYOY4O.js +379 -0
- package/dist/chunk-4RVYOY4O.js.map +1 -0
- package/dist/chunk-5CVXZA3Y.js +292 -0
- package/dist/chunk-5CVXZA3Y.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-5J4FOW6Q.js} +2 -10
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/{chunk-KVERXCFY.js → chunk-5KYZIQFY.js} +40 -3
- package/dist/chunk-5KYZIQFY.js.map +1 -0
- package/dist/{chunk-KNDIXZN6.js → chunk-7UAMYXJY.js} +25 -18
- package/dist/{chunk-KNDIXZN6.js.map → chunk-7UAMYXJY.js.map} +1 -1
- package/dist/{chunk-SEEVCT36.js → chunk-A4UJB2MM.js} +2 -2
- package/dist/{chunk-X4HIN6TV.js → chunk-AFLPWUQ7.js} +71 -162
- package/dist/chunk-AFLPWUQ7.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/{chunk-XEJEOAOV.js → chunk-B7WMS3CS.js} +51 -40
- package/dist/chunk-B7WMS3CS.js.map +1 -0
- package/dist/{chunk-S5JQ4R22.js → chunk-BDKR6BAR.js} +54 -24
- package/dist/chunk-BDKR6BAR.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-DYWBUX5Q.js} +24 -12
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-YZ3YYMOM.js → chunk-E3G7SAYZ.js} +252 -624
- package/dist/chunk-E3G7SAYZ.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/{chunk-5AZSK6MV.js → chunk-LWNLETQT.js} +5 -5
- package/dist/{chunk-USXVQACU.js → chunk-M7LBYP63.js} +5 -4
- package/dist/{chunk-USXVQACU.js.map → chunk-M7LBYP63.js.map} +1 -1
- package/dist/{chunk-L4OSXQ6H.js → chunk-NBOHNRLJ.js} +1289 -1060
- package/dist/chunk-NBOHNRLJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-3E2AITTZ.js → chunk-WUISZ3WU.js} +32 -28
- package/dist/{chunk-3E2AITTZ.js.map → chunk-WUISZ3WU.js.map} +1 -1
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-DUEBTL3N.js → chunk-Y2PRMYLC.js} +4 -3
- package/dist/{chunk-DUEBTL3N.js.map → chunk-Y2PRMYLC.js.map} +1 -1
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/{chunk-V2ABIVYJ.js → chunk-ZXSEDZP7.js} +70 -6
- package/dist/chunk-ZXSEDZP7.js.map +1 -0
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +46 -0
- package/dist/components/artifacts/index.d.ts +7 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/browser-select-control.d.ts +3 -1
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +20 -3
- package/dist/components/open-geni-links.d.ts +16 -0
- package/dist/components/session-conversation.d.ts +20 -1
- package/dist/components/timeline-pill-geometry.d.ts +8 -0
- package/dist/components/timeline-pill-placement.d.ts +24 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/composer.js +5 -5
- package/dist/{computer-viewer-WMJE6OA3.js → computer-viewer-KJWLH3GI.js} +7 -5
- package/dist/connect.js +7 -6
- package/dist/connect.js.map +1 -1
- package/dist/conversation-timeline.d.ts +1 -1
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +26 -0
- package/dist/desktop.js.map +1 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +1 -1
- package/dist/hooks/use-composer.d.ts +4 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +460 -1386
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +8 -6
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-QB4EUCW5.js → latest-question-IS445HOW.js} +3 -3
- package/dist/lib/format.d.ts +6 -1
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/machines.js +2 -2
- package/dist/{markdown-table-layout-PFUEZ2U4.js → markdown-table-layout-QXSLK4BY.js} +37 -5
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/{queue-surface-implementation-IM2HD3SK.js → queue-surface-implementation-2CAQAIHM.js} +2 -2
- package/dist/realtime.js +3 -3
- package/dist/{sandbox-files-PXPIZAFG.js → sandbox-files-AEJTWGT4.js} +8 -5
- package/dist/sandbox-files-AEJTWGT4.js.map +1 -0
- package/dist/session-ui.d.ts +9 -2
- package/dist/session-ui.js +39 -20
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +4 -4
- package/dist/sites.js +4 -289
- package/dist/sites.js.map +1 -1
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/genie-loading.d.ts +3 -1
- package/dist/timeline/types.d.ts +8 -1
- package/dist/{timeline-annotation-selection-ODLVF4OI.js → timeline-annotation-selection-DMLMM2TX.js} +6 -1
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-pill-geometry-Z23V243M.js +110 -0
- package/dist/timeline-pill-geometry-Z23V243M.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +1073 -0
- package/dist/usage.js.map +1 -0
- package/package.json +19 -3
- package/src/artifacts.ts +37 -0
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +3 -1
- package/src/components/artifacts/editable-artifact-view.tsx +187 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +34 -36
- package/src/components/artifacts/editable-presentation.tsx +21 -16
- package/src/components/artifacts/index.ts +41 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/browser-select-control.tsx +4 -1
- package/src/components/browser-viewer.tsx +81 -22
- package/src/components/code-editor.tsx +13 -16
- package/src/components/computer-viewer.tsx +51 -23
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/markdown-table-layout.ts +49 -5
- package/src/components/markdown.tsx +5 -2
- package/src/components/message-timeline.tsx +557 -516
- package/src/components/open-geni-links.tsx +24 -0
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/session-chrome.tsx +22 -3
- package/src/components/session-conversation.tsx +195 -102
- package/src/components/timeline-annotation-selection.tsx +7 -0
- package/src/components/timeline-pill-geometry.ts +169 -0
- package/src/components/timeline-pill-placement.ts +99 -0
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/conversation-timeline.ts +10 -2
- package/src/desktop.ts +24 -0
- package/src/editor.ts +13 -0
- package/src/hooks/use-available-models.ts +1 -1
- package/src/hooks/use-composer.ts +57 -2
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-usage.ts +100 -0
- package/src/index.ts +22 -0
- package/src/lib/format.ts +32 -6
- package/src/lib/workbench-peers.ts +62 -0
- package/src/session-ui.ts +22 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +5 -3
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/genie-loading.tsx +16 -6
- package/src/timeline/projection.ts +78 -4
- package/src/timeline/types.ts +8 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +50 -0
- package/styles/compiled.css +325 -0
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-KVERXCFY.js.map +0 -1
- package/dist/chunk-L4OSXQ6H.js.map +0 -1
- package/dist/chunk-S5JQ4R22.js.map +0 -1
- package/dist/chunk-UDD6DLLT.js.map +0 -1
- package/dist/chunk-UGMKPUEM.js +0 -299
- package/dist/chunk-UGMKPUEM.js.map +0 -1
- package/dist/chunk-V2ABIVYJ.js.map +0 -1
- package/dist/chunk-X4HIN6TV.js.map +0 -1
- package/dist/chunk-XEJEOAOV.js.map +0 -1
- package/dist/chunk-YZ3YYMOM.js.map +0 -1
- package/dist/markdown-table-layout-PFUEZ2U4.js.map +0 -1
- package/dist/timeline-annotation-selection-ODLVF4OI.js.map +0 -1
- /package/dist/{browser-viewer-2E6R2Y4W.js.map → browser-viewer-Z7S7QEJL.js.map} +0 -0
- /package/dist/{computer-viewer-WMJE6OA3.js.map → chat-interactive-block-UGBTNB3L.js.map} +0 -0
- /package/dist/{chunk-Y3QTZN2F.js.map → chunk-2VFNCX4E.js.map} +0 -0
- /package/dist/{chunk-SEEVCT36.js.map → chunk-A4UJB2MM.js.map} +0 -0
- /package/dist/{chunk-5AZSK6MV.js.map → chunk-LWNLETQT.js.map} +0 -0
- /package/dist/{sandbox-files-PXPIZAFG.js.map → computer-viewer-KJWLH3GI.js.map} +0 -0
- /package/dist/{latest-question-QB4EUCW5.js.map → latest-question-IS445HOW.js.map} +0 -0
- /package/dist/{queue-surface-implementation-IM2HD3SK.js.map → queue-surface-implementation-2CAQAIHM.js.map} +0 -0
|
@@ -208,6 +208,11 @@ 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;
|
|
211
216
|
const [selection, setSelection] = useState<BrowserSelection>(() =>
|
|
212
217
|
initialBrowserSessionId ? { sessionId: initialBrowserSessionId, pinned: true } : null,
|
|
213
218
|
);
|
|
@@ -769,7 +774,8 @@ export function BrowserViewer({
|
|
|
769
774
|
);
|
|
770
775
|
}
|
|
771
776
|
if (liveSessions.length === 0) {
|
|
772
|
-
if (renderEmpty
|
|
777
|
+
if (renderEmpty && !unavailableBrowser)
|
|
778
|
+
return <>{renderEmpty(() => createBrowser(), creating)}</>;
|
|
773
779
|
return (
|
|
774
780
|
<div
|
|
775
781
|
className={cn("flex h-full min-h-0 items-center justify-center bg-og-bg p-6", className)}
|
|
@@ -778,16 +784,22 @@ export function BrowserViewer({
|
|
|
778
784
|
<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">
|
|
779
785
|
<Globe2Icon className="size-4.5" />
|
|
780
786
|
</span>
|
|
781
|
-
|
|
782
|
-
{
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
+
)}
|
|
791
803
|
<BrowserLaunchMenu
|
|
792
804
|
attachedBridges={onlineAttachedBridges}
|
|
793
805
|
attachedDevices={attached.devices}
|
|
@@ -855,6 +867,7 @@ export function BrowserViewer({
|
|
|
855
867
|
/>
|
|
856
868
|
{!selectedRegistrySession ? (
|
|
857
869
|
<BrowserUnselectedPanel
|
|
870
|
+
unavailableBrowser={unavailableBrowser}
|
|
858
871
|
peerCount={liveSessions.length}
|
|
859
872
|
creating={creating}
|
|
860
873
|
onCreate={() => createBrowser()}
|
|
@@ -1186,6 +1199,7 @@ function BrowserToolbar(props: {
|
|
|
1186
1199
|
}
|
|
1187
1200
|
|
|
1188
1201
|
function BrowserUnselectedPanel(props: {
|
|
1202
|
+
unavailableBrowser: BrowserSession | undefined;
|
|
1189
1203
|
peerCount: number;
|
|
1190
1204
|
creating: boolean;
|
|
1191
1205
|
onCreate: () => void;
|
|
@@ -1196,7 +1210,11 @@ function BrowserUnselectedPanel(props: {
|
|
|
1196
1210
|
<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">
|
|
1197
1211
|
<Globe2Icon className="size-4.5" />
|
|
1198
1212
|
</span>
|
|
1199
|
-
|
|
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
|
+
)}
|
|
1200
1218
|
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
1201
1219
|
{props.peerCount === 1
|
|
1202
1220
|
? "One workspace browser is available from the browser menu."
|
|
@@ -1216,6 +1234,25 @@ function BrowserUnselectedPanel(props: {
|
|
|
1216
1234
|
);
|
|
1217
1235
|
}
|
|
1218
1236
|
|
|
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
|
+
|
|
1219
1256
|
function BrowserSessionGroup(props: {
|
|
1220
1257
|
label: string;
|
|
1221
1258
|
sessions: BrowserSession[];
|
|
@@ -1719,7 +1756,8 @@ function BrowserProfileMenu(props: {
|
|
|
1719
1756
|
) : null}
|
|
1720
1757
|
{canSave ? (
|
|
1721
1758
|
<p className="mt-2 text-[10px] leading-4 text-og-fg-subtle">
|
|
1722
|
-
Saving
|
|
1759
|
+
Saving restarts this browser. Save unfinished work on its pages first; unsaved form
|
|
1760
|
+
entries may be lost. Other open browsers are unchanged.
|
|
1723
1761
|
</p>
|
|
1724
1762
|
) : null}
|
|
1725
1763
|
</div>
|
|
@@ -1910,6 +1948,7 @@ function BrowserViewport(props: {
|
|
|
1910
1948
|
controllerGeneration: string;
|
|
1911
1949
|
targetId: string;
|
|
1912
1950
|
fencedInputBatches?: true | undefined;
|
|
1951
|
+
focusedInputObservations?: true | undefined;
|
|
1913
1952
|
expiresAt: string;
|
|
1914
1953
|
} | null;
|
|
1915
1954
|
activityLabel?: string | undefined;
|
|
@@ -1962,6 +2001,7 @@ function BrowserViewport(props: {
|
|
|
1962
2001
|
} | null>(null);
|
|
1963
2002
|
const actionRef = useRef(props.onAction);
|
|
1964
2003
|
const readClipboardRef = useRef(props.onReadClipboard);
|
|
2004
|
+
const observeInputRef = useRef(props.onObserveForInput);
|
|
1965
2005
|
const errorRef = useRef(props.onError);
|
|
1966
2006
|
const actionTailRef = useRef<Promise<void>>(Promise.resolve());
|
|
1967
2007
|
const actionQueueEpochRef = useRef(0);
|
|
@@ -1978,6 +2018,7 @@ function BrowserViewport(props: {
|
|
|
1978
2018
|
const mountedRef = useRef(true);
|
|
1979
2019
|
actionRef.current = props.onAction;
|
|
1980
2020
|
readClipboardRef.current = props.onReadClipboard;
|
|
2021
|
+
observeInputRef.current = props.onObserveForInput;
|
|
1981
2022
|
errorRef.current = props.onError;
|
|
1982
2023
|
|
|
1983
2024
|
const clearBufferedInput = useCallback(() => {
|
|
@@ -2154,8 +2195,23 @@ function BrowserViewport(props: {
|
|
|
2154
2195
|
// frame from that render even when its input fence contains no bytes.
|
|
2155
2196
|
const receipt = await actionRef.current(dispatchedAction, frame);
|
|
2156
2197
|
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2157
|
-
|
|
2158
|
-
|
|
2198
|
+
// Alt+Down explicitly opens a native dropdown from the keyboard.
|
|
2199
|
+
// Existing controllers return focused-input metadata for clicks only,
|
|
2200
|
+
// so use the ordinary authorized read for this explicit gesture.
|
|
2201
|
+
const keyboardSelect =
|
|
2202
|
+
dispatchedAction.type === "press" && dispatchedAction.key === "Alt+ArrowDown";
|
|
2203
|
+
if ((selectAnchor || keyboardSelect) && inputSequence === inputSequenceRef.current) {
|
|
2204
|
+
const observation = keyboardSelect
|
|
2205
|
+
? await observeInputRef.current()
|
|
2206
|
+
: receipt.observation;
|
|
2207
|
+
if (
|
|
2208
|
+
mountedRef.current &&
|
|
2209
|
+
epoch === actionQueueEpochRef.current &&
|
|
2210
|
+
inputSequence === inputSequenceRef.current &&
|
|
2211
|
+
observation
|
|
2212
|
+
) {
|
|
2213
|
+
setSelectPopup({ observation, anchor: selectAnchor ?? null });
|
|
2214
|
+
}
|
|
2159
2215
|
}
|
|
2160
2216
|
await after?.(receipt);
|
|
2161
2217
|
})
|
|
@@ -2448,6 +2504,7 @@ function BrowserViewport(props: {
|
|
|
2448
2504
|
onPaste={paste}
|
|
2449
2505
|
className="pointer-events-none absolute left-1/2 top-1/2 size-px resize-none overflow-hidden opacity-0"
|
|
2450
2506
|
aria-label="Browser keyboard input"
|
|
2507
|
+
aria-keyshortcuts="Alt+ArrowDown"
|
|
2451
2508
|
autoCapitalize="off"
|
|
2452
2509
|
autoCorrect="off"
|
|
2453
2510
|
spellCheck={false}
|
|
@@ -2467,6 +2524,7 @@ function BrowserViewport(props: {
|
|
|
2467
2524
|
) : null}
|
|
2468
2525
|
{showCanvas ? (
|
|
2469
2526
|
<BrowserSelectControl
|
|
2527
|
+
manualFallback={props.inputBatchAttachment?.focusedInputObservations !== true}
|
|
2470
2528
|
activation={selectPopup}
|
|
2471
2529
|
onDismiss={() => inputRef.current?.focus({ preventScroll: true })}
|
|
2472
2530
|
observe={async () => {
|
|
@@ -2740,13 +2798,14 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2740
2798
|
id="browser-page-title"
|
|
2741
2799
|
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2742
2800
|
>
|
|
2743
|
-
|
|
2801
|
+
Tab diagnostics
|
|
2744
2802
|
</h3>
|
|
2803
|
+
<p className="mt-1 text-og-xs text-og-fg-subtle">Includes earlier pages in this tab.</p>
|
|
2745
2804
|
<dl className="mt-2 grid grid-cols-2 gap-2">
|
|
2746
|
-
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount
|
|
2747
|
-
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount
|
|
2748
|
-
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount
|
|
2749
|
-
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount
|
|
2805
|
+
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount} />
|
|
2806
|
+
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount} />
|
|
2807
|
+
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount} />
|
|
2808
|
+
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount} />
|
|
2750
2809
|
</dl>
|
|
2751
2810
|
{session?.failureCode ? (
|
|
2752
2811
|
<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">
|
|
@@ -3044,7 +3103,7 @@ function DiagnosticFact(props: { label: string; value: string }) {
|
|
|
3044
3103
|
);
|
|
3045
3104
|
}
|
|
3046
3105
|
|
|
3047
|
-
function DiagnosticCount(props: { label: string; value: number }) {
|
|
3106
|
+
function DiagnosticCount(props: { label: string; value: number | undefined }) {
|
|
3048
3107
|
return (
|
|
3049
3108
|
<div className="rounded-og-sm border border-og-border bg-og-bg px-2.5 py-2">
|
|
3050
3109
|
<dt className="text-[10px] text-og-fg-subtle">{props.label}</dt>
|
|
@@ -3054,7 +3113,7 @@ function DiagnosticCount(props: { label: string; value: number }) {
|
|
|
3054
3113
|
props.value ? "text-og-fg" : "text-og-fg-muted",
|
|
3055
3114
|
)}
|
|
3056
3115
|
>
|
|
3057
|
-
{props.value}
|
|
3116
|
+
{props.value ?? "Unavailable"}
|
|
3058
3117
|
</dd>
|
|
3059
3118
|
</div>
|
|
3060
3119
|
);
|
|
@@ -8,8 +8,10 @@ import {
|
|
|
8
8
|
useMemo,
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
|
+
useSyncExternalStore,
|
|
11
12
|
} from "react";
|
|
12
13
|
import { cn } from "../lib/cn";
|
|
14
|
+
import { codeEditorPeers, type CodeEditorLanguage } from "../lib/workbench-peers";
|
|
13
15
|
|
|
14
16
|
// --- Lazy CodeMirror surface ----------------------------------------------
|
|
15
17
|
//
|
|
@@ -42,19 +44,8 @@ type EditorBundle = {
|
|
|
42
44
|
languageExtension: unknown | null;
|
|
43
45
|
};
|
|
44
46
|
|
|
45
|
-
/** Language grammar loaders, keyed by the extension class we infer from the path. */
|
|
46
|
-
const LANGUAGE_LOADERS: Record<string, () => Promise<unknown>> = {
|
|
47
|
-
javascript: async () =>
|
|
48
|
-
(await import("@codemirror/lang-javascript")).javascript({ jsx: true, typescript: true }),
|
|
49
|
-
json: async () => (await import("@codemirror/lang-json")).json(),
|
|
50
|
-
python: async () => (await import("@codemirror/lang-python")).python(),
|
|
51
|
-
markdown: async () => (await import("@codemirror/lang-markdown")).markdown(),
|
|
52
|
-
css: async () => (await import("@codemirror/lang-css")).css(),
|
|
53
|
-
html: async () => (await import("@codemirror/lang-html")).html(),
|
|
54
|
-
};
|
|
55
|
-
|
|
56
47
|
/** Map a filename to a grammar key (or null for plain text — still fully editable). */
|
|
57
|
-
export function languageForPath(path: string):
|
|
48
|
+
export function languageForPath(path: string): string | null {
|
|
58
49
|
const name = path.split("/").pop() ?? path;
|
|
59
50
|
const ext = name.includes(".") ? (name.split(".").pop() ?? "").toLowerCase() : "";
|
|
60
51
|
switch (ext) {
|
|
@@ -182,6 +173,11 @@ export function CodeEditor({
|
|
|
182
173
|
}: CodeEditorProps) {
|
|
183
174
|
const [failed, setFailed] = useState(false);
|
|
184
175
|
const [bundle, setBundle] = useState<EditorBundle | null>(null);
|
|
176
|
+
const peerRevision = useSyncExternalStore(
|
|
177
|
+
codeEditorPeers.subscribe,
|
|
178
|
+
codeEditorPeers.revision,
|
|
179
|
+
codeEditorPeers.revision,
|
|
180
|
+
);
|
|
185
181
|
|
|
186
182
|
// The live buffer and its compare-and-swap baseline are one state value so a
|
|
187
183
|
// remote refresh cannot update one without the other.
|
|
@@ -233,12 +229,13 @@ export function CodeEditor({
|
|
|
233
229
|
|
|
234
230
|
// Resolve the lazy bundle (editor + keymap/Prec helpers + language grammar)
|
|
235
231
|
// once, re-resolving the grammar when the file's language class changes.
|
|
236
|
-
const langKey = useMemo(() => languageForPath(path), [path]);
|
|
232
|
+
const langKey = useMemo(() => languageForPath(path) as CodeEditorLanguage | null, [path]);
|
|
237
233
|
useEffect(() => {
|
|
238
234
|
let cancelled = false;
|
|
239
235
|
void (async () => {
|
|
240
236
|
try {
|
|
241
|
-
const
|
|
237
|
+
const peers = await codeEditorPeers.load();
|
|
238
|
+
const cmMod = peers.module as {
|
|
242
239
|
default: ReactCodeMirrorComponent;
|
|
243
240
|
keymap: { of: (binds: unknown[]) => unknown };
|
|
244
241
|
Prec: { highest: (ext: unknown) => unknown };
|
|
@@ -248,7 +245,7 @@ export function CodeEditor({
|
|
|
248
245
|
let languageExtension: unknown | null = null;
|
|
249
246
|
if (langKey) {
|
|
250
247
|
try {
|
|
251
|
-
languageExtension = (await
|
|
248
|
+
languageExtension = (await peers.languages?.[langKey]?.()) ?? null;
|
|
252
249
|
} catch {
|
|
253
250
|
languageExtension = null;
|
|
254
251
|
}
|
|
@@ -283,7 +280,7 @@ export function CodeEditor({
|
|
|
283
280
|
return () => {
|
|
284
281
|
cancelled = true;
|
|
285
282
|
};
|
|
286
|
-
}, [langKey]);
|
|
283
|
+
}, [langKey, peerRevision]);
|
|
287
284
|
|
|
288
285
|
// Keep the freshest save closure reachable from the (stable) keymap binding.
|
|
289
286
|
const saveRef = useRef<() => void>(() => {});
|
|
@@ -289,13 +289,19 @@ 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;
|
|
292
298
|
const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
|
|
293
299
|
if (!rfbStream || !frames.attachment) return null;
|
|
294
300
|
const bounds = computer.selectedTarget?.bounds;
|
|
295
301
|
return {
|
|
296
302
|
transport: "vnc-ws",
|
|
297
303
|
client: "novnc",
|
|
298
|
-
mode: "interactive",
|
|
304
|
+
mode: rfbInputEnabled ? "interactive" : "read-only",
|
|
299
305
|
url: rfbStream.url,
|
|
300
306
|
token: null,
|
|
301
307
|
expiresAt: frames.attachment.expiresAt,
|
|
@@ -307,8 +313,7 @@ export function ComputerViewer({
|
|
|
307
313
|
sharedSessionIds: [],
|
|
308
314
|
reason: null,
|
|
309
315
|
};
|
|
310
|
-
}, [computer.selectedTarget?.bounds, frames.attachment, rfbStream]);
|
|
311
|
-
const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
|
|
316
|
+
}, [computer.selectedTarget?.bounds, frames.attachment, rfbInputEnabled, rfbStream]);
|
|
312
317
|
const currentIds = useMemo(() => new Set(relevant.map((session) => session.id)), [relevant]);
|
|
313
318
|
|
|
314
319
|
useEffect(() => {
|
|
@@ -452,7 +457,8 @@ export function ComputerViewer({
|
|
|
452
457
|
|
|
453
458
|
const copyFromRfb = useCallback(
|
|
454
459
|
(event: ClipboardEvent<HTMLDivElement>) => {
|
|
455
|
-
if (!rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
460
|
+
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
461
|
+
return;
|
|
456
462
|
event.preventDefault();
|
|
457
463
|
event.stopPropagation();
|
|
458
464
|
void perform({ type: "clipboard", operation: "copy" }, null)
|
|
@@ -464,12 +470,13 @@ export function ComputerViewer({
|
|
|
464
470
|
})
|
|
465
471
|
.catch((cause) => notifyError(cause, "Could not copy from the desktop."));
|
|
466
472
|
},
|
|
467
|
-
[computer, notifyError, perform, rfbStream],
|
|
473
|
+
[computer, notifyError, perform, rfbInputEnabled, rfbStream],
|
|
468
474
|
);
|
|
469
475
|
|
|
470
476
|
const pasteIntoRfb = useCallback(
|
|
471
477
|
(text: string): boolean => {
|
|
472
|
-
if (!rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
478
|
+
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
479
|
+
return false;
|
|
473
480
|
// Keep paste on the canonical ComputerSession action path. RFB
|
|
474
481
|
// ClientCutText synchronization varies by server and can acknowledge a
|
|
475
482
|
// local paste without ever updating the remote graphical seat. These two
|
|
@@ -484,7 +491,7 @@ export function ComputerViewer({
|
|
|
484
491
|
})().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
|
|
485
492
|
return true;
|
|
486
493
|
},
|
|
487
|
-
[computer, notifyError, perform, rfbStream],
|
|
494
|
+
[computer, notifyError, perform, rfbInputEnabled, rfbStream],
|
|
488
495
|
);
|
|
489
496
|
|
|
490
497
|
const hideGenericCreate = suppressGenericCreate && liveRelevant.length === 0;
|
|
@@ -623,13 +630,18 @@ export function ComputerViewer({
|
|
|
623
630
|
<div className="relative min-h-0 flex-1 bg-black" onCopyCapture={copyFromRfb}>
|
|
624
631
|
<DesktopViewer
|
|
625
632
|
capability={rfbCapability}
|
|
626
|
-
interactive
|
|
633
|
+
interactive={rfbInputEnabled}
|
|
627
634
|
showControlToggle={false}
|
|
628
635
|
webSocketProtocols={rfbStream.protocols}
|
|
629
636
|
onPasteText={pasteIntoRfb}
|
|
630
637
|
targetPlatform={computer.session?.platform ?? null}
|
|
631
638
|
className="h-full"
|
|
632
639
|
/>
|
|
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}
|
|
633
645
|
</div>
|
|
634
646
|
) : (
|
|
635
647
|
<ComputerViewport
|
|
@@ -648,6 +660,8 @@ export function ComputerViewer({
|
|
|
648
660
|
mutating={computer.mutating}
|
|
649
661
|
backgroundActions={computer.session?.capabilities?.backgroundActions === true}
|
|
650
662
|
clipboardEnabled={computer.session?.capabilities?.clipboard === true}
|
|
663
|
+
pointerInput={computer.session?.capabilities?.pointerInput === true}
|
|
664
|
+
keyboardInput={computer.session?.capabilities?.keyboardInput === true}
|
|
651
665
|
onAction={perform}
|
|
652
666
|
onReadClipboard={computer.readClipboard}
|
|
653
667
|
onReconnect={
|
|
@@ -1011,6 +1025,8 @@ function ComputerViewport(props: {
|
|
|
1011
1025
|
mutating: boolean;
|
|
1012
1026
|
backgroundActions: boolean;
|
|
1013
1027
|
clipboardEnabled: boolean;
|
|
1028
|
+
pointerInput: boolean;
|
|
1029
|
+
keyboardInput: boolean;
|
|
1014
1030
|
onAction: (action: ComputerAction, frame: ComputerFrame | null) => Promise<void>;
|
|
1015
1031
|
onReadClipboard: () => Promise<ComputerClipboard>;
|
|
1016
1032
|
onReconnect: () => void;
|
|
@@ -1070,6 +1086,9 @@ function ComputerViewport(props: {
|
|
|
1070
1086
|
if (inputRef.current) inputRef.current.value = "";
|
|
1071
1087
|
}, []);
|
|
1072
1088
|
|
|
1089
|
+
const pointerInputEnabled = rawInputEnabled && props.pointerInput;
|
|
1090
|
+
const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
|
|
1091
|
+
|
|
1073
1092
|
const paintQueuedFrames = useCallback(() => {
|
|
1074
1093
|
if (decodingFrameRef.current) return;
|
|
1075
1094
|
decodingFrameRef.current = true;
|
|
@@ -1159,11 +1178,11 @@ function ComputerViewport(props: {
|
|
|
1159
1178
|
}, [clearBufferedInput, paintQueuedFrames, props.frame]);
|
|
1160
1179
|
|
|
1161
1180
|
useLayoutEffect(() => {
|
|
1162
|
-
if (!rawInputEnabled) {
|
|
1181
|
+
if (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) {
|
|
1163
1182
|
actionQueueEpochRef.current += 1;
|
|
1164
1183
|
clearBufferedInput();
|
|
1165
1184
|
}
|
|
1166
|
-
}, [clearBufferedInput, rawInputEnabled]);
|
|
1185
|
+
}, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
|
|
1167
1186
|
|
|
1168
1187
|
const enqueue = useCallback(
|
|
1169
1188
|
(
|
|
@@ -1248,6 +1267,8 @@ function ComputerViewport(props: {
|
|
|
1248
1267
|
if (!frame || props.mutating || !rawInputEnabled || event.button !== 0) return;
|
|
1249
1268
|
// Preserve the keyboard sink's focus through the canvas pointer default action.
|
|
1250
1269
|
event.preventDefault();
|
|
1270
|
+
if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
|
|
1271
|
+
if (!pointerInputEnabled) return;
|
|
1251
1272
|
flushPendingWheel();
|
|
1252
1273
|
flushPendingText();
|
|
1253
1274
|
pointerStartRef.current = {
|
|
@@ -1257,11 +1278,10 @@ function ComputerViewport(props: {
|
|
|
1257
1278
|
frame,
|
|
1258
1279
|
};
|
|
1259
1280
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1260
|
-
inputRef.current?.focus({ preventScroll: true });
|
|
1261
1281
|
};
|
|
1262
1282
|
|
|
1263
1283
|
const pointerUp = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1264
|
-
if (!
|
|
1284
|
+
if (!pointerInputEnabled) return;
|
|
1265
1285
|
const start = pointerStartRef.current;
|
|
1266
1286
|
pointerStartRef.current = null;
|
|
1267
1287
|
if (!start || start.pointerId !== event.pointerId) return;
|
|
@@ -1326,7 +1346,7 @@ function ComputerViewport(props: {
|
|
|
1326
1346
|
|
|
1327
1347
|
const contextMenu = (event: MouseEvent<HTMLCanvasElement>) => {
|
|
1328
1348
|
event.preventDefault();
|
|
1329
|
-
if (!
|
|
1349
|
+
if (!pointerInputEnabled) return;
|
|
1330
1350
|
const frame = paintedFrameRef.current;
|
|
1331
1351
|
if (!frame) return;
|
|
1332
1352
|
flushPendingWheel();
|
|
@@ -1349,7 +1369,7 @@ function ComputerViewport(props: {
|
|
|
1349
1369
|
};
|
|
1350
1370
|
|
|
1351
1371
|
const wheel = (event: WheelEvent<HTMLCanvasElement>) => {
|
|
1352
|
-
if (!
|
|
1372
|
+
if (!pointerInputEnabled) return;
|
|
1353
1373
|
const frame = paintedFrameRef.current;
|
|
1354
1374
|
if (!frame) return;
|
|
1355
1375
|
const at = point(frame, event.clientX, event.clientY);
|
|
@@ -1377,7 +1397,7 @@ function ComputerViewport(props: {
|
|
|
1377
1397
|
};
|
|
1378
1398
|
|
|
1379
1399
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
1380
|
-
if (!
|
|
1400
|
+
if (!keyboardInputEnabled) return;
|
|
1381
1401
|
flushPendingWheel();
|
|
1382
1402
|
const command = event.metaKey || event.ctrlKey;
|
|
1383
1403
|
if (
|
|
@@ -1398,7 +1418,7 @@ function ComputerViewport(props: {
|
|
|
1398
1418
|
};
|
|
1399
1419
|
|
|
1400
1420
|
const copy = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1401
|
-
if (!props.clipboardEnabled || !
|
|
1421
|
+
if (!props.clipboardEnabled || !keyboardInputEnabled) return;
|
|
1402
1422
|
event.preventDefault();
|
|
1403
1423
|
flushPendingWheel();
|
|
1404
1424
|
flushPendingText();
|
|
@@ -1415,7 +1435,7 @@ function ComputerViewport(props: {
|
|
|
1415
1435
|
};
|
|
1416
1436
|
|
|
1417
1437
|
const paste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1418
|
-
if (!props.clipboardEnabled || !
|
|
1438
|
+
if (!props.clipboardEnabled || !keyboardInputEnabled) return;
|
|
1419
1439
|
event.preventDefault();
|
|
1420
1440
|
flushPendingWheel();
|
|
1421
1441
|
flushPendingText();
|
|
@@ -1432,7 +1452,7 @@ function ComputerViewport(props: {
|
|
|
1432
1452
|
};
|
|
1433
1453
|
|
|
1434
1454
|
const input = (value: string) => {
|
|
1435
|
-
if (!value || !
|
|
1455
|
+
if (!value || !keyboardInputEnabled) return;
|
|
1436
1456
|
flushPendingWheel();
|
|
1437
1457
|
flushPendingClick();
|
|
1438
1458
|
const pending = pendingTextRef.current;
|
|
@@ -1462,7 +1482,7 @@ function ComputerViewport(props: {
|
|
|
1462
1482
|
className={cn(
|
|
1463
1483
|
"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",
|
|
1464
1484
|
!showCanvas && "invisible",
|
|
1465
|
-
!
|
|
1485
|
+
!pointerInputEnabled && "cursor-default",
|
|
1466
1486
|
)}
|
|
1467
1487
|
onPointerDown={pointerDown}
|
|
1468
1488
|
onPointerUp={pointerUp}
|
|
@@ -1485,7 +1505,7 @@ function ComputerViewport(props: {
|
|
|
1485
1505
|
autoCapitalize="off"
|
|
1486
1506
|
autoCorrect="off"
|
|
1487
1507
|
spellCheck={false}
|
|
1488
|
-
disabled={!
|
|
1508
|
+
disabled={!keyboardInputEnabled}
|
|
1489
1509
|
/>
|
|
1490
1510
|
{!showCanvas ? (
|
|
1491
1511
|
<ComputerViewportFallback
|
|
@@ -1502,10 +1522,18 @@ function ComputerViewport(props: {
|
|
|
1502
1522
|
<LoaderCircleIcon className="size-3 animate-spin" /> Acting
|
|
1503
1523
|
</div>
|
|
1504
1524
|
) : null}
|
|
1505
|
-
{showCanvas && !rawInputEnabled ? (
|
|
1525
|
+
{showCanvas && (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) ? (
|
|
1506
1526
|
<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">
|
|
1507
|
-
<span>
|
|
1508
|
-
|
|
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) ? (
|
|
1509
1537
|
<button
|
|
1510
1538
|
type="button"
|
|
1511
1539
|
disabled={props.mutating}
|
|
@@ -30,7 +30,7 @@ export type DesktopViewerProps = {
|
|
|
30
30
|
/** Render the built-in Watching ⇄ Take control toggle (default true). */
|
|
31
31
|
showControlToggle?: boolean | undefined;
|
|
32
32
|
scaleViewport?: boolean | undefined;
|
|
33
|
-
/** Custom RFB factory (
|
|
33
|
+
/** Custom RFB factory; otherwise enableDesktopViewer() supplies lazy noVNC. */
|
|
34
34
|
rfbFactory?: DesktopRfbFactory | undefined;
|
|
35
35
|
/** Authenticated controller protocols for proxied RFB transports. */
|
|
36
36
|
webSocketProtocols?: string[] | undefined;
|
|
@@ -16,6 +16,37 @@ export function markdownTableWidth({
|
|
|
16
16
|
return Math.max(columnWidth, Math.min(preferredWidth, available));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function measurementCopy(source: Node, document: Document): Node {
|
|
20
|
+
if (source.nodeType === Node.ELEMENT_NODE) {
|
|
21
|
+
const element = source as Element;
|
|
22
|
+
// Inert prevents focus, not custom-element construction, media loading or
|
|
23
|
+
// embedded browsing contexts. Do not clone those host-rendered surfaces.
|
|
24
|
+
if (
|
|
25
|
+
element.localName.includes("-") ||
|
|
26
|
+
["iframe", "object", "embed", "video", "audio", "canvas", "script"].includes(
|
|
27
|
+
element.localName,
|
|
28
|
+
)
|
|
29
|
+
) {
|
|
30
|
+
const bounds = element.getBoundingClientRect();
|
|
31
|
+
const placeholder = document.createElement("span");
|
|
32
|
+
placeholder.style.display = "inline-block";
|
|
33
|
+
placeholder.style.width = `${bounds.width}px`;
|
|
34
|
+
placeholder.style.height = `${bounds.height}px`;
|
|
35
|
+
return placeholder;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const copy = document.importNode(source, false);
|
|
39
|
+
if (copy.nodeType === Node.ELEMENT_NODE) {
|
|
40
|
+
const element = copy as Element;
|
|
41
|
+
element.removeAttribute("id");
|
|
42
|
+
for (const attribute of [...element.attributes]) {
|
|
43
|
+
if (attribute.name.startsWith("on")) element.removeAttribute(attribute.name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const child of source.childNodes) copy.appendChild(measurementCopy(child, document));
|
|
47
|
+
return copy;
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
/** Loaded only for assistant tables. Keep prose and nested scroll owners intact. */
|
|
20
51
|
export function observeMarkdownTableLayout(wrapper: HTMLDivElement, table: HTMLTableElement) {
|
|
21
52
|
const body = wrapper.parentElement;
|
|
@@ -24,6 +55,8 @@ export function observeMarkdownTableLayout(wrapper: HTMLDivElement, table: HTMLT
|
|
|
24
55
|
?.closest<HTMLElement>("[data-og-timeline-scroller]");
|
|
25
56
|
if (!body?.classList.contains("og-markdown-body") || !scroller) return;
|
|
26
57
|
|
|
58
|
+
// Build the static copy without a browsing context before inserting its probe.
|
|
59
|
+
const measurementDocument = table.ownerDocument.implementation.createHTMLDocument("");
|
|
27
60
|
let width: number | null = null;
|
|
28
61
|
const reset = () => {
|
|
29
62
|
width = null;
|
|
@@ -50,14 +83,25 @@ export function observeMarkdownTableLayout(wrapper: HTMLDivElement, table: HTMLT
|
|
|
50
83
|
const right =
|
|
51
84
|
panel.left + scroller.clientLeft + scroller.clientWidth - parseFloat(panelStyle.paddingRight);
|
|
52
85
|
|
|
53
|
-
//
|
|
54
|
-
|
|
86
|
+
// Even a temporary max-content width on the live table changes its height
|
|
87
|
+
// during forced layout. Browser scroll anchoring can retain that displacement
|
|
88
|
+
// after the width is restored. Measure a copy without touching live geometry.
|
|
89
|
+
const probe = table.ownerDocument.createElement("div");
|
|
90
|
+
probe.setAttribute("aria-hidden", "true");
|
|
91
|
+
probe.inert = true;
|
|
92
|
+
// A zero-sized, contained box cannot extend the scroller's overflow area,
|
|
93
|
+
// including inside transformed ancestors. Keep the same inherited styling.
|
|
94
|
+
probe.style.cssText =
|
|
95
|
+
"position:absolute;width:0;height:0;overflow:hidden;contain:layout size paint;visibility:hidden;pointer-events:none";
|
|
96
|
+
const copy = measurementCopy(table, measurementDocument) as HTMLTableElement;
|
|
97
|
+
copy.style.width = "max-content";
|
|
98
|
+
probe.append(copy);
|
|
55
99
|
let preferred: number;
|
|
56
100
|
try {
|
|
57
|
-
table.
|
|
58
|
-
preferred =
|
|
101
|
+
table.parentElement!.append(probe);
|
|
102
|
+
preferred = copy.getBoundingClientRect().width;
|
|
59
103
|
} finally {
|
|
60
|
-
|
|
104
|
+
probe.remove();
|
|
61
105
|
}
|
|
62
106
|
const next = markdownTableWidth({
|
|
63
107
|
columnLeft: column.left,
|
|
@@ -391,12 +391,15 @@ function ActionMarkdownLink({
|
|
|
391
391
|
type="button"
|
|
392
392
|
className={cn(
|
|
393
393
|
MARKDOWN_LINK_CLASS,
|
|
394
|
-
"inline-flex min-h-7 cursor-pointer items-center disabled:cursor-wait disabled:opacity-70 pointer-coarse:min-h-11",
|
|
394
|
+
"inline-flex min-h-7 cursor-pointer items-center aria-disabled:cursor-wait aria-disabled:opacity-70 pointer-coarse:min-h-11",
|
|
395
395
|
)}
|
|
396
|
-
disabled
|
|
396
|
+
// Not `disabled`: disabling the focused control drops focus to the page,
|
|
397
|
+
// so a host viewer could not return focus to the link that opened it.
|
|
398
|
+
aria-disabled={state === "loading" || undefined}
|
|
397
399
|
aria-busy={state === "loading"}
|
|
398
400
|
title={state === "error" ? "Couldn't open this file. Select to retry." : title}
|
|
399
401
|
onClick={() => {
|
|
402
|
+
if (state === "loading") return;
|
|
400
403
|
setState("loading");
|
|
401
404
|
void Promise.resolve()
|
|
402
405
|
.then(onOpen)
|