@opengeni/react 1.0.1 → 1.0.2-canary.37202341988001
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 +660 -21
- package/dist/accounts.d.ts +64 -0
- package/dist/accounts.js +822 -0
- package/dist/accounts.js.map +1 -0
- package/dist/approvals.d.ts +2 -1
- 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 +4 -1
- package/dist/artifacts.js +518 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-SHYKQRNX.js +16 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/chat-interactive-block-UGBTNB3L.js.map +1 -0
- package/dist/chunk-26ZNDDLZ.js +453 -0
- package/dist/chunk-26ZNDDLZ.js.map +1 -0
- package/dist/chunk-3CTRFMAK.js +359 -0
- package/dist/chunk-3CTRFMAK.js.map +1 -0
- package/dist/chunk-447JLCBU.js +209 -0
- package/dist/chunk-447JLCBU.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-5J4FOW6Q.js +108 -0
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/chunk-742CZ3L3.js +481 -0
- package/dist/chunk-742CZ3L3.js.map +1 -0
- package/dist/chunk-A2PI6UMY.js +41 -0
- package/dist/chunk-A2PI6UMY.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-A4UJB2MM.js} +37 -27
- package/dist/chunk-A4UJB2MM.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/chunk-BRM6FVZA.js +302 -0
- package/dist/chunk-BRM6FVZA.js.map +1 -0
- package/dist/chunk-C5X5AJXK.js +182 -0
- package/dist/chunk-C5X5AJXK.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-DO544OSV.js} +1075 -79
- package/dist/chunk-DO544OSV.js.map +1 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-DYWBUX5Q.js} +29 -123
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-EESD3PY3.js} +792 -537
- package/dist/chunk-EESD3PY3.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-EN3YTHKR.js} +4117 -3896
- package/dist/chunk-EN3YTHKR.js.map +1 -0
- package/dist/chunk-ERGYJIBB.js +21 -0
- package/dist/chunk-ERGYJIBB.js.map +1 -0
- package/dist/chunk-FZCP527Y.js +116 -0
- package/dist/chunk-FZCP527Y.js.map +1 -0
- package/dist/chunk-GIMRELBW.js +14 -0
- package/dist/chunk-GIMRELBW.js.map +1 -0
- package/dist/chunk-GYVRFZDY.js +2733 -0
- package/dist/chunk-GYVRFZDY.js.map +1 -0
- package/dist/{chunk-LLOLUHVG.js → chunk-HDJWDK7W.js} +1358 -2746
- package/dist/chunk-HDJWDK7W.js.map +1 -0
- package/dist/chunk-I7IWRQLN.js +1608 -0
- package/dist/chunk-I7IWRQLN.js.map +1 -0
- package/dist/chunk-IT4BJUGF.js +13055 -0
- package/dist/chunk-IT4BJUGF.js.map +1 -0
- package/dist/chunk-IWIDV7SB.js +1 -0
- package/dist/chunk-IWIDV7SB.js.map +1 -0
- package/dist/chunk-J2QWVSQY.js +312 -0
- package/dist/chunk-J2QWVSQY.js.map +1 -0
- package/dist/chunk-J6DBS4T3.js +371 -0
- package/dist/chunk-J6DBS4T3.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-JIQYUGVL.js} +1331 -1163
- package/dist/chunk-JIQYUGVL.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/chunk-K3HQAKQX.js +24 -0
- package/dist/chunk-K3HQAKQX.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-PNY3JKBJ.js} +309 -74
- package/dist/chunk-PNY3JKBJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-RLUSC2WA.js +299 -0
- package/dist/chunk-RLUSC2WA.js.map +1 -0
- package/dist/{chunk-IGC6A2ZI.js → chunk-RSI7XYXL.js} +777 -246
- package/dist/chunk-RSI7XYXL.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-TKOTKRWY.js} +5 -4
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-TKOTKRWY.js.map} +1 -1
- package/dist/chunk-TMYGL6X5.js +742 -0
- package/dist/chunk-TMYGL6X5.js.map +1 -0
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/chunk-UK4TINKT.js +27 -0
- package/dist/chunk-UK4TINKT.js.map +1 -0
- package/dist/chunk-UTRTG6LH.js +220 -0
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WGXVEJO5.js +319 -0
- package/dist/chunk-WGXVEJO5.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-WUISZ3WU.js} +223 -115
- package/dist/chunk-WUISZ3WU.js.map +1 -0
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-Y2PRMYLC.js} +11 -6
- package/dist/chunk-Y2PRMYLC.js.map +1 -0
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YM2DE7BQ.js +3124 -0
- package/dist/chunk-YM2DE7BQ.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/chunk-ZYRGEKWS.js +216 -0
- package/dist/chunk-ZYRGEKWS.js.map +1 -0
- package/dist/client.d.ts +7 -4
- package/dist/clipboard.js +1 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +15 -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/artifact-surface.d.ts +3 -1
- 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/document-editor.d.ts +4 -2
- 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/editable-document.d.ts +1 -1
- package/dist/components/artifacts/editable-presentation.d.ts +2 -2
- package/dist/components/artifacts/editable-spreadsheet.d.ts +2 -1
- package/dist/components/artifacts/index.d.ts +8 -1
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/presentation-editor.d.ts +6 -2
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +48 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/browser-select-control.d.ts +16 -0
- package/dist/components/browser-viewer.d.ts +10 -1
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +5 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer.d.ts +45 -4
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +10 -1
- package/dist/components/fleet-tile.d.ts +2 -2
- package/dist/components/grok-mark.d.ts +26 -0
- package/dist/components/human-input-form.d.ts +21 -2
- package/dist/components/human-input-surface.d.ts +6 -3
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown-table-layout.d.ts +12 -0
- package/dist/components/markdown.d.ts +47 -3
- package/dist/components/message-timeline.d.ts +72 -9
- package/dist/components/model-mark.d.ts +29 -0
- package/dist/components/model-policy-picker-menu.d.ts +9 -0
- package/dist/components/model-policy-picker.d.ts +62 -13
- package/dist/components/open-geni-chat.d.ts +58 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/sandbox-files.d.ts +13 -2
- package/dist/components/sandbox-workspace.d.ts +43 -3
- package/dist/components/session-capability-frame.d.ts +35 -0
- package/dist/components/session-chrome.d.ts +66 -8
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-conversation.d.ts +89 -0
- package/dist/components/session-list.d.ts +36 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +21 -0
- package/dist/components/timeline-anchor.d.ts +24 -0
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +3 -2
- package/dist/components/timeline-annotations.d.ts +13 -3
- package/dist/components/timeline-reading-progress.d.ts +11 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/components/timeline-settlement.d.ts +16 -0
- package/dist/components/tip-follow.d.ts +16 -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/components/use-viewer-menu-dismiss.d.ts +2 -0
- package/dist/components/user-message-body.d.ts +10 -1
- package/dist/components/workspace-dock.d.ts +6 -2
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +15 -9
- package/dist/computer-viewer-6SRGWRUX.js +19 -0
- package/dist/computer-viewer-6SRGWRUX.js.map +1 -0
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +15 -0
- package/dist/connect-chooser.d.ts +18 -0
- package/dist/connect-panel.d.ts +18 -0
- package/dist/connect-setup.d.ts +16 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2129 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-catalog.d.ts +38 -0
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +17 -0
- package/dist/connection-logo.d.ts +9 -0
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/connection-type-picker.d.ts +6 -0
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +26 -0
- package/dist/desktop.js.map +1 -0
- package/dist/device-authorization.d.ts +23 -0
- package/dist/diffs.d.ts +8 -0
- package/dist/diffs.js +14 -0
- package/dist/diffs.js.map +1 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-session-client.d.ts +23 -0
- package/dist/file-node-visibility.d.ts +15 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
- package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
- package/dist/hooks/use-browser-session.d.ts +12 -1
- package/dist/hooks/use-channels.d.ts +1 -0
- package/dist/hooks/use-codex-accounts.d.ts +25 -7
- package/dist/hooks/use-composer.d.ts +45 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
- package/dist/hooks/use-computer-input-posture.d.ts +13 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-connect.d.ts +22 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +21 -2
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +5 -1
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-session-events.d.ts +30 -8
- package/dist/hooks/use-session-lineage.d.ts +4 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +6 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +27 -5
- package/dist/human-input.d.ts +3 -1
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +60 -17
- package/dist/index.js +2238 -6201
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +35 -184
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-HTRIS4KJ.js +117 -0
- package/dist/latest-question-HTRIS4KJ.js.map +1 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +36 -2
- package/dist/lib/host-theme.d.ts +70 -0
- package/dist/lib/interaction-errors.d.ts +6 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +14 -4
- package/dist/markdown-table-layout-QXSLK4BY.js +106 -0
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-policy-picker-menu-M54ISBMN.js +14 -0
- package/dist/model-policy-picker-menu-M54ISBMN.js.map +1 -0
- package/dist/model-policy.d.ts +19 -3
- package/dist/model-policy.js +9 -1
- package/dist/older-history.d.ts +30 -0
- package/dist/platform-activity-row-627BFZ46.js +182 -0
- package/dist/platform-activity-row-627BFZ46.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/provider.d.ts +13 -1
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-ZHFI5JBP.js} +131 -44
- package/dist/queue-surface-implementation-ZHFI5JBP.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +6 -3
- package/dist/realtime.js +133 -74
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-GORZOUUJ.js +20 -0
- package/dist/sandbox-files-GORZOUUJ.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +12 -0
- package/dist/session-ui.d.ts +30 -2
- package/dist/session-ui.js +85 -7
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +30 -19
- package/dist/sites-ui.d.ts +34 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +11 -0
- package/dist/sites.js.map +1 -0
- package/dist/skill-discovery.d.ts +47 -0
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +10 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/genie-loading.d.ts +34 -0
- package/dist/timeline/index.d.ts +6 -2
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/platform-activity-row.d.ts +14 -0
- package/dist/timeline/presented-image.d.ts +6 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/registry.d.ts +2 -0
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +16 -1
- package/dist/timeline/shared.d.ts +8 -3
- package/dist/timeline/startup-preference.d.ts +3 -0
- package/dist/timeline/startup-timings.d.ts +4 -0
- package/dist/timeline/tool-display-name.d.ts +2 -1
- package/dist/timeline/turn-summary.d.ts +34 -3
- package/dist/timeline/types.d.ts +147 -7
- package/dist/timeline-annotation-markers-GNDHTGBD.js +165 -0
- package/dist/timeline-annotation-markers-GNDHTGBD.js.map +1 -0
- package/dist/timeline-annotation-selection-DMLMM2TX.js +241 -0
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-annotations-dialog-ELRDBHTS.js +273 -0
- package/dist/timeline-annotations-dialog-ELRDBHTS.js.map +1 -0
- package/dist/types/machines.d.ts +2 -2
- 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 +1074 -0
- package/dist/usage.js.map +1 -0
- package/package.json +44 -4
- package/src/accounts.tsx +1072 -0
- package/src/approvals.ts +8 -1
- package/src/artifacts.ts +42 -0
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +22 -13
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/anthropic-mark.tsx +10 -0
- package/src/components/approval-surface.tsx +171 -37
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/artifact-surface.tsx +39 -19
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/document-editor.tsx +141 -16
- package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
- 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 +37 -36
- package/src/components/artifacts/editable-presentation.tsx +36 -18
- package/src/components/artifacts/editable-spreadsheet.tsx +5 -2
- package/src/components/artifacts/index.ts +44 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/presentation-editor.tsx +323 -67
- package/src/components/artifacts/published-html-artifact-frame.tsx +338 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +12 -13
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/browser-select-control.tsx +312 -0
- package/src/components/browser-viewer.tsx +1054 -314
- package/src/components/chat-composer.tsx +131 -85
- package/src/components/child-session-link.tsx +27 -0
- package/src/components/claude-mark.tsx +10 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +7 -5
- package/src/components/composer.tsx +376 -49
- package/src/components/computer-viewer.tsx +1041 -329
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +65 -7
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +158 -34
- package/src/components/fleet-tile.tsx +8 -16
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +33 -0
- package/src/components/human-input-form.tsx +455 -114
- package/src/components/human-input-surface.tsx +18 -7
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/machine-card.tsx +119 -4
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +23 -2
- package/src/components/machines/machine-detail.tsx +52 -2
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +8 -17
- package/src/components/markdown-table-layout.ts +132 -0
- package/src/components/markdown.tsx +473 -44
- package/src/components/message-timeline.tsx +2754 -665
- package/src/components/model-mark.tsx +90 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +424 -0
- package/src/components/model-policy-picker.tsx +337 -381
- package/src/components/open-geni-chat.tsx +338 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +36 -11
- package/src/components/preview-loading.tsx +154 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +132 -42
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/sandbox-files.tsx +267 -35
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/sandbox-workspace.tsx +337 -135
- package/src/components/session-capability-frame.tsx +192 -0
- package/src/components/session-chrome.tsx +1027 -328
- package/src/components/session-commands-panel.tsx +126 -0
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +526 -0
- package/src/components/session-list.tsx +250 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +92 -0
- package/src/components/session-status.tsx +1 -1
- package/src/components/timeline-anchor.tsx +119 -0
- package/src/components/timeline-annotation-chrome.tsx +196 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +106 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +185 -128
- package/src/components/timeline-annotations.tsx +189 -45
- package/src/components/timeline-reading-progress.ts +80 -0
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/timeline-settlement.ts +60 -0
- package/src/components/tip-follow.ts +70 -7
- package/src/components/tooltip.tsx +38 -5
- 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/components/use-viewer-menu-dismiss.ts +33 -0
- package/src/components/user-message-body.tsx +194 -49
- package/src/components/workspace-dock.tsx +141 -175
- package/src/composer.ts +6 -1
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +233 -0
- package/src/connect-chooser.tsx +336 -0
- package/src/connect-panel.tsx +99 -0
- package/src/connect-setup.tsx +307 -0
- package/src/connect.ts +60 -0
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +162 -0
- package/src/connection-discovery.tsx +235 -0
- package/src/connection-installed.tsx +51 -0
- package/src/connection-logo.tsx +37 -0
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/connection-type-picker.tsx +36 -0
- package/src/conversation-timeline.ts +92 -0
- package/src/desktop.ts +24 -0
- package/src/device-authorization.tsx +98 -0
- package/src/diffs.ts +16 -0
- package/src/discovery-cache.ts +42 -0
- package/src/editor.ts +13 -0
- package/src/embedded-session-client.ts +104 -0
- package/src/file-node-visibility.ts +66 -0
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-available-models.ts +20 -14
- package/src/hooks/use-browser-frame-stream.ts +25 -3
- package/src/hooks/use-browser-session.ts +271 -71
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-codex-accounts.ts +78 -21
- package/src/hooks/use-composer.ts +1034 -320
- package/src/hooks/use-computer-frame-stream.ts +39 -16
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +110 -32
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +118 -10
- package/src/hooks/use-goal.ts +36 -9
- package/src/hooks/use-human-input.ts +31 -4
- package/src/hooks/use-last-started-turn-policy.ts +15 -13
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +253 -150
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +67 -0
- package/src/hooks/use-session-capabilities.ts +21 -2
- package/src/hooks/use-session-events.ts +759 -433
- package/src/hooks/use-session-lineage.ts +28 -6
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session.ts +75 -16
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +113 -13
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +12 -1
- package/src/hooks/use-workspace-capture.ts +3 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/hooks/use-workspace-sessions.ts +99 -11
- package/src/human-input.ts +12 -1
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +157 -12
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +150 -14
- package/src/lib/host-theme.ts +511 -0
- package/src/lib/interaction-errors.ts +33 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/machines.ts +9 -1
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-policy.ts +223 -69
- package/src/older-history.ts +87 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/provider.tsx +55 -14
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/realtime-control.tsx +133 -46
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +83 -0
- package/src/session-context.ts +4 -3
- package/src/session-ui.ts +66 -2
- package/src/session.ts +18 -1
- package/src/sites-ui.tsx +402 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +171 -80
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +42 -0
- package/src/timeline/entrance.tsx +28 -3
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/genie-loading.tsx +136 -0
- package/src/timeline/index.ts +11 -0
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/parsers.ts +2 -2
- package/src/timeline/platform-activity-row.tsx +216 -0
- package/src/timeline/presented-image.ts +49 -0
- package/src/timeline/projection.ts +1609 -84
- package/src/timeline/registry.ts +15 -3
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/rolling-activity.tsx +92 -0
- package/src/timeline/screenshot-lightbox.tsx +115 -17
- package/src/timeline/shared.tsx +47 -15
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +8 -6
- package/src/timeline/tool-renderers.tsx +497 -128
- package/src/timeline/turn-summary.tsx +218 -44
- package/src/timeline/types.ts +192 -7
- package/src/types/machines.ts +10 -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 +2098 -757
- package/styles/connect.css +284 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +201 -9
- package/styles/responsive.css +27 -1
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +135 -65
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-7WCLAEFV.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-FK6VG4LL.js +0 -90
- package/dist/chunk-FK6VG4LL.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.js.map +0 -1
- package/dist/chunk-KCGXPP5V.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LAKLF4PA.js +0 -141
- package/dist/chunk-LAKLF4PA.js.map +0 -1
- package/dist/chunk-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js +0 -465
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js +0 -218
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js +0 -8810
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-SHYKQRNX.js.map} +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AttachedBrowserBridge,
|
|
2
3
|
AttachedBrowserDevice,
|
|
3
4
|
BrowserAction,
|
|
5
|
+
BrowserActionBatch,
|
|
4
6
|
BrowserActionReceipt,
|
|
5
7
|
BrowserDiagnosticBatch,
|
|
6
8
|
BrowserDownload,
|
|
@@ -15,10 +17,14 @@ import type {
|
|
|
15
17
|
InteractionPlacement,
|
|
16
18
|
InteractionIntervention,
|
|
17
19
|
InteractionSemanticNode,
|
|
20
|
+
SiteAuthConnection,
|
|
18
21
|
} from "@opengeni/sdk/interaction";
|
|
22
|
+
import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
|
|
19
23
|
import {
|
|
20
24
|
BugIcon,
|
|
21
25
|
ArchiveIcon,
|
|
26
|
+
ArrowLeftIcon,
|
|
27
|
+
ArrowRightIcon,
|
|
22
28
|
CheckIcon,
|
|
23
29
|
ChevronDownIcon,
|
|
24
30
|
CircleAlertIcon,
|
|
@@ -47,6 +53,7 @@ import {
|
|
|
47
53
|
useCallback,
|
|
48
54
|
useEffect,
|
|
49
55
|
useId,
|
|
56
|
+
useLayoutEffect,
|
|
50
57
|
useMemo,
|
|
51
58
|
useRef,
|
|
52
59
|
useState,
|
|
@@ -58,15 +65,22 @@ import {
|
|
|
58
65
|
import { useAttachedBrowsers } from "../hooks/use-attached-browsers";
|
|
59
66
|
import { useBrowserIdentities } from "../hooks/use-browser-identities";
|
|
60
67
|
import { useBrowserDownloads } from "../hooks/use-browser-downloads";
|
|
61
|
-
import { useBrowserSession } from "../hooks/use-browser-session";
|
|
68
|
+
import { useBrowserSession, type BrowserFrameInputFence } from "../hooks/use-browser-session";
|
|
62
69
|
import { useBrowserSessions } from "../hooks/use-browser-sessions";
|
|
63
70
|
import { useInteractionInterventions } from "../hooks/use-interaction-interventions";
|
|
71
|
+
import { useSiteAuthConnections } from "../hooks/use-site-auth-connections";
|
|
64
72
|
import { cn } from "../lib/cn";
|
|
65
73
|
import { copyTextToClipboard } from "../lib/clipboard";
|
|
66
74
|
import { formatBytes } from "../lib/format";
|
|
75
|
+
import {
|
|
76
|
+
isInteractionControlUnavailable,
|
|
77
|
+
isSourcePlacementChangedError,
|
|
78
|
+
} from "../lib/interaction-errors";
|
|
67
79
|
import type { EmbeddedBrowserInteractionClientOverride } from "../session-context";
|
|
68
80
|
import { browserKey, HUMAN_BROWSER_HOME_URL, normalizeBrowserAddress } from "./browser-input";
|
|
69
81
|
import { InteractionInterventionBanner } from "./interaction-intervention-banner";
|
|
82
|
+
import { BrowserSelectControl } from "./browser-select-control";
|
|
83
|
+
import { useViewerMenuDismiss } from "./use-viewer-menu-dismiss";
|
|
70
84
|
|
|
71
85
|
export type BrowserViewerNotification = {
|
|
72
86
|
kind: "error" | "info";
|
|
@@ -77,6 +91,9 @@ export type BrowserViewerProps = EmbeddedBrowserInteractionClientOverride & {
|
|
|
77
91
|
/** The selected OpenGeni agent/session. Peer BrowserSessions stay discoverable. */
|
|
78
92
|
sessionId: string;
|
|
79
93
|
enabled?: boolean | undefined;
|
|
94
|
+
/** Whether the Browser tab is selected. Keeps the mounted media stream warm
|
|
95
|
+
* while pausing semantic reads behind other dock tabs. */
|
|
96
|
+
active?: boolean | undefined;
|
|
80
97
|
className?: string | undefined;
|
|
81
98
|
onNotify?: ((notification: BrowserViewerNotification) => void) | undefined;
|
|
82
99
|
/** Tests/demos only. Production uses the browser's native WebSocket. */
|
|
@@ -93,18 +110,28 @@ export type BrowserViewerProps = EmbeddedBrowserInteractionClientOverride & {
|
|
|
93
110
|
| undefined;
|
|
94
111
|
/** Navigate to the exact linked ComputerSession; never a lookalike desktop. */
|
|
95
112
|
onOpenComputer?: ((computerSessionId: string) => void) | undefined;
|
|
113
|
+
/** Restore and report the BrowserSession selected in this task's dock. */
|
|
114
|
+
initialBrowserSessionId?: string | null | undefined;
|
|
115
|
+
onBrowserSessionIdChange?: ((browserSessionId: string | null) => void) | undefined;
|
|
116
|
+
/** Host-owned install/setup surface for attaching an existing Chrome profile.
|
|
117
|
+
* The machine bridge remains discoverable even when this URL is omitted. */
|
|
118
|
+
browserExtensionSetupUrl?: string | undefined;
|
|
96
119
|
};
|
|
97
120
|
|
|
98
121
|
type BrowserSelection = { sessionId: string; pinned: boolean } | null;
|
|
99
122
|
type BrowserLaunchChoice =
|
|
100
123
|
| { kind: "clean" }
|
|
101
|
-
| { kind: "profile"; identityId: string }
|
|
124
|
+
| { kind: "profile"; identityId: string; baseRevisionId?: string }
|
|
102
125
|
| { kind: "attached"; device: AttachedBrowserDevice };
|
|
126
|
+
// ArrayBuffer is opaque to React development prop diagnostics; a Uint8Array
|
|
127
|
+
// exposes every image byte as an enumerable property retained in timing entries.
|
|
128
|
+
type BrowserViewportFrame = Omit<BrowserFrame, "data"> & { data: ArrayBuffer };
|
|
129
|
+
|
|
103
130
|
type PointerStart = {
|
|
104
131
|
x: number;
|
|
105
132
|
y: number;
|
|
106
133
|
pointerId: number;
|
|
107
|
-
frame:
|
|
134
|
+
frame: BrowserViewportFrame;
|
|
108
135
|
};
|
|
109
136
|
type BrowserResumeAttempt = {
|
|
110
137
|
operationId: string;
|
|
@@ -128,32 +155,78 @@ type BrowserDiagnosticsView = {
|
|
|
128
155
|
export function BrowserViewer({
|
|
129
156
|
sessionId,
|
|
130
157
|
enabled = true,
|
|
158
|
+
active = true,
|
|
131
159
|
className,
|
|
132
160
|
onNotify,
|
|
133
161
|
webSocketFactory,
|
|
134
162
|
renderEmpty,
|
|
135
163
|
createLinkedComputer,
|
|
136
164
|
onOpenComputer,
|
|
165
|
+
initialBrowserSessionId,
|
|
166
|
+
onBrowserSessionIdChange,
|
|
167
|
+
browserExtensionSetupUrl,
|
|
137
168
|
...override
|
|
138
169
|
}: BrowserViewerProps) {
|
|
139
170
|
const registry = useBrowserSessions({ ...override, sessionId, enabled });
|
|
140
171
|
const attached = useAttachedBrowsers({ ...override, enabled });
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
[profiles.identities],
|
|
172
|
+
const onlineAttachedBridges = useMemo(
|
|
173
|
+
() => attached.bridges.filter((bridge) => bridge.state === "online"),
|
|
174
|
+
[attached.bridges],
|
|
145
175
|
);
|
|
176
|
+
const profiles = useBrowserIdentities({ ...override, enabled, includeArchived: true });
|
|
177
|
+
const siteAuth = useSiteAuthConnections({
|
|
178
|
+
...override,
|
|
179
|
+
enabled: enabled && profiles.identities.length > 0,
|
|
180
|
+
});
|
|
146
181
|
const createRegistryBrowser = registry.create;
|
|
182
|
+
const refreshRegistry = registry.refresh;
|
|
147
183
|
const loadProfileRevisions = profiles.revisions;
|
|
148
184
|
const liveSessions = useMemo(
|
|
149
185
|
() => registry.sessions.filter((session) => isLiveBrowser(session)),
|
|
150
186
|
[registry.sessions],
|
|
151
187
|
);
|
|
188
|
+
const attachedGenerationLoss = useMemo(
|
|
189
|
+
() => attachedChromeGenerationLoss(registry.sessions),
|
|
190
|
+
[registry.sessions],
|
|
191
|
+
);
|
|
192
|
+
const endedGenerationLossRef = useRef(new Set<string>());
|
|
193
|
+
const endStaleBrowser = registry.end;
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (!enabled) return;
|
|
196
|
+
for (const session of registry.sessions) {
|
|
197
|
+
if (
|
|
198
|
+
session.lifecycle !== "lost" ||
|
|
199
|
+
session.failureCode !== "controller_transition_expired" ||
|
|
200
|
+
session.placement.kind !== "attached_device"
|
|
201
|
+
) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (endedGenerationLossRef.current.has(session.id)) continue;
|
|
205
|
+
endedGenerationLossRef.current.add(session.id);
|
|
206
|
+
void endStaleBrowser(session.id).catch(() => {
|
|
207
|
+
endedGenerationLossRef.current.delete(session.id);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}, [enabled, endStaleBrowser, registry.sessions]);
|
|
152
211
|
const relevant = useMemo(
|
|
153
212
|
() => registry.relevantSessions.filter((session) => isLiveBrowser(session)),
|
|
154
213
|
[registry.relevantSessions],
|
|
155
214
|
);
|
|
156
|
-
const
|
|
215
|
+
const latestRelevantBrowser = registry.relevantSessions[0];
|
|
216
|
+
const unavailableBrowser =
|
|
217
|
+
latestRelevantBrowser?.lifecycle === "lost" || latestRelevantBrowser?.lifecycle === "failed"
|
|
218
|
+
? latestRelevantBrowser
|
|
219
|
+
: undefined;
|
|
220
|
+
const [selection, setSelection] = useState<BrowserSelection>(() =>
|
|
221
|
+
initialBrowserSessionId ? { sessionId: initialBrowserSessionId, pinned: true } : null,
|
|
222
|
+
);
|
|
223
|
+
const selectBrowserSession = useCallback(
|
|
224
|
+
(next: BrowserSelection) => {
|
|
225
|
+
setSelection(next);
|
|
226
|
+
onBrowserSessionIdChange?.(next?.sessionId ?? null);
|
|
227
|
+
},
|
|
228
|
+
[onBrowserSessionIdChange],
|
|
229
|
+
);
|
|
157
230
|
const interventions = useInteractionInterventions({
|
|
158
231
|
...override,
|
|
159
232
|
enabled,
|
|
@@ -175,6 +248,7 @@ export function BrowserViewer({
|
|
|
175
248
|
const previousSessionIdRef = useRef(sessionId);
|
|
176
249
|
const seenInterventionIdsRef = useRef(new Set<string>());
|
|
177
250
|
const diagnosticRequestRef = useRef(0);
|
|
251
|
+
const viewportFocusHandoffRef = useRef<string | null>(null);
|
|
178
252
|
const [diagnosticsView, setDiagnosticsView] = useState<BrowserDiagnosticsView | null>(null);
|
|
179
253
|
|
|
180
254
|
const notifyError = useCallback(
|
|
@@ -188,11 +262,14 @@ export function BrowserViewer({
|
|
|
188
262
|
useEffect(() => {
|
|
189
263
|
if (previousSessionIdRef.current !== sessionId) {
|
|
190
264
|
previousSessionIdRef.current = sessionId;
|
|
191
|
-
setSelection(
|
|
265
|
+
setSelection(
|
|
266
|
+
initialBrowserSessionId ? { sessionId: initialBrowserSessionId, pinned: true } : null,
|
|
267
|
+
);
|
|
192
268
|
}
|
|
193
|
-
}, [sessionId]);
|
|
269
|
+
}, [initialBrowserSessionId, sessionId]);
|
|
194
270
|
|
|
195
271
|
useEffect(() => {
|
|
272
|
+
if (!enabled || registry.loading) return;
|
|
196
273
|
const selectedStillLive = liveSessions.some((session) => session.id === selection?.sessionId);
|
|
197
274
|
// A manually selected workspace browser stays selected, but an unrelated
|
|
198
275
|
// browser must never become the implicit browser for this agent. That made
|
|
@@ -200,15 +277,15 @@ export function BrowserViewer({
|
|
|
200
277
|
if (selection?.pinned && selectedStillLive) return;
|
|
201
278
|
const preferred = relevant[0] ?? null;
|
|
202
279
|
if (!preferred) {
|
|
203
|
-
if (selection)
|
|
280
|
+
if (selection) selectBrowserSession(null);
|
|
204
281
|
return;
|
|
205
282
|
}
|
|
206
283
|
if (!selectedStillLive || !selection?.pinned) {
|
|
207
284
|
if (selection?.sessionId !== preferred.id || selection.pinned) {
|
|
208
|
-
|
|
285
|
+
selectBrowserSession({ sessionId: preferred.id, pinned: false });
|
|
209
286
|
}
|
|
210
287
|
}
|
|
211
|
-
}, [liveSessions, relevant, selection]);
|
|
288
|
+
}, [enabled, liveSessions, registry.loading, relevant, selectBrowserSession, selection]);
|
|
212
289
|
|
|
213
290
|
const selectedRegistrySession = useMemo(
|
|
214
291
|
() => liveSessions.find((session) => session.id === selection?.sessionId) ?? null,
|
|
@@ -227,6 +304,7 @@ export function BrowserViewer({
|
|
|
227
304
|
);
|
|
228
305
|
const controllerReady = selectedRegistrySession?.lifecycle === "active";
|
|
229
306
|
const resumeSession = registry.resume;
|
|
307
|
+
const [hasLiveFrame, setHasLiveFrame] = useState(false);
|
|
230
308
|
|
|
231
309
|
const wakeBrowser = useCallback(
|
|
232
310
|
async (session: BrowserSession, retry = false): Promise<void> => {
|
|
@@ -277,18 +355,19 @@ export function BrowserViewer({
|
|
|
277
355
|
setResumeAttempt((visible) =>
|
|
278
356
|
visible?.sessionId === selectedRegistrySession.id ? null : visible,
|
|
279
357
|
);
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (selectedRegistrySession.lifecycle === "suspended") {
|
|
283
|
-
void wakeBrowser(selectedRegistrySession);
|
|
284
358
|
}
|
|
285
|
-
}, [selectedRegistrySession
|
|
359
|
+
}, [selectedRegistrySession]);
|
|
286
360
|
|
|
287
361
|
const browser = useBrowserSession({
|
|
288
362
|
...override,
|
|
289
363
|
browserSessionId: selection?.sessionId ?? null,
|
|
290
364
|
enabled: enabled && selection !== null && controllerReady,
|
|
365
|
+
semanticObservationEnabled: active && !hasLiveFrame,
|
|
291
366
|
});
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
if (!isSourcePlacementChangedError(browser.error, "browser_session")) return;
|
|
369
|
+
void refreshRegistry();
|
|
370
|
+
}, [browser.error, refreshRegistry]);
|
|
292
371
|
const downloads = useBrowserDownloads({
|
|
293
372
|
...override,
|
|
294
373
|
browserSessionId: selection?.sessionId ?? null,
|
|
@@ -342,19 +421,47 @@ export function BrowserViewer({
|
|
|
342
421
|
stream: { format: "jpeg", quality: 76, maxWidth: 1_920, maxHeight: 1_200 },
|
|
343
422
|
...(webSocketFactory ? { webSocketFactory } : {}),
|
|
344
423
|
});
|
|
345
|
-
const
|
|
424
|
+
const receivedFrame = frameMatchesSelectedTarget(
|
|
346
425
|
frames.frame,
|
|
347
426
|
browser.session,
|
|
348
427
|
browser.selectedTarget,
|
|
349
428
|
)
|
|
350
429
|
? frames.frame
|
|
351
430
|
: null;
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
431
|
+
const displayedFrame = useMemo<BrowserViewportFrame | null>(
|
|
432
|
+
() => (receivedFrame ? { ...receivedFrame, data: receivedFrame.data.slice().buffer } : null),
|
|
433
|
+
[receivedFrame],
|
|
434
|
+
);
|
|
435
|
+
const frameIsLive = frames.state === "live" && displayedFrame !== null;
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
setHasLiveFrame(frameIsLive);
|
|
438
|
+
}, [frameIsLive]);
|
|
439
|
+
const supportsLiveFrames =
|
|
440
|
+
(browser.session ?? selectedRegistrySession)?.capabilities.liveFrames === true;
|
|
441
|
+
const controlUnavailable = isInteractionControlUnavailable(browser.error);
|
|
442
|
+
const connectionError = controlUnavailable ? browser.error : (frames.error ?? browser.error);
|
|
443
|
+
// A managed controller can reject a stale attachment while the same browser
|
|
444
|
+
// remains healthy. Only extension-attached Chrome requires a new browser on
|
|
445
|
+
// connection-generation loss; an unrelated lost Chrome must not poison the
|
|
446
|
+
// selected managed browser's recovery UI.
|
|
447
|
+
const selectedPlacement = (browser.session ?? selectedRegistrySession)?.placement;
|
|
448
|
+
const selectedChromeGenerationLoss =
|
|
449
|
+
selectedPlacement?.kind === "attached_device" &&
|
|
450
|
+
isAttachedChromeGenerationLossError(connectionError);
|
|
451
|
+
const replacementChromeDevice =
|
|
452
|
+
selectedChromeGenerationLoss && selectedPlacement.kind === "attached_device"
|
|
453
|
+
? attached.devices.find((candidate) => candidate.id === selectedPlacement.deviceId)
|
|
454
|
+
: null;
|
|
455
|
+
const displayConnectionState =
|
|
456
|
+
connectionError && (!frameIsLive || controlUnavailable)
|
|
457
|
+
? "error"
|
|
458
|
+
: !browser.session
|
|
459
|
+
? "connecting"
|
|
460
|
+
: supportsLiveFrames
|
|
461
|
+
? frames.state === "live" && !displayedFrame
|
|
462
|
+
? "connecting"
|
|
463
|
+
: frames.state
|
|
464
|
+
: "semantic";
|
|
358
465
|
const selectedProfile = useMemo(
|
|
359
466
|
() =>
|
|
360
467
|
profiles.identities.find(
|
|
@@ -363,6 +470,15 @@ export function BrowserViewer({
|
|
|
363
470
|
) ?? null,
|
|
364
471
|
[browser.session?.identityId, profiles.identities, selectedRegistrySession?.identityId],
|
|
365
472
|
);
|
|
473
|
+
const selectedProfileAuth = useMemo(
|
|
474
|
+
() =>
|
|
475
|
+
selectedProfile
|
|
476
|
+
? siteAuth.connections.filter(
|
|
477
|
+
(connection) => connection.preferredIdentityId === selectedProfile.id,
|
|
478
|
+
)
|
|
479
|
+
: [],
|
|
480
|
+
[selectedProfile, siteAuth.connections],
|
|
481
|
+
);
|
|
366
482
|
|
|
367
483
|
useEffect(() => {
|
|
368
484
|
diagnosticRequestRef.current += 1;
|
|
@@ -469,8 +585,15 @@ export function BrowserViewer({
|
|
|
469
585
|
if (creating) return;
|
|
470
586
|
const identity =
|
|
471
587
|
choice.kind === "profile"
|
|
472
|
-
?
|
|
588
|
+
? profiles.identities.find((candidate) => candidate.id === choice.identityId)
|
|
473
589
|
: null;
|
|
590
|
+
if (choice.kind === "profile" && (!identity || identity.status !== "active")) {
|
|
591
|
+
onNotify?.({
|
|
592
|
+
kind: "error",
|
|
593
|
+
message: "This browser profile is no longer available. Refresh and choose another.",
|
|
594
|
+
});
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
474
597
|
const device = choice.kind === "attached" ? choice.device : null;
|
|
475
598
|
const browserName =
|
|
476
599
|
device?.profileLabel ?? device?.name ?? (identity ? `${identity.name} browser` : "Browser");
|
|
@@ -481,7 +604,7 @@ export function BrowserViewer({
|
|
|
481
604
|
if (createLinkedComputer) {
|
|
482
605
|
try {
|
|
483
606
|
linkedComputer = await createLinkedComputer(
|
|
484
|
-
`${browserName}
|
|
607
|
+
`${browserName} desktop`,
|
|
485
608
|
device ? { kind: "attached_device", deviceId: device.id } : undefined,
|
|
486
609
|
);
|
|
487
610
|
} catch {
|
|
@@ -514,13 +637,20 @@ export function BrowserViewer({
|
|
|
514
637
|
placement: linkedComputer.placement,
|
|
515
638
|
}
|
|
516
639
|
: {}),
|
|
517
|
-
...(identity
|
|
640
|
+
...(identity
|
|
641
|
+
? {
|
|
642
|
+
identityId: identity.id,
|
|
643
|
+
...(choice.kind === "profile" && choice.baseRevisionId
|
|
644
|
+
? { baseRevisionId: choice.baseRevisionId }
|
|
645
|
+
: {}),
|
|
646
|
+
}
|
|
647
|
+
: {}),
|
|
518
648
|
});
|
|
519
|
-
|
|
649
|
+
selectBrowserSession({ sessionId: response.session.id, pinned: true });
|
|
520
650
|
if (computerViewUnavailable) {
|
|
521
651
|
onNotify?.({
|
|
522
652
|
kind: "info",
|
|
523
|
-
message: "Browser opened.
|
|
653
|
+
message: "Browser opened. Desktop view is unavailable on this placement.",
|
|
524
654
|
});
|
|
525
655
|
}
|
|
526
656
|
})()
|
|
@@ -533,7 +663,8 @@ export function BrowserViewer({
|
|
|
533
663
|
creating,
|
|
534
664
|
notifyError,
|
|
535
665
|
onNotify,
|
|
536
|
-
|
|
666
|
+
selectBrowserSession,
|
|
667
|
+
profiles.identities,
|
|
537
668
|
sessionId,
|
|
538
669
|
],
|
|
539
670
|
);
|
|
@@ -647,30 +778,45 @@ export function BrowserViewer({
|
|
|
647
778
|
);
|
|
648
779
|
}
|
|
649
780
|
if (liveSessions.length === 0) {
|
|
650
|
-
if (renderEmpty
|
|
781
|
+
if (renderEmpty && !unavailableBrowser)
|
|
782
|
+
return <>{renderEmpty(() => createBrowser(), creating)}</>;
|
|
651
783
|
return (
|
|
652
784
|
<div
|
|
653
785
|
className={cn("flex h-full min-h-0 items-center justify-center bg-og-bg p-6", className)}
|
|
654
786
|
>
|
|
655
787
|
<div className="max-w-sm text-center">
|
|
656
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
788
|
+
<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">
|
|
657
789
|
<Globe2Icon className="size-4.5" />
|
|
658
790
|
</span>
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
791
|
+
{unavailableBrowser && !attachedGenerationLoss ? (
|
|
792
|
+
<BrowserUnavailableNotice session={unavailableBrowser} />
|
|
793
|
+
) : (
|
|
794
|
+
<>
|
|
795
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">
|
|
796
|
+
{attachedGenerationLoss
|
|
797
|
+
? "Chrome reconnected—open a fresh browser/desktop."
|
|
798
|
+
: "No browser open"}
|
|
799
|
+
</p>
|
|
800
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
801
|
+
{attachedGenerationLoss
|
|
802
|
+
? "This Chrome profile is live again, but the previous browser cannot move to the new connection. Use Browser → New browser → Connected Chrome."
|
|
803
|
+
: "A browser appears here when this agent—or another agent in the workspace—opens one."}
|
|
804
|
+
</p>
|
|
805
|
+
</>
|
|
806
|
+
)}
|
|
663
807
|
<BrowserLaunchMenu
|
|
808
|
+
attachedBridges={onlineAttachedBridges}
|
|
664
809
|
attachedDevices={attached.devices}
|
|
665
810
|
identities={profiles.identities}
|
|
666
811
|
creating={creating}
|
|
667
812
|
mutatingProfile={savingProfile}
|
|
668
813
|
onCreate={createBrowser}
|
|
669
814
|
onRestore={(identity) => updateProfile(identity, { status: "active" })}
|
|
815
|
+
setupUrl={browserExtensionSetupUrl}
|
|
670
816
|
prominent
|
|
671
817
|
/>
|
|
672
818
|
{registry.error ? (
|
|
673
|
-
<p className="mt-3 text-og-control text-og-
|
|
819
|
+
<p className="mt-3 text-og-control text-og-danger">{registry.error.message}</p>
|
|
674
820
|
) : null}
|
|
675
821
|
</div>
|
|
676
822
|
</div>
|
|
@@ -686,23 +832,31 @@ export function BrowserViewer({
|
|
|
686
832
|
relevantSessionIds={new Set(relevant.map((session) => session.id))}
|
|
687
833
|
selectedSessionId={selection?.sessionId ?? null}
|
|
688
834
|
identities={profiles.identities}
|
|
835
|
+
attachedBridges={onlineAttachedBridges}
|
|
689
836
|
attachedDevices={attached.devices}
|
|
690
837
|
selectedProfile={selectedProfile}
|
|
691
838
|
baseRevisionOrdinal={baseRevisionOrdinal}
|
|
692
839
|
profileHistory={profileHistory}
|
|
840
|
+
authConnections={selectedProfileAuth}
|
|
693
841
|
creating={creating}
|
|
694
842
|
savingProfile={savingProfile}
|
|
695
843
|
refreshing={registry.refreshing || attached.refreshing}
|
|
696
844
|
interventionCounts={interventionCounts}
|
|
697
|
-
onSelect={(browserSessionId) =>
|
|
845
|
+
onSelect={(browserSessionId) =>
|
|
846
|
+
selectBrowserSession({ sessionId: browserSessionId, pinned: true })
|
|
847
|
+
}
|
|
698
848
|
onFollow={() => {
|
|
699
849
|
const preferred = relevant[0];
|
|
700
|
-
if (preferred)
|
|
850
|
+
if (preferred) selectBrowserSession({ sessionId: preferred.id, pinned: false });
|
|
701
851
|
}}
|
|
702
852
|
onCreate={createBrowser}
|
|
703
853
|
onSaveProfile={saveProfileVersion}
|
|
704
854
|
onUpdateProfile={updateProfile}
|
|
855
|
+
onOpenProfileVersion={(identityId, baseRevisionId) =>
|
|
856
|
+
createBrowser({ kind: "profile", identityId, baseRevisionId })
|
|
857
|
+
}
|
|
705
858
|
onRefresh={() => void Promise.all([registry.refresh(), attached.refresh()])}
|
|
859
|
+
setupUrl={browserExtensionSetupUrl}
|
|
706
860
|
/>
|
|
707
861
|
<InteractionInterventionBanner
|
|
708
862
|
interventions={selectedInterventions}
|
|
@@ -717,6 +871,7 @@ export function BrowserViewer({
|
|
|
717
871
|
/>
|
|
718
872
|
{!selectedRegistrySession ? (
|
|
719
873
|
<BrowserUnselectedPanel
|
|
874
|
+
unavailableBrowser={unavailableBrowser}
|
|
720
875
|
peerCount={liveSessions.length}
|
|
721
876
|
creating={creating}
|
|
722
877
|
onCreate={() => createBrowser()}
|
|
@@ -760,6 +915,22 @@ export function BrowserViewer({
|
|
|
760
915
|
.act({ type: "navigate", url })
|
|
761
916
|
.catch((cause) => notifyError(cause, "Could not navigate."))
|
|
762
917
|
}
|
|
918
|
+
onHistory={async (direction) => {
|
|
919
|
+
try {
|
|
920
|
+
const action = { type: "history", direction } as const;
|
|
921
|
+
if (receivedFrame) await browser.actFromFrame(action, receivedFrame);
|
|
922
|
+
else {
|
|
923
|
+
if (!browser.observation)
|
|
924
|
+
throw new Error("The browser page is not ready for input.");
|
|
925
|
+
await browser.actFromObservation(action, browser.observation);
|
|
926
|
+
}
|
|
927
|
+
} catch (cause) {
|
|
928
|
+
notifyError(
|
|
929
|
+
cause,
|
|
930
|
+
direction === "back" ? "Could not go back." : "Could not go forward.",
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}}
|
|
763
934
|
onReload={() => {
|
|
764
935
|
const url = browser.selectedTarget?.url;
|
|
765
936
|
if (url)
|
|
@@ -768,33 +939,136 @@ export function BrowserViewer({
|
|
|
768
939
|
.catch((cause) => notifyError(cause, "Could not reload."));
|
|
769
940
|
}}
|
|
770
941
|
/>
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
942
|
+
{browser.inputFailure ? (
|
|
943
|
+
<div
|
|
944
|
+
role="alert"
|
|
945
|
+
aria-label="Browser input status"
|
|
946
|
+
className="flex shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-3 py-2 text-og-xs"
|
|
947
|
+
>
|
|
948
|
+
<CircleAlertIcon className="size-4 shrink-0 text-og-status-failed" />
|
|
949
|
+
<div className="min-w-0 flex-1">
|
|
950
|
+
<span className="font-medium">
|
|
951
|
+
{browser.inputFailure.state === "failed"
|
|
952
|
+
? "Browser input failed"
|
|
953
|
+
: "Input result unknown"}
|
|
954
|
+
</span>
|
|
955
|
+
<p className="text-og-fg-subtle">
|
|
956
|
+
{browser.inputFailure.error?.message ?? "Inspect the page before continuing."}
|
|
957
|
+
</p>
|
|
958
|
+
</div>
|
|
959
|
+
<button
|
|
960
|
+
type="button"
|
|
961
|
+
onClick={() => void browser.refresh()}
|
|
962
|
+
className="shrink-0 rounded border border-og-border px-2 py-1 transition hover:bg-og-surface-2"
|
|
963
|
+
>
|
|
964
|
+
Check browser
|
|
965
|
+
</button>
|
|
966
|
+
</div>
|
|
967
|
+
) : null}
|
|
968
|
+
<div className="relative flex min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
969
|
+
<BrowserViewport
|
|
970
|
+
// A new control scope must discard every buffered gesture and queued action.
|
|
971
|
+
key={JSON.stringify([
|
|
972
|
+
selection?.sessionId,
|
|
973
|
+
browser.selectedTarget?.id,
|
|
974
|
+
browser.selectedTarget?.targetGeneration,
|
|
975
|
+
browser.selectedTarget?.documentGeneration,
|
|
976
|
+
frames.attachment?.controllerGeneration,
|
|
977
|
+
])}
|
|
978
|
+
focusHandoffRef={viewportFocusHandoffRef}
|
|
979
|
+
focusScope={JSON.stringify([
|
|
980
|
+
selection?.sessionId,
|
|
981
|
+
browser.selectedTarget?.id,
|
|
982
|
+
browser.selectedTarget?.targetGeneration,
|
|
983
|
+
])}
|
|
984
|
+
frame={displayedFrame}
|
|
985
|
+
connectionState={displayConnectionState}
|
|
986
|
+
supportsLiveFrames={supportsLiveFrames}
|
|
987
|
+
connectionError={connectionError}
|
|
988
|
+
controlUnavailable={controlUnavailable}
|
|
989
|
+
observation={browser.observation}
|
|
990
|
+
mutating={browser.mutating || savingProfile}
|
|
991
|
+
activityLabel={savingProfile ? "Saving browser version…" : undefined}
|
|
992
|
+
clipboardEnabled={browser.session?.capabilities.clipboard === true}
|
|
993
|
+
inputBatchAttachment={frames.attachment}
|
|
994
|
+
onAction={async (action, frame) => {
|
|
995
|
+
const focusedInput =
|
|
996
|
+
frame &&
|
|
997
|
+
action.type === "pointer" &&
|
|
998
|
+
action.action === "click" &&
|
|
999
|
+
(action.button === undefined || action.button === "left") &&
|
|
1000
|
+
frames.attachment?.focusedInputObservations === true &&
|
|
1001
|
+
frames.attachment.browserSessionId === frame.browserSessionId &&
|
|
1002
|
+
frames.attachment.controllerGeneration === frame.controllerGeneration &&
|
|
1003
|
+
frames.attachment.targetId === frame.targetId &&
|
|
1004
|
+
Date.parse(frames.attachment.expiresAt) > Date.now();
|
|
1005
|
+
const receipt = frame
|
|
1006
|
+
? await browser.actFromFrame(
|
|
1007
|
+
action,
|
|
1008
|
+
frame,
|
|
1009
|
+
undefined,
|
|
1010
|
+
focusedInput ? "input" : "none",
|
|
1011
|
+
)
|
|
1012
|
+
: await browser.act(action);
|
|
1013
|
+
if (receipt.state !== "completed") {
|
|
1014
|
+
throw new Error(receipt.error?.message ?? "Browser input did not complete.");
|
|
1015
|
+
}
|
|
1016
|
+
return receipt;
|
|
1017
|
+
}}
|
|
1018
|
+
onReadClipboard={browser.readClipboard}
|
|
1019
|
+
onObserveForInput={browser.observeForInput}
|
|
1020
|
+
onSelectFromObservation={browser.actFromObservation}
|
|
1021
|
+
onReconnect={
|
|
1022
|
+
selectedChromeGenerationLoss
|
|
1023
|
+
? () => {
|
|
1024
|
+
if (replacementChromeDevice) {
|
|
1025
|
+
createBrowser({ kind: "attached", device: replacementChromeDevice });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
: () => {
|
|
1029
|
+
void browser.refresh();
|
|
1030
|
+
frames.reconnect();
|
|
1031
|
+
}
|
|
786
1032
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1033
|
+
reconnectLabel={
|
|
1034
|
+
selectedChromeGenerationLoss && replacementChromeDevice
|
|
1035
|
+
? "Open a fresh Connected Chrome"
|
|
1036
|
+
: undefined
|
|
1037
|
+
}
|
|
1038
|
+
reconnectMessage={
|
|
1039
|
+
selectedChromeGenerationLoss
|
|
1040
|
+
? "Chrome reconnected—open a fresh browser/desktop."
|
|
1041
|
+
: undefined
|
|
1042
|
+
}
|
|
1043
|
+
onError={(cause) => notifyError(cause, "Browser input failed.")}
|
|
1044
|
+
/>
|
|
1045
|
+
{diagnosticsView ? (
|
|
1046
|
+
<BrowserDiagnosticsDrawer
|
|
1047
|
+
session={browser.session}
|
|
1048
|
+
profile={selectedProfile}
|
|
1049
|
+
target={browser.selectedTarget}
|
|
1050
|
+
observation={browser.observation}
|
|
1051
|
+
baseRevisionOrdinal={baseRevisionOrdinal}
|
|
1052
|
+
state={diagnosticsView}
|
|
1053
|
+
downloads={downloads.downloads}
|
|
1054
|
+
downloadsEnabled={browser.session?.capabilities.downloads === true}
|
|
1055
|
+
downloadsLoading={downloads.loading}
|
|
1056
|
+
downloadsRefreshing={downloads.refreshing}
|
|
1057
|
+
downloadsError={downloads.error}
|
|
1058
|
+
savingDownloadIds={downloads.savingDownloadIds}
|
|
1059
|
+
onSaveDownload={saveDownload}
|
|
1060
|
+
onRefreshDownloads={() => void downloads.refresh()}
|
|
1061
|
+
onRefresh={refreshDiagnostics}
|
|
1062
|
+
onClose={closeDiagnostics}
|
|
1063
|
+
/>
|
|
1064
|
+
) : null}
|
|
1065
|
+
</div>
|
|
793
1066
|
<BrowserStatusBar
|
|
794
1067
|
session={browser.session}
|
|
795
1068
|
profile={selectedProfile}
|
|
796
1069
|
target={browser.selectedTarget}
|
|
797
1070
|
observation={browser.observation}
|
|
1071
|
+
inputFailure={browser.inputFailure}
|
|
798
1072
|
connectionState={displayConnectionState}
|
|
799
1073
|
refreshing={registry.refreshing}
|
|
800
1074
|
diagnosticsOpen={diagnosticsView !== null}
|
|
@@ -810,26 +1084,6 @@ export function BrowserViewer({
|
|
|
810
1084
|
/>
|
|
811
1085
|
</>
|
|
812
1086
|
)}
|
|
813
|
-
{diagnosticsView ? (
|
|
814
|
-
<BrowserDiagnosticsDrawer
|
|
815
|
-
session={browser.session}
|
|
816
|
-
profile={selectedProfile}
|
|
817
|
-
target={browser.selectedTarget}
|
|
818
|
-
observation={browser.observation}
|
|
819
|
-
baseRevisionOrdinal={baseRevisionOrdinal}
|
|
820
|
-
state={diagnosticsView}
|
|
821
|
-
downloads={downloads.downloads}
|
|
822
|
-
downloadsEnabled={browser.session?.capabilities.downloads === true}
|
|
823
|
-
downloadsLoading={downloads.loading}
|
|
824
|
-
downloadsRefreshing={downloads.refreshing}
|
|
825
|
-
downloadsError={downloads.error}
|
|
826
|
-
savingDownloadIds={downloads.savingDownloadIds}
|
|
827
|
-
onSaveDownload={saveDownload}
|
|
828
|
-
onRefreshDownloads={() => void downloads.refresh()}
|
|
829
|
-
onRefresh={refreshDiagnostics}
|
|
830
|
-
onClose={closeDiagnostics}
|
|
831
|
-
/>
|
|
832
|
-
) : null}
|
|
833
1087
|
</div>
|
|
834
1088
|
);
|
|
835
1089
|
}
|
|
@@ -857,20 +1111,20 @@ function BrowserLifecyclePanel(props: {
|
|
|
857
1111
|
return (
|
|
858
1112
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center">
|
|
859
1113
|
<div className="max-w-sm">
|
|
860
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
1114
|
+
<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">
|
|
861
1115
|
{failed ? (
|
|
862
|
-
<CircleAlertIcon className="size-4.5 text-og-
|
|
1116
|
+
<CircleAlertIcon className="size-4.5 text-og-danger" />
|
|
863
1117
|
) : (
|
|
864
1118
|
<LoaderCircleIcon className="size-4.5 animate-spin" />
|
|
865
1119
|
)}
|
|
866
1120
|
</span>
|
|
867
1121
|
<p className="mt-3 text-og-menu font-medium text-og-fg">{label}</p>
|
|
868
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">{detail}</p>
|
|
1122
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">{detail}</p>
|
|
869
1123
|
{failed || (props.session.lifecycle === "suspended" && !props.attempt?.running) ? (
|
|
870
1124
|
<button
|
|
871
1125
|
type="button"
|
|
872
1126
|
onClick={props.onResume}
|
|
873
|
-
className="mt-4 inline-flex h-8 items-center rounded-og-sm border border-og-border bg-og-
|
|
1127
|
+
className="mt-4 inline-flex h-8 items-center rounded-og-sm border border-og-primary-border bg-og-primary px-3 text-og-control font-medium text-og-primary-fg transition hover:bg-og-primary-hover"
|
|
874
1128
|
>
|
|
875
1129
|
Open browser
|
|
876
1130
|
</button>
|
|
@@ -884,11 +1138,13 @@ function BrowserToolbar(props: {
|
|
|
884
1138
|
sessions: BrowserSession[];
|
|
885
1139
|
relevantSessionIds: Set<string>;
|
|
886
1140
|
selectedSessionId: string | null;
|
|
1141
|
+
attachedBridges: AttachedBrowserBridge[];
|
|
887
1142
|
attachedDevices: AttachedBrowserDevice[];
|
|
888
1143
|
identities: BrowserIdentity[];
|
|
889
1144
|
selectedProfile: BrowserIdentity | null;
|
|
890
1145
|
baseRevisionOrdinal: number | null;
|
|
891
1146
|
profileHistory: { identityId: string; loading: boolean; revisions: BrowserRevision[] } | null;
|
|
1147
|
+
authConnections: SiteAuthConnection[];
|
|
892
1148
|
creating: boolean;
|
|
893
1149
|
savingProfile: boolean;
|
|
894
1150
|
refreshing: boolean;
|
|
@@ -901,9 +1157,11 @@ function BrowserToolbar(props: {
|
|
|
901
1157
|
identity: BrowserIdentity,
|
|
902
1158
|
update: { name?: string; status?: "active" | "archived"; defaultRevisionId?: string },
|
|
903
1159
|
) => Promise<boolean>;
|
|
1160
|
+
onOpenProfileVersion: (identityId: string, baseRevisionId: string) => void;
|
|
904
1161
|
onRefresh: () => void;
|
|
1162
|
+
setupUrl?: string | undefined;
|
|
905
1163
|
}) {
|
|
906
|
-
const detailsRef =
|
|
1164
|
+
const detailsRef = useViewerMenuDismiss();
|
|
907
1165
|
const selected = props.sessions.find((session) => session.id === props.selectedSessionId);
|
|
908
1166
|
const current = props.sessions.filter((session) => props.relevantSessionIds.has(session.id));
|
|
909
1167
|
const others = props.sessions.filter((session) => !props.relevantSessionIds.has(session.id));
|
|
@@ -912,34 +1170,36 @@ function BrowserToolbar(props: {
|
|
|
912
1170
|
detailsRef.current?.removeAttribute("open");
|
|
913
1171
|
};
|
|
914
1172
|
return (
|
|
915
|
-
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-
|
|
916
|
-
<details ref={detailsRef} className="
|
|
1173
|
+
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-1 px-2">
|
|
1174
|
+
<details ref={detailsRef} className="min-w-0">
|
|
917
1175
|
<summary className="flex h-7 max-w-52 cursor-pointer list-none items-center gap-2 rounded-og-sm px-2 text-og-control text-og-fg transition hover:bg-og-surface-2 [&::-webkit-details-marker]:hidden">
|
|
918
|
-
<Globe2Icon className="size-3.5 shrink-0 text-og-muted" />
|
|
1176
|
+
<Globe2Icon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
919
1177
|
<span className="truncate font-medium">{selected?.name ?? "Browser"}</span>
|
|
920
1178
|
{(props.interventionCounts.get(selected?.id ?? "") ?? 0) > 0 ? (
|
|
921
1179
|
<span className="size-1.5 shrink-0 rounded-full bg-og-status-waiting" />
|
|
922
1180
|
) : null}
|
|
923
|
-
<ChevronDownIcon className="size-3 shrink-0 text-og-subtle" />
|
|
1181
|
+
<ChevronDownIcon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
924
1182
|
</summary>
|
|
925
|
-
<div className="absolute left-
|
|
926
|
-
<
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1183
|
+
<div className="absolute left-2 top-10 z-30 flex max-h-[calc(100%-3rem)] w-72 max-w-[calc(100%-1rem)] flex-col overflow-hidden rounded-og-md border border-og-border bg-og-surface-1 p-1 shadow-xl">
|
|
1184
|
+
<div className="min-h-0 max-h-96 overflow-y-auto overscroll-contain">
|
|
1185
|
+
<BrowserSessionGroup
|
|
1186
|
+
label="Current agent"
|
|
1187
|
+
sessions={current}
|
|
1188
|
+
identities={props.identities}
|
|
1189
|
+
selectedId={props.selectedSessionId}
|
|
1190
|
+
interventionCounts={props.interventionCounts}
|
|
1191
|
+
onSelect={choose}
|
|
1192
|
+
/>
|
|
1193
|
+
<BrowserSessionGroup
|
|
1194
|
+
label="Workspace browsers"
|
|
1195
|
+
sessions={others}
|
|
1196
|
+
identities={props.identities}
|
|
1197
|
+
selectedId={props.selectedSessionId}
|
|
1198
|
+
interventionCounts={props.interventionCounts}
|
|
1199
|
+
onSelect={choose}
|
|
1200
|
+
/>
|
|
1201
|
+
</div>
|
|
1202
|
+
<div className="mt-1 flex shrink-0 gap-1 border-t border-og-border pt-1">
|
|
943
1203
|
{current.length > 0 ? (
|
|
944
1204
|
<MenuButton onClick={props.onFollow}>Follow agent</MenuButton>
|
|
945
1205
|
) : null}
|
|
@@ -949,7 +1209,7 @@ function BrowserToolbar(props: {
|
|
|
949
1209
|
</div>
|
|
950
1210
|
</div>
|
|
951
1211
|
</details>
|
|
952
|
-
<span className="min-w-0 flex-1 truncate text-og-xs text-og-subtle">
|
|
1212
|
+
<span className="min-w-0 flex-1 truncate text-og-xs text-og-fg-subtle">
|
|
953
1213
|
{placementLabel(selected)}
|
|
954
1214
|
</span>
|
|
955
1215
|
<BrowserProfileMenu
|
|
@@ -957,31 +1217,36 @@ function BrowserToolbar(props: {
|
|
|
957
1217
|
identity={props.selectedProfile}
|
|
958
1218
|
baseRevisionOrdinal={props.baseRevisionOrdinal}
|
|
959
1219
|
history={props.profileHistory}
|
|
1220
|
+
authConnections={props.authConnections}
|
|
960
1221
|
saving={props.savingProfile}
|
|
961
1222
|
onSave={props.onSaveProfile}
|
|
962
1223
|
onUpdate={props.onUpdateProfile}
|
|
1224
|
+
onOpenVersion={props.onOpenProfileVersion}
|
|
963
1225
|
/>
|
|
964
1226
|
<button
|
|
965
1227
|
type="button"
|
|
966
1228
|
onClick={props.onRefresh}
|
|
967
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
1229
|
+
className="grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
968
1230
|
aria-label="Refresh browsers"
|
|
969
1231
|
>
|
|
970
1232
|
<RefreshCwIcon className={cn("size-3.5", props.refreshing && "animate-spin")} />
|
|
971
1233
|
</button>
|
|
972
1234
|
<BrowserLaunchMenu
|
|
1235
|
+
attachedBridges={props.attachedBridges}
|
|
973
1236
|
attachedDevices={props.attachedDevices}
|
|
974
1237
|
identities={props.identities}
|
|
975
1238
|
creating={props.creating}
|
|
976
1239
|
mutatingProfile={props.savingProfile}
|
|
977
1240
|
onCreate={props.onCreate}
|
|
978
1241
|
onRestore={(identity) => props.onUpdateProfile(identity, { status: "active" })}
|
|
1242
|
+
setupUrl={props.setupUrl}
|
|
979
1243
|
/>
|
|
980
1244
|
</div>
|
|
981
1245
|
);
|
|
982
1246
|
}
|
|
983
1247
|
|
|
984
1248
|
function BrowserUnselectedPanel(props: {
|
|
1249
|
+
unavailableBrowser: BrowserSession | undefined;
|
|
985
1250
|
peerCount: number;
|
|
986
1251
|
creating: boolean;
|
|
987
1252
|
onCreate: () => void;
|
|
@@ -989,11 +1254,15 @@ function BrowserUnselectedPanel(props: {
|
|
|
989
1254
|
return (
|
|
990
1255
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center">
|
|
991
1256
|
<div className="max-w-sm">
|
|
992
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
1257
|
+
<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">
|
|
993
1258
|
<Globe2Icon className="size-4.5" />
|
|
994
1259
|
</span>
|
|
995
|
-
|
|
996
|
-
|
|
1260
|
+
{props.unavailableBrowser ? (
|
|
1261
|
+
<BrowserUnavailableNotice session={props.unavailableBrowser} />
|
|
1262
|
+
) : (
|
|
1263
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">No browser for this agent</p>
|
|
1264
|
+
)}
|
|
1265
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
997
1266
|
{props.peerCount === 1
|
|
998
1267
|
? "One workspace browser is available from the browser menu."
|
|
999
1268
|
: `${props.peerCount} workspace browsers are available from the browser menu.`}
|
|
@@ -1002,7 +1271,7 @@ function BrowserUnselectedPanel(props: {
|
|
|
1002
1271
|
type="button"
|
|
1003
1272
|
disabled={props.creating}
|
|
1004
1273
|
onClick={props.onCreate}
|
|
1005
|
-
className="mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-border bg-og-
|
|
1274
|
+
className="mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-primary-border bg-og-primary px-3 text-og-control font-medium text-og-primary-fg transition hover:bg-og-primary-hover disabled:cursor-not-allowed disabled:opacity-50"
|
|
1006
1275
|
>
|
|
1007
1276
|
<PlusIcon className="size-3.5" />
|
|
1008
1277
|
{props.creating ? "Opening…" : "New browser"}
|
|
@@ -1012,6 +1281,25 @@ function BrowserUnselectedPanel(props: {
|
|
|
1012
1281
|
);
|
|
1013
1282
|
}
|
|
1014
1283
|
|
|
1284
|
+
function BrowserUnavailableNotice({ session }: { session: BrowserSession }) {
|
|
1285
|
+
const reason =
|
|
1286
|
+
session.failureCode === "provider_deadline_rotation"
|
|
1287
|
+
? "The computer running it reached its time limit."
|
|
1288
|
+
: session.failureCode === "source_placement_changed"
|
|
1289
|
+
? "This chat moved to another computer."
|
|
1290
|
+
: session.lifecycle === "failed"
|
|
1291
|
+
? "It stopped unexpectedly."
|
|
1292
|
+
: "The connection to it was lost.";
|
|
1293
|
+
return (
|
|
1294
|
+
<div role="status">
|
|
1295
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">Browser unavailable</p>
|
|
1296
|
+
<p className="mt-1 break-words text-og-control leading-5 text-og-fg-muted">
|
|
1297
|
+
{session.name}. {reason} Open a new browser to continue.
|
|
1298
|
+
</p>
|
|
1299
|
+
</div>
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1015
1303
|
function BrowserSessionGroup(props: {
|
|
1016
1304
|
label: string;
|
|
1017
1305
|
sessions: BrowserSession[];
|
|
@@ -1023,7 +1311,7 @@ function BrowserSessionGroup(props: {
|
|
|
1023
1311
|
if (props.sessions.length === 0) return null;
|
|
1024
1312
|
return (
|
|
1025
1313
|
<div className="py-1">
|
|
1026
|
-
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1314
|
+
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1027
1315
|
{props.label}
|
|
1028
1316
|
</p>
|
|
1029
1317
|
{props.sessions.map((session) => {
|
|
@@ -1033,6 +1321,8 @@ function BrowserSessionGroup(props: {
|
|
|
1033
1321
|
<button
|
|
1034
1322
|
key={session.id}
|
|
1035
1323
|
type="button"
|
|
1324
|
+
aria-label={`${session.name}${identity ? ` · ${identity.name}` : ""} · ${placementLabel(session)}`}
|
|
1325
|
+
aria-pressed={session.id === props.selectedId}
|
|
1036
1326
|
onClick={() => props.onSelect(session.id)}
|
|
1037
1327
|
className={cn(
|
|
1038
1328
|
"flex w-full items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
|
|
@@ -1047,7 +1337,7 @@ function BrowserSessionGroup(props: {
|
|
|
1047
1337
|
/>
|
|
1048
1338
|
<span className="min-w-0 flex-1">
|
|
1049
1339
|
<span className="block truncate text-og-control text-og-fg">{session.name}</span>
|
|
1050
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1340
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1051
1341
|
{identity ? `${identity.name} · ` : ""}
|
|
1052
1342
|
{placementLabel(session)}
|
|
1053
1343
|
</span>
|
|
@@ -1065,15 +1355,17 @@ function BrowserSessionGroup(props: {
|
|
|
1065
1355
|
}
|
|
1066
1356
|
|
|
1067
1357
|
function BrowserLaunchMenu(props: {
|
|
1358
|
+
attachedBridges: AttachedBrowserBridge[];
|
|
1068
1359
|
attachedDevices: AttachedBrowserDevice[];
|
|
1069
1360
|
identities: BrowserIdentity[];
|
|
1070
1361
|
creating: boolean;
|
|
1071
1362
|
mutatingProfile: boolean;
|
|
1072
1363
|
onCreate: (choice?: BrowserLaunchChoice) => void;
|
|
1073
1364
|
onRestore: (identity: BrowserIdentity) => Promise<boolean>;
|
|
1365
|
+
setupUrl?: string | undefined;
|
|
1074
1366
|
prominent?: boolean;
|
|
1075
1367
|
}) {
|
|
1076
|
-
const detailsRef =
|
|
1368
|
+
const detailsRef = useViewerMenuDismiss();
|
|
1077
1369
|
const choose = (choice?: BrowserLaunchChoice) => {
|
|
1078
1370
|
detailsRef.current?.removeAttribute("open");
|
|
1079
1371
|
props.onCreate(choice);
|
|
@@ -1085,13 +1377,13 @@ function BrowserLaunchMenu(props: {
|
|
|
1085
1377
|
const activeIdentities = props.identities.filter((identity) => identity.status === "active");
|
|
1086
1378
|
const archivedIdentities = props.identities.filter((identity) => identity.status === "archived");
|
|
1087
1379
|
return (
|
|
1088
|
-
<details ref={detailsRef} className={cn(
|
|
1380
|
+
<details ref={detailsRef} className={cn(props.prominent && "mt-4 inline-block")}>
|
|
1089
1381
|
<summary
|
|
1090
1382
|
onClick={(event) => {
|
|
1091
1383
|
if (busy) event.preventDefault();
|
|
1092
1384
|
}}
|
|
1093
1385
|
className={cn(
|
|
1094
|
-
"cursor-pointer list-none items-center justify-center gap-1.5 text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden",
|
|
1386
|
+
"cursor-pointer list-none items-center justify-center gap-1.5 text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden",
|
|
1095
1387
|
props.prominent
|
|
1096
1388
|
? "inline-flex h-8 rounded-og-sm border border-og-border bg-og-surface-1 px-3 text-og-control font-medium text-og-fg"
|
|
1097
1389
|
: "grid size-7 place-items-center rounded-og-sm",
|
|
@@ -1109,13 +1401,13 @@ function BrowserLaunchMenu(props: {
|
|
|
1109
1401
|
</summary>
|
|
1110
1402
|
<div
|
|
1111
1403
|
className={cn(
|
|
1112
|
-
"absolute z-40 w-72 overflow-
|
|
1113
|
-
props.prominent ? "left-1/2 top-
|
|
1404
|
+
"absolute z-40 max-h-[calc(100%-3rem)] w-72 max-w-[calc(100%-1rem)] overflow-y-auto rounded-og-md border border-og-border bg-og-surface-1 p-1 text-left shadow-xl",
|
|
1405
|
+
props.prominent ? "left-1/2 top-2 -translate-x-1/2" : "right-2 top-10",
|
|
1114
1406
|
)}
|
|
1115
1407
|
>
|
|
1116
|
-
{props.attachedDevices.length > 0 ? (
|
|
1408
|
+
{props.attachedDevices.length > 0 || props.attachedBridges.length > 0 ? (
|
|
1117
1409
|
<div className="pb-1">
|
|
1118
|
-
<p className="px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1410
|
+
<p className="px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1119
1411
|
Connected Chrome
|
|
1120
1412
|
</p>
|
|
1121
1413
|
{props.attachedDevices.map((device) => (
|
|
@@ -1125,12 +1417,12 @@ function BrowserLaunchMenu(props: {
|
|
|
1125
1417
|
onClick={() => choose({ kind: "attached", device })}
|
|
1126
1418
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1127
1419
|
>
|
|
1128
|
-
<MonitorIcon className="size-3.5 shrink-0 text-og-muted" />
|
|
1420
|
+
<MonitorIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1129
1421
|
<span className="min-w-0 flex-1">
|
|
1130
1422
|
<span className="block truncate text-og-control text-og-fg">
|
|
1131
1423
|
{device.profileLabel ?? device.name}
|
|
1132
1424
|
</span>
|
|
1133
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1425
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1134
1426
|
{device.browserName} · {device.tabCount} open{" "}
|
|
1135
1427
|
{device.tabCount === 1 ? "tab" : "tabs"}
|
|
1136
1428
|
</span>
|
|
@@ -1138,12 +1430,48 @@ function BrowserLaunchMenu(props: {
|
|
|
1138
1430
|
<span className="size-1.5 shrink-0 rounded-full bg-og-status-running" />
|
|
1139
1431
|
</button>
|
|
1140
1432
|
))}
|
|
1433
|
+
{props.attachedBridges.length > 0 ? (
|
|
1434
|
+
props.setupUrl ? (
|
|
1435
|
+
<a
|
|
1436
|
+
href={props.setupUrl}
|
|
1437
|
+
target="_blank"
|
|
1438
|
+
rel="noreferrer"
|
|
1439
|
+
onClick={() => detailsRef.current?.removeAttribute("open")}
|
|
1440
|
+
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1441
|
+
>
|
|
1442
|
+
<PlusIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1443
|
+
<span className="min-w-0 flex-1">
|
|
1444
|
+
<span className="block truncate text-og-control text-og-fg">
|
|
1445
|
+
{props.attachedDevices.length > 0
|
|
1446
|
+
? "Connect another Chrome profile"
|
|
1447
|
+
: "Connect this Chrome profile"}
|
|
1448
|
+
</span>
|
|
1449
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1450
|
+
Machine connected · Chrome extension missing
|
|
1451
|
+
</span>
|
|
1452
|
+
</span>
|
|
1453
|
+
</a>
|
|
1454
|
+
) : (
|
|
1455
|
+
<div className="flex w-full items-center gap-2 px-2 py-2 text-left">
|
|
1456
|
+
<MonitorIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1457
|
+
<span className="min-w-0 flex-1">
|
|
1458
|
+
<span className="block truncate text-og-control text-og-fg">
|
|
1459
|
+
Chrome connection ready
|
|
1460
|
+
</span>
|
|
1461
|
+
<span className="block text-og-xs leading-4 text-og-fg-subtle">
|
|
1462
|
+
Install the Opengeni Browser extension in the profile you want to use.
|
|
1463
|
+
</span>
|
|
1464
|
+
</span>
|
|
1465
|
+
</div>
|
|
1466
|
+
)
|
|
1467
|
+
) : null}
|
|
1141
1468
|
</div>
|
|
1142
1469
|
) : null}
|
|
1143
1470
|
<p
|
|
1144
1471
|
className={cn(
|
|
1145
|
-
"px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle",
|
|
1146
|
-
props.attachedDevices.length > 0
|
|
1472
|
+
"px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle",
|
|
1473
|
+
(props.attachedDevices.length > 0 || props.attachedBridges.length > 0) &&
|
|
1474
|
+
"border-t border-og-border",
|
|
1147
1475
|
)}
|
|
1148
1476
|
>
|
|
1149
1477
|
Open isolated browser
|
|
@@ -1153,10 +1481,12 @@ function BrowserLaunchMenu(props: {
|
|
|
1153
1481
|
onClick={() => choose({ kind: "clean" })}
|
|
1154
1482
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1155
1483
|
>
|
|
1156
|
-
<Globe2Icon className="size-3.5 text-og-muted" />
|
|
1484
|
+
<Globe2Icon className="size-3.5 text-og-fg-muted" />
|
|
1157
1485
|
<span>
|
|
1158
1486
|
<span className="block text-og-control text-og-fg">Fresh browser</span>
|
|
1159
|
-
<span className="block text-og-xs text-og-subtle">
|
|
1487
|
+
<span className="block text-og-xs text-og-fg-subtle">
|
|
1488
|
+
Visual browser · no saved state
|
|
1489
|
+
</span>
|
|
1160
1490
|
</span>
|
|
1161
1491
|
</button>
|
|
1162
1492
|
{activeIdentities.length > 0 ? (
|
|
@@ -1168,10 +1498,10 @@ function BrowserLaunchMenu(props: {
|
|
|
1168
1498
|
onClick={() => choose({ kind: "profile", identityId: identity.id })}
|
|
1169
1499
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1170
1500
|
>
|
|
1171
|
-
<UserRoundIcon className="size-3.5 text-og-muted" />
|
|
1501
|
+
<UserRoundIcon className="size-3.5 text-og-fg-muted" />
|
|
1172
1502
|
<span className="min-w-0">
|
|
1173
1503
|
<span className="block truncate text-og-control text-og-fg">{identity.name}</span>
|
|
1174
|
-
<span className="block text-og-xs text-og-subtle">
|
|
1504
|
+
<span className="block text-og-xs text-og-fg-subtle">
|
|
1175
1505
|
{identity.defaultRevisionId
|
|
1176
1506
|
? `${identity.revisionCount} saved version${identity.revisionCount === 1 ? "" : "s"}`
|
|
1177
1507
|
: "Ready for first sign-in"}
|
|
@@ -1183,7 +1513,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1183
1513
|
) : null}
|
|
1184
1514
|
{archivedIdentities.length > 0 ? (
|
|
1185
1515
|
<details className="mt-1 border-t border-og-border pt-1">
|
|
1186
|
-
<summary className="flex cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 py-1.5 text-og-xs text-og-subtle transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden">
|
|
1516
|
+
<summary className="flex cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 py-1.5 text-og-xs text-og-fg-subtle transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden">
|
|
1187
1517
|
<ArchiveIcon className="size-3" /> Hidden profiles · {archivedIdentities.length}
|
|
1188
1518
|
<ChevronDownIcon className="ml-auto size-3" />
|
|
1189
1519
|
</summary>
|
|
@@ -1195,7 +1525,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1195
1525
|
onClick={() => void restore(identity)}
|
|
1196
1526
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2 disabled:opacity-50"
|
|
1197
1527
|
>
|
|
1198
|
-
<RotateCcwIcon className="size-3.5 text-og-muted" />
|
|
1528
|
+
<RotateCcwIcon className="size-3.5 text-og-fg-muted" />
|
|
1199
1529
|
<span className="min-w-0 flex-1 truncate text-og-control text-og-fg">
|
|
1200
1530
|
Restore {identity.name}
|
|
1201
1531
|
</span>
|
|
@@ -1213,14 +1543,16 @@ function BrowserProfileMenu(props: {
|
|
|
1213
1543
|
identity: BrowserIdentity | null;
|
|
1214
1544
|
baseRevisionOrdinal: number | null;
|
|
1215
1545
|
history: { identityId: string; loading: boolean; revisions: BrowserRevision[] } | null;
|
|
1546
|
+
authConnections: SiteAuthConnection[];
|
|
1216
1547
|
saving: boolean;
|
|
1217
1548
|
onSave: (newProfileName?: string) => Promise<boolean>;
|
|
1218
1549
|
onUpdate: (
|
|
1219
1550
|
identity: BrowserIdentity,
|
|
1220
1551
|
update: { name?: string; status?: "active" | "archived"; defaultRevisionId?: string },
|
|
1221
1552
|
) => Promise<boolean>;
|
|
1553
|
+
onOpenVersion: (identityId: string, baseRevisionId: string) => void;
|
|
1222
1554
|
}) {
|
|
1223
|
-
const detailsRef =
|
|
1555
|
+
const detailsRef = useViewerMenuDismiss();
|
|
1224
1556
|
const nameInputId = useId();
|
|
1225
1557
|
const [name, setName] = useState("");
|
|
1226
1558
|
const attached = props.session?.placement.kind === "attached_device";
|
|
@@ -1237,6 +1569,10 @@ function BrowserProfileMenu(props: {
|
|
|
1237
1569
|
detailsRef.current?.removeAttribute("open");
|
|
1238
1570
|
}
|
|
1239
1571
|
};
|
|
1572
|
+
const openVersion = (identityId: string, baseRevisionId: string) => {
|
|
1573
|
+
detailsRef.current?.removeAttribute("open");
|
|
1574
|
+
props.onOpenVersion(identityId, baseRevisionId);
|
|
1575
|
+
};
|
|
1240
1576
|
const versionLabel = attached
|
|
1241
1577
|
? "live"
|
|
1242
1578
|
: props.identity
|
|
@@ -1247,8 +1583,8 @@ function BrowserProfileMenu(props: {
|
|
|
1247
1583
|
: "unsaved"
|
|
1248
1584
|
: "temporary";
|
|
1249
1585
|
return (
|
|
1250
|
-
<details ref={detailsRef} className="
|
|
1251
|
-
<summary className="flex h-7 max-w-40 cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 text-og-xs text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden">
|
|
1586
|
+
<details ref={detailsRef} className="min-w-0">
|
|
1587
|
+
<summary className="flex h-7 max-w-40 cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 text-og-xs text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden">
|
|
1252
1588
|
{props.saving ? (
|
|
1253
1589
|
<LoaderCircleIcon className="size-3 animate-spin" />
|
|
1254
1590
|
) : attached ? (
|
|
@@ -1260,13 +1596,13 @@ function BrowserProfileMenu(props: {
|
|
|
1260
1596
|
{attached ? props.session?.name : (props.identity?.name ?? "Temporary")}
|
|
1261
1597
|
</span>
|
|
1262
1598
|
{attached || props.identity ? (
|
|
1263
|
-
<span className="shrink-0 text-og-subtle">· {versionLabel}</span>
|
|
1599
|
+
<span className="shrink-0 text-og-fg-subtle">· {versionLabel}</span>
|
|
1264
1600
|
) : null}
|
|
1265
|
-
<ChevronDownIcon className="size-3 shrink-0 text-og-subtle" />
|
|
1601
|
+
<ChevronDownIcon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
1266
1602
|
</summary>
|
|
1267
|
-
<div className="absolute right-
|
|
1603
|
+
<div className="absolute right-2 top-10 z-40 max-h-[calc(100%-3rem)] w-72 max-w-[calc(100%-1rem)] overflow-y-auto rounded-og-md border border-og-border bg-og-surface-1 p-3 shadow-xl">
|
|
1268
1604
|
<div className="flex items-start gap-2">
|
|
1269
|
-
<span className="grid size-7 shrink-0 place-items-center rounded-og-sm bg-og-surface-2 text-og-muted">
|
|
1605
|
+
<span className="grid size-7 shrink-0 place-items-center rounded-og-sm bg-og-surface-2 text-og-fg-muted">
|
|
1270
1606
|
{attached ? (
|
|
1271
1607
|
<MonitorIcon className="size-3.5" />
|
|
1272
1608
|
) : (
|
|
@@ -1277,7 +1613,7 @@ function BrowserProfileMenu(props: {
|
|
|
1277
1613
|
<p className="truncate text-og-control font-medium text-og-fg">
|
|
1278
1614
|
{attached ? props.session?.name : (props.identity?.name ?? "Temporary browser")}
|
|
1279
1615
|
</p>
|
|
1280
|
-
<p className="mt-0.5 text-og-xs leading-4 text-og-subtle">
|
|
1616
|
+
<p className="mt-0.5 text-og-xs leading-4 text-og-fg-subtle">
|
|
1281
1617
|
{attached
|
|
1282
1618
|
? "This session drives the existing Chrome profile and its current login state."
|
|
1283
1619
|
: props.identity?.status === "archived"
|
|
@@ -1292,53 +1628,101 @@ function BrowserProfileMenu(props: {
|
|
|
1292
1628
|
</div>
|
|
1293
1629
|
{props.identity && revisions.length > 0 ? (
|
|
1294
1630
|
<div className="mt-3 border-t border-og-border pt-2">
|
|
1295
|
-
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1631
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1296
1632
|
Saved versions
|
|
1297
1633
|
</p>
|
|
1298
1634
|
<div className="mt-1 max-h-40 overflow-y-auto">
|
|
1299
1635
|
{revisions.map((revision) => {
|
|
1300
1636
|
const isDefault = revision.id === props.identity?.defaultRevisionId;
|
|
1301
1637
|
const isCurrent = revision.ordinal === props.baseRevisionOrdinal;
|
|
1638
|
+
const materialization = revisionMaterializationSummary(revision);
|
|
1302
1639
|
return (
|
|
1303
|
-
<
|
|
1640
|
+
<div
|
|
1304
1641
|
key={revision.id}
|
|
1305
|
-
|
|
1306
|
-
disabled={isDefault || props.saving || props.identity?.status === "archived"}
|
|
1307
|
-
onClick={() =>
|
|
1308
|
-
void props.onUpdate(props.identity!, { defaultRevisionId: revision.id })
|
|
1309
|
-
}
|
|
1310
|
-
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2 disabled:cursor-default disabled:opacity-70"
|
|
1642
|
+
className="flex w-full items-center gap-1 rounded-og-sm transition hover:bg-og-surface-2"
|
|
1311
1643
|
>
|
|
1312
|
-
<
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
? "
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1644
|
+
<button
|
|
1645
|
+
type="button"
|
|
1646
|
+
disabled={props.saving || props.identity?.status === "archived"}
|
|
1647
|
+
aria-label={`Open ${props.identity!.name} version ${revision.ordinal}`}
|
|
1648
|
+
onClick={() => openVersion(props.identity!.id, revision.id)}
|
|
1649
|
+
className="flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-left disabled:opacity-50"
|
|
1650
|
+
>
|
|
1651
|
+
<span className="grid size-4 place-items-center text-og-fg-muted">
|
|
1652
|
+
{isCurrent ? <CheckIcon className="size-3.5 text-og-accent" /> : null}
|
|
1653
|
+
</span>
|
|
1654
|
+
<span className="min-w-0 flex-1">
|
|
1655
|
+
<span className="block text-og-control text-og-fg">
|
|
1656
|
+
Version {revision.ordinal}
|
|
1657
|
+
</span>
|
|
1658
|
+
<span className="block truncate text-[10px] text-og-fg-subtle">
|
|
1659
|
+
{materialization.label}
|
|
1660
|
+
</span>
|
|
1661
|
+
</span>
|
|
1662
|
+
{isCurrent ? (
|
|
1663
|
+
<span className="text-[10px] text-og-fg-subtle">Current</span>
|
|
1664
|
+
) : null}
|
|
1665
|
+
</button>
|
|
1666
|
+
<button
|
|
1667
|
+
type="button"
|
|
1668
|
+
disabled={isDefault || props.saving || props.identity?.status === "archived"}
|
|
1669
|
+
aria-label={
|
|
1670
|
+
isDefault
|
|
1671
|
+
? `${props.identity!.name} version ${revision.ordinal} is the default`
|
|
1672
|
+
: `Use ${props.identity!.name} version ${revision.ordinal} by default`
|
|
1673
|
+
}
|
|
1674
|
+
title={isDefault ? "Default version" : "Use by default"}
|
|
1675
|
+
onClick={() =>
|
|
1676
|
+
void props.onUpdate(props.identity!, { defaultRevisionId: revision.id })
|
|
1677
|
+
}
|
|
1678
|
+
className="mr-1 shrink-0 rounded-og-sm px-2 py-1 text-[10px] text-og-fg-subtle transition hover:bg-og-surface-3 hover:text-og-fg disabled:cursor-default disabled:opacity-70"
|
|
1679
|
+
>
|
|
1680
|
+
{isDefault ? "Default" : "Make default"}
|
|
1681
|
+
</button>
|
|
1682
|
+
</div>
|
|
1328
1683
|
);
|
|
1329
1684
|
})}
|
|
1330
1685
|
</div>
|
|
1331
|
-
<p className="mt-1 px-2 text-[10px] leading-4 text-og-subtle">
|
|
1332
|
-
Changing the default affects only browsers opened later.
|
|
1686
|
+
<p className="mt-1 px-2 text-[10px] leading-4 text-og-fg-subtle">
|
|
1687
|
+
Changing the default affects only browsers opened later. Saved browser data can be
|
|
1688
|
+
copied; a website may still expire or re-verify its own session.
|
|
1333
1689
|
</p>
|
|
1334
1690
|
</div>
|
|
1335
1691
|
) : props.identity &&
|
|
1336
1692
|
props.history?.identityId === props.identity.id &&
|
|
1337
1693
|
props.history.loading ? (
|
|
1338
|
-
<p className="mt-3 flex items-center gap-1.5 border-t border-og-border px-2 pt-2 text-og-xs text-og-subtle">
|
|
1694
|
+
<p className="mt-3 flex items-center gap-1.5 border-t border-og-border px-2 pt-2 text-og-xs text-og-fg-subtle">
|
|
1339
1695
|
<LoaderCircleIcon className="size-3 animate-spin" /> Loading saved versions…
|
|
1340
1696
|
</p>
|
|
1341
1697
|
) : null}
|
|
1698
|
+
{props.identity && props.authConnections.length > 0 ? (
|
|
1699
|
+
<div className="mt-3 border-t border-og-border pt-2">
|
|
1700
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1701
|
+
Login health
|
|
1702
|
+
</p>
|
|
1703
|
+
<div className="mt-1 space-y-1">
|
|
1704
|
+
{props.authConnections.map((connection) => {
|
|
1705
|
+
const health = siteAuthHealth(connection);
|
|
1706
|
+
return (
|
|
1707
|
+
<div
|
|
1708
|
+
key={connection.id}
|
|
1709
|
+
className="flex items-start gap-2 rounded-og-sm px-2 py-1.5"
|
|
1710
|
+
>
|
|
1711
|
+
<span className={cn("mt-1 size-1.5 shrink-0 rounded-full", health.dotClass)} />
|
|
1712
|
+
<span className="min-w-0 flex-1">
|
|
1713
|
+
<span className="block truncate text-og-control text-og-fg">
|
|
1714
|
+
{connection.name}
|
|
1715
|
+
</span>
|
|
1716
|
+
<span className="block truncate text-[10px] text-og-fg-subtle">
|
|
1717
|
+
{connection.accountLabel} · {health.label}
|
|
1718
|
+
</span>
|
|
1719
|
+
</span>
|
|
1720
|
+
</div>
|
|
1721
|
+
);
|
|
1722
|
+
})}
|
|
1723
|
+
</div>
|
|
1724
|
+
</div>
|
|
1725
|
+
) : null}
|
|
1342
1726
|
{canSave ? (
|
|
1343
1727
|
props.identity ? (
|
|
1344
1728
|
<button
|
|
@@ -1362,7 +1746,7 @@ function BrowserProfileMenu(props: {
|
|
|
1362
1746
|
if (name.trim()) void save(name);
|
|
1363
1747
|
}}
|
|
1364
1748
|
>
|
|
1365
|
-
<label className="text-og-xs font-medium text-og-muted" htmlFor={nameInputId}>
|
|
1749
|
+
<label className="text-og-xs font-medium text-og-fg-muted" htmlFor={nameInputId}>
|
|
1366
1750
|
Profile name
|
|
1367
1751
|
</label>
|
|
1368
1752
|
<div className="mt-1 flex gap-1.5">
|
|
@@ -1372,12 +1756,12 @@ function BrowserProfileMenu(props: {
|
|
|
1372
1756
|
onInput={(event) => setName(event.currentTarget.value)}
|
|
1373
1757
|
maxLength={200}
|
|
1374
1758
|
placeholder="Work"
|
|
1375
|
-
className="h-8 min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-bg px-2 text-og-control text-og-fg placeholder:text-og-subtle focus:border-og-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent"
|
|
1759
|
+
className="h-8 min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-bg px-2 text-og-control text-og-fg placeholder:text-og-fg-subtle focus:border-og-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent"
|
|
1376
1760
|
/>
|
|
1377
1761
|
<button
|
|
1378
1762
|
type="submit"
|
|
1379
1763
|
disabled={!name.trim() || props.saving}
|
|
1380
|
-
className="inline-flex h-8 items-center gap-1 rounded-og-sm bg-og-
|
|
1764
|
+
className="inline-flex h-8 items-center gap-1 rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-2.5 text-og-control font-medium transition hover:bg-og-primary-hover disabled:opacity-50"
|
|
1381
1765
|
>
|
|
1382
1766
|
{props.saving ? (
|
|
1383
1767
|
<LoaderCircleIcon className="size-3.5 animate-spin" />
|
|
@@ -1390,9 +1774,9 @@ function BrowserProfileMenu(props: {
|
|
|
1390
1774
|
</form>
|
|
1391
1775
|
)
|
|
1392
1776
|
) : (
|
|
1393
|
-
<p className="mt-3 rounded-og-sm bg-og-surface-2 px-2.5 py-2 text-og-xs text-og-subtle">
|
|
1777
|
+
<p className="mt-3 rounded-og-sm bg-og-surface-2 px-2.5 py-2 text-og-xs text-og-fg-subtle">
|
|
1394
1778
|
{attached
|
|
1395
|
-
? "Chrome keeps this profile's state directly;
|
|
1779
|
+
? "Chrome keeps this profile's state directly; Opengeni does not copy it automatically."
|
|
1396
1780
|
: props.identity?.status === "archived"
|
|
1397
1781
|
? "Restore this profile before saving another version."
|
|
1398
1782
|
: "This browser cannot save reusable profile state."}
|
|
@@ -1407,7 +1791,7 @@ function BrowserProfileMenu(props: {
|
|
|
1407
1791
|
status: props.identity!.status === "active" ? "archived" : "active",
|
|
1408
1792
|
})
|
|
1409
1793
|
}
|
|
1410
|
-
className="mt-2 flex h-8 w-full items-center justify-center gap-1.5 rounded-og-sm px-3 text-og-control text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
1794
|
+
className="mt-2 flex h-8 w-full items-center justify-center gap-1.5 rounded-og-sm px-3 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
1411
1795
|
>
|
|
1412
1796
|
{props.identity.status === "active" ? (
|
|
1413
1797
|
<ArchiveIcon className="size-3.5" />
|
|
@@ -1418,8 +1802,9 @@ function BrowserProfileMenu(props: {
|
|
|
1418
1802
|
</button>
|
|
1419
1803
|
) : null}
|
|
1420
1804
|
{canSave ? (
|
|
1421
|
-
<p className="mt-2 text-[10px] leading-4 text-og-subtle">
|
|
1422
|
-
Saving
|
|
1805
|
+
<p className="mt-2 text-[10px] leading-4 text-og-fg-subtle">
|
|
1806
|
+
Saving restarts this browser. Save unfinished work on its pages first; unsaved form
|
|
1807
|
+
entries may be lost. Other open browsers are unchanged.
|
|
1423
1808
|
</p>
|
|
1424
1809
|
) : null}
|
|
1425
1810
|
</div>
|
|
@@ -1427,13 +1812,52 @@ function BrowserProfileMenu(props: {
|
|
|
1427
1812
|
);
|
|
1428
1813
|
}
|
|
1429
1814
|
|
|
1815
|
+
function revisionMaterializationSummary(revision: BrowserRevision): { label: string } {
|
|
1816
|
+
const materializations = revision.components.map((component) => component.materialization);
|
|
1817
|
+
const placementBound = materializations.find(
|
|
1818
|
+
(materialization) => materialization.portability === "placement_bound",
|
|
1819
|
+
);
|
|
1820
|
+
if (placementBound) {
|
|
1821
|
+
return {
|
|
1822
|
+
label: placementBound.reason
|
|
1823
|
+
? `This machine only · ${placementBound.reason}`
|
|
1824
|
+
: "This machine only",
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
const providerBound = materializations.find(
|
|
1828
|
+
(materialization) => materialization.portability === "provider_bound",
|
|
1829
|
+
);
|
|
1830
|
+
if (providerBound) {
|
|
1831
|
+
return {
|
|
1832
|
+
label: providerBound.reason ? `Provider-bound · ${providerBound.reason}` : "Provider-bound",
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
return { label: "Portable browser data" };
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function siteAuthHealth(connection: SiteAuthConnection): {
|
|
1839
|
+
label: string;
|
|
1840
|
+
dotClass: string;
|
|
1841
|
+
} {
|
|
1842
|
+
switch (connection.verificationState) {
|
|
1843
|
+
case "verified":
|
|
1844
|
+
return { label: "Verified", dotClass: "bg-og-status-running" };
|
|
1845
|
+
case "needs_repair":
|
|
1846
|
+
return { label: "Sign-in needs attention", dotClass: "bg-og-status-waiting" };
|
|
1847
|
+
case "failed":
|
|
1848
|
+
return { label: "Verification failed", dotClass: "bg-og-status-error" };
|
|
1849
|
+
case "unknown":
|
|
1850
|
+
return { label: "Not checked yet", dotClass: "bg-og-muted" };
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1430
1854
|
function MenuButton(props: { children: ReactNode; onClick: () => void; disabled?: boolean }) {
|
|
1431
1855
|
return (
|
|
1432
1856
|
<button
|
|
1433
1857
|
type="button"
|
|
1434
1858
|
onClick={props.onClick}
|
|
1435
1859
|
disabled={props.disabled}
|
|
1436
|
-
className="flex h-7 flex-1 items-center justify-center gap-1 rounded-og-sm px-2 text-og-control text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
1860
|
+
className="flex h-7 flex-1 items-center justify-center gap-1 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
1437
1861
|
>
|
|
1438
1862
|
{props.children}
|
|
1439
1863
|
</button>
|
|
@@ -1451,7 +1875,7 @@ function BrowserTabs(props: {
|
|
|
1451
1875
|
}) {
|
|
1452
1876
|
const pages = props.targets.filter((target) => target.kind === "page" || target.kind === "popup");
|
|
1453
1877
|
return (
|
|
1454
|
-
<div className="flex h-9 shrink-0 items-end gap-px overflow-x-auto border-b border-og-border bg-og-surface-
|
|
1878
|
+
<div className="flex h-9 shrink-0 items-end gap-px overflow-x-auto border-b border-og-border bg-og-surface-1 px-1 pt-1">
|
|
1455
1879
|
{pages.map((target) => (
|
|
1456
1880
|
<div
|
|
1457
1881
|
key={target.id}
|
|
@@ -1459,7 +1883,7 @@ function BrowserTabs(props: {
|
|
|
1459
1883
|
"group flex h-8 min-w-24 max-w-48 items-center rounded-t-og-sm border border-b-0 border-transparent px-2",
|
|
1460
1884
|
target.id === props.selectedTargetId
|
|
1461
1885
|
? "border-og-border bg-og-bg text-og-fg"
|
|
1462
|
-
: "text-og-muted hover:bg-og-surface-1",
|
|
1886
|
+
: "text-og-fg-muted hover:bg-og-surface-1",
|
|
1463
1887
|
)}
|
|
1464
1888
|
>
|
|
1465
1889
|
<button
|
|
@@ -1487,7 +1911,7 @@ function BrowserTabs(props: {
|
|
|
1487
1911
|
type="button"
|
|
1488
1912
|
onClick={props.onOpen}
|
|
1489
1913
|
disabled={props.mutating}
|
|
1490
|
-
className="mb-1 grid size-6 shrink-0 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40"
|
|
1914
|
+
className="mb-1 grid size-6 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40"
|
|
1491
1915
|
aria-label="New tab"
|
|
1492
1916
|
>
|
|
1493
1917
|
<PlusIcon className="size-3.5" />
|
|
@@ -1501,34 +1925,73 @@ function BrowserAddressBar(props: {
|
|
|
1501
1925
|
target: BrowserTarget | null;
|
|
1502
1926
|
loading: boolean;
|
|
1503
1927
|
onNavigate: (url: string) => void;
|
|
1928
|
+
onHistory: (direction: "back" | "forward") => Promise<void>;
|
|
1504
1929
|
onReload: () => void;
|
|
1505
1930
|
}) {
|
|
1506
1931
|
const [draft, setDraft] = useState(props.target?.url ?? "");
|
|
1507
1932
|
const focusedRef = useRef(false);
|
|
1933
|
+
const historyPendingRef = useRef(false);
|
|
1934
|
+
const [historyPending, setHistoryPending] = useState(false);
|
|
1508
1935
|
useEffect(() => {
|
|
1509
1936
|
if (!focusedRef.current) setDraft(props.target?.url ?? "");
|
|
1510
1937
|
}, [props.target?.id, props.target?.url]);
|
|
1511
1938
|
const submit = (event: FormEvent) => {
|
|
1512
1939
|
event.preventDefault();
|
|
1940
|
+
if (historyPendingRef.current) return;
|
|
1513
1941
|
const normalized = normalizeBrowserAddress(draft);
|
|
1514
1942
|
if (normalized) props.onNavigate(normalized);
|
|
1515
1943
|
};
|
|
1944
|
+
const navigateHistory = async (direction: "back" | "forward") => {
|
|
1945
|
+
if (!props.target || props.loading || historyPendingRef.current) return;
|
|
1946
|
+
historyPendingRef.current = true;
|
|
1947
|
+
setHistoryPending(true);
|
|
1948
|
+
try {
|
|
1949
|
+
await props.onHistory(direction);
|
|
1950
|
+
} finally {
|
|
1951
|
+
historyPendingRef.current = false;
|
|
1952
|
+
setHistoryPending(false);
|
|
1953
|
+
}
|
|
1954
|
+
};
|
|
1516
1955
|
return (
|
|
1517
1956
|
<form
|
|
1518
1957
|
onSubmit={submit}
|
|
1958
|
+
aria-busy={historyPending || undefined}
|
|
1519
1959
|
className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-bg px-2"
|
|
1520
1960
|
>
|
|
1961
|
+
{(["back", "forward"] as const).map((direction) => (
|
|
1962
|
+
<button
|
|
1963
|
+
key={direction}
|
|
1964
|
+
type="button"
|
|
1965
|
+
onClick={() => void navigateHistory(direction)}
|
|
1966
|
+
disabled={!props.target || props.loading}
|
|
1967
|
+
aria-disabled={historyPending || undefined}
|
|
1968
|
+
className={cn(
|
|
1969
|
+
"grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-35",
|
|
1970
|
+
historyPending && "opacity-50",
|
|
1971
|
+
)}
|
|
1972
|
+
aria-label={direction === "back" ? "Back" : "Forward"}
|
|
1973
|
+
title={direction === "back" ? "Back" : "Forward"}
|
|
1974
|
+
>
|
|
1975
|
+
{direction === "back" ? (
|
|
1976
|
+
<ArrowLeftIcon className="size-3.5" />
|
|
1977
|
+
) : (
|
|
1978
|
+
<ArrowRightIcon className="size-3.5" />
|
|
1979
|
+
)}
|
|
1980
|
+
</button>
|
|
1981
|
+
))}
|
|
1521
1982
|
<button
|
|
1522
1983
|
type="button"
|
|
1523
1984
|
onClick={props.onReload}
|
|
1524
|
-
disabled={!props.target || props.loading}
|
|
1525
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-35"
|
|
1985
|
+
disabled={!props.target || props.loading || historyPending}
|
|
1986
|
+
className="grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-35"
|
|
1526
1987
|
aria-label="Reload"
|
|
1527
1988
|
>
|
|
1528
|
-
<RefreshCwIcon
|
|
1989
|
+
<RefreshCwIcon
|
|
1990
|
+
className={cn("size-3.5", (props.loading || historyPending) && "animate-spin")}
|
|
1991
|
+
/>
|
|
1529
1992
|
</button>
|
|
1530
|
-
<div className="flex h-7 min-w-0 flex-1 items-center gap-2 rounded-og-sm border border-og-border bg-og-surface-
|
|
1531
|
-
<Globe2Icon className="size-3 shrink-0 text-og-subtle" />
|
|
1993
|
+
<div className="flex h-7 min-w-0 flex-1 items-center gap-2 rounded-og-sm border border-og-border bg-og-surface-1 px-2 focus-within:border-og-accent/60">
|
|
1994
|
+
<Globe2Icon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
1532
1995
|
<input
|
|
1533
1996
|
value={draft}
|
|
1534
1997
|
onChange={(event) => setDraft(event.target.value)}
|
|
@@ -1541,11 +2004,12 @@ function BrowserAddressBar(props: {
|
|
|
1541
2004
|
onKeyDown={(event) => {
|
|
1542
2005
|
if (event.key !== "Enter") return;
|
|
1543
2006
|
event.preventDefault();
|
|
2007
|
+
if (historyPendingRef.current) return;
|
|
1544
2008
|
const normalized = normalizeBrowserAddress(draft);
|
|
1545
2009
|
if (normalized) props.onNavigate(normalized);
|
|
1546
2010
|
}}
|
|
1547
2011
|
disabled={!props.target}
|
|
1548
|
-
className="min-w-0 flex-1 bg-transparent text-og-control text-og-fg placeholder:text-og-subtle focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50"
|
|
2012
|
+
className="min-w-0 flex-1 bg-transparent text-og-control text-og-fg placeholder:text-og-fg-subtle focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50"
|
|
1549
2013
|
aria-label="Address"
|
|
1550
2014
|
spellCheck={false}
|
|
1551
2015
|
placeholder="Search or enter address"
|
|
@@ -1556,53 +2020,107 @@ function BrowserAddressBar(props: {
|
|
|
1556
2020
|
}
|
|
1557
2021
|
|
|
1558
2022
|
function BrowserViewport(props: {
|
|
1559
|
-
|
|
2023
|
+
focusHandoffRef: { current: string | null };
|
|
2024
|
+
focusScope: string;
|
|
2025
|
+
frame: BrowserViewportFrame | null;
|
|
1560
2026
|
connectionState: string;
|
|
1561
2027
|
supportsLiveFrames: boolean;
|
|
1562
2028
|
connectionError: Error | null;
|
|
2029
|
+
controlUnavailable: boolean;
|
|
1563
2030
|
observation: ReturnType<typeof useBrowserSession>["observation"];
|
|
1564
2031
|
mutating: boolean;
|
|
1565
2032
|
clipboardEnabled: boolean;
|
|
2033
|
+
inputBatchAttachment?: {
|
|
2034
|
+
browserSessionId: string;
|
|
2035
|
+
controllerGeneration: string;
|
|
2036
|
+
targetId: string;
|
|
2037
|
+
fencedInputBatches?: true | undefined;
|
|
2038
|
+
focusedInputObservations?: true | undefined;
|
|
2039
|
+
expiresAt: string;
|
|
2040
|
+
} | null;
|
|
1566
2041
|
activityLabel?: string | undefined;
|
|
1567
|
-
onAction: (
|
|
2042
|
+
onAction: (
|
|
2043
|
+
action: BrowserAction | BrowserActionBatch,
|
|
2044
|
+
frame: BrowserFrameInputFence | null,
|
|
2045
|
+
) => Promise<BrowserActionReceipt>;
|
|
1568
2046
|
onReadClipboard: () => Promise<{ text: string }>;
|
|
2047
|
+
onObserveForInput: () => Promise<BrowserObservation>;
|
|
2048
|
+
onSelectFromObservation: (
|
|
2049
|
+
action: BrowserAction,
|
|
2050
|
+
observation: BrowserObservation,
|
|
2051
|
+
) => Promise<BrowserActionReceipt>;
|
|
1569
2052
|
onReconnect: () => void;
|
|
2053
|
+
reconnectLabel?: string | undefined;
|
|
2054
|
+
reconnectMessage?: string | undefined;
|
|
1570
2055
|
onError: (cause: unknown) => void;
|
|
1571
2056
|
}) {
|
|
1572
2057
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
1573
2058
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
2059
|
+
const [selectPopup, setSelectPopup] = useState<{
|
|
2060
|
+
observation: BrowserObservation | null;
|
|
2061
|
+
anchor: { x: number; y: number } | null;
|
|
2062
|
+
} | null>(null);
|
|
2063
|
+
const inputSequenceRef = useRef(0);
|
|
2064
|
+
const dismissSelectPopup = useCallback(() => {
|
|
2065
|
+
inputSequenceRef.current += 1;
|
|
2066
|
+
setSelectPopup({ observation: null, anchor: null });
|
|
2067
|
+
}, []);
|
|
1574
2068
|
const composingRef = useRef(false);
|
|
1575
2069
|
const pointerStartRef = useRef<PointerStart | null>(null);
|
|
1576
2070
|
const lastClickRef = useRef<{
|
|
1577
2071
|
at: number;
|
|
1578
2072
|
x: number;
|
|
1579
2073
|
y: number;
|
|
1580
|
-
frame:
|
|
2074
|
+
frame: BrowserViewportFrame;
|
|
1581
2075
|
} | null>(null);
|
|
1582
2076
|
const wheelRef = useRef<{
|
|
1583
2077
|
x: number;
|
|
1584
2078
|
y: number;
|
|
1585
2079
|
deltaX: number;
|
|
1586
2080
|
deltaY: number;
|
|
1587
|
-
frame:
|
|
2081
|
+
frame: BrowserViewportFrame;
|
|
1588
2082
|
timer: ReturnType<typeof setTimeout> | null;
|
|
1589
2083
|
} | null>(null);
|
|
1590
2084
|
const pendingTextRef = useRef<{
|
|
1591
2085
|
text: string;
|
|
1592
|
-
frame:
|
|
2086
|
+
frame: BrowserViewportFrame | null;
|
|
1593
2087
|
timer: ReturnType<typeof setTimeout>;
|
|
1594
2088
|
} | null>(null);
|
|
1595
2089
|
const actionRef = useRef(props.onAction);
|
|
1596
2090
|
const readClipboardRef = useRef(props.onReadClipboard);
|
|
2091
|
+
const observeInputRef = useRef(props.onObserveForInput);
|
|
1597
2092
|
const errorRef = useRef(props.onError);
|
|
1598
2093
|
const actionTailRef = useRef<Promise<void>>(Promise.resolve());
|
|
1599
|
-
const
|
|
2094
|
+
const actionQueueEpochRef = useRef(0);
|
|
2095
|
+
const queuedTypingRef = useRef<{
|
|
2096
|
+
actions: BrowserAction[];
|
|
2097
|
+
frame: BrowserFrameInputFence;
|
|
2098
|
+
epoch: number;
|
|
2099
|
+
} | null>(null);
|
|
2100
|
+
const queuedFrameRef = useRef<BrowserViewportFrame | null>(null);
|
|
2101
|
+
const currentFrameRef = useRef<BrowserViewportFrame | null>(props.frame);
|
|
2102
|
+
const paintedFrameRef = useRef<BrowserViewportFrame | null>(null);
|
|
2103
|
+
const [paintedFrame, setPaintedFrame] = useState<BrowserViewportFrame | null>(null);
|
|
1600
2104
|
const decodingFrameRef = useRef(false);
|
|
1601
2105
|
const mountedRef = useRef(true);
|
|
1602
2106
|
actionRef.current = props.onAction;
|
|
1603
2107
|
readClipboardRef.current = props.onReadClipboard;
|
|
2108
|
+
observeInputRef.current = props.onObserveForInput;
|
|
1604
2109
|
errorRef.current = props.onError;
|
|
1605
2110
|
|
|
2111
|
+
const clearBufferedInput = useCallback(() => {
|
|
2112
|
+
dismissSelectPopup();
|
|
2113
|
+
if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
|
|
2114
|
+
if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
|
|
2115
|
+
wheelRef.current = null;
|
|
2116
|
+
pendingTextRef.current = null;
|
|
2117
|
+
queuedTypingRef.current = null;
|
|
2118
|
+
pointerStartRef.current = null;
|
|
2119
|
+
lastClickRef.current = null;
|
|
2120
|
+
composingRef.current = false;
|
|
2121
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
2122
|
+
}, [dismissSelectPopup]);
|
|
2123
|
+
|
|
1606
2124
|
const paintQueuedFrames = useCallback(() => {
|
|
1607
2125
|
if (decodingFrameRef.current) return;
|
|
1608
2126
|
decodingFrameRef.current = true;
|
|
@@ -1615,15 +2133,22 @@ function BrowserViewport(props: {
|
|
|
1615
2133
|
|
|
1616
2134
|
let objectUrl: string | null = null;
|
|
1617
2135
|
try {
|
|
1618
|
-
const blob = new Blob([frame.data
|
|
2136
|
+
const blob = new Blob([frame.data], {
|
|
1619
2137
|
type: frame.mediaType,
|
|
1620
2138
|
});
|
|
1621
2139
|
if (typeof createImageBitmap === "function") {
|
|
1622
2140
|
const bitmap = await createImageBitmap(blob);
|
|
1623
2141
|
try {
|
|
1624
2142
|
const canvas = canvasRef.current;
|
|
1625
|
-
if (
|
|
2143
|
+
if (
|
|
2144
|
+
mountedRef.current &&
|
|
2145
|
+
canvas &&
|
|
2146
|
+
currentFrameRef.current &&
|
|
2147
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2148
|
+
) {
|
|
1626
2149
|
paintCanvas(canvas, bitmap, frame.width, frame.height);
|
|
2150
|
+
paintedFrameRef.current = frame;
|
|
2151
|
+
setPaintedFrame(frame);
|
|
1627
2152
|
}
|
|
1628
2153
|
} finally {
|
|
1629
2154
|
bitmap.close();
|
|
@@ -1633,11 +2158,23 @@ function BrowserViewport(props: {
|
|
|
1633
2158
|
objectUrl = URL.createObjectURL(blob);
|
|
1634
2159
|
const image = await loadImage(objectUrl);
|
|
1635
2160
|
const canvas = canvasRef.current;
|
|
1636
|
-
if (
|
|
2161
|
+
if (
|
|
2162
|
+
mountedRef.current &&
|
|
2163
|
+
canvas &&
|
|
2164
|
+
currentFrameRef.current &&
|
|
2165
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2166
|
+
) {
|
|
1637
2167
|
paintCanvas(canvas, image, frame.width, frame.height);
|
|
2168
|
+
paintedFrameRef.current = frame;
|
|
2169
|
+
setPaintedFrame(frame);
|
|
1638
2170
|
}
|
|
1639
2171
|
} catch (cause) {
|
|
1640
|
-
if (
|
|
2172
|
+
if (
|
|
2173
|
+
mountedRef.current &&
|
|
2174
|
+
currentFrameRef.current &&
|
|
2175
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2176
|
+
)
|
|
2177
|
+
errorRef.current(cause);
|
|
1641
2178
|
} finally {
|
|
1642
2179
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1643
2180
|
}
|
|
@@ -1649,47 +2186,135 @@ function BrowserViewport(props: {
|
|
|
1649
2186
|
})();
|
|
1650
2187
|
}, []);
|
|
1651
2188
|
|
|
1652
|
-
|
|
2189
|
+
useLayoutEffect(() => {
|
|
2190
|
+
const keyboardInput = inputRef.current;
|
|
2191
|
+
const focusHandoff = props.focusHandoffRef;
|
|
1653
2192
|
mountedRef.current = true;
|
|
2193
|
+
if (focusHandoff.current === props.focusScope) {
|
|
2194
|
+
keyboardInput?.focus({ preventScroll: true });
|
|
2195
|
+
}
|
|
2196
|
+
focusHandoff.current = null;
|
|
1654
2197
|
return () => {
|
|
2198
|
+
// Only a same-target replacement may inherit focus the keyboard sink owned.
|
|
2199
|
+
if (document.activeElement === keyboardInput) {
|
|
2200
|
+
focusHandoff.current = props.focusScope;
|
|
2201
|
+
}
|
|
1655
2202
|
mountedRef.current = false;
|
|
2203
|
+
actionQueueEpochRef.current += 1;
|
|
1656
2204
|
queuedFrameRef.current = null;
|
|
2205
|
+
clearBufferedInput();
|
|
1657
2206
|
};
|
|
1658
|
-
}, []);
|
|
2207
|
+
}, [clearBufferedInput, props.focusHandoffRef, props.focusScope]);
|
|
1659
2208
|
|
|
1660
|
-
|
|
1661
|
-
if (!props.frame)
|
|
2209
|
+
useLayoutEffect(() => {
|
|
2210
|
+
if (currentFrameRef.current && !props.frame) {
|
|
2211
|
+
actionQueueEpochRef.current += 1;
|
|
2212
|
+
clearBufferedInput();
|
|
2213
|
+
}
|
|
2214
|
+
currentFrameRef.current = props.frame;
|
|
2215
|
+
if (!props.frame) {
|
|
2216
|
+
queuedFrameRef.current = null;
|
|
2217
|
+
paintedFrameRef.current = null;
|
|
2218
|
+
setPaintedFrame(null);
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
1662
2221
|
queuedFrameRef.current = props.frame;
|
|
1663
2222
|
paintQueuedFrames();
|
|
1664
|
-
}, [paintQueuedFrames, props.frame]);
|
|
1665
|
-
|
|
1666
|
-
useEffect(
|
|
1667
|
-
() => () => {
|
|
1668
|
-
if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
|
|
1669
|
-
if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
|
|
1670
|
-
},
|
|
1671
|
-
[],
|
|
1672
|
-
);
|
|
2223
|
+
}, [clearBufferedInput, paintQueuedFrames, props.frame]);
|
|
1673
2224
|
|
|
1674
2225
|
const enqueue = useCallback(
|
|
1675
2226
|
(
|
|
1676
2227
|
action: BrowserAction,
|
|
1677
|
-
frame:
|
|
2228
|
+
frame: BrowserFrameInputFence | null,
|
|
1678
2229
|
after?: (receipt: BrowserActionReceipt) => Promise<void>,
|
|
2230
|
+
selectAnchor?: { x: number; y: number },
|
|
1679
2231
|
) => {
|
|
2232
|
+
const inputSequence = ++inputSequenceRef.current;
|
|
2233
|
+
setSelectPopup({ observation: null, anchor: null });
|
|
2234
|
+
// Detach image bytes even when the caller supplied a complete painted frame.
|
|
2235
|
+
frame = frame
|
|
2236
|
+
? {
|
|
2237
|
+
browserSessionId: frame.browserSessionId,
|
|
2238
|
+
controllerGeneration: frame.controllerGeneration,
|
|
2239
|
+
targetId: frame.targetId,
|
|
2240
|
+
targetGeneration: frame.targetGeneration,
|
|
2241
|
+
documentGeneration: frame.documentGeneration,
|
|
2242
|
+
frameId: frame.frameId,
|
|
2243
|
+
}
|
|
2244
|
+
: null;
|
|
2245
|
+
const epoch = actionQueueEpochRef.current;
|
|
2246
|
+
const attachment = props.inputBatchAttachment;
|
|
2247
|
+
const canBatchTyping =
|
|
2248
|
+
action.type === "type" &&
|
|
2249
|
+
!after &&
|
|
2250
|
+
frame &&
|
|
2251
|
+
attachment?.fencedInputBatches === true &&
|
|
2252
|
+
attachment.browserSessionId === frame.browserSessionId &&
|
|
2253
|
+
attachment.controllerGeneration === frame.controllerGeneration &&
|
|
2254
|
+
attachment.targetId === frame.targetId &&
|
|
2255
|
+
Date.parse(attachment.expiresAt) > Date.now();
|
|
2256
|
+
const previous = queuedTypingRef.current;
|
|
2257
|
+
if (
|
|
2258
|
+
canBatchTyping &&
|
|
2259
|
+
frame &&
|
|
2260
|
+
previous &&
|
|
2261
|
+
previous.epoch === epoch &&
|
|
2262
|
+
sameFrameFence(previous.frame, frame) &&
|
|
2263
|
+
previous.actions.length < 16
|
|
2264
|
+
) {
|
|
2265
|
+
// Keep every existing text/input event boundary. Only share transport.
|
|
2266
|
+
previous.actions.push(action);
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const typing = canBatchTyping && frame ? { actions: [action], frame, epoch } : null;
|
|
2270
|
+
// Every non-typing action is an ordering barrier, including keys and paste.
|
|
2271
|
+
queuedTypingRef.current = typing;
|
|
1680
2272
|
actionTailRef.current = actionTailRef.current
|
|
1681
2273
|
.catch(() => undefined)
|
|
1682
2274
|
.then(async () => {
|
|
1683
|
-
|
|
2275
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2276
|
+
if (queuedTypingRef.current === typing) queuedTypingRef.current = null;
|
|
2277
|
+
const dispatchedAction =
|
|
2278
|
+
typing && typing.actions.length > 1
|
|
2279
|
+
? { type: "batch" as const, actions: typing.actions, fenceEachAction: true as const }
|
|
2280
|
+
: action;
|
|
2281
|
+
// Read after the epoch guard: a queued render callback can retain every
|
|
2282
|
+
// frame from that render even when its input fence contains no bytes.
|
|
2283
|
+
const receipt = await actionRef.current(dispatchedAction, frame);
|
|
2284
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2285
|
+
// Alt+Down explicitly opens a native dropdown from the keyboard.
|
|
2286
|
+
// Existing controllers return focused-input metadata for clicks only,
|
|
2287
|
+
// so use the ordinary authorized read for this explicit gesture.
|
|
2288
|
+
const keyboardSelect =
|
|
2289
|
+
dispatchedAction.type === "press" && dispatchedAction.key === "Alt+ArrowDown";
|
|
2290
|
+
if ((selectAnchor || keyboardSelect) && inputSequence === inputSequenceRef.current) {
|
|
2291
|
+
const observation = keyboardSelect
|
|
2292
|
+
? await observeInputRef.current()
|
|
2293
|
+
: receipt.observation;
|
|
2294
|
+
if (
|
|
2295
|
+
mountedRef.current &&
|
|
2296
|
+
epoch === actionQueueEpochRef.current &&
|
|
2297
|
+
inputSequence === inputSequenceRef.current &&
|
|
2298
|
+
observation
|
|
2299
|
+
) {
|
|
2300
|
+
setSelectPopup({ observation, anchor: selectAnchor ?? null });
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
1684
2303
|
await after?.(receipt);
|
|
1685
2304
|
})
|
|
1686
|
-
.catch((cause) =>
|
|
2305
|
+
.catch((cause) => {
|
|
2306
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2307
|
+
// Do not replay or continue input collected behind a failed request.
|
|
2308
|
+
actionQueueEpochRef.current += 1;
|
|
2309
|
+
clearBufferedInput();
|
|
2310
|
+
errorRef.current(cause);
|
|
2311
|
+
});
|
|
1687
2312
|
},
|
|
1688
|
-
[],
|
|
2313
|
+
[clearBufferedInput, props.inputBatchAttachment],
|
|
1689
2314
|
);
|
|
1690
2315
|
|
|
1691
2316
|
const point = useCallback(
|
|
1692
|
-
(frame:
|
|
2317
|
+
(frame: BrowserViewportFrame, clientX: number, clientY: number) =>
|
|
1693
2318
|
browserPoint(canvasRef.current, frame, clientX, clientY),
|
|
1694
2319
|
[],
|
|
1695
2320
|
);
|
|
@@ -1706,19 +2331,40 @@ function BrowserViewport(props: {
|
|
|
1706
2331
|
lastClickRef.current = null;
|
|
1707
2332
|
}, []);
|
|
1708
2333
|
|
|
2334
|
+
const flushPendingWheel = useCallback(() => {
|
|
2335
|
+
const batch = wheelRef.current;
|
|
2336
|
+
if (!batch) return;
|
|
2337
|
+
if (batch.timer) clearTimeout(batch.timer);
|
|
2338
|
+
wheelRef.current = null;
|
|
2339
|
+
enqueue(
|
|
2340
|
+
{
|
|
2341
|
+
type: "pointer",
|
|
2342
|
+
action: "scroll",
|
|
2343
|
+
x: batch.x,
|
|
2344
|
+
y: batch.y,
|
|
2345
|
+
deltaX: batch.deltaX,
|
|
2346
|
+
deltaY: batch.deltaY,
|
|
2347
|
+
},
|
|
2348
|
+
batch.frame,
|
|
2349
|
+
);
|
|
2350
|
+
}, [enqueue]);
|
|
2351
|
+
|
|
1709
2352
|
const pointerDown = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1710
|
-
|
|
2353
|
+
const frame = paintedFrameRef.current;
|
|
2354
|
+
if (!frame || event.button !== 0) return;
|
|
1711
2355
|
// Keep the hidden keyboard sink focused after the browser performs the
|
|
1712
2356
|
// pointerdown default action for the canvas. Without preventing that
|
|
1713
2357
|
// default, Chrome immediately moves focus back to the document and all
|
|
1714
2358
|
// subsequent typing/paste is silently lost.
|
|
1715
2359
|
event.preventDefault();
|
|
2360
|
+
dismissSelectPopup();
|
|
2361
|
+
flushPendingWheel();
|
|
1716
2362
|
flushPendingText();
|
|
1717
2363
|
pointerStartRef.current = {
|
|
1718
2364
|
x: event.clientX,
|
|
1719
2365
|
y: event.clientY,
|
|
1720
2366
|
pointerId: event.pointerId,
|
|
1721
|
-
frame
|
|
2367
|
+
frame,
|
|
1722
2368
|
};
|
|
1723
2369
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1724
2370
|
inputRef.current?.focus({ preventScroll: true });
|
|
@@ -1762,13 +2408,22 @@ function BrowserViewport(props: {
|
|
|
1762
2408
|
return;
|
|
1763
2409
|
}
|
|
1764
2410
|
lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
|
|
1765
|
-
|
|
2411
|
+
const viewportBounds = canvasRef.current?.parentElement?.getBoundingClientRect();
|
|
2412
|
+
enqueue(
|
|
2413
|
+
{ type: "pointer", action: "click", x: to.x, y: to.y },
|
|
2414
|
+
start.frame,
|
|
2415
|
+
undefined,
|
|
2416
|
+
viewportBounds
|
|
2417
|
+
? { x: event.clientX - viewportBounds.left, y: event.clientY - viewportBounds.top + 8 }
|
|
2418
|
+
: undefined,
|
|
2419
|
+
);
|
|
1766
2420
|
};
|
|
1767
2421
|
|
|
1768
2422
|
const contextMenu = (event: MouseEvent<HTMLCanvasElement>) => {
|
|
1769
2423
|
event.preventDefault();
|
|
1770
|
-
const frame =
|
|
2424
|
+
const frame = paintedFrameRef.current;
|
|
1771
2425
|
if (!frame) return;
|
|
2426
|
+
flushPendingWheel();
|
|
1772
2427
|
flushPendingText();
|
|
1773
2428
|
flushPendingClick();
|
|
1774
2429
|
const at = point(frame, event.clientX, event.clientY);
|
|
@@ -1776,41 +2431,47 @@ function BrowserViewport(props: {
|
|
|
1776
2431
|
};
|
|
1777
2432
|
|
|
1778
2433
|
const wheel = (event: WheelEvent<HTMLCanvasElement>) => {
|
|
1779
|
-
|
|
2434
|
+
dismissSelectPopup();
|
|
2435
|
+
const frame = paintedFrameRef.current;
|
|
1780
2436
|
if (!frame) return;
|
|
1781
2437
|
const at = point(frame, event.clientX, event.clientY);
|
|
1782
2438
|
if (!at) return;
|
|
1783
2439
|
flushPendingText();
|
|
1784
2440
|
flushPendingClick();
|
|
1785
2441
|
event.preventDefault();
|
|
1786
|
-
|
|
1787
|
-
if (
|
|
2442
|
+
let pending = wheelRef.current;
|
|
2443
|
+
if (
|
|
2444
|
+
pending &&
|
|
2445
|
+
(!sameBrowserDocument(pending.frame, frame) ||
|
|
2446
|
+
Math.hypot(pending.x - at.x, pending.y - at.y) > 6)
|
|
2447
|
+
) {
|
|
2448
|
+
flushPendingWheel();
|
|
2449
|
+
pending = null;
|
|
2450
|
+
}
|
|
1788
2451
|
wheelRef.current = {
|
|
1789
2452
|
x: at.x,
|
|
1790
2453
|
y: at.y,
|
|
1791
|
-
deltaX: (pending
|
|
1792
|
-
deltaY: (pending
|
|
2454
|
+
deltaX: (pending?.deltaX ?? 0) + event.deltaX,
|
|
2455
|
+
deltaY: (pending?.deltaY ?? 0) + event.deltaY,
|
|
1793
2456
|
frame,
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
wheelRef.current = null;
|
|
1797
|
-
if (batch)
|
|
1798
|
-
enqueue(
|
|
1799
|
-
{
|
|
1800
|
-
type: "pointer",
|
|
1801
|
-
action: "scroll",
|
|
1802
|
-
x: batch.x,
|
|
1803
|
-
y: batch.y,
|
|
1804
|
-
deltaX: batch.deltaX,
|
|
1805
|
-
deltaY: batch.deltaY,
|
|
1806
|
-
},
|
|
1807
|
-
batch.frame,
|
|
1808
|
-
);
|
|
1809
|
-
}, 45),
|
|
2457
|
+
// Retain the first event's deadline so a continuous gesture keeps moving.
|
|
2458
|
+
timer: pending?.timer ?? setTimeout(flushPendingWheel, 45),
|
|
1810
2459
|
};
|
|
1811
2460
|
};
|
|
1812
2461
|
|
|
1813
2462
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
2463
|
+
// Candidate selection/editing belongs to the local IME. Forward only the
|
|
2464
|
+
// committed text; Enter must not submit the remote page during composition.
|
|
2465
|
+
// Safari can end composition before keydown while retaining keyCode 229.
|
|
2466
|
+
if (
|
|
2467
|
+
composingRef.current ||
|
|
2468
|
+
event.nativeEvent.isComposing ||
|
|
2469
|
+
event.nativeEvent.keyCode === 229
|
|
2470
|
+
) {
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
dismissSelectPopup();
|
|
2474
|
+
flushPendingWheel();
|
|
1814
2475
|
const command = event.metaKey || event.ctrlKey;
|
|
1815
2476
|
if (
|
|
1816
2477
|
props.clipboardEnabled &&
|
|
@@ -1826,26 +2487,31 @@ function BrowserViewport(props: {
|
|
|
1826
2487
|
event.preventDefault();
|
|
1827
2488
|
flushPendingText();
|
|
1828
2489
|
flushPendingClick();
|
|
1829
|
-
enqueue({ type: "press", key },
|
|
2490
|
+
enqueue({ type: "press", key }, paintedFrameRef.current);
|
|
1830
2491
|
};
|
|
1831
2492
|
|
|
2493
|
+
const finishCopy = useCallback(async () => {
|
|
2494
|
+
const clipboard = await readClipboardRef.current();
|
|
2495
|
+
if (!mountedRef.current) return;
|
|
2496
|
+
if (clipboard.text.length === 0) return;
|
|
2497
|
+
if (!(await copyTextToClipboard(clipboard.text))) {
|
|
2498
|
+
throw new Error("Browser text could not be copied to the local clipboard");
|
|
2499
|
+
}
|
|
2500
|
+
}, []);
|
|
2501
|
+
|
|
1832
2502
|
const copy = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1833
2503
|
if (!props.clipboardEnabled) return;
|
|
1834
2504
|
event.preventDefault();
|
|
2505
|
+
flushPendingWheel();
|
|
1835
2506
|
flushPendingText();
|
|
1836
2507
|
flushPendingClick();
|
|
1837
|
-
enqueue({ type: "clipboard", operation: "copy" },
|
|
1838
|
-
const clipboard = await readClipboardRef.current();
|
|
1839
|
-
if (clipboard.text.length === 0) return;
|
|
1840
|
-
if (!(await copyTextToClipboard(clipboard.text))) {
|
|
1841
|
-
throw new Error("Browser text could not be copied to the local clipboard");
|
|
1842
|
-
}
|
|
1843
|
-
});
|
|
2508
|
+
enqueue({ type: "clipboard", operation: "copy" }, paintedFrameRef.current, finishCopy);
|
|
1844
2509
|
};
|
|
1845
2510
|
|
|
1846
2511
|
const paste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1847
2512
|
if (!props.clipboardEnabled) return;
|
|
1848
2513
|
event.preventDefault();
|
|
2514
|
+
flushPendingWheel();
|
|
1849
2515
|
flushPendingText();
|
|
1850
2516
|
flushPendingClick();
|
|
1851
2517
|
enqueue(
|
|
@@ -1854,16 +2520,18 @@ function BrowserViewport(props: {
|
|
|
1854
2520
|
operation: "paste",
|
|
1855
2521
|
text: event.clipboardData.getData("text/plain"),
|
|
1856
2522
|
},
|
|
1857
|
-
|
|
2523
|
+
paintedFrameRef.current,
|
|
1858
2524
|
);
|
|
1859
2525
|
};
|
|
1860
2526
|
|
|
1861
2527
|
const input = (value: string, nativeComposing = false) => {
|
|
2528
|
+
dismissSelectPopup();
|
|
1862
2529
|
if (composingRef.current || nativeComposing) return;
|
|
1863
2530
|
if (!value) return;
|
|
2531
|
+
flushPendingWheel();
|
|
1864
2532
|
flushPendingClick();
|
|
1865
2533
|
const pending = pendingTextRef.current;
|
|
1866
|
-
if (pending && sameOptionalBrowserFrame(pending.frame,
|
|
2534
|
+
if (pending && sameOptionalBrowserFrame(pending.frame, paintedFrameRef.current)) {
|
|
1867
2535
|
clearTimeout(pending.timer);
|
|
1868
2536
|
pending.text += value;
|
|
1869
2537
|
pending.timer = setTimeout(flushPendingText, 16);
|
|
@@ -1871,7 +2539,7 @@ function BrowserViewport(props: {
|
|
|
1871
2539
|
flushPendingText();
|
|
1872
2540
|
pendingTextRef.current = {
|
|
1873
2541
|
text: value,
|
|
1874
|
-
frame:
|
|
2542
|
+
frame: paintedFrameRef.current,
|
|
1875
2543
|
timer: setTimeout(flushPendingText, 16),
|
|
1876
2544
|
};
|
|
1877
2545
|
}
|
|
@@ -1887,7 +2555,11 @@ function BrowserViewport(props: {
|
|
|
1887
2555
|
input(event.currentTarget.value || event.data);
|
|
1888
2556
|
};
|
|
1889
2557
|
|
|
1890
|
-
const showCanvas =
|
|
2558
|
+
const showCanvas =
|
|
2559
|
+
!props.controlUnavailable &&
|
|
2560
|
+
props.frame !== null &&
|
|
2561
|
+
paintedFrame !== null &&
|
|
2562
|
+
sameBrowserDocument(props.frame, paintedFrame);
|
|
1891
2563
|
return (
|
|
1892
2564
|
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
|
1893
2565
|
<canvas
|
|
@@ -1907,6 +2579,7 @@ function BrowserViewport(props: {
|
|
|
1907
2579
|
/>
|
|
1908
2580
|
<textarea
|
|
1909
2581
|
ref={inputRef}
|
|
2582
|
+
disabled={props.controlUnavailable}
|
|
1910
2583
|
defaultValue=""
|
|
1911
2584
|
onInput={(event) =>
|
|
1912
2585
|
input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
|
|
@@ -1918,6 +2591,7 @@ function BrowserViewport(props: {
|
|
|
1918
2591
|
onPaste={paste}
|
|
1919
2592
|
className="pointer-events-none absolute left-1/2 top-1/2 size-px resize-none overflow-hidden opacity-0"
|
|
1920
2593
|
aria-label="Browser keyboard input"
|
|
2594
|
+
aria-keyshortcuts="Alt+ArrowDown"
|
|
1921
2595
|
autoCapitalize="off"
|
|
1922
2596
|
autoCorrect="off"
|
|
1923
2597
|
spellCheck={false}
|
|
@@ -1928,8 +2602,26 @@ function BrowserViewport(props: {
|
|
|
1928
2602
|
connectionState={props.connectionState}
|
|
1929
2603
|
supportsLiveFrames={props.supportsLiveFrames}
|
|
1930
2604
|
error={props.connectionError}
|
|
2605
|
+
controlUnavailable={props.controlUnavailable}
|
|
1931
2606
|
onAction={(action) => enqueue(action, null)}
|
|
1932
2607
|
onReconnect={props.onReconnect}
|
|
2608
|
+
reconnectLabel={props.reconnectLabel}
|
|
2609
|
+
reconnectMessage={props.reconnectMessage}
|
|
2610
|
+
/>
|
|
2611
|
+
) : null}
|
|
2612
|
+
{showCanvas ? (
|
|
2613
|
+
<BrowserSelectControl
|
|
2614
|
+
manualFallback={props.inputBatchAttachment?.focusedInputObservations !== true}
|
|
2615
|
+
activation={selectPopup}
|
|
2616
|
+
onDismiss={() => inputRef.current?.focus({ preventScroll: true })}
|
|
2617
|
+
observe={async () => {
|
|
2618
|
+
flushPendingText();
|
|
2619
|
+
flushPendingWheel();
|
|
2620
|
+
await actionTailRef.current;
|
|
2621
|
+
if (!mountedRef.current) throw new Error("The browser page changed.");
|
|
2622
|
+
return await props.onObserveForInput();
|
|
2623
|
+
}}
|
|
2624
|
+
act={props.onSelectFromObservation}
|
|
1933
2625
|
/>
|
|
1934
2626
|
) : null}
|
|
1935
2627
|
{props.mutating ? (
|
|
@@ -1946,35 +2638,52 @@ function SemanticBrowserFallback(props: {
|
|
|
1946
2638
|
connectionState: string;
|
|
1947
2639
|
supportsLiveFrames: boolean;
|
|
1948
2640
|
error: Error | null;
|
|
2641
|
+
controlUnavailable: boolean;
|
|
1949
2642
|
onAction: (action: BrowserAction) => void;
|
|
1950
2643
|
onReconnect: () => void;
|
|
2644
|
+
reconnectLabel?: string | undefined;
|
|
2645
|
+
reconnectMessage?: string | undefined;
|
|
1951
2646
|
}) {
|
|
2647
|
+
const controlFailure = interactionControlFailureFromError(props.error);
|
|
2648
|
+
const { controlUnavailable } = props;
|
|
2649
|
+
const generationLoss = Boolean(props.reconnectMessage);
|
|
1952
2650
|
const nodes = semanticNodes(
|
|
1953
2651
|
props.observation?.semantic?.kind === "snapshot" ? props.observation.semantic.roots : [],
|
|
1954
2652
|
);
|
|
1955
2653
|
const interactive = nodes.filter((node) => node.actions.includes("click")).slice(0, 8);
|
|
1956
2654
|
return (
|
|
1957
2655
|
<div className="absolute inset-0 grid place-items-center bg-og-bg p-6">
|
|
1958
|
-
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-
|
|
2656
|
+
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-lg">
|
|
1959
2657
|
<div className="flex items-center gap-2">
|
|
1960
2658
|
{props.error ? (
|
|
1961
|
-
<CircleAlertIcon className="size-4 text-og-
|
|
1962
|
-
) :
|
|
1963
|
-
<ZapIcon className="size-4 text-og-muted" />
|
|
2659
|
+
<CircleAlertIcon className="size-4 text-og-danger" />
|
|
2660
|
+
) : props.connectionState === "semantic" ? (
|
|
2661
|
+
<ZapIcon className="size-4 text-og-fg-muted" />
|
|
1964
2662
|
) : (
|
|
1965
|
-
<LoaderCircleIcon className="size-4 animate-spin text-og-muted" />
|
|
2663
|
+
<LoaderCircleIcon className="size-4 animate-spin text-og-fg-muted" />
|
|
1966
2664
|
)}
|
|
1967
2665
|
<p className="text-og-menu font-medium text-og-fg">
|
|
1968
|
-
{
|
|
1969
|
-
? "
|
|
1970
|
-
:
|
|
1971
|
-
?
|
|
1972
|
-
:
|
|
2666
|
+
{generationLoss
|
|
2667
|
+
? "Chrome reconnected—open a fresh browser/desktop."
|
|
2668
|
+
: controlUnavailable
|
|
2669
|
+
? "Browser controls unavailable"
|
|
2670
|
+
: props.error
|
|
2671
|
+
? props.supportsLiveFrames
|
|
2672
|
+
? "Live view disconnected"
|
|
2673
|
+
: "Browser unavailable"
|
|
2674
|
+
: props.connectionState === "semantic"
|
|
2675
|
+
? "Semantic browser"
|
|
2676
|
+
: browserConnectionLabel(props.connectionState)}
|
|
1973
2677
|
</p>
|
|
1974
2678
|
</div>
|
|
1975
|
-
{
|
|
2679
|
+
{props.error || props.reconnectMessage ? (
|
|
2680
|
+
<p className="mt-2 text-og-control leading-5 text-og-fg-muted">
|
|
2681
|
+
{props.reconnectMessage ?? controlFailure?.message ?? props.error?.message}
|
|
2682
|
+
</p>
|
|
2683
|
+
) : null}
|
|
2684
|
+
{interactive.length > 0 && !controlUnavailable ? (
|
|
1976
2685
|
<div className="mt-3 border-t border-og-border pt-3">
|
|
1977
|
-
<p className="mb-2 text-og-xs text-og-subtle">
|
|
2686
|
+
<p className="mb-2 text-og-xs text-og-fg-subtle">
|
|
1978
2687
|
{props.supportsLiveFrames
|
|
1979
2688
|
? "Page controls remain available"
|
|
1980
2689
|
: "Available page controls"}
|
|
@@ -1998,13 +2707,14 @@ function SemanticBrowserFallback(props: {
|
|
|
1998
2707
|
</div>
|
|
1999
2708
|
</div>
|
|
2000
2709
|
) : null}
|
|
2001
|
-
{props.error && props.
|
|
2710
|
+
{props.error && (!generationLoss || props.reconnectLabel) ? (
|
|
2002
2711
|
<button
|
|
2003
2712
|
type="button"
|
|
2004
2713
|
onClick={props.onReconnect}
|
|
2005
2714
|
className="mt-3 text-og-control font-medium text-og-accent hover:underline"
|
|
2006
2715
|
>
|
|
2007
|
-
|
|
2716
|
+
{props.reconnectLabel ??
|
|
2717
|
+
(controlFailure?.retryable === false ? "Try again" : "Reconnect")}
|
|
2008
2718
|
</button>
|
|
2009
2719
|
) : null}
|
|
2010
2720
|
</div>
|
|
@@ -2017,6 +2727,7 @@ function BrowserStatusBar(props: {
|
|
|
2017
2727
|
profile: BrowserIdentity | null;
|
|
2018
2728
|
target: BrowserTarget | null;
|
|
2019
2729
|
observation: ReturnType<typeof useBrowserSession>["observation"];
|
|
2730
|
+
inputFailure: ReturnType<typeof useBrowserSession>["inputFailure"];
|
|
2020
2731
|
connectionState: string;
|
|
2021
2732
|
refreshing: boolean;
|
|
2022
2733
|
diagnosticsOpen: boolean;
|
|
@@ -2028,7 +2739,7 @@ function BrowserStatusBar(props: {
|
|
|
2028
2739
|
(props.observation?.diagnostics.pageErrorCount ?? 0);
|
|
2029
2740
|
const failed = props.observation?.diagnostics.failedRequestCount ?? 0;
|
|
2030
2741
|
return (
|
|
2031
|
-
<div className="flex h-7 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-
|
|
2742
|
+
<div className="flex h-7 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-2 text-og-xs text-og-fg-subtle">
|
|
2032
2743
|
<span
|
|
2033
2744
|
className={cn(
|
|
2034
2745
|
"size-1.5 rounded-full",
|
|
@@ -2044,6 +2755,11 @@ function BrowserStatusBar(props: {
|
|
|
2044
2755
|
? "Semantic"
|
|
2045
2756
|
: browserConnectionLabel(props.connectionState)}
|
|
2046
2757
|
</span>
|
|
2758
|
+
{props.inputFailure ? (
|
|
2759
|
+
<span className="text-og-status-failed">
|
|
2760
|
+
{props.inputFailure.state === "failed" ? "Input failed" : "Input result unknown"}
|
|
2761
|
+
</span>
|
|
2762
|
+
) : null}
|
|
2047
2763
|
<span>{props.profile?.name ?? "Temporary browser"}</span>
|
|
2048
2764
|
<span className="min-w-0 flex-1 truncate">{props.target?.title}</span>
|
|
2049
2765
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
@@ -2052,10 +2768,10 @@ function BrowserStatusBar(props: {
|
|
|
2052
2768
|
type="button"
|
|
2053
2769
|
onClick={props.onOpenComputer}
|
|
2054
2770
|
className="flex items-center gap-1 rounded px-1.5 py-0.5 transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
2055
|
-
aria-label="Open this browser window in
|
|
2771
|
+
aria-label="Open this browser window in Desktop"
|
|
2056
2772
|
>
|
|
2057
2773
|
<MonitorIcon className="size-3" />
|
|
2058
|
-
|
|
2774
|
+
Desktop
|
|
2059
2775
|
</button>
|
|
2060
2776
|
) : null}
|
|
2061
2777
|
<button
|
|
@@ -2109,13 +2825,13 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2109
2825
|
id="browser-diagnostics-drawer"
|
|
2110
2826
|
role="region"
|
|
2111
2827
|
aria-label="Browser diagnostics"
|
|
2112
|
-
className="absolute
|
|
2828
|
+
className="absolute inset-y-0 right-0 z-40 flex min-h-0 w-full max-w-md flex-col border-l border-og-border bg-og-surface-1 shadow-2xl"
|
|
2113
2829
|
>
|
|
2114
|
-
<div className="flex h-11 shrink-0 items-center gap-2 border-b border-og-border px-3">
|
|
2115
|
-
<BugIcon className="size-4 text-og-muted" />
|
|
2830
|
+
<div className="flex min-h-11 shrink-0 items-center gap-2 border-b border-og-border px-3 py-2">
|
|
2831
|
+
<BugIcon className="size-4 shrink-0 text-og-fg-muted" />
|
|
2116
2832
|
<div className="min-w-0 flex-1">
|
|
2117
2833
|
<p className="text-og-control font-semibold text-og-fg">Browser diagnostics</p>
|
|
2118
|
-
<p className="truncate text-og-xs text-og-subtle">
|
|
2834
|
+
<p className="truncate text-og-xs text-og-fg-subtle">
|
|
2119
2835
|
{props.target?.title || shortUrl(props.target?.url ?? "") || "Current tab"}
|
|
2120
2836
|
</p>
|
|
2121
2837
|
</div>
|
|
@@ -2123,7 +2839,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2123
2839
|
type="button"
|
|
2124
2840
|
onClick={props.onRefresh}
|
|
2125
2841
|
disabled={props.state.loading}
|
|
2126
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40"
|
|
2842
|
+
className="grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40"
|
|
2127
2843
|
aria-label="Refresh browser diagnostics"
|
|
2128
2844
|
>
|
|
2129
2845
|
<RefreshCwIcon className={cn("size-3.5", props.state.loading && "animate-spin")} />
|
|
@@ -2131,7 +2847,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2131
2847
|
<button
|
|
2132
2848
|
type="button"
|
|
2133
2849
|
onClick={props.onClose}
|
|
2134
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
2850
|
+
className="grid size-7 shrink-0 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
2135
2851
|
aria-label="Close browser diagnostics"
|
|
2136
2852
|
>
|
|
2137
2853
|
<XIcon className="size-3.5" />
|
|
@@ -2142,7 +2858,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2142
2858
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-runtime-title">
|
|
2143
2859
|
<h3
|
|
2144
2860
|
id="browser-runtime-title"
|
|
2145
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2861
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2146
2862
|
>
|
|
2147
2863
|
Runtime
|
|
2148
2864
|
</h3>
|
|
@@ -2173,18 +2889,19 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2173
2889
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-page-title">
|
|
2174
2890
|
<h3
|
|
2175
2891
|
id="browser-page-title"
|
|
2176
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2892
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2177
2893
|
>
|
|
2178
|
-
|
|
2894
|
+
Tab diagnostics
|
|
2179
2895
|
</h3>
|
|
2896
|
+
<p className="mt-1 text-og-xs text-og-fg-subtle">Includes earlier pages in this tab.</p>
|
|
2180
2897
|
<dl className="mt-2 grid grid-cols-2 gap-2">
|
|
2181
|
-
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount
|
|
2182
|
-
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount
|
|
2183
|
-
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount
|
|
2184
|
-
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount
|
|
2898
|
+
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount} />
|
|
2899
|
+
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount} />
|
|
2900
|
+
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount} />
|
|
2901
|
+
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount} />
|
|
2185
2902
|
</dl>
|
|
2186
2903
|
{session?.failureCode ? (
|
|
2187
|
-
<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-
|
|
2904
|
+
<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">
|
|
2188
2905
|
Browser state: {session.failureCode}
|
|
2189
2906
|
</p>
|
|
2190
2907
|
) : null}
|
|
@@ -2206,21 +2923,21 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2206
2923
|
<div className="flex items-center justify-between gap-2">
|
|
2207
2924
|
<h3
|
|
2208
2925
|
id="browser-events-title"
|
|
2209
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2926
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2210
2927
|
>
|
|
2211
2928
|
Recent events
|
|
2212
2929
|
</h3>
|
|
2213
2930
|
{props.state.batch?.truncated ? (
|
|
2214
|
-
<span className="text-[10px] text-og-subtle">Newest 100</span>
|
|
2931
|
+
<span className="text-[10px] text-og-fg-subtle">Newest 100</span>
|
|
2215
2932
|
) : null}
|
|
2216
2933
|
</div>
|
|
2217
2934
|
{props.state.loading && !props.state.batch ? (
|
|
2218
|
-
<div className="flex items-center gap-2 py-6 text-og-control text-og-muted">
|
|
2935
|
+
<div className="flex items-center gap-2 py-6 text-og-control text-og-fg-muted">
|
|
2219
2936
|
<LoaderCircleIcon className="size-3.5 animate-spin" /> Loading diagnostics…
|
|
2220
2937
|
</div>
|
|
2221
2938
|
) : props.state.error ? (
|
|
2222
2939
|
<div className="py-5">
|
|
2223
|
-
<p className="text-og-control text-og-
|
|
2940
|
+
<p className="text-og-control text-og-danger">{props.state.error.message}</p>
|
|
2224
2941
|
<button
|
|
2225
2942
|
type="button"
|
|
2226
2943
|
onClick={props.onRefresh}
|
|
@@ -2236,7 +2953,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2236
2953
|
key={entry.sequence}
|
|
2237
2954
|
className="rounded-og-sm border border-og-border bg-og-bg p-2.5"
|
|
2238
2955
|
>
|
|
2239
|
-
<div className="flex items-center gap-2 text-[10px] text-og-subtle">
|
|
2956
|
+
<div className="flex items-center gap-2 text-[10px] text-og-fg-subtle">
|
|
2240
2957
|
<span
|
|
2241
2958
|
className={cn(
|
|
2242
2959
|
"font-semibold uppercase tracking-wide",
|
|
@@ -2251,7 +2968,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2251
2968
|
{entry.message}
|
|
2252
2969
|
</p>
|
|
2253
2970
|
{entry.url || entry.filename || entry.method || entry.status ? (
|
|
2254
|
-
<p className="mt-1 truncate font-og-mono text-[10px] text-og-subtle">
|
|
2971
|
+
<p className="mt-1 truncate font-og-mono text-[10px] text-og-fg-subtle">
|
|
2255
2972
|
{[entry.method, entry.status, entry.filename, entry.url]
|
|
2256
2973
|
.filter((value) => value !== null)
|
|
2257
2974
|
.join(" · ")}
|
|
@@ -2261,7 +2978,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2261
2978
|
))}
|
|
2262
2979
|
</ol>
|
|
2263
2980
|
) : (
|
|
2264
|
-
<p className="py-6 text-og-control text-og-muted">No diagnostics for this tab.</p>
|
|
2981
|
+
<p className="py-6 text-og-control text-og-fg-muted">No diagnostics for this tab.</p>
|
|
2265
2982
|
)}
|
|
2266
2983
|
</section>
|
|
2267
2984
|
</div>
|
|
@@ -2308,25 +3025,25 @@ function BrowserDownloadsPanel(props: {
|
|
|
2308
3025
|
return (
|
|
2309
3026
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-downloads-title">
|
|
2310
3027
|
<div className="flex items-center gap-2">
|
|
2311
|
-
<DownloadIcon className="size-3.5 text-og-muted" />
|
|
3028
|
+
<DownloadIcon className="size-3.5 text-og-fg-muted" />
|
|
2312
3029
|
<h3
|
|
2313
3030
|
id="browser-downloads-title"
|
|
2314
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
3031
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2315
3032
|
>
|
|
2316
3033
|
Downloads
|
|
2317
3034
|
</h3>
|
|
2318
3035
|
{props.refreshing ? (
|
|
2319
|
-
<LoaderCircleIcon className="ml-auto size-3 animate-spin text-og-muted" />
|
|
3036
|
+
<LoaderCircleIcon className="ml-auto size-3 animate-spin text-og-fg-muted" />
|
|
2320
3037
|
) : null}
|
|
2321
3038
|
</div>
|
|
2322
3039
|
|
|
2323
3040
|
{props.loading ? (
|
|
2324
|
-
<div className="flex items-center gap-2 py-5 text-og-control text-og-muted">
|
|
3041
|
+
<div className="flex items-center gap-2 py-5 text-og-control text-og-fg-muted">
|
|
2325
3042
|
<LoaderCircleIcon className="size-3.5 animate-spin" /> Loading downloads…
|
|
2326
3043
|
</div>
|
|
2327
3044
|
) : props.error ? (
|
|
2328
3045
|
<div className="py-4">
|
|
2329
|
-
<p className="text-og-control text-og-
|
|
3046
|
+
<p className="text-og-control text-og-danger">{props.error.message}</p>
|
|
2330
3047
|
<button
|
|
2331
3048
|
type="button"
|
|
2332
3049
|
onClick={props.onRefresh}
|
|
@@ -2336,7 +3053,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2336
3053
|
</button>
|
|
2337
3054
|
</div>
|
|
2338
3055
|
) : props.downloads.length === 0 ? (
|
|
2339
|
-
<p className="py-4 text-og-control text-og-muted">No files downloaded yet.</p>
|
|
3056
|
+
<p className="py-4 text-og-control text-og-fg-muted">No files downloaded yet.</p>
|
|
2340
3057
|
) : (
|
|
2341
3058
|
<ol className="mt-2 space-y-2">
|
|
2342
3059
|
{props.downloads.map((download) => {
|
|
@@ -2349,9 +3066,9 @@ function BrowserDownloadsPanel(props: {
|
|
|
2349
3066
|
className="rounded-og-sm border border-og-border bg-og-bg p-2.5"
|
|
2350
3067
|
>
|
|
2351
3068
|
<div className="flex min-w-0 items-start gap-2">
|
|
2352
|
-
<span className="mt-0.5 grid size-6 shrink-0 place-items-center rounded bg-og-surface-2 text-og-muted">
|
|
3069
|
+
<span className="mt-0.5 grid size-6 shrink-0 place-items-center rounded bg-og-surface-2 text-og-fg-muted">
|
|
2353
3070
|
{isSaved ? (
|
|
2354
|
-
<FileCheck2Icon className="size-3.5 text-og-status-
|
|
3071
|
+
<FileCheck2Icon className="size-3.5 text-og-status-idle" />
|
|
2355
3072
|
) : (
|
|
2356
3073
|
<DownloadIcon className="size-3.5" />
|
|
2357
3074
|
)}
|
|
@@ -2363,7 +3080,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2363
3080
|
>
|
|
2364
3081
|
{download.filename}
|
|
2365
3082
|
</p>
|
|
2366
|
-
<p className="mt-0.5 text-[10px] text-og-subtle">
|
|
3083
|
+
<p className="mt-0.5 text-[10px] text-og-fg-subtle">
|
|
2367
3084
|
{downloadStatusLabel(download)}
|
|
2368
3085
|
</p>
|
|
2369
3086
|
</div>
|
|
@@ -2385,7 +3102,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2385
3102
|
}}
|
|
2386
3103
|
disabled={isSaving}
|
|
2387
3104
|
aria-label={`Workspace path for ${download.filename}`}
|
|
2388
|
-
className="h-7 min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-surface-
|
|
3105
|
+
className="h-7 min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-og-mono text-[10px] text-og-fg transition focus:border-og-accent focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:opacity-50"
|
|
2389
3106
|
/>
|
|
2390
3107
|
<button
|
|
2391
3108
|
type="button"
|
|
@@ -2411,7 +3128,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2411
3128
|
: "Save"}
|
|
2412
3129
|
</button>
|
|
2413
3130
|
</div>
|
|
2414
|
-
<label className="mt-1.5 flex w-fit items-center gap-1.5 text-[10px] text-og-subtle">
|
|
3131
|
+
<label className="mt-1.5 flex w-fit items-center gap-1.5 text-[10px] text-og-fg-subtle">
|
|
2415
3132
|
<input
|
|
2416
3133
|
type="checkbox"
|
|
2417
3134
|
checked={overwrite[download.id] ?? false}
|
|
@@ -2428,7 +3145,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2428
3145
|
Replace an existing workspace file
|
|
2429
3146
|
</label>
|
|
2430
3147
|
{errors[download.id] ? (
|
|
2431
|
-
<p className="mt-1.5 break-words text-[10px] text-og-
|
|
3148
|
+
<p className="mt-1.5 break-words text-[10px] text-og-danger">
|
|
2432
3149
|
{errors[download.id]}
|
|
2433
3150
|
</p>
|
|
2434
3151
|
) : null}
|
|
@@ -2471,7 +3188,7 @@ function defaultDownloadDestination(download: BrowserDownload): string {
|
|
|
2471
3188
|
function DiagnosticFact(props: { label: string; value: string }) {
|
|
2472
3189
|
return (
|
|
2473
3190
|
<>
|
|
2474
|
-
<dt className="text-og-subtle">{props.label}</dt>
|
|
3191
|
+
<dt className="text-og-fg-subtle">{props.label}</dt>
|
|
2475
3192
|
<dd className="min-w-0 truncate text-og-fg" title={props.value}>
|
|
2476
3193
|
{props.value}
|
|
2477
3194
|
</dd>
|
|
@@ -2479,17 +3196,17 @@ function DiagnosticFact(props: { label: string; value: string }) {
|
|
|
2479
3196
|
);
|
|
2480
3197
|
}
|
|
2481
3198
|
|
|
2482
|
-
function DiagnosticCount(props: { label: string; value: number }) {
|
|
3199
|
+
function DiagnosticCount(props: { label: string; value: number | undefined }) {
|
|
2483
3200
|
return (
|
|
2484
3201
|
<div className="rounded-og-sm border border-og-border bg-og-bg px-2.5 py-2">
|
|
2485
|
-
<dt className="text-[10px] text-og-subtle">{props.label}</dt>
|
|
3202
|
+
<dt className="text-[10px] text-og-fg-subtle">{props.label}</dt>
|
|
2486
3203
|
<dd
|
|
2487
3204
|
className={cn(
|
|
2488
3205
|
"mt-0.5 text-og-sm font-semibold",
|
|
2489
|
-
props.value ? "text-og-fg" : "text-og-muted",
|
|
3206
|
+
props.value ? "text-og-fg" : "text-og-fg-muted",
|
|
2490
3207
|
)}
|
|
2491
3208
|
>
|
|
2492
|
-
{props.value}
|
|
3209
|
+
{props.value ?? "Unavailable"}
|
|
2493
3210
|
</dd>
|
|
2494
3211
|
</div>
|
|
2495
3212
|
);
|
|
@@ -2520,9 +3237,9 @@ function diagnosticKindLabel(kind: BrowserDiagnosticBatch["entries"][number]["ki
|
|
|
2520
3237
|
}
|
|
2521
3238
|
|
|
2522
3239
|
function diagnosticTone(level: BrowserDiagnosticBatch["entries"][number]["level"]): string {
|
|
2523
|
-
if (level === "error") return "text-og-
|
|
3240
|
+
if (level === "error") return "text-og-danger";
|
|
2524
3241
|
if (level === "warning") return "text-og-status-waiting";
|
|
2525
|
-
return "text-og-muted";
|
|
3242
|
+
return "text-og-fg-muted";
|
|
2526
3243
|
}
|
|
2527
3244
|
|
|
2528
3245
|
function formatDiagnosticTime(value: string): string {
|
|
@@ -2536,7 +3253,7 @@ function BrowserNotice(props: { icon: ReactNode; text: string; className?: strin
|
|
|
2536
3253
|
return (
|
|
2537
3254
|
<div
|
|
2538
3255
|
className={cn(
|
|
2539
|
-
"grid h-full place-items-center bg-og-bg text-og-control text-og-muted",
|
|
3256
|
+
"grid h-full place-items-center bg-og-bg text-og-control text-og-fg-muted",
|
|
2540
3257
|
props.className,
|
|
2541
3258
|
)}
|
|
2542
3259
|
>
|
|
@@ -2572,7 +3289,7 @@ function loadImage(url: string): Promise<HTMLImageElement> {
|
|
|
2572
3289
|
|
|
2573
3290
|
function browserPoint(
|
|
2574
3291
|
canvas: HTMLCanvasElement | null,
|
|
2575
|
-
frame:
|
|
3292
|
+
frame: BrowserViewportFrame | null,
|
|
2576
3293
|
clientX: number,
|
|
2577
3294
|
clientY: number,
|
|
2578
3295
|
): { x: number; y: number } | null {
|
|
@@ -2619,18 +3336,24 @@ function frameMatchesSelectedTarget(
|
|
|
2619
3336
|
);
|
|
2620
3337
|
}
|
|
2621
3338
|
|
|
2622
|
-
function sameFrameFence(left:
|
|
3339
|
+
function sameFrameFence(left: BrowserFrameInputFence, right: BrowserFrameInputFence): boolean {
|
|
3340
|
+
return sameBrowserDocument(left, right) && left.frameId === right.frameId;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
function sameBrowserDocument(left: BrowserFrameInputFence, right: BrowserFrameInputFence): boolean {
|
|
2623
3344
|
return (
|
|
2624
3345
|
left.browserSessionId === right.browserSessionId &&
|
|
2625
3346
|
left.controllerGeneration === right.controllerGeneration &&
|
|
2626
3347
|
left.targetId === right.targetId &&
|
|
2627
3348
|
left.targetGeneration === right.targetGeneration &&
|
|
2628
|
-
left.documentGeneration === right.documentGeneration
|
|
2629
|
-
left.frameId === right.frameId
|
|
3349
|
+
left.documentGeneration === right.documentGeneration
|
|
2630
3350
|
);
|
|
2631
3351
|
}
|
|
2632
3352
|
|
|
2633
|
-
function sameOptionalBrowserFrame(
|
|
3353
|
+
function sameOptionalBrowserFrame(
|
|
3354
|
+
left: BrowserViewportFrame | null,
|
|
3355
|
+
right: BrowserViewportFrame | null,
|
|
3356
|
+
): boolean {
|
|
2634
3357
|
return left === null || right === null ? left === right : sameFrameFence(left, right);
|
|
2635
3358
|
}
|
|
2636
3359
|
|
|
@@ -2638,6 +3361,23 @@ function isLiveBrowser(session: BrowserSession): boolean {
|
|
|
2638
3361
|
return !["ending", "ended", "failed", "lost"].includes(session.lifecycle);
|
|
2639
3362
|
}
|
|
2640
3363
|
|
|
3364
|
+
function attachedChromeGenerationLoss(
|
|
3365
|
+
sessions: readonly BrowserSession[],
|
|
3366
|
+
): { deviceId: string } | null {
|
|
3367
|
+
const lost = sessions.find(
|
|
3368
|
+
(session) =>
|
|
3369
|
+
session.lifecycle === "lost" &&
|
|
3370
|
+
session.failureCode === "controller_transition_expired" &&
|
|
3371
|
+
session.placement.kind === "attached_device",
|
|
3372
|
+
);
|
|
3373
|
+
return lost?.placement.kind === "attached_device" ? { deviceId: lost.placement.deviceId } : null;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
function isAttachedChromeGenerationLossError(error: Error | null): boolean {
|
|
3377
|
+
if (!error) return false;
|
|
3378
|
+
return /placement instance changed|controller authority changed/i.test(error.message);
|
|
3379
|
+
}
|
|
3380
|
+
|
|
2641
3381
|
function countInterventions(
|
|
2642
3382
|
interventions: readonly InteractionIntervention[],
|
|
2643
3383
|
): Map<string, number> {
|