@opengeni/react 0.48.0 → 0.57.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.
- package/README.md +138 -2
- package/dist/artifacts-document.d.ts +3 -0
- package/dist/artifacts-document.js +17 -0
- package/dist/artifacts-document.js.map +1 -0
- package/dist/artifacts-presentation.d.ts +3 -0
- package/dist/artifacts-presentation.js +17 -0
- package/dist/artifacts-presentation.js.map +1 -0
- package/dist/artifacts-spreadsheet.d.ts +3 -0
- package/dist/artifacts-spreadsheet.js +17 -0
- package/dist/artifacts-spreadsheet.js.map +1 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +199 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
- package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
- package/dist/chunk-6KLX2RH5.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
- package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
- package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
- package/dist/chunk-C5ZPSSS5.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
- package/dist/chunk-HA46CYQK.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
- package/dist/chunk-IGC6A2ZI.js.map +1 -0
- package/dist/chunk-JVYQRRC4.js +53 -0
- package/dist/chunk-JVYQRRC4.js.map +1 -0
- package/dist/chunk-KCGXPP5V.js +157 -0
- package/dist/chunk-KCGXPP5V.js.map +1 -0
- package/dist/chunk-LLOLUHVG.js +5937 -0
- package/dist/chunk-LLOLUHVG.js.map +1 -0
- package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
- package/dist/chunk-MKPIAGVL.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/chunk-R3AZOAB5.js +933 -0
- package/dist/chunk-R3AZOAB5.js.map +1 -0
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
- package/dist/chunk-XBZWOM2K.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
- package/dist/chunk-Y7OEDUJ5.js.map +1 -0
- package/dist/client.d.ts +10 -2
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +7 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/components/approval-surface.d.ts +2 -2
- package/dist/components/artifacts/artifact-surface.d.ts +18 -0
- package/dist/components/artifacts/document-editor.d.ts +172 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
- package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
- package/dist/components/artifacts/editable-document.d.ts +24 -0
- package/dist/components/artifacts/editable-presentation.d.ts +24 -0
- package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
- package/dist/components/artifacts/index.d.ts +9 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
- package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
- package/dist/components/browser-input.d.ts +7 -0
- package/dist/components/browser-viewer.d.ts +30 -0
- package/dist/components/chat-composer.d.ts +8 -8
- package/dist/components/code-editor.d.ts +25 -3
- package/dist/components/command-palette.d.ts +2 -2
- package/dist/components/composer-transcription-control.d.ts +2 -2
- package/dist/components/composer.d.ts +17 -17
- package/dist/components/computer-viewer.d.ts +24 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +50 -22
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +1 -1
- package/dist/components/enrollment-device-flow.d.ts +1 -1
- package/dist/components/file-browser.d.ts +2 -2
- package/dist/components/fleet-tile.d.ts +1 -1
- package/dist/components/generated-video-player.d.ts +10 -0
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +2 -2
- package/dist/components/interaction-intervention-banner.d.ts +11 -0
- package/dist/components/interactive-workbench-viewers.d.ts +2 -0
- package/dist/components/machine-card.d.ts +2 -2
- package/dist/components/machine-dock-bar.d.ts +3 -3
- package/dist/components/machine-health-pill.d.ts +2 -2
- package/dist/components/machine-input-display.d.ts +1 -1
- package/dist/components/machine-metrics.d.ts +2 -2
- package/dist/components/machine-status-pill.d.ts +4 -4
- package/dist/components/machines/health.d.ts +1 -1
- package/dist/components/machines/machine-detail.d.ts +3 -3
- package/dist/components/machines/metric-history-chart.d.ts +1 -1
- package/dist/components/machines/metric-sparkline.d.ts +2 -2
- package/dist/components/machines/series.d.ts +2 -2
- package/dist/components/machines-dashboard.d.ts +2 -2
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +12 -5
- package/dist/components/model-picker.d.ts +2 -2
- package/dist/components/model-policy-picker.d.ts +7 -7
- package/dist/components/pierre-diff.d.ts +1 -1
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/queue-draft-policy.d.ts +1 -1
- package/dist/components/queue-surface-implementation.d.ts +3 -3
- package/dist/components/queue-surface-state.d.ts +4 -4
- package/dist/components/queue-surface.d.ts +5 -5
- package/dist/components/sandbox-files.d.ts +3 -3
- package/dist/components/sandbox-terminal.d.ts +24 -4
- package/dist/components/sandbox-workspace.d.ts +28 -9
- package/dist/components/session-chrome.d.ts +4 -4
- package/dist/components/session-status.d.ts +2 -2
- package/dist/components/timeline-annotation-selection.d.ts +9 -0
- package/dist/components/timeline-annotations-dialog.d.ts +16 -0
- package/dist/components/timeline-annotations.d.ts +13 -0
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/user-message-body.d.ts +2 -2
- package/dist/components/workbench-changes.d.ts +2 -2
- package/dist/components/workspace-dock.d.ts +1 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-auth-runs.d.ts +32 -0
- package/dist/hooks/use-available-models.d.ts +2 -2
- package/dist/hooks/use-billing-usage.d.ts +1 -1
- package/dist/hooks/use-browser-downloads.d.ts +25 -0
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +30 -0
- package/dist/hooks/use-browser-session.d.ts +29 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-channels.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +2 -2
- package/dist/hooks/use-composer.d.ts +36 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
- package/dist/hooks/use-computer-session.d.ts +29 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +5 -1
- package/dist/hooks/use-environments.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +1 -1
- package/dist/hooks/use-goal.d.ts +2 -2
- package/dist/hooks/use-human-input.d.ts +2 -2
- package/dist/hooks/use-interaction-interventions.d.ts +27 -0
- package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
- package/dist/hooks/use-machine-chip.d.ts +2 -2
- package/dist/hooks/use-machines.d.ts +4 -4
- package/dist/hooks/use-network-routes.d.ts +25 -0
- package/dist/hooks/use-packs.d.ts +7 -2
- package/dist/hooks/use-rigs.d.ts +1 -1
- package/dist/hooks/use-sandbox-files.d.ts +6 -4
- package/dist/hooks/use-sandbox-git.d.ts +6 -5
- package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
- package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
- package/dist/hooks/use-session-capabilities.d.ts +5 -4
- package/dist/hooks/use-session-control.d.ts +1 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/hooks/use-session-lineage.d.ts +1 -1
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +2 -2
- package/dist/hooks/use-site-auth-connections.d.ts +25 -0
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
- package/dist/hooks/use-terminal-stream.d.ts +11 -2
- package/dist/hooks/use-turn-queue.d.ts +2 -2
- package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +1 -1
- package/dist/hooks/use-workspace-capture.d.ts +1 -1
- package/dist/hooks/use-workspace-edit.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +1 -1
- package/dist/hooks/use-workspaces.d.ts +1 -1
- package/dist/index.d.ts +162 -155
- package/dist/index.js +1822 -1703
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +33 -0
- package/dist/interaction.js +550 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
- package/dist/lib/host-platform.d.ts +5 -0
- package/dist/lib/relay-wire.d.ts +24 -1
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +2 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +6 -5
- package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
- package/dist/realtime/dropdown-menu.d.ts +8 -8
- package/dist/realtime/realtime-control.d.ts +8 -8
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +38 -11
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +28 -2
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +5 -4
- package/dist/session.d.ts +28 -28
- package/dist/session.js +10 -10
- package/dist/timeline/activity-rail.d.ts +5 -3
- package/dist/timeline/disclosure-context.d.ts +1 -1
- package/dist/timeline/fleet-decision-projection.d.ts +1 -1
- package/dist/timeline/fleet-decision-row.d.ts +3 -3
- package/dist/timeline/index.d.ts +19 -19
- package/dist/timeline/parsers.d.ts +4 -1
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +8 -2
- package/dist/timeline/screenshot-lightbox.d.ts +2 -2
- package/dist/timeline/shared.d.ts +21 -10
- package/dist/timeline/tool-diff.d.ts +2 -2
- package/dist/timeline/tool-renderers.d.ts +1 -1
- package/dist/timeline/turn-summary.d.ts +3 -3
- package/dist/timeline/types.d.ts +44 -3
- package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
- package/package.json +36 -7
- package/src/artifacts-document.ts +31 -0
- package/src/artifacts-presentation.ts +36 -0
- package/src/artifacts-spreadsheet.ts +21 -0
- package/src/artifacts.ts +89 -0
- package/src/client.ts +157 -1
- package/src/clipboard.ts +2 -0
- package/src/components/artifacts/artifact-surface.tsx +93 -0
- package/src/components/artifacts/document-editor.tsx +2255 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
- package/src/components/artifacts/editable-document.tsx +657 -0
- package/src/components/artifacts/editable-presentation.tsx +1020 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
- package/src/components/artifacts/index.ts +104 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-input.ts +65 -0
- package/src/components/browser-viewer.tsx +2702 -0
- package/src/components/chat-composer.tsx +13 -0
- package/src/components/code-editor.tsx +89 -30
- package/src/components/composer-transcription-control.tsx +10 -6
- package/src/components/computer-viewer.tsx +1636 -0
- package/src/components/desktop-viewer.tsx +215 -74
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interaction-intervention-banner.tsx +99 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +1 -1
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/model-policy-picker.tsx +15 -0
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +128 -46
- package/src/components/sandbox-workspace.tsx +256 -90
- package/src/components/session-chrome.tsx +53 -13
- package/src/components/timeline-annotation-selection.tsx +221 -0
- package/src/components/timeline-annotations-dialog.tsx +213 -0
- package/src/components/timeline-annotations.tsx +84 -0
- package/src/components/workbench-changes.tsx +188 -31
- package/src/components/workspace-dock.tsx +59 -5
- package/src/hooks/use-attached-browsers.ts +154 -0
- package/src/hooks/use-auth-runs.ts +278 -0
- package/src/hooks/use-browser-downloads.ts +218 -0
- package/src/hooks/use-browser-frame-stream.ts +425 -0
- package/src/hooks/use-browser-identities.ts +266 -0
- package/src/hooks/use-browser-session.ts +543 -0
- package/src/hooks/use-browser-sessions.ts +286 -0
- package/src/hooks/use-channels.ts +99 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +434 -0
- package/src/hooks/use-computer-session.ts +462 -0
- package/src/hooks/use-computer-sessions.ts +251 -0
- package/src/hooks/use-desktop-stream.ts +39 -7
- package/src/hooks/use-interaction-interventions.ts +242 -0
- package/src/hooks/use-interaction-invalidation.ts +66 -0
- package/src/hooks/use-machine-chip.ts +15 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-network-routes.ts +214 -0
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +98 -55
- package/src/hooks/use-sandbox-git.ts +93 -38
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +59 -46
- package/src/hooks/use-site-auth-connections.ts +226 -0
- package/src/hooks/use-supergrok-accounts.ts +81 -0
- package/src/hooks/use-terminal-stream.ts +359 -47
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +18 -0
- package/src/interaction.ts +101 -0
- package/src/lib/host-platform.ts +10 -0
- package/src/lib/relay-wire.ts +47 -4
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/model-policy.ts +19 -4
- package/src/provider.tsx +133 -1
- package/src/realtime/realtime-control.tsx +34 -6
- package/src/session-context.ts +156 -1
- package/src/timeline/activity-rail.tsx +25 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +269 -14
- package/src/timeline/registry.ts +13 -1
- package/src/timeline/screenshot-lightbox.tsx +19 -9
- package/src/timeline/shared.tsx +56 -8
- package/src/timeline/tool-renderers.tsx +303 -50
- package/src/timeline/types.ts +57 -3
- package/styles/compiled.css +312 -21
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-23EJ676W.js.map +0 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FWXGSNXT.js.map +0 -1
- package/dist/chunk-LHLYWG5R.js.map +0 -1
- package/dist/chunk-O4VXOBGF.js.map +0 -1
- package/dist/chunk-OL6QVXGF.js.map +0 -1
- package/dist/chunk-WYA65Y3F.js.map +0 -1
- package/dist/chunk-XCNTXWBB.js.map +0 -1
- package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
- package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GitFileDiff } from "@opengeni/sdk";
|
|
2
2
|
import { ArrowUpRightIcon, FileWarningIcon, HistoryIcon } from "lucide-react";
|
|
3
3
|
import {
|
|
4
|
+
Component,
|
|
4
5
|
type ReactNode,
|
|
5
6
|
useCallback,
|
|
6
7
|
useEffect,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
useRef,
|
|
11
12
|
useState,
|
|
12
13
|
} from "react";
|
|
13
|
-
import { VList } from "virtua";
|
|
14
|
+
import { VList, type VListHandle } from "virtua";
|
|
14
15
|
import { cn } from "../lib/cn";
|
|
15
16
|
import { useUnicodeFallbackFonts } from "../lib/use-unicode-fonts";
|
|
16
17
|
import { useThemeType } from "../lib/use-theme-type";
|
|
@@ -96,6 +97,153 @@ type RailRow =
|
|
|
96
97
|
| { kind: "group"; label: string; count: number }
|
|
97
98
|
| { kind: "file"; file: SandboxGitFileDiff; index: number };
|
|
98
99
|
|
|
100
|
+
type ChangesRailVListProps = {
|
|
101
|
+
children: ReactNode;
|
|
102
|
+
className: string;
|
|
103
|
+
coarsePointer: boolean;
|
|
104
|
+
files: readonly SandboxGitFileDiff[];
|
|
105
|
+
rowCount: number;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
type ChangesRailVListState = {
|
|
109
|
+
renderedRowCount: number;
|
|
110
|
+
resetVersion: number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type ChangesRailSnapshot = {
|
|
114
|
+
focusedPath: string | null;
|
|
115
|
+
scrollOffset: number;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** Virtua must reset its cached range when the rail shrinks. Keep that remount
|
|
119
|
+
* scoped to shrink transitions and preserve the reader's focus/scroll context. */
|
|
120
|
+
class ChangesRailVList extends Component<
|
|
121
|
+
ChangesRailVListProps,
|
|
122
|
+
ChangesRailVListState,
|
|
123
|
+
ChangesRailSnapshot | null
|
|
124
|
+
> {
|
|
125
|
+
state: ChangesRailVListState = {
|
|
126
|
+
renderedRowCount: this.props.rowCount,
|
|
127
|
+
resetVersion: 0,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
private host: HTMLDivElement | null = null;
|
|
131
|
+
private listHandle: VListHandle | null = null;
|
|
132
|
+
private pendingFocusedPath: string | null = null;
|
|
133
|
+
private restoreFrame: number | null = null;
|
|
134
|
+
|
|
135
|
+
static getDerivedStateFromProps(
|
|
136
|
+
props: ChangesRailVListProps,
|
|
137
|
+
state: ChangesRailVListState,
|
|
138
|
+
): ChangesRailVListState | null {
|
|
139
|
+
if (props.rowCount === state.renderedRowCount) return null;
|
|
140
|
+
return {
|
|
141
|
+
renderedRowCount: props.rowCount,
|
|
142
|
+
resetVersion:
|
|
143
|
+
props.rowCount < state.renderedRowCount ? state.resetVersion + 1 : state.resetVersion,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getSnapshotBeforeUpdate(
|
|
148
|
+
_previousProps: ChangesRailVListProps,
|
|
149
|
+
previousState: ChangesRailVListState,
|
|
150
|
+
): ChangesRailSnapshot | null {
|
|
151
|
+
if (previousState.resetVersion === this.state.resetVersion) return null;
|
|
152
|
+
const scroller = this.host?.firstElementChild;
|
|
153
|
+
if (!(scroller instanceof HTMLElement)) return null;
|
|
154
|
+
const active = document.activeElement;
|
|
155
|
+
const focusedRow =
|
|
156
|
+
active instanceof HTMLElement && scroller.contains(active)
|
|
157
|
+
? active.closest<HTMLElement>("[data-rail-file-path]")
|
|
158
|
+
: null;
|
|
159
|
+
return {
|
|
160
|
+
focusedPath: focusedRow?.dataset.railFilePath ?? this.pendingFocusedPath,
|
|
161
|
+
scrollOffset: scroller.scrollTop,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
componentDidUpdate(
|
|
166
|
+
_previousProps: ChangesRailVListProps,
|
|
167
|
+
_previousState: ChangesRailVListState,
|
|
168
|
+
snapshot: ChangesRailSnapshot | null,
|
|
169
|
+
): void {
|
|
170
|
+
if (!snapshot || !this.listHandle) return;
|
|
171
|
+
this.cancelRestoreFrame();
|
|
172
|
+
const maxOffset = Math.max(0, this.listHandle.scrollSize - this.listHandle.viewportSize);
|
|
173
|
+
this.listHandle.scrollTo(Math.min(Math.max(snapshot.scrollOffset, 0), maxOffset));
|
|
174
|
+
const focusedPath = snapshot.focusedPath;
|
|
175
|
+
if (!focusedPath) return;
|
|
176
|
+
this.pendingFocusedPath = focusedPath;
|
|
177
|
+
this.continueFocusRestore();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
componentWillUnmount(): void {
|
|
181
|
+
this.cancelRestoreFrame();
|
|
182
|
+
this.pendingFocusedPath = null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private cancelRestoreFrame(): void {
|
|
186
|
+
if (this.restoreFrame === null) return;
|
|
187
|
+
if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(this.restoreFrame);
|
|
188
|
+
this.restoreFrame = null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private continueFocusRestore = (): void => {
|
|
192
|
+
const path = this.pendingFocusedPath;
|
|
193
|
+
if (!path) return;
|
|
194
|
+
if (this.restoreFocusedPath(path)) {
|
|
195
|
+
this.pendingFocusedPath = null;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (this.restoreFrame === null && typeof requestAnimationFrame === "function") {
|
|
199
|
+
this.restoreFrame = requestAnimationFrame(() => {
|
|
200
|
+
this.restoreFrame = null;
|
|
201
|
+
this.continueFocusRestore();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
private restoreFocusedPath(path: string): boolean {
|
|
207
|
+
const host = this.host;
|
|
208
|
+
if (!host) return true;
|
|
209
|
+
const active = document.activeElement;
|
|
210
|
+
if (active instanceof HTMLElement && active.isConnected && active !== document.body) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
if (!this.props.files.some((file) => file.path === path)) return true;
|
|
214
|
+
const row = Array.from(host.querySelectorAll<HTMLButtonElement>("[data-rail-file-path]")).find(
|
|
215
|
+
(candidate) => candidate.dataset.railFilePath === path,
|
|
216
|
+
);
|
|
217
|
+
if (!row) return false;
|
|
218
|
+
row.focus({ preventScroll: true });
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private setHost = (host: HTMLDivElement | null): void => {
|
|
223
|
+
this.host = host;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
private setListHandle = (handle: VListHandle | null): void => {
|
|
227
|
+
this.listHandle = handle;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
render() {
|
|
231
|
+
return (
|
|
232
|
+
<div ref={this.setHost} data-opengeni-changes-rail className={this.props.className}>
|
|
233
|
+
<VList
|
|
234
|
+
key={this.state.resetVersion}
|
|
235
|
+
ref={this.setListHandle}
|
|
236
|
+
className="h-full"
|
|
237
|
+
itemSize={this.props.coarsePointer ? 44 : 28}
|
|
238
|
+
ssrCount={Math.min(30, this.props.rowCount)}
|
|
239
|
+
>
|
|
240
|
+
{this.props.children}
|
|
241
|
+
</VList>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
99
247
|
/** Order the files and build the rail rows (grouped past the threshold). The
|
|
100
248
|
* returned `orderedFiles` drives BOTH the rail and the diff pane so a rail row's
|
|
101
249
|
* `index` addresses the matching pane section. Exported for tests. */
|
|
@@ -330,7 +478,13 @@ export function WorkbenchChanges({
|
|
|
330
478
|
))}
|
|
331
479
|
</select>
|
|
332
480
|
</div>
|
|
333
|
-
<div
|
|
481
|
+
<div
|
|
482
|
+
aria-label="Changed file diff"
|
|
483
|
+
className="min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent"
|
|
484
|
+
data-opengeni-changes-pane
|
|
485
|
+
role="region"
|
|
486
|
+
tabIndex={0}
|
|
487
|
+
>
|
|
334
488
|
{activeFile ? (
|
|
335
489
|
<DiffSection
|
|
336
490
|
file={activeFile}
|
|
@@ -344,42 +498,44 @@ export function WorkbenchChanges({
|
|
|
344
498
|
) : (
|
|
345
499
|
<div className="flex min-h-0 flex-1">
|
|
346
500
|
{/* File rail — virtualized (virtua): a dense change set is fine. */}
|
|
347
|
-
<
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
</div>
|
|
501
|
+
<ChangesRailVList
|
|
502
|
+
rowCount={rows.length}
|
|
503
|
+
coarsePointer={coarsePointer}
|
|
504
|
+
files={orderedFiles}
|
|
505
|
+
className="w-[clamp(12rem,28%,15rem)] shrink-0 border-r border-og-border bg-og-surface-1/35"
|
|
506
|
+
>
|
|
507
|
+
{rows.map((row) =>
|
|
508
|
+
row.kind === "group" ? (
|
|
509
|
+
<div
|
|
510
|
+
key={`g:${row.label}`}
|
|
511
|
+
data-rail-group
|
|
512
|
+
className="flex items-center gap-1.5 px-2 pb-0.5 pt-2 text-og-xs font-medium uppercase tracking-wide text-og-fg-subtle"
|
|
513
|
+
>
|
|
514
|
+
<span className="min-w-0 truncate">{row.label}</span>
|
|
515
|
+
<span className="shrink-0">{row.count}</span>
|
|
516
|
+
</div>
|
|
517
|
+
) : (
|
|
518
|
+
<RailFileRow
|
|
519
|
+
key={row.file.path}
|
|
520
|
+
file={row.file}
|
|
521
|
+
grouped={grouped}
|
|
522
|
+
active={row.index === activeIndex}
|
|
523
|
+
onClick={() => jumpTo(row.index)}
|
|
524
|
+
/>
|
|
525
|
+
),
|
|
526
|
+
)}
|
|
527
|
+
</ChangesRailVList>
|
|
375
528
|
|
|
376
529
|
{/* Diff pane — windowed file sections. Only the sections inside the
|
|
377
530
|
visible window ± overscan are in the DOM; the container reserves the
|
|
378
531
|
full scroll height so scrolling + the rail-jump stay accurate. */}
|
|
379
532
|
<div
|
|
380
533
|
ref={windowed.scrollRef}
|
|
381
|
-
|
|
534
|
+
aria-label="Changed files diff"
|
|
535
|
+
className="min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent"
|
|
382
536
|
data-opengeni-changes-pane
|
|
537
|
+
role="region"
|
|
538
|
+
tabIndex={0}
|
|
383
539
|
>
|
|
384
540
|
<div style={{ position: "relative", height: windowed.totalHeight }}>
|
|
385
541
|
{orderedFiles.map((file, index) => {
|
|
@@ -479,6 +635,7 @@ function RailFileRow({
|
|
|
479
635
|
onClick={onClick}
|
|
480
636
|
title={file.path}
|
|
481
637
|
data-rail-file
|
|
638
|
+
data-rail-file-path={file.path}
|
|
482
639
|
className={cn(
|
|
483
640
|
"relative flex min-h-7 w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm transition-colors hover:bg-og-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent pointer-coarse:min-h-11",
|
|
484
641
|
grouped && "pl-3",
|
|
@@ -536,6 +536,8 @@ function DockChrome({
|
|
|
536
536
|
/** Right-aligned chrome controls (maximize / collapse, or the overlay close). */
|
|
537
537
|
controls: ReactNode;
|
|
538
538
|
}) {
|
|
539
|
+
const chromeRef = useRef<HTMLDivElement | null>(null);
|
|
540
|
+
const [compact, setCompact] = useState(false);
|
|
539
541
|
const active = tabs.find((t) => t.id === current) ?? tabs[0];
|
|
540
542
|
const tabsetId = useId();
|
|
541
543
|
const tabRefs = useRef<Array<HTMLButtonElement | null>>([]);
|
|
@@ -544,6 +546,31 @@ function DockChrome({
|
|
|
544
546
|
const [tabOverflow, setTabOverflow] = useState({ start: false, end: false });
|
|
545
547
|
const activeId = active?.id ?? "";
|
|
546
548
|
const activeTabIndex = tabs.findIndex((tab) => tab.id === activeId);
|
|
549
|
+
|
|
550
|
+
// A desktop dock can be much narrower than the viewport after the user drags
|
|
551
|
+
// its separator. Viewport breakpoints therefore cannot protect the tab strip:
|
|
552
|
+
// the status + controls used to squeeze it to zero width, leaving visible but
|
|
553
|
+
// unclickable ARIA tabs. At narrow *dock* widths, give status/controls their
|
|
554
|
+
// own row and the horizontally scrollable tabs the complete second row.
|
|
555
|
+
useDockLayoutEffect(() => {
|
|
556
|
+
const node = chromeRef.current;
|
|
557
|
+
if (!node) return;
|
|
558
|
+
const update = () => {
|
|
559
|
+
const desktop =
|
|
560
|
+
typeof window.matchMedia !== "function" || window.matchMedia("(min-width: 1024px)").matches;
|
|
561
|
+
const next = desktop && node.getBoundingClientRect().width < 420;
|
|
562
|
+
setCompact((previous) => (previous === next ? previous : next));
|
|
563
|
+
};
|
|
564
|
+
update();
|
|
565
|
+
if (typeof ResizeObserver === "undefined") {
|
|
566
|
+
window.addEventListener("resize", update);
|
|
567
|
+
return () => window.removeEventListener("resize", update);
|
|
568
|
+
}
|
|
569
|
+
const observer = new ResizeObserver(update);
|
|
570
|
+
observer.observe(node);
|
|
571
|
+
return () => observer.disconnect();
|
|
572
|
+
}, []);
|
|
573
|
+
|
|
547
574
|
const keepActiveTabVisible = useCallback(() => {
|
|
548
575
|
const selected = activeTabIndex >= 0 ? tabRefs.current[activeTabIndex] : null;
|
|
549
576
|
selected?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
@@ -631,8 +658,22 @@ function DockChrome({
|
|
|
631
658
|
|
|
632
659
|
return (
|
|
633
660
|
<>
|
|
634
|
-
<div
|
|
635
|
-
|
|
661
|
+
<div
|
|
662
|
+
ref={chromeRef}
|
|
663
|
+
data-dock-chrome
|
|
664
|
+
data-compact={compact ? "true" : undefined}
|
|
665
|
+
className={cn(
|
|
666
|
+
"grid shrink-0 grid-cols-[auto_minmax(0,1fr)_auto_auto] items-center gap-x-2 border-b border-og-border px-1.5 py-1 max-[1023px]:grid-cols-[auto_minmax(0,1fr)_auto] max-[1023px]:gap-y-0 max-[1023px]:px-2 max-[1023px]:pb-1 max-[1023px]:pt-0 min-[640px]:max-[1023px]:grid-cols-[auto_minmax(0,1fr)_auto_auto]",
|
|
667
|
+
compact && "gap-y-1",
|
|
668
|
+
)}
|
|
669
|
+
style={compact ? { gridTemplateColumns: "minmax(0, 1fr) auto" } : undefined}
|
|
670
|
+
>
|
|
671
|
+
<div
|
|
672
|
+
className={cn(
|
|
673
|
+
"flex min-w-0 shrink-0 items-center max-[1023px]:min-h-11 min-[640px]:max-[1023px]:col-start-1 min-[640px]:max-[1023px]:row-start-1",
|
|
674
|
+
compact && "hidden",
|
|
675
|
+
)}
|
|
676
|
+
>
|
|
636
677
|
{leading ?? (
|
|
637
678
|
<span className="hidden truncate px-1 text-og-sm font-semibold text-og-fg max-[1023px]:inline">
|
|
638
679
|
Workspace
|
|
@@ -648,7 +689,10 @@ function DockChrome({
|
|
|
648
689
|
absorbing any unusually long host-injected labels. */}
|
|
649
690
|
<div
|
|
650
691
|
ref={tabListRef}
|
|
651
|
-
className=
|
|
692
|
+
className={cn(
|
|
693
|
+
"flex min-w-0 items-center gap-0.5 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden max-[1023px]:col-span-3 max-[1023px]:row-start-2 max-[1023px]:w-full min-[640px]:max-[1023px]:col-span-1 min-[640px]:max-[1023px]:col-start-2 min-[640px]:max-[1023px]:row-start-1",
|
|
694
|
+
compact && "col-span-2 col-start-1 row-start-2 w-full",
|
|
695
|
+
)}
|
|
652
696
|
role="tablist"
|
|
653
697
|
aria-orientation="horizontal"
|
|
654
698
|
style={{
|
|
@@ -683,10 +727,20 @@ function DockChrome({
|
|
|
683
727
|
</button>
|
|
684
728
|
))}
|
|
685
729
|
</div>
|
|
686
|
-
<div
|
|
730
|
+
<div
|
|
731
|
+
className={cn(
|
|
732
|
+
"flex min-w-0 shrink-0 items-center justify-self-end min-[640px]:max-[1023px]:col-start-3 min-[640px]:max-[1023px]:row-start-1",
|
|
733
|
+
compact && "col-start-1 row-start-1 justify-self-start",
|
|
734
|
+
)}
|
|
735
|
+
>
|
|
687
736
|
{accessory}
|
|
688
737
|
</div>
|
|
689
|
-
<div
|
|
738
|
+
<div
|
|
739
|
+
className={cn(
|
|
740
|
+
"flex shrink-0 items-center gap-0.5 text-og-fg-subtle min-[640px]:max-[1023px]:col-start-4 min-[640px]:max-[1023px]:row-start-1",
|
|
741
|
+
compact && "col-start-2 row-start-1",
|
|
742
|
+
)}
|
|
743
|
+
>
|
|
690
744
|
{controls}
|
|
691
745
|
</div>
|
|
692
746
|
</div>
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { AttachedBrowserDevice } from "@opengeni/sdk/interaction";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import type { EmbeddedBrowserInteractionClientLike } from "../client";
|
|
4
|
+
import {
|
|
5
|
+
type EmbeddedBrowserInteractionClientOverride,
|
|
6
|
+
useEmbeddedBrowserInteraction,
|
|
7
|
+
} from "../session-context";
|
|
8
|
+
import { useInteractionInvalidation } from "./use-interaction-invalidation";
|
|
9
|
+
|
|
10
|
+
export type UseAttachedBrowsersOptions = EmbeddedBrowserInteractionClientOverride & {
|
|
11
|
+
enabled?: boolean | undefined;
|
|
12
|
+
includeDisconnected?: boolean | undefined;
|
|
13
|
+
pollIntervalMs?: number | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type UseAttachedBrowsersResult = {
|
|
17
|
+
revision: number;
|
|
18
|
+
devices: AttachedBrowserDevice[];
|
|
19
|
+
loading: boolean;
|
|
20
|
+
refreshing: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
refresh: () => Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Workspace-wide inventory of Chrome profiles currently connected through an
|
|
26
|
+
* enrolled OpenGeni machine. BrowserSessions remain the execution authority. */
|
|
27
|
+
export function useAttachedBrowsers(
|
|
28
|
+
options: UseAttachedBrowsersOptions = {},
|
|
29
|
+
): UseAttachedBrowsersResult {
|
|
30
|
+
const { client, workspaceId, workspaceInteractionEvent, workspaceInteractionConnectionState } =
|
|
31
|
+
useEmbeddedBrowserInteraction(options);
|
|
32
|
+
const enabled = options.enabled ?? true;
|
|
33
|
+
const includeDisconnected = options.includeDisconnected ?? false;
|
|
34
|
+
const [state, setState] = useState(() => emptyState(workspaceId, enabled));
|
|
35
|
+
const requestRef = useRef<{ id: number; controller: AbortController | null }>({
|
|
36
|
+
id: 0,
|
|
37
|
+
controller: null,
|
|
38
|
+
});
|
|
39
|
+
const mountedRef = useRef(true);
|
|
40
|
+
const visible = state.workspaceId === workspaceId ? state : emptyState(workspaceId, enabled);
|
|
41
|
+
|
|
42
|
+
const cancelLoad = useCallback(() => {
|
|
43
|
+
const id = requestRef.current.id + 1;
|
|
44
|
+
requestRef.current.controller?.abort();
|
|
45
|
+
requestRef.current = { id, controller: null };
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const load = useCallback(
|
|
49
|
+
async (foreground: boolean): Promise<void> => {
|
|
50
|
+
if (!enabled) return;
|
|
51
|
+
const id = requestRef.current.id + 1;
|
|
52
|
+
requestRef.current.controller?.abort();
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
requestRef.current = { id, controller };
|
|
55
|
+
setState((current) => ({
|
|
56
|
+
...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
|
|
57
|
+
loading: foreground,
|
|
58
|
+
refreshing: !foreground,
|
|
59
|
+
error: foreground || current.workspaceId !== workspaceId ? null : current.error,
|
|
60
|
+
}));
|
|
61
|
+
try {
|
|
62
|
+
const response = await client.listAttachedBrowsers(workspaceId, {
|
|
63
|
+
includeDisconnected,
|
|
64
|
+
signal: controller.signal,
|
|
65
|
+
});
|
|
66
|
+
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
67
|
+
setState({
|
|
68
|
+
workspaceId,
|
|
69
|
+
revision: response.revision,
|
|
70
|
+
devices: sortDevices(response.devices),
|
|
71
|
+
loading: false,
|
|
72
|
+
refreshing: false,
|
|
73
|
+
error: null,
|
|
74
|
+
});
|
|
75
|
+
} catch (cause) {
|
|
76
|
+
if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
|
|
77
|
+
return;
|
|
78
|
+
setState((current) => ({
|
|
79
|
+
...(current.workspaceId === workspaceId ? current : emptyState(workspaceId, true)),
|
|
80
|
+
loading: false,
|
|
81
|
+
refreshing: false,
|
|
82
|
+
error: cause instanceof Error ? cause : new Error(String(cause)),
|
|
83
|
+
}));
|
|
84
|
+
} finally {
|
|
85
|
+
if (requestRef.current.id === id) requestRef.current = { id, controller: null };
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
[client, enabled, includeDisconnected, workspaceId],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const refresh = useCallback(async () => await load(false), [load]);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
mountedRef.current = true;
|
|
95
|
+
if (!enabled) {
|
|
96
|
+
cancelLoad();
|
|
97
|
+
setState(emptyState(workspaceId, false));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
void load(true);
|
|
101
|
+
return () => {
|
|
102
|
+
mountedRef.current = false;
|
|
103
|
+
cancelLoad();
|
|
104
|
+
};
|
|
105
|
+
}, [cancelLoad, enabled, load, workspaceId]);
|
|
106
|
+
|
|
107
|
+
useInteractionInvalidation({
|
|
108
|
+
workspaceId,
|
|
109
|
+
key: `${workspaceId}:${includeDisconnected}`,
|
|
110
|
+
enabled,
|
|
111
|
+
event: workspaceInteractionEvent,
|
|
112
|
+
connectionState: workspaceInteractionConnectionState,
|
|
113
|
+
knownRevision: visible.revision,
|
|
114
|
+
refresh,
|
|
115
|
+
fallbackPollIntervalMs: options.pollIntervalMs,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
revision: visible.revision,
|
|
120
|
+
devices: visible.devices,
|
|
121
|
+
loading: visible.loading,
|
|
122
|
+
refreshing: visible.refreshing,
|
|
123
|
+
error: visible.error,
|
|
124
|
+
refresh,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function emptyState(workspaceId: string, loading: boolean) {
|
|
129
|
+
return {
|
|
130
|
+
workspaceId,
|
|
131
|
+
revision: 0,
|
|
132
|
+
devices: [] as AttachedBrowserDevice[],
|
|
133
|
+
loading,
|
|
134
|
+
refreshing: false,
|
|
135
|
+
error: null as Error | null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sortDevices(devices: readonly AttachedBrowserDevice[]): AttachedBrowserDevice[] {
|
|
140
|
+
return [...devices].sort(
|
|
141
|
+
(left, right) =>
|
|
142
|
+
Number(right.state === "connected") - Number(left.state === "connected") ||
|
|
143
|
+
timestamp(right.lastSeenAt) - timestamp(left.lastSeenAt) ||
|
|
144
|
+
left.name.localeCompare(right.name),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function timestamp(value: string): number {
|
|
149
|
+
const parsed = Date.parse(value);
|
|
150
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Exported for narrow proxy implementations and tests. */
|
|
154
|
+
export type AttachedBrowsersClient = EmbeddedBrowserInteractionClientLike;
|