@opengeni/react 3.1.0 → 3.1.2
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 +769 -22
- package/dist/accounts.js +89 -11
- package/dist/accounts.js.map +1 -1
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +5 -3
- package/dist/artifacts-presentation.js +5 -3
- package/dist/artifacts-spreadsheet.d.ts +1 -1
- package/dist/artifacts-spreadsheet.js +5 -3
- package/dist/artifacts.d.ts +5 -1
- package/dist/artifacts.js +536 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-ZWR6MWJ7.js +19 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-BO2IWXVK.js +14 -0
- package/dist/chunk-2DDJ6YH6.js +41 -0
- package/dist/chunk-2DDJ6YH6.js.map +1 -0
- package/dist/chunk-2HVLQ26H.js +183 -0
- package/dist/chunk-2HVLQ26H.js.map +1 -0
- package/dist/{chunk-RGALCTWO.js → chunk-2I6R3GCZ.js} +4285 -3980
- package/dist/chunk-2I6R3GCZ.js.map +1 -0
- package/dist/{chunk-D3UIAJKY.js → chunk-2SFFYIH3.js} +1179 -166
- package/dist/chunk-2SFFYIH3.js.map +1 -0
- package/dist/chunk-3PVDYTTB.js +24 -0
- package/dist/chunk-3PVDYTTB.js.map +1 -0
- package/dist/chunk-3UZCWK46.js +298 -0
- package/dist/chunk-3UZCWK46.js.map +1 -0
- package/dist/chunk-47CKUXTW.js +293 -0
- package/dist/chunk-47CKUXTW.js.map +1 -0
- package/dist/chunk-4QYP6QUI.js +109 -0
- package/dist/chunk-4QYP6QUI.js.map +1 -0
- package/dist/chunk-5EY26VZ7.js +1053 -0
- package/dist/chunk-5EY26VZ7.js.map +1 -0
- package/dist/chunk-5I4VRWBB.js +56 -0
- package/dist/chunk-5I4VRWBB.js.map +1 -0
- package/dist/chunk-5WOR7UCL.js +191 -0
- package/dist/chunk-5WOR7UCL.js.map +1 -0
- package/dist/chunk-5YJLGOM5.js +51 -0
- package/dist/chunk-5YJLGOM5.js.map +1 -0
- package/dist/chunk-6JFN3OWG.js +386 -0
- package/dist/chunk-6JFN3OWG.js.map +1 -0
- package/dist/chunk-7QJT5O4S.js +35 -0
- package/dist/chunk-7QJT5O4S.js.map +1 -0
- package/dist/chunk-A55KV72M.js +402 -0
- package/dist/chunk-A55KV72M.js.map +1 -0
- package/dist/chunk-ADWCLI4G.js +2028 -0
- package/dist/chunk-ADWCLI4G.js.map +1 -0
- package/dist/chunk-B27E3V37.js +29 -0
- package/dist/chunk-B27E3V37.js.map +1 -0
- package/dist/chunk-B7TC2A33.js +83 -0
- package/dist/chunk-B7TC2A33.js.map +1 -0
- package/dist/chunk-BLL56F7R.js +385 -0
- package/dist/chunk-BLL56F7R.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/chunk-CM7LVUKC.js +35 -0
- package/dist/chunk-CM7LVUKC.js.map +1 -0
- package/dist/chunk-CORUP6JB.js +122 -0
- package/dist/chunk-CORUP6JB.js.map +1 -0
- package/dist/chunk-CSX5FFKZ.js +35 -0
- package/dist/chunk-CSX5FFKZ.js.map +1 -0
- package/dist/chunk-EGVHJKGJ.js +3125 -0
- package/dist/chunk-EGVHJKGJ.js.map +1 -0
- package/dist/{chunk-NEXLLTP4.js → chunk-G76LSA76.js} +6 -5
- package/dist/chunk-G76LSA76.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-GQMRL52F.js} +311 -75
- package/dist/chunk-GQMRL52F.js.map +1 -0
- package/dist/{chunk-ELIYWBZR.js → chunk-GUUXOISA.js} +1113 -457
- package/dist/chunk-GUUXOISA.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-I7X2P4FU.js} +225 -116
- package/dist/chunk-I7X2P4FU.js.map +1 -0
- package/dist/chunk-IGFZFLI2.js +1390 -0
- package/dist/chunk-IGFZFLI2.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-IMSUVYQB.js} +26 -13
- package/dist/chunk-IMSUVYQB.js.map +1 -0
- package/dist/chunk-IOYHDEP3.js +38 -0
- package/dist/chunk-IOYHDEP3.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-KF33GTWN.js +343 -0
- package/dist/chunk-KF33GTWN.js.map +1 -0
- package/dist/chunk-KSGQ5ODA.js +457 -0
- package/dist/chunk-KSGQ5ODA.js.map +1 -0
- package/dist/{chunk-OAM4WAAM.js → chunk-KZC6QRNR.js} +1513 -697
- package/dist/chunk-KZC6QRNR.js.map +1 -0
- package/dist/chunk-LJUMYUBO.js +218 -0
- package/dist/chunk-LJUMYUBO.js.map +1 -0
- package/dist/chunk-LSW2OJVV.js +380 -0
- package/dist/chunk-LSW2OJVV.js.map +1 -0
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/chunk-M357MHH3.js.map +1 -0
- package/dist/chunk-MK2TQO4Y.js +24 -0
- package/dist/chunk-MK2TQO4Y.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-XXWH2JLC.js → chunk-NKMYZFHA.js} +616 -621
- package/dist/chunk-NKMYZFHA.js.map +1 -0
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/chunk-O7F6UJO4.js +71 -0
- package/dist/chunk-O7F6UJO4.js.map +1 -0
- package/dist/chunk-OVB5WIG5.js +14064 -0
- package/dist/chunk-OVB5WIG5.js.map +1 -0
- package/dist/chunk-OYQQJL3J.js +16 -0
- package/dist/chunk-OYQQJL3J.js.map +1 -0
- package/dist/{chunk-H5YGYNXS.js → chunk-P4BNUO57.js} +60 -17
- package/dist/chunk-P4BNUO57.js.map +1 -0
- package/dist/{chunk-JVYQRRC4.js → chunk-PEHTX3V7.js} +21 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-PMV53IMM.js +26 -0
- package/dist/chunk-PMV53IMM.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-PYEOPA4M.js} +13 -7
- package/dist/chunk-PYEOPA4M.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-QHUE7E4N.js} +8 -3
- package/dist/chunk-QHUE7E4N.js.map +1 -0
- package/dist/chunk-QV3DJVRZ.js +11 -0
- package/dist/chunk-QV3DJVRZ.js.map +1 -0
- package/dist/chunk-R5SBNG7S.js +810 -0
- package/dist/chunk-R5SBNG7S.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/chunk-S2QH6EZ5.js +211 -0
- package/dist/chunk-S2QH6EZ5.js.map +1 -0
- package/dist/chunk-SHLZMAJN.js +304 -0
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/chunk-TGXK4BAZ.js +293 -0
- package/dist/chunk-TGXK4BAZ.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-TNEZNMSN.js} +4 -10
- package/dist/chunk-TNEZNMSN.js.map +1 -0
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/chunk-UNJPZGNK.js +63 -0
- package/dist/chunk-UNJPZGNK.js.map +1 -0
- package/dist/{chunk-MZ2G2XBA.js → chunk-V5ZUJT2T.js} +205 -45
- package/dist/chunk-V5ZUJT2T.js.map +1 -0
- package/dist/chunk-VIUEOHXY.js +394 -0
- package/dist/chunk-VIUEOHXY.js.map +1 -0
- package/dist/chunk-WIUKB3LD.js +1911 -0
- package/dist/chunk-WIUKB3LD.js.map +1 -0
- package/dist/{chunk-ICA6R6TW.js → chunk-WNN7NO2N.js} +1451 -355
- package/dist/chunk-WNN7NO2N.js.map +1 -0
- package/dist/{chunk-YJFGAFBD.js → chunk-X5UVUIMF.js} +1892 -1223
- package/dist/chunk-X5UVUIMF.js.map +1 -0
- package/dist/chunk-XDA5QJE7.js +402 -0
- package/dist/chunk-XDA5QJE7.js.map +1 -0
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/chunk-XHF7OYAN.js +1150 -0
- package/dist/chunk-XHF7OYAN.js.map +1 -0
- package/dist/chunk-YSRBFJMQ.js +43 -0
- package/dist/chunk-YSRBFJMQ.js.map +1 -0
- package/dist/chunk-ZOZAPAUO.js +174 -0
- package/dist/chunk-ZOZAPAUO.js.map +1 -0
- package/dist/chunk-ZQV5W7YC.js +809 -0
- package/dist/chunk-ZQV5W7YC.js.map +1 -0
- package/dist/chunk-ZUWF4NF7.js +13 -0
- package/dist/chunk-ZUWF4NF7.js.map +1 -0
- package/dist/client.d.ts +16 -7
- package/dist/clipboard.js +3 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +19 -6
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +30 -0
- package/dist/components/artifacts/artifact-surface.d.ts +4 -2
- 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 +48 -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 +8 -3
- package/dist/components/artifacts/index.d.ts +9 -2
- 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 +23 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +26 -0
- package/dist/components/artifacts/spreadsheet-command-queue.d.ts +20 -0
- package/dist/components/artifacts/spreadsheet-download.d.ts +7 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +10 -3
- package/dist/components/artifacts/spreadsheet-resize.d.ts +50 -0
- package/dist/components/artifacts/spreadsheet-submissions.d.ts +9 -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 +11 -2
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +9 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer-transcription-control.d.ts +7 -0
- package/dist/components/composer.d.ts +34 -67
- package/dist/components/computer-viewer.d.ts +1 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -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 +28 -32
- package/dist/components/human-input-surface.d.ts +7 -2
- package/dist/components/linkified-text.d.ts +18 -0
- package/dist/components/machine-input-display.d.ts +22 -1
- package/dist/components/machines/agent-status-icon.d.ts +6 -0
- package/dist/components/machines/machine-detail.d.ts +7 -1
- package/dist/components/machines/machine-status.d.ts +48 -0
- package/dist/components/markdown-table-layout.d.ts +18 -0
- package/dist/components/markdown.d.ts +37 -4
- package/dist/components/message-timeline.d.ts +86 -7
- 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 +41 -25
- package/dist/components/new-conversation.d.ts +32 -0
- package/dist/components/open-geni-chat.d.ts +73 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/provider-recovery-notice.d.ts +16 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/rehype-secret-refs.d.ts +23 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-files.d.ts +3 -1
- package/dist/components/sandbox-workspace.d.ts +18 -0
- package/dist/components/secret-question-field.d.ts +31 -0
- package/dist/components/secret-ref-chip.d.ts +35 -0
- package/dist/components/session-capability-frame.d.ts +37 -0
- package/dist/components/session-chrome.d.ts +57 -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 +157 -0
- package/dist/components/session-list.d.ts +39 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +35 -0
- package/dist/components/session-status.d.ts +4 -1
- package/dist/components/session-usage-limit-notice.d.ts +39 -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/tool-action-review.d.ts +47 -0
- package/dist/components/tool-review-history.d.ts +25 -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/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer-messages.js.map +1 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +26 -13
- package/dist/computer-viewer-GMLDXPON.js +20 -0
- package/dist/computer-viewer-GMLDXPON.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 +18 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2149 -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 +27 -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 +15 -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 +22 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-new-voice-OCRNT6U5.js +18 -0
- package/dist/embedded-new-voice-OCRNT6U5.js.map +1 -0
- package/dist/embedded-voice-RPHPYVBT.js +18 -0
- package/dist/embedded-voice-RPHPYVBT.js.map +1 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-SI63O5V4.js} +4 -1
- package/dist/fleet-decision-row-SI63O5V4.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/shared-query.d.ts +113 -0
- package/dist/hooks/use-attached-browsers.d.ts +1 -1
- package/dist/hooks/use-auth-runs.d.ts +1 -4
- package/dist/hooks/use-available-models.d.ts +1 -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-client-config-flags.d.ts +49 -0
- package/dist/hooks/use-codex-accounts.d.ts +59 -10
- package/dist/hooks/use-composer.d.ts +24 -2
- 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 +4 -0
- package/dist/hooks/use-connect.d.ts +23 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +12 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/hooks/use-last-started-turn-policy.d.ts +5 -1
- package/dist/hooks/use-new-conversation.d.ts +56 -0
- package/dist/hooks/use-new-session-draft.d.ts +74 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-session-conversation.d.ts +49 -0
- package/dist/hooks/use-session-events.d.ts +28 -7
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session-model-account.d.ts +48 -0
- package/dist/hooks/use-supergrok-accounts.d.ts +7 -1
- package/dist/hooks/use-terminal-stream.d.ts +11 -5
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-vaults.d.ts +53 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +22 -5
- package/dist/human-input-model.d.ts +122 -0
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +82 -20
- package/dist/index.js +2350 -6605
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +14 -24
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-R77KWSUP.js +118 -0
- package/dist/latest-question-R77KWSUP.js.map +1 -0
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +58 -4
- package/dist/lib/host-theme.d.ts +77 -0
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/notify-observer.d.ts +2 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/provider-recovery.d.ts +55 -0
- package/dist/lib/scrollbars.d.ts +11 -0
- package/dist/lib/terminal-input-readiness.d.ts +11 -0
- package/dist/lib/transient-retry.d.ts +25 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.js +9 -5
- package/dist/markdown-table-layout-3PV2UAAH.js +113 -0
- package/dist/markdown-table-layout-3PV2UAAH.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-mark-paths.js.map +1 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js +17 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js.map +1 -0
- package/dist/model-policy.d.ts +30 -3
- package/dist/model-policy.js +14 -1
- package/dist/native-previews.d.ts +28 -0
- package/dist/native-previews.js +82 -0
- package/dist/native-previews.js.map +1 -0
- package/dist/new-session-draft.d.ts +7 -0
- package/dist/new-session-draft.js +639 -0
- package/dist/new-session-draft.js.map +1 -0
- package/dist/notification-text.d.ts +27 -0
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/older-history.d.ts +3 -2
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-5OCKOQWZ.js} +16 -78
- package/dist/platform-activity-row-5OCKOQWZ.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/preview-loading-paint.d.ts +20 -0
- package/dist/provider.d.ts +25 -2
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-BSKUWHRI.js → queue-surface-implementation-GJBH7NKO.js} +65 -265
- package/dist/queue-surface-implementation-GJBH7NKO.js.map +1 -0
- package/dist/realtime/embedded-new-voice.d.ts +1 -0
- package/dist/realtime/embedded-voice.d.ts +1 -0
- package/dist/realtime/realtime-control.d.ts +4 -65
- package/dist/realtime/session-realtime.d.ts +78 -0
- package/dist/realtime.js +25 -1294
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-O27IPOZW.js +21 -0
- package/dist/sandbox-files-O27IPOZW.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +30 -0
- package/dist/sandbox-label-model.js +20 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/scrollbars.d.ts +1 -0
- package/dist/scrollbars.js +10 -0
- package/dist/scrollbars.js.map +1 -0
- package/dist/secret-reference-label.d.ts +14 -0
- package/dist/secrets-ui.d.ts +57 -0
- package/dist/session-agents-model.d.ts +10 -0
- package/dist/session-agents-model.js +25 -0
- package/dist/session-agents-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +47 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +31 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +138 -0
- package/dist/session-list-model.js +274 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-realtime-7DJOIHQW.js +21 -0
- package/dist/session-realtime-7DJOIHQW.js.map +1 -0
- package/dist/session-realtime.d.ts +7 -0
- package/dist/session-realtime.js +19 -0
- package/dist/session-realtime.js.map +1 -0
- package/dist/session-status-model.d.ts +36 -0
- package/dist/session-ui.d.ts +39 -1
- package/dist/session-ui.js +126 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +35 -20
- package/dist/site-snapshot.d.ts +14 -0
- package/dist/sites-ui.d.ts +24 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +14 -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 +32 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +367 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/agent-identity.d.ts +30 -0
- package/dist/timeline/agent-row.d.ts +31 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +46 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/focus-sequence.d.ts +9 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +47 -0
- package/dist/timeline/index.d.ts +10 -5
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/message-sender.d.ts +19 -0
- package/dist/timeline/notice-presentation.d.ts +13 -0
- package/dist/timeline/parsers.d.ts +11 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +21 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/presented-image.d.ts +11 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/provider-logo.d.ts +15 -0
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +12 -1
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/retained-loaders.d.ts +27 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +23 -1
- package/dist/timeline/session-usage-limit-row.d.ts +11 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/startup-dispatch-details.d.ts +5 -0
- 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 +5 -2
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +95 -0
- package/dist/timeline/turn-summary.d.ts +20 -54
- package/dist/timeline/types.d.ts +179 -7
- package/dist/timeline/work-presentation.d.ts +60 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js +166 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js.map +1 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js +242 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js.map +1 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js +274 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js.map +1 -0
- package/dist/timeline-model.d.ts +29 -0
- package/dist/timeline-model.js +254 -0
- package/dist/timeline-model.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/session-usage-limit.d.ts +88 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +1187 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +107 -7
- package/src/accounts.tsx +116 -14
- package/src/approvals.ts +8 -1
- package/src/artifacts-spreadsheet.ts +1 -0
- package/src/artifacts.ts +44 -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 +20 -31
- 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 +401 -138
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +189 -0
- package/src/components/artifacts/artifact-surface.tsx +40 -20
- 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 +197 -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 +543 -103
- package/src/components/artifacts/index.ts +45 -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 +122 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +378 -0
- package/src/components/artifacts/site-view.tsx +121 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-command-queue.ts +184 -0
- package/src/components/artifacts/spreadsheet-download.tsx +109 -0
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +344 -144
- package/src/components/artifacts/spreadsheet-resize.tsx +421 -0
- package/src/components/artifacts/spreadsheet-submissions.tsx +46 -0
- 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 +778 -318
- package/src/components/chat-composer.tsx +47 -5
- package/src/components/child-session-link.tsx +36 -0
- package/src/components/claude-mark.tsx +11 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +128 -30
- package/src/components/composer.tsx +404 -195
- package/src/components/computer-viewer.tsx +880 -253
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +1 -1
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +10 -14
- package/src/components/fleet-tile.tsx +9 -18
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +34 -0
- package/src/components/human-input-form.tsx +590 -283
- package/src/components/human-input-surface.tsx +11 -0
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/linkified-text.tsx +79 -0
- package/src/components/machine-card.tsx +35 -78
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +197 -3
- package/src/components/machines/agent-status-icon.tsx +31 -0
- package/src/components/machines/machine-detail.tsx +200 -31
- package/src/components/machines/machine-operation-policy.tsx +1 -1
- package/src/components/machines/machine-status.ts +241 -0
- package/src/components/machines-dashboard.tsx +2 -17
- package/src/components/markdown-table-layout.ts +143 -0
- package/src/components/markdown.tsx +466 -129
- package/src/components/message-timeline.tsx +2567 -728
- package/src/components/model-mark.tsx +102 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +371 -0
- package/src/components/model-policy-picker.tsx +256 -412
- package/src/components/new-conversation.tsx +221 -0
- package/src/components/open-geni-chat.tsx +309 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +35 -10
- package/src/components/preview-loading.tsx +109 -0
- package/src/components/provider-recovery-notice.tsx +49 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +51 -356
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/rehype-secret-refs.ts +98 -0
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-files.tsx +91 -15
- package/src/components/sandbox-terminal.tsx +84 -13
- package/src/components/sandbox-workspace.tsx +147 -51
- package/src/components/secret-question-field.tsx +324 -0
- package/src/components/secret-ref-chip.tsx +185 -0
- package/src/components/session-capability-frame.tsx +197 -0
- package/src/components/session-chrome.tsx +842 -251
- 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 +876 -0
- package/src/components/session-list.tsx +257 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +153 -0
- package/src/components/session-status.tsx +26 -17
- package/src/components/session-usage-limit-notice.tsx +174 -0
- 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 +52 -0
- package/src/components/tool-action-review.tsx +650 -0
- package/src/components/tool-review-history.tsx +88 -0
- 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 +27 -2
- package/src/components/workspace-dock.tsx +18 -1
- package/src/composer-messages.ts +150 -0
- package/src/composer-voice-events.ts +12 -0
- 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 +355 -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 +238 -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/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/shared-query.ts +721 -0
- package/src/hooks/use-attached-browsers.ts +1 -1
- package/src/hooks/use-auth-runs.ts +0 -27
- package/src/hooks/use-available-models.ts +21 -15
- package/src/hooks/use-browser-frame-stream.ts +19 -2
- package/src/hooks/use-browser-session.ts +680 -205
- package/src/hooks/use-browser-sessions.ts +32 -1
- package/src/hooks/use-client-config-flags.ts +118 -0
- package/src/hooks/use-codex-accounts.ts +247 -47
- package/src/hooks/use-composer.ts +276 -47
- package/src/hooks/use-computer-frame-stream.ts +25 -7
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +409 -95
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +80 -4
- package/src/hooks/use-goal.ts +107 -13
- package/src/hooks/use-last-started-turn-policy.ts +46 -14
- package/src/hooks/use-machines.ts +4 -0
- package/src/hooks/use-new-conversation.ts +374 -0
- package/src/hooks/use-new-session-draft.ts +813 -0
- package/src/hooks/use-realtime-voice-models.ts +46 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +22 -11
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-conversation.ts +150 -0
- package/src/hooks/use-session-events.ts +703 -382
- package/src/hooks/use-session-lineage.ts +20 -16
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session-model-account.ts +174 -0
- package/src/hooks/use-session.ts +53 -21
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-supergrok-accounts.ts +22 -8
- package/src/hooks/use-terminal-stream.ts +153 -27
- package/src/hooks/use-transcription.ts +6 -0
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-vaults.ts +153 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +127 -19
- 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 +97 -32
- package/src/human-input-model.ts +359 -0
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +262 -16
- package/src/interaction.ts +1 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +213 -16
- package/src/lib/host-theme.ts +529 -0
- package/src/lib/interaction-errors.ts +15 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/notify-observer.ts +15 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/provider-recovery.ts +243 -0
- package/src/lib/scrollbars.ts +30 -0
- package/src/lib/terminal-input-readiness.ts +51 -0
- package/src/lib/transient-retry.ts +99 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +299 -74
- package/src/native-previews.ts +100 -0
- package/src/new-session-draft.ts +22 -0
- package/src/notification-text.ts +81 -0
- package/src/older-history.ts +22 -4
- package/src/organization-model.ts +118 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/preview-loading-paint.ts +68 -0
- package/src/provider.tsx +74 -15
- package/src/queue-presentation-model.ts +464 -0
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/embedded-new-voice.tsx +1 -0
- package/src/realtime/embedded-voice.tsx +3 -0
- package/src/realtime/realtime-control.tsx +138 -501
- package/src/realtime/session-realtime.ts +566 -0
- package/src/sandbox-label-model.ts +53 -0
- package/src/scrollbars.ts +3 -0
- package/src/secret-reference-label.ts +43 -0
- package/src/secrets-ui.tsx +59 -0
- package/src/session-agents-model.ts +33 -0
- package/src/session-attention-model.ts +62 -0
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +196 -0
- package/src/session-context.ts +3 -4
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +434 -0
- package/src/session-realtime.ts +18 -0
- package/src/session-status-model.ts +99 -0
- package/src/session-ui.ts +98 -1
- package/src/session.ts +17 -1
- package/src/site-snapshot.ts +74 -0
- package/src/sites-ui.tsx +348 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/testing/session-scenarios.ts +404 -0
- package/src/timeline/activity-rail.tsx +242 -101
- package/src/timeline/agent-identity.tsx +84 -0
- package/src/timeline/agent-row.tsx +231 -0
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +115 -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/focus-sequence.ts +44 -0
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +143 -0
- package/src/timeline/index.ts +30 -2
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/message-sender.tsx +71 -0
- package/src/timeline/notice-presentation.ts +48 -0
- package/src/timeline/parsers.ts +42 -2
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +166 -0
- package/src/timeline/platform-activity-row.tsx +15 -90
- package/src/timeline/presented-image.ts +63 -0
- package/src/timeline/projection.ts +1746 -76
- package/src/timeline/provider-logo.tsx +53 -0
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +26 -4
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/retained-loaders.ts +91 -0
- package/src/timeline/rolling-activity.tsx +80 -0
- package/src/timeline/screenshot-lightbox.tsx +228 -5
- package/src/timeline/session-usage-limit-row.tsx +42 -0
- package/src/timeline/shared.tsx +42 -15
- package/src/timeline/startup-dispatch-details.tsx +36 -0
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +19 -7
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +835 -1138
- package/src/timeline/turn-summary-model.ts +343 -0
- package/src/timeline/turn-summary.tsx +272 -365
- package/src/timeline/types.ts +189 -6
- package/src/timeline/work-presentation.ts +189 -0
- package/src/timeline-model.ts +143 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/session-usage-limit.ts +266 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +70 -0
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +2319 -748
- package/styles/connect.css +290 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +309 -9
- package/styles/responsive.css +99 -7
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +134 -66
- package/dist/browser-viewer-AEFHUYZK.js +0 -14
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-D3UIAJKY.js.map +0 -1
- package/dist/chunk-ELIYWBZR.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HQVCKCLG.js +0 -9458
- package/dist/chunk-HQVCKCLG.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-JVYQRRC4.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-MZ2G2XBA.js.map +0 -1
- package/dist/chunk-NEXLLTP4.js.map +0 -1
- package/dist/chunk-OAM4WAAM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-RGALCTWO.js.map +0 -1
- package/dist/chunk-W6NFJXSA.js +0 -454
- package/dist/chunk-W6NFJXSA.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-X4A66EWZ.js +0 -223
- package/dist/chunk-X4A66EWZ.js.map +0 -1
- package/dist/chunk-XXWH2JLC.js.map +0 -1
- package/dist/chunk-YFKJ7VZH.js +0 -268
- package/dist/chunk-YFKJ7VZH.js.map +0 -1
- package/dist/chunk-YJFGAFBD.js.map +0 -1
- package/dist/computer-viewer-FWEZISW4.js +0 -16
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-environments.d.ts +0 -41
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/dist/queue-surface-implementation-BSKUWHRI.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-environments.ts +0 -182
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-BO2IWXVK.js.map} +0 -0
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
AttachedBrowserBridge,
|
|
3
3
|
AttachedBrowserDevice,
|
|
4
4
|
BrowserAction,
|
|
5
|
+
BrowserActionBatch,
|
|
5
6
|
BrowserActionReceipt,
|
|
6
7
|
BrowserDiagnosticBatch,
|
|
7
8
|
BrowserDownload,
|
|
@@ -22,6 +23,8 @@ import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
|
|
|
22
23
|
import {
|
|
23
24
|
BugIcon,
|
|
24
25
|
ArchiveIcon,
|
|
26
|
+
ArrowLeftIcon,
|
|
27
|
+
ArrowRightIcon,
|
|
25
28
|
CheckIcon,
|
|
26
29
|
ChevronDownIcon,
|
|
27
30
|
CircleAlertIcon,
|
|
@@ -50,6 +53,7 @@ import {
|
|
|
50
53
|
useCallback,
|
|
51
54
|
useEffect,
|
|
52
55
|
useId,
|
|
56
|
+
useLayoutEffect,
|
|
53
57
|
useMemo,
|
|
54
58
|
useRef,
|
|
55
59
|
useState,
|
|
@@ -61,17 +65,38 @@ import {
|
|
|
61
65
|
import { useAttachedBrowsers } from "../hooks/use-attached-browsers";
|
|
62
66
|
import { useBrowserIdentities } from "../hooks/use-browser-identities";
|
|
63
67
|
import { useBrowserDownloads } from "../hooks/use-browser-downloads";
|
|
64
|
-
import { useBrowserSession } from "../hooks/use-browser-session";
|
|
68
|
+
import { useBrowserSession, type BrowserFrameInputFence } from "../hooks/use-browser-session";
|
|
65
69
|
import { useBrowserSessions } from "../hooks/use-browser-sessions";
|
|
66
70
|
import { useInteractionInterventions } from "../hooks/use-interaction-interventions";
|
|
67
71
|
import { useSiteAuthConnections } from "../hooks/use-site-auth-connections";
|
|
68
72
|
import { cn } from "../lib/cn";
|
|
69
73
|
import { copyTextToClipboard } from "../lib/clipboard";
|
|
70
74
|
import { formatBytes } from "../lib/format";
|
|
71
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
isInteractionControlUnavailable,
|
|
77
|
+
isSourcePlacementChangedError,
|
|
78
|
+
} from "../lib/interaction-errors";
|
|
72
79
|
import type { EmbeddedBrowserInteractionClientOverride } from "../session-context";
|
|
73
80
|
import { browserKey, HUMAN_BROWSER_HOME_URL, normalizeBrowserAddress } from "./browser-input";
|
|
74
81
|
import { InteractionInterventionBanner } from "./interaction-intervention-banner";
|
|
82
|
+
import { BrowserSelectControl } from "./browser-select-control";
|
|
83
|
+
import { useViewerMenuDismiss } from "./use-viewer-menu-dismiss";
|
|
84
|
+
|
|
85
|
+
/** Auto-ending a lost attached-device browser: bounded retries for transient failures only. */
|
|
86
|
+
const STALE_BROWSER_END_MAX_ATTEMPTS = 3;
|
|
87
|
+
const STALE_BROWSER_END_RETRY_BASE_MS = 5_000;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Delay before retrying an automatic end of a lost attached-device browser, or null to stop.
|
|
91
|
+
* A refusal (4xx, e.g. 409 when the server will not end it) is never retried: re-sending it on
|
|
92
|
+
* every registry refresh looped about one request per second on prod.
|
|
93
|
+
*/
|
|
94
|
+
export function staleBrowserEndRetryDelayMs(cause: unknown, failedAttempts: number): number | null {
|
|
95
|
+
const status = (cause as { status?: unknown } | null)?.status;
|
|
96
|
+
if (typeof status === "number" && status >= 400 && status < 500) return null;
|
|
97
|
+
if (failedAttempts >= STALE_BROWSER_END_MAX_ATTEMPTS) return null;
|
|
98
|
+
return STALE_BROWSER_END_RETRY_BASE_MS * 2 ** (failedAttempts - 1);
|
|
99
|
+
}
|
|
75
100
|
|
|
76
101
|
export type BrowserViewerNotification = {
|
|
77
102
|
kind: "error" | "info";
|
|
@@ -79,9 +104,12 @@ export type BrowserViewerNotification = {
|
|
|
79
104
|
};
|
|
80
105
|
|
|
81
106
|
export type BrowserViewerProps = EmbeddedBrowserInteractionClientOverride & {
|
|
82
|
-
/** The selected
|
|
107
|
+
/** The selected Opengeni agent/session. Peer BrowserSessions stay discoverable. */
|
|
83
108
|
sessionId: string;
|
|
84
109
|
enabled?: boolean | undefined;
|
|
110
|
+
/** Whether the Browser tab is selected. Keeps the mounted media stream warm
|
|
111
|
+
* while pausing semantic reads behind other dock tabs. */
|
|
112
|
+
active?: boolean | undefined;
|
|
85
113
|
className?: string | undefined;
|
|
86
114
|
onNotify?: ((notification: BrowserViewerNotification) => void) | undefined;
|
|
87
115
|
/** Tests/demos only. Production uses the browser's native WebSocket. */
|
|
@@ -111,11 +139,15 @@ type BrowserLaunchChoice =
|
|
|
111
139
|
| { kind: "clean" }
|
|
112
140
|
| { kind: "profile"; identityId: string; baseRevisionId?: string }
|
|
113
141
|
| { kind: "attached"; device: AttachedBrowserDevice };
|
|
142
|
+
// ArrayBuffer is opaque to React development prop diagnostics; a Uint8Array
|
|
143
|
+
// exposes every image byte as an enumerable property retained in timing entries.
|
|
144
|
+
type BrowserViewportFrame = Omit<BrowserFrame, "data"> & { data: ArrayBuffer };
|
|
145
|
+
|
|
114
146
|
type PointerStart = {
|
|
115
147
|
x: number;
|
|
116
148
|
y: number;
|
|
117
149
|
pointerId: number;
|
|
118
|
-
frame:
|
|
150
|
+
frame: BrowserViewportFrame;
|
|
119
151
|
};
|
|
120
152
|
type BrowserResumeAttempt = {
|
|
121
153
|
operationId: string;
|
|
@@ -139,6 +171,7 @@ type BrowserDiagnosticsView = {
|
|
|
139
171
|
export function BrowserViewer({
|
|
140
172
|
sessionId,
|
|
141
173
|
enabled = true,
|
|
174
|
+
active = true,
|
|
142
175
|
className,
|
|
143
176
|
onNotify,
|
|
144
177
|
webSocketFactory,
|
|
@@ -173,6 +206,7 @@ export function BrowserViewer({
|
|
|
173
206
|
[registry.sessions],
|
|
174
207
|
);
|
|
175
208
|
const endedGenerationLossRef = useRef(new Set<string>());
|
|
209
|
+
const endStaleBrowserAttemptsRef = useRef(new Map<string, number>());
|
|
176
210
|
const endStaleBrowser = registry.end;
|
|
177
211
|
useEffect(() => {
|
|
178
212
|
if (!enabled) return;
|
|
@@ -186,21 +220,24 @@ export function BrowserViewer({
|
|
|
186
220
|
}
|
|
187
221
|
if (endedGenerationLossRef.current.has(session.id)) continue;
|
|
188
222
|
endedGenerationLossRef.current.add(session.id);
|
|
189
|
-
void endStaleBrowser(session.id).catch(() => {
|
|
190
|
-
|
|
223
|
+
void endStaleBrowser(session.id).catch((cause: unknown) => {
|
|
224
|
+
const attempts = (endStaleBrowserAttemptsRef.current.get(session.id) ?? 0) + 1;
|
|
225
|
+
endStaleBrowserAttemptsRef.current.set(session.id, attempts);
|
|
226
|
+
const delay = staleBrowserEndRetryDelayMs(cause, attempts);
|
|
227
|
+
if (delay === null) return;
|
|
228
|
+
setTimeout(() => endedGenerationLossRef.current.delete(session.id), delay);
|
|
191
229
|
});
|
|
192
230
|
}
|
|
193
231
|
}, [enabled, endStaleBrowser, registry.sessions]);
|
|
194
|
-
const replacementChromeDevice = useMemo(() => {
|
|
195
|
-
if (!attachedGenerationLoss) return null;
|
|
196
|
-
return (
|
|
197
|
-
attached.devices.find((candidate) => candidate.id === attachedGenerationLoss.deviceId) ?? null
|
|
198
|
-
);
|
|
199
|
-
}, [attached.devices, attachedGenerationLoss]);
|
|
200
232
|
const relevant = useMemo(
|
|
201
233
|
() => registry.relevantSessions.filter((session) => isLiveBrowser(session)),
|
|
202
234
|
[registry.relevantSessions],
|
|
203
235
|
);
|
|
236
|
+
const latestRelevantBrowser = registry.relevantSessions[0];
|
|
237
|
+
const unavailableBrowser =
|
|
238
|
+
latestRelevantBrowser?.lifecycle === "lost" || latestRelevantBrowser?.lifecycle === "failed"
|
|
239
|
+
? latestRelevantBrowser
|
|
240
|
+
: undefined;
|
|
204
241
|
const [selection, setSelection] = useState<BrowserSelection>(() =>
|
|
205
242
|
initialBrowserSessionId ? { sessionId: initialBrowserSessionId, pinned: true } : null,
|
|
206
243
|
);
|
|
@@ -232,6 +269,7 @@ export function BrowserViewer({
|
|
|
232
269
|
const previousSessionIdRef = useRef(sessionId);
|
|
233
270
|
const seenInterventionIdsRef = useRef(new Set<string>());
|
|
234
271
|
const diagnosticRequestRef = useRef(0);
|
|
272
|
+
const viewportFocusHandoffRef = useRef<string | null>(null);
|
|
235
273
|
const [diagnosticsView, setDiagnosticsView] = useState<BrowserDiagnosticsView | null>(null);
|
|
236
274
|
|
|
237
275
|
const notifyError = useCallback(
|
|
@@ -287,6 +325,7 @@ export function BrowserViewer({
|
|
|
287
325
|
);
|
|
288
326
|
const controllerReady = selectedRegistrySession?.lifecycle === "active";
|
|
289
327
|
const resumeSession = registry.resume;
|
|
328
|
+
const [hasLiveFrame, setHasLiveFrame] = useState(false);
|
|
290
329
|
|
|
291
330
|
const wakeBrowser = useCallback(
|
|
292
331
|
async (session: BrowserSession, retry = false): Promise<void> => {
|
|
@@ -337,17 +376,14 @@ export function BrowserViewer({
|
|
|
337
376
|
setResumeAttempt((visible) =>
|
|
338
377
|
visible?.sessionId === selectedRegistrySession.id ? null : visible,
|
|
339
378
|
);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (selectedRegistrySession.lifecycle === "suspended") {
|
|
343
|
-
void wakeBrowser(selectedRegistrySession);
|
|
344
379
|
}
|
|
345
|
-
}, [selectedRegistrySession
|
|
380
|
+
}, [selectedRegistrySession]);
|
|
346
381
|
|
|
347
382
|
const browser = useBrowserSession({
|
|
348
383
|
...override,
|
|
349
384
|
browserSessionId: selection?.sessionId ?? null,
|
|
350
385
|
enabled: enabled && selection !== null && controllerReady,
|
|
386
|
+
semanticObservationEnabled: active && !hasLiveFrame,
|
|
351
387
|
});
|
|
352
388
|
useEffect(() => {
|
|
353
389
|
if (!isSourcePlacementChangedError(browser.error, "browser_session")) return;
|
|
@@ -406,19 +442,47 @@ export function BrowserViewer({
|
|
|
406
442
|
stream: { format: "jpeg", quality: 76, maxWidth: 1_920, maxHeight: 1_200 },
|
|
407
443
|
...(webSocketFactory ? { webSocketFactory } : {}),
|
|
408
444
|
});
|
|
409
|
-
const
|
|
445
|
+
const receivedFrame = frameMatchesSelectedTarget(
|
|
410
446
|
frames.frame,
|
|
411
447
|
browser.session,
|
|
412
448
|
browser.selectedTarget,
|
|
413
449
|
)
|
|
414
450
|
? frames.frame
|
|
415
451
|
: null;
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
452
|
+
const displayedFrame = useMemo<BrowserViewportFrame | null>(
|
|
453
|
+
() => (receivedFrame ? { ...receivedFrame, data: receivedFrame.data.slice().buffer } : null),
|
|
454
|
+
[receivedFrame],
|
|
455
|
+
);
|
|
456
|
+
const frameIsLive = frames.state === "live" && displayedFrame !== null;
|
|
457
|
+
useEffect(() => {
|
|
458
|
+
setHasLiveFrame(frameIsLive);
|
|
459
|
+
}, [frameIsLive]);
|
|
460
|
+
const supportsLiveFrames =
|
|
461
|
+
(browser.session ?? selectedRegistrySession)?.capabilities.liveFrames === true;
|
|
462
|
+
const controlUnavailable = isInteractionControlUnavailable(browser.error);
|
|
463
|
+
const connectionError = controlUnavailable ? browser.error : (frames.error ?? browser.error);
|
|
464
|
+
// A managed controller can reject a stale attachment while the same browser
|
|
465
|
+
// remains healthy. Only extension-attached Chrome requires a new browser on
|
|
466
|
+
// connection-generation loss; an unrelated lost Chrome must not poison the
|
|
467
|
+
// selected managed browser's recovery UI.
|
|
468
|
+
const selectedPlacement = (browser.session ?? selectedRegistrySession)?.placement;
|
|
469
|
+
const selectedChromeGenerationLoss =
|
|
470
|
+
selectedPlacement?.kind === "attached_device" &&
|
|
471
|
+
isAttachedChromeGenerationLossError(connectionError);
|
|
472
|
+
const replacementChromeDevice =
|
|
473
|
+
selectedChromeGenerationLoss && selectedPlacement.kind === "attached_device"
|
|
474
|
+
? attached.devices.find((candidate) => candidate.id === selectedPlacement.deviceId)
|
|
475
|
+
: null;
|
|
476
|
+
const displayConnectionState =
|
|
477
|
+
connectionError && (!frameIsLive || controlUnavailable)
|
|
478
|
+
? "error"
|
|
479
|
+
: !browser.session
|
|
480
|
+
? "connecting"
|
|
481
|
+
: supportsLiveFrames
|
|
482
|
+
? frames.state === "live" && !displayedFrame
|
|
483
|
+
? "connecting"
|
|
484
|
+
: frames.state
|
|
485
|
+
: "semantic";
|
|
422
486
|
const selectedProfile = useMemo(
|
|
423
487
|
() =>
|
|
424
488
|
profiles.identities.find(
|
|
@@ -735,25 +799,32 @@ export function BrowserViewer({
|
|
|
735
799
|
);
|
|
736
800
|
}
|
|
737
801
|
if (liveSessions.length === 0) {
|
|
738
|
-
if (renderEmpty
|
|
802
|
+
if (renderEmpty && !unavailableBrowser)
|
|
803
|
+
return <>{renderEmpty(() => createBrowser(), creating)}</>;
|
|
739
804
|
return (
|
|
740
805
|
<div
|
|
741
806
|
className={cn("flex h-full min-h-0 items-center justify-center bg-og-bg p-6", className)}
|
|
742
807
|
>
|
|
743
808
|
<div className="max-w-sm text-center">
|
|
744
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
809
|
+
<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">
|
|
745
810
|
<Globe2Icon className="size-4.5" />
|
|
746
811
|
</span>
|
|
747
|
-
|
|
748
|
-
{
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
812
|
+
{unavailableBrowser && !attachedGenerationLoss ? (
|
|
813
|
+
<BrowserUnavailableNotice session={unavailableBrowser} />
|
|
814
|
+
) : (
|
|
815
|
+
<>
|
|
816
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">
|
|
817
|
+
{attachedGenerationLoss
|
|
818
|
+
? "Chrome reconnected—open a fresh browser/desktop."
|
|
819
|
+
: "No browser open"}
|
|
820
|
+
</p>
|
|
821
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
822
|
+
{attachedGenerationLoss
|
|
823
|
+
? "This Chrome profile is live again, but the previous browser cannot move to the new connection. Use Browser → New browser → Connected Chrome."
|
|
824
|
+
: "A browser appears here when this agent—or another agent in the workspace—opens one."}
|
|
825
|
+
</p>
|
|
826
|
+
</>
|
|
827
|
+
)}
|
|
757
828
|
<BrowserLaunchMenu
|
|
758
829
|
attachedBridges={onlineAttachedBridges}
|
|
759
830
|
attachedDevices={attached.devices}
|
|
@@ -766,7 +837,7 @@ export function BrowserViewer({
|
|
|
766
837
|
prominent
|
|
767
838
|
/>
|
|
768
839
|
{registry.error ? (
|
|
769
|
-
<p className="mt-3 text-og-control text-og-
|
|
840
|
+
<p className="mt-3 text-og-control text-og-danger">{registry.error.message}</p>
|
|
770
841
|
) : null}
|
|
771
842
|
</div>
|
|
772
843
|
</div>
|
|
@@ -821,6 +892,7 @@ export function BrowserViewer({
|
|
|
821
892
|
/>
|
|
822
893
|
{!selectedRegistrySession ? (
|
|
823
894
|
<BrowserUnselectedPanel
|
|
895
|
+
unavailableBrowser={unavailableBrowser}
|
|
824
896
|
peerCount={liveSessions.length}
|
|
825
897
|
creating={creating}
|
|
826
898
|
onCreate={() => createBrowser()}
|
|
@@ -864,6 +936,22 @@ export function BrowserViewer({
|
|
|
864
936
|
.act({ type: "navigate", url })
|
|
865
937
|
.catch((cause) => notifyError(cause, "Could not navigate."))
|
|
866
938
|
}
|
|
939
|
+
onHistory={async (direction) => {
|
|
940
|
+
try {
|
|
941
|
+
const action = { type: "history", direction } as const;
|
|
942
|
+
if (receivedFrame) await browser.actFromFrame(action, receivedFrame);
|
|
943
|
+
else {
|
|
944
|
+
if (!browser.observation)
|
|
945
|
+
throw new Error("The browser page is not ready for input.");
|
|
946
|
+
await browser.actFromObservation(action, browser.observation);
|
|
947
|
+
}
|
|
948
|
+
} catch (cause) {
|
|
949
|
+
notifyError(
|
|
950
|
+
cause,
|
|
951
|
+
direction === "back" ? "Could not go back." : "Could not go forward.",
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}}
|
|
867
955
|
onReload={() => {
|
|
868
956
|
const url = browser.selectedTarget?.url;
|
|
869
957
|
if (url)
|
|
@@ -872,52 +960,136 @@ export function BrowserViewer({
|
|
|
872
960
|
.catch((cause) => notifyError(cause, "Could not reload."));
|
|
873
961
|
}}
|
|
874
962
|
/>
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
963
|
+
{browser.inputFailure ? (
|
|
964
|
+
<div
|
|
965
|
+
role="alert"
|
|
966
|
+
aria-label="Browser input status"
|
|
967
|
+
className="flex shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-3 py-2 text-og-xs"
|
|
968
|
+
>
|
|
969
|
+
<CircleAlertIcon className="size-4 shrink-0 text-og-status-failed" />
|
|
970
|
+
<div className="min-w-0 flex-1">
|
|
971
|
+
<span className="font-medium">
|
|
972
|
+
{browser.inputFailure.state === "failed"
|
|
973
|
+
? "Browser input failed"
|
|
974
|
+
: "Input result unknown"}
|
|
975
|
+
</span>
|
|
976
|
+
<p className="text-og-fg-subtle">
|
|
977
|
+
{browser.inputFailure.error?.message ?? "Inspect the page before continuing."}
|
|
978
|
+
</p>
|
|
979
|
+
</div>
|
|
980
|
+
<button
|
|
981
|
+
type="button"
|
|
982
|
+
onClick={() => void browser.refresh()}
|
|
983
|
+
className="shrink-0 rounded border border-og-border px-2 py-1 transition hover:bg-og-surface-2"
|
|
984
|
+
>
|
|
985
|
+
Check browser
|
|
986
|
+
</button>
|
|
987
|
+
</div>
|
|
988
|
+
) : null}
|
|
989
|
+
<div className="relative flex min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
990
|
+
<BrowserViewport
|
|
991
|
+
// A new control scope must discard every buffered gesture and queued action.
|
|
992
|
+
key={JSON.stringify([
|
|
993
|
+
selection?.sessionId,
|
|
994
|
+
browser.selectedTarget?.id,
|
|
995
|
+
browser.selectedTarget?.targetGeneration,
|
|
996
|
+
browser.selectedTarget?.documentGeneration,
|
|
997
|
+
frames.attachment?.controllerGeneration,
|
|
998
|
+
])}
|
|
999
|
+
focusHandoffRef={viewportFocusHandoffRef}
|
|
1000
|
+
focusScope={JSON.stringify([
|
|
1001
|
+
selection?.sessionId,
|
|
1002
|
+
browser.selectedTarget?.id,
|
|
1003
|
+
browser.selectedTarget?.targetGeneration,
|
|
1004
|
+
])}
|
|
1005
|
+
frame={displayedFrame}
|
|
1006
|
+
connectionState={displayConnectionState}
|
|
1007
|
+
supportsLiveFrames={supportsLiveFrames}
|
|
1008
|
+
connectionError={connectionError}
|
|
1009
|
+
controlUnavailable={controlUnavailable}
|
|
1010
|
+
observation={browser.observation}
|
|
1011
|
+
mutating={browser.mutating || savingProfile}
|
|
1012
|
+
activityLabel={savingProfile ? "Saving browser version…" : undefined}
|
|
1013
|
+
clipboardEnabled={browser.session?.capabilities.clipboard === true}
|
|
1014
|
+
inputBatchAttachment={frames.attachment}
|
|
1015
|
+
onAction={async (action, frame) => {
|
|
1016
|
+
const focusedInput =
|
|
1017
|
+
frame &&
|
|
1018
|
+
action.type === "pointer" &&
|
|
1019
|
+
action.action === "click" &&
|
|
1020
|
+
(action.button === undefined || action.button === "left") &&
|
|
1021
|
+
frames.attachment?.focusedInputObservations === true &&
|
|
1022
|
+
frames.attachment.browserSessionId === frame.browserSessionId &&
|
|
1023
|
+
frames.attachment.controllerGeneration === frame.controllerGeneration &&
|
|
1024
|
+
frames.attachment.targetId === frame.targetId &&
|
|
1025
|
+
Date.parse(frames.attachment.expiresAt) > Date.now();
|
|
1026
|
+
const receipt = frame
|
|
1027
|
+
? await browser.actFromFrame(
|
|
1028
|
+
action,
|
|
1029
|
+
frame,
|
|
1030
|
+
undefined,
|
|
1031
|
+
focusedInput ? "input" : "none",
|
|
1032
|
+
)
|
|
1033
|
+
: await browser.act(action);
|
|
1034
|
+
if (receipt.state !== "completed") {
|
|
1035
|
+
throw new Error(receipt.error?.message ?? "Browser input did not complete.");
|
|
1036
|
+
}
|
|
1037
|
+
return receipt;
|
|
1038
|
+
}}
|
|
1039
|
+
onReadClipboard={browser.readClipboard}
|
|
1040
|
+
onObserveForInput={browser.observeForInput}
|
|
1041
|
+
onSelectFromObservation={browser.actFromObservation}
|
|
1042
|
+
onReconnect={
|
|
1043
|
+
selectedChromeGenerationLoss
|
|
1044
|
+
? () => {
|
|
1045
|
+
if (replacementChromeDevice) {
|
|
1046
|
+
createBrowser({ kind: "attached", device: replacementChromeDevice });
|
|
1047
|
+
}
|
|
899
1048
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1049
|
+
: () => {
|
|
1050
|
+
void browser.refresh();
|
|
1051
|
+
frames.reconnect();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
reconnectLabel={
|
|
1055
|
+
selectedChromeGenerationLoss && replacementChromeDevice
|
|
1056
|
+
? "Open a fresh Connected Chrome"
|
|
1057
|
+
: undefined
|
|
1058
|
+
}
|
|
1059
|
+
reconnectMessage={
|
|
1060
|
+
selectedChromeGenerationLoss
|
|
1061
|
+
? "Chrome reconnected—open a fresh browser/desktop."
|
|
1062
|
+
: undefined
|
|
1063
|
+
}
|
|
1064
|
+
onError={(cause) => notifyError(cause, "Browser input failed.")}
|
|
1065
|
+
/>
|
|
1066
|
+
{diagnosticsView ? (
|
|
1067
|
+
<BrowserDiagnosticsDrawer
|
|
1068
|
+
session={browser.session}
|
|
1069
|
+
profile={selectedProfile}
|
|
1070
|
+
target={browser.selectedTarget}
|
|
1071
|
+
observation={browser.observation}
|
|
1072
|
+
baseRevisionOrdinal={baseRevisionOrdinal}
|
|
1073
|
+
state={diagnosticsView}
|
|
1074
|
+
downloads={downloads.downloads}
|
|
1075
|
+
downloadsEnabled={browser.session?.capabilities.downloads === true}
|
|
1076
|
+
downloadsLoading={downloads.loading}
|
|
1077
|
+
downloadsRefreshing={downloads.refreshing}
|
|
1078
|
+
downloadsError={downloads.error}
|
|
1079
|
+
savingDownloadIds={downloads.savingDownloadIds}
|
|
1080
|
+
onSaveDownload={saveDownload}
|
|
1081
|
+
onRefreshDownloads={() => void downloads.refresh()}
|
|
1082
|
+
onRefresh={refreshDiagnostics}
|
|
1083
|
+
onClose={closeDiagnostics}
|
|
1084
|
+
/>
|
|
1085
|
+
) : null}
|
|
1086
|
+
</div>
|
|
916
1087
|
<BrowserStatusBar
|
|
917
1088
|
session={browser.session}
|
|
918
1089
|
profile={selectedProfile}
|
|
919
1090
|
target={browser.selectedTarget}
|
|
920
1091
|
observation={browser.observation}
|
|
1092
|
+
inputFailure={browser.inputFailure}
|
|
921
1093
|
connectionState={displayConnectionState}
|
|
922
1094
|
refreshing={registry.refreshing}
|
|
923
1095
|
diagnosticsOpen={diagnosticsView !== null}
|
|
@@ -933,26 +1105,6 @@ export function BrowserViewer({
|
|
|
933
1105
|
/>
|
|
934
1106
|
</>
|
|
935
1107
|
)}
|
|
936
|
-
{diagnosticsView ? (
|
|
937
|
-
<BrowserDiagnosticsDrawer
|
|
938
|
-
session={browser.session}
|
|
939
|
-
profile={selectedProfile}
|
|
940
|
-
target={browser.selectedTarget}
|
|
941
|
-
observation={browser.observation}
|
|
942
|
-
baseRevisionOrdinal={baseRevisionOrdinal}
|
|
943
|
-
state={diagnosticsView}
|
|
944
|
-
downloads={downloads.downloads}
|
|
945
|
-
downloadsEnabled={browser.session?.capabilities.downloads === true}
|
|
946
|
-
downloadsLoading={downloads.loading}
|
|
947
|
-
downloadsRefreshing={downloads.refreshing}
|
|
948
|
-
downloadsError={downloads.error}
|
|
949
|
-
savingDownloadIds={downloads.savingDownloadIds}
|
|
950
|
-
onSaveDownload={saveDownload}
|
|
951
|
-
onRefreshDownloads={() => void downloads.refresh()}
|
|
952
|
-
onRefresh={refreshDiagnostics}
|
|
953
|
-
onClose={closeDiagnostics}
|
|
954
|
-
/>
|
|
955
|
-
) : null}
|
|
956
1108
|
</div>
|
|
957
1109
|
);
|
|
958
1110
|
}
|
|
@@ -980,20 +1132,20 @@ function BrowserLifecyclePanel(props: {
|
|
|
980
1132
|
return (
|
|
981
1133
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center">
|
|
982
1134
|
<div className="max-w-sm">
|
|
983
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
1135
|
+
<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">
|
|
984
1136
|
{failed ? (
|
|
985
|
-
<CircleAlertIcon className="size-4.5 text-og-
|
|
1137
|
+
<CircleAlertIcon className="size-4.5 text-og-danger" />
|
|
986
1138
|
) : (
|
|
987
1139
|
<LoaderCircleIcon className="size-4.5 animate-spin" />
|
|
988
1140
|
)}
|
|
989
1141
|
</span>
|
|
990
1142
|
<p className="mt-3 text-og-menu font-medium text-og-fg">{label}</p>
|
|
991
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">{detail}</p>
|
|
1143
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">{detail}</p>
|
|
992
1144
|
{failed || (props.session.lifecycle === "suspended" && !props.attempt?.running) ? (
|
|
993
1145
|
<button
|
|
994
1146
|
type="button"
|
|
995
1147
|
onClick={props.onResume}
|
|
996
|
-
className="mt-4 inline-flex h-8 items-center rounded-og-sm border border-og-border bg-og-
|
|
1148
|
+
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"
|
|
997
1149
|
>
|
|
998
1150
|
Open browser
|
|
999
1151
|
</button>
|
|
@@ -1030,7 +1182,7 @@ function BrowserToolbar(props: {
|
|
|
1030
1182
|
onRefresh: () => void;
|
|
1031
1183
|
setupUrl?: string | undefined;
|
|
1032
1184
|
}) {
|
|
1033
|
-
const detailsRef =
|
|
1185
|
+
const detailsRef = useViewerMenuDismiss();
|
|
1034
1186
|
const selected = props.sessions.find((session) => session.id === props.selectedSessionId);
|
|
1035
1187
|
const current = props.sessions.filter((session) => props.relevantSessionIds.has(session.id));
|
|
1036
1188
|
const others = props.sessions.filter((session) => !props.relevantSessionIds.has(session.id));
|
|
@@ -1039,34 +1191,36 @@ function BrowserToolbar(props: {
|
|
|
1039
1191
|
detailsRef.current?.removeAttribute("open");
|
|
1040
1192
|
};
|
|
1041
1193
|
return (
|
|
1042
|
-
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-
|
|
1043
|
-
<details ref={detailsRef} className="
|
|
1194
|
+
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-1 px-2">
|
|
1195
|
+
<details ref={detailsRef} className="min-w-0">
|
|
1044
1196
|
<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">
|
|
1045
|
-
<Globe2Icon className="size-3.5 shrink-0 text-og-muted" />
|
|
1197
|
+
<Globe2Icon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1046
1198
|
<span className="truncate font-medium">{selected?.name ?? "Browser"}</span>
|
|
1047
1199
|
{(props.interventionCounts.get(selected?.id ?? "") ?? 0) > 0 ? (
|
|
1048
1200
|
<span className="size-1.5 shrink-0 rounded-full bg-og-status-waiting" />
|
|
1049
1201
|
) : null}
|
|
1050
|
-
<ChevronDownIcon className="size-3 shrink-0 text-og-subtle" />
|
|
1202
|
+
<ChevronDownIcon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
1051
1203
|
</summary>
|
|
1052
|
-
<div className="absolute left-
|
|
1053
|
-
<
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1204
|
+
<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">
|
|
1205
|
+
<div className="min-h-0 max-h-96 overflow-y-auto overscroll-contain">
|
|
1206
|
+
<BrowserSessionGroup
|
|
1207
|
+
label="Current agent"
|
|
1208
|
+
sessions={current}
|
|
1209
|
+
identities={props.identities}
|
|
1210
|
+
selectedId={props.selectedSessionId}
|
|
1211
|
+
interventionCounts={props.interventionCounts}
|
|
1212
|
+
onSelect={choose}
|
|
1213
|
+
/>
|
|
1214
|
+
<BrowserSessionGroup
|
|
1215
|
+
label="Workspace browsers"
|
|
1216
|
+
sessions={others}
|
|
1217
|
+
identities={props.identities}
|
|
1218
|
+
selectedId={props.selectedSessionId}
|
|
1219
|
+
interventionCounts={props.interventionCounts}
|
|
1220
|
+
onSelect={choose}
|
|
1221
|
+
/>
|
|
1222
|
+
</div>
|
|
1223
|
+
<div className="mt-1 flex shrink-0 gap-1 border-t border-og-border pt-1">
|
|
1070
1224
|
{current.length > 0 ? (
|
|
1071
1225
|
<MenuButton onClick={props.onFollow}>Follow agent</MenuButton>
|
|
1072
1226
|
) : null}
|
|
@@ -1076,7 +1230,7 @@ function BrowserToolbar(props: {
|
|
|
1076
1230
|
</div>
|
|
1077
1231
|
</div>
|
|
1078
1232
|
</details>
|
|
1079
|
-
<span className="min-w-0 flex-1 truncate text-og-xs text-og-subtle">
|
|
1233
|
+
<span className="min-w-0 flex-1 truncate text-og-xs text-og-fg-subtle">
|
|
1080
1234
|
{placementLabel(selected)}
|
|
1081
1235
|
</span>
|
|
1082
1236
|
<BrowserProfileMenu
|
|
@@ -1093,7 +1247,7 @@ function BrowserToolbar(props: {
|
|
|
1093
1247
|
<button
|
|
1094
1248
|
type="button"
|
|
1095
1249
|
onClick={props.onRefresh}
|
|
1096
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
1250
|
+
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"
|
|
1097
1251
|
aria-label="Refresh browsers"
|
|
1098
1252
|
>
|
|
1099
1253
|
<RefreshCwIcon className={cn("size-3.5", props.refreshing && "animate-spin")} />
|
|
@@ -1113,6 +1267,7 @@ function BrowserToolbar(props: {
|
|
|
1113
1267
|
}
|
|
1114
1268
|
|
|
1115
1269
|
function BrowserUnselectedPanel(props: {
|
|
1270
|
+
unavailableBrowser: BrowserSession | undefined;
|
|
1116
1271
|
peerCount: number;
|
|
1117
1272
|
creating: boolean;
|
|
1118
1273
|
onCreate: () => void;
|
|
@@ -1120,11 +1275,15 @@ function BrowserUnselectedPanel(props: {
|
|
|
1120
1275
|
return (
|
|
1121
1276
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center">
|
|
1122
1277
|
<div className="max-w-sm">
|
|
1123
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
1278
|
+
<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">
|
|
1124
1279
|
<Globe2Icon className="size-4.5" />
|
|
1125
1280
|
</span>
|
|
1126
|
-
|
|
1127
|
-
|
|
1281
|
+
{props.unavailableBrowser ? (
|
|
1282
|
+
<BrowserUnavailableNotice session={props.unavailableBrowser} />
|
|
1283
|
+
) : (
|
|
1284
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">No browser for this agent</p>
|
|
1285
|
+
)}
|
|
1286
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
1128
1287
|
{props.peerCount === 1
|
|
1129
1288
|
? "One workspace browser is available from the browser menu."
|
|
1130
1289
|
: `${props.peerCount} workspace browsers are available from the browser menu.`}
|
|
@@ -1133,7 +1292,7 @@ function BrowserUnselectedPanel(props: {
|
|
|
1133
1292
|
type="button"
|
|
1134
1293
|
disabled={props.creating}
|
|
1135
1294
|
onClick={props.onCreate}
|
|
1136
|
-
className="mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-border bg-og-
|
|
1295
|
+
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"
|
|
1137
1296
|
>
|
|
1138
1297
|
<PlusIcon className="size-3.5" />
|
|
1139
1298
|
{props.creating ? "Opening…" : "New browser"}
|
|
@@ -1143,6 +1302,27 @@ function BrowserUnselectedPanel(props: {
|
|
|
1143
1302
|
);
|
|
1144
1303
|
}
|
|
1145
1304
|
|
|
1305
|
+
function BrowserUnavailableNotice({ session }: { session: BrowserSession }) {
|
|
1306
|
+
const reason =
|
|
1307
|
+
session.failureCode === "provider_deadline_rotation"
|
|
1308
|
+
? "The computer running it reached its time limit."
|
|
1309
|
+
: session.failureCode === "source_placement_changed"
|
|
1310
|
+
? "This chat moved to another computer."
|
|
1311
|
+
: session.failureCode === "idle_released"
|
|
1312
|
+
? "It stopped after it went unused."
|
|
1313
|
+
: session.lifecycle === "failed"
|
|
1314
|
+
? "It stopped unexpectedly."
|
|
1315
|
+
: "The connection to it was lost.";
|
|
1316
|
+
return (
|
|
1317
|
+
<div role="status">
|
|
1318
|
+
<p className="mt-3 text-og-menu font-medium text-og-fg">Browser unavailable</p>
|
|
1319
|
+
<p className="mt-1 break-words text-og-control leading-5 text-og-fg-muted">
|
|
1320
|
+
{session.name}. {reason} Open a new browser to continue.
|
|
1321
|
+
</p>
|
|
1322
|
+
</div>
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1146
1326
|
function BrowserSessionGroup(props: {
|
|
1147
1327
|
label: string;
|
|
1148
1328
|
sessions: BrowserSession[];
|
|
@@ -1154,7 +1334,7 @@ function BrowserSessionGroup(props: {
|
|
|
1154
1334
|
if (props.sessions.length === 0) return null;
|
|
1155
1335
|
return (
|
|
1156
1336
|
<div className="py-1">
|
|
1157
|
-
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1337
|
+
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1158
1338
|
{props.label}
|
|
1159
1339
|
</p>
|
|
1160
1340
|
{props.sessions.map((session) => {
|
|
@@ -1164,6 +1344,8 @@ function BrowserSessionGroup(props: {
|
|
|
1164
1344
|
<button
|
|
1165
1345
|
key={session.id}
|
|
1166
1346
|
type="button"
|
|
1347
|
+
aria-label={`${session.name}${identity ? ` · ${identity.name}` : ""} · ${placementLabel(session)}`}
|
|
1348
|
+
aria-pressed={session.id === props.selectedId}
|
|
1167
1349
|
onClick={() => props.onSelect(session.id)}
|
|
1168
1350
|
className={cn(
|
|
1169
1351
|
"flex w-full items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
|
|
@@ -1178,7 +1360,7 @@ function BrowserSessionGroup(props: {
|
|
|
1178
1360
|
/>
|
|
1179
1361
|
<span className="min-w-0 flex-1">
|
|
1180
1362
|
<span className="block truncate text-og-control text-og-fg">{session.name}</span>
|
|
1181
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1363
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1182
1364
|
{identity ? `${identity.name} · ` : ""}
|
|
1183
1365
|
{placementLabel(session)}
|
|
1184
1366
|
</span>
|
|
@@ -1206,7 +1388,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1206
1388
|
setupUrl?: string | undefined;
|
|
1207
1389
|
prominent?: boolean;
|
|
1208
1390
|
}) {
|
|
1209
|
-
const detailsRef =
|
|
1391
|
+
const detailsRef = useViewerMenuDismiss();
|
|
1210
1392
|
const choose = (choice?: BrowserLaunchChoice) => {
|
|
1211
1393
|
detailsRef.current?.removeAttribute("open");
|
|
1212
1394
|
props.onCreate(choice);
|
|
@@ -1218,13 +1400,13 @@ function BrowserLaunchMenu(props: {
|
|
|
1218
1400
|
const activeIdentities = props.identities.filter((identity) => identity.status === "active");
|
|
1219
1401
|
const archivedIdentities = props.identities.filter((identity) => identity.status === "archived");
|
|
1220
1402
|
return (
|
|
1221
|
-
<details ref={detailsRef} className={cn(
|
|
1403
|
+
<details ref={detailsRef} className={cn(props.prominent && "mt-4 inline-block")}>
|
|
1222
1404
|
<summary
|
|
1223
1405
|
onClick={(event) => {
|
|
1224
1406
|
if (busy) event.preventDefault();
|
|
1225
1407
|
}}
|
|
1226
1408
|
className={cn(
|
|
1227
|
-
"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",
|
|
1409
|
+
"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",
|
|
1228
1410
|
props.prominent
|
|
1229
1411
|
? "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"
|
|
1230
1412
|
: "grid size-7 place-items-center rounded-og-sm",
|
|
@@ -1242,13 +1424,13 @@ function BrowserLaunchMenu(props: {
|
|
|
1242
1424
|
</summary>
|
|
1243
1425
|
<div
|
|
1244
1426
|
className={cn(
|
|
1245
|
-
"absolute z-40 w-72 overflow-
|
|
1246
|
-
props.prominent ? "left-1/2 top-
|
|
1427
|
+
"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",
|
|
1428
|
+
props.prominent ? "left-1/2 top-2 -translate-x-1/2" : "right-2 top-10",
|
|
1247
1429
|
)}
|
|
1248
1430
|
>
|
|
1249
1431
|
{props.attachedDevices.length > 0 || props.attachedBridges.length > 0 ? (
|
|
1250
1432
|
<div className="pb-1">
|
|
1251
|
-
<p className="px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1433
|
+
<p className="px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1252
1434
|
Connected Chrome
|
|
1253
1435
|
</p>
|
|
1254
1436
|
{props.attachedDevices.map((device) => (
|
|
@@ -1258,12 +1440,12 @@ function BrowserLaunchMenu(props: {
|
|
|
1258
1440
|
onClick={() => choose({ kind: "attached", device })}
|
|
1259
1441
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1260
1442
|
>
|
|
1261
|
-
<MonitorIcon className="size-3.5 shrink-0 text-og-muted" />
|
|
1443
|
+
<MonitorIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1262
1444
|
<span className="min-w-0 flex-1">
|
|
1263
1445
|
<span className="block truncate text-og-control text-og-fg">
|
|
1264
1446
|
{device.profileLabel ?? device.name}
|
|
1265
1447
|
</span>
|
|
1266
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1448
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1267
1449
|
{device.browserName} · {device.tabCount} open{" "}
|
|
1268
1450
|
{device.tabCount === 1 ? "tab" : "tabs"}
|
|
1269
1451
|
</span>
|
|
@@ -1280,27 +1462,27 @@ function BrowserLaunchMenu(props: {
|
|
|
1280
1462
|
onClick={() => detailsRef.current?.removeAttribute("open")}
|
|
1281
1463
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1282
1464
|
>
|
|
1283
|
-
<PlusIcon className="size-3.5 shrink-0 text-og-muted" />
|
|
1465
|
+
<PlusIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1284
1466
|
<span className="min-w-0 flex-1">
|
|
1285
1467
|
<span className="block truncate text-og-control text-og-fg">
|
|
1286
1468
|
{props.attachedDevices.length > 0
|
|
1287
1469
|
? "Connect another Chrome profile"
|
|
1288
1470
|
: "Connect this Chrome profile"}
|
|
1289
1471
|
</span>
|
|
1290
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1472
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
1291
1473
|
Machine connected · Chrome extension missing
|
|
1292
1474
|
</span>
|
|
1293
1475
|
</span>
|
|
1294
1476
|
</a>
|
|
1295
1477
|
) : (
|
|
1296
1478
|
<div className="flex w-full items-center gap-2 px-2 py-2 text-left">
|
|
1297
|
-
<MonitorIcon className="size-3.5 shrink-0 text-og-muted" />
|
|
1479
|
+
<MonitorIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
1298
1480
|
<span className="min-w-0 flex-1">
|
|
1299
1481
|
<span className="block truncate text-og-control text-og-fg">
|
|
1300
1482
|
Chrome connection ready
|
|
1301
1483
|
</span>
|
|
1302
|
-
<span className="block text-og-xs leading-4 text-og-subtle">
|
|
1303
|
-
Install the
|
|
1484
|
+
<span className="block text-og-xs leading-4 text-og-fg-subtle">
|
|
1485
|
+
Install the Opengeni Browser extension in the profile you want to use.
|
|
1304
1486
|
</span>
|
|
1305
1487
|
</span>
|
|
1306
1488
|
</div>
|
|
@@ -1310,7 +1492,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1310
1492
|
) : null}
|
|
1311
1493
|
<p
|
|
1312
1494
|
className={cn(
|
|
1313
|
-
"px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle",
|
|
1495
|
+
"px-2 pb-1 pt-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle",
|
|
1314
1496
|
(props.attachedDevices.length > 0 || props.attachedBridges.length > 0) &&
|
|
1315
1497
|
"border-t border-og-border",
|
|
1316
1498
|
)}
|
|
@@ -1322,10 +1504,12 @@ function BrowserLaunchMenu(props: {
|
|
|
1322
1504
|
onClick={() => choose({ kind: "clean" })}
|
|
1323
1505
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1324
1506
|
>
|
|
1325
|
-
<Globe2Icon className="size-3.5 text-og-muted" />
|
|
1507
|
+
<Globe2Icon className="size-3.5 text-og-fg-muted" />
|
|
1326
1508
|
<span>
|
|
1327
1509
|
<span className="block text-og-control text-og-fg">Fresh browser</span>
|
|
1328
|
-
<span className="block text-og-xs text-og-subtle">
|
|
1510
|
+
<span className="block text-og-xs text-og-fg-subtle">
|
|
1511
|
+
Visual browser · no saved state
|
|
1512
|
+
</span>
|
|
1329
1513
|
</span>
|
|
1330
1514
|
</button>
|
|
1331
1515
|
{activeIdentities.length > 0 ? (
|
|
@@ -1337,10 +1521,10 @@ function BrowserLaunchMenu(props: {
|
|
|
1337
1521
|
onClick={() => choose({ kind: "profile", identityId: identity.id })}
|
|
1338
1522
|
className="flex w-full items-center gap-2 rounded-og-sm px-2 py-2 text-left transition hover:bg-og-surface-2"
|
|
1339
1523
|
>
|
|
1340
|
-
<UserRoundIcon className="size-3.5 text-og-muted" />
|
|
1524
|
+
<UserRoundIcon className="size-3.5 text-og-fg-muted" />
|
|
1341
1525
|
<span className="min-w-0">
|
|
1342
1526
|
<span className="block truncate text-og-control text-og-fg">{identity.name}</span>
|
|
1343
|
-
<span className="block text-og-xs text-og-subtle">
|
|
1527
|
+
<span className="block text-og-xs text-og-fg-subtle">
|
|
1344
1528
|
{identity.defaultRevisionId
|
|
1345
1529
|
? `${identity.revisionCount} saved version${identity.revisionCount === 1 ? "" : "s"}`
|
|
1346
1530
|
: "Ready for first sign-in"}
|
|
@@ -1352,7 +1536,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1352
1536
|
) : null}
|
|
1353
1537
|
{archivedIdentities.length > 0 ? (
|
|
1354
1538
|
<details className="mt-1 border-t border-og-border pt-1">
|
|
1355
|
-
<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">
|
|
1539
|
+
<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">
|
|
1356
1540
|
<ArchiveIcon className="size-3" /> Hidden profiles · {archivedIdentities.length}
|
|
1357
1541
|
<ChevronDownIcon className="ml-auto size-3" />
|
|
1358
1542
|
</summary>
|
|
@@ -1364,7 +1548,7 @@ function BrowserLaunchMenu(props: {
|
|
|
1364
1548
|
onClick={() => void restore(identity)}
|
|
1365
1549
|
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"
|
|
1366
1550
|
>
|
|
1367
|
-
<RotateCcwIcon className="size-3.5 text-og-muted" />
|
|
1551
|
+
<RotateCcwIcon className="size-3.5 text-og-fg-muted" />
|
|
1368
1552
|
<span className="min-w-0 flex-1 truncate text-og-control text-og-fg">
|
|
1369
1553
|
Restore {identity.name}
|
|
1370
1554
|
</span>
|
|
@@ -1391,7 +1575,7 @@ function BrowserProfileMenu(props: {
|
|
|
1391
1575
|
) => Promise<boolean>;
|
|
1392
1576
|
onOpenVersion: (identityId: string, baseRevisionId: string) => void;
|
|
1393
1577
|
}) {
|
|
1394
|
-
const detailsRef =
|
|
1578
|
+
const detailsRef = useViewerMenuDismiss();
|
|
1395
1579
|
const nameInputId = useId();
|
|
1396
1580
|
const [name, setName] = useState("");
|
|
1397
1581
|
const attached = props.session?.placement.kind === "attached_device";
|
|
@@ -1422,8 +1606,8 @@ function BrowserProfileMenu(props: {
|
|
|
1422
1606
|
: "unsaved"
|
|
1423
1607
|
: "temporary";
|
|
1424
1608
|
return (
|
|
1425
|
-
<details ref={detailsRef} className="
|
|
1426
|
-
<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">
|
|
1609
|
+
<details ref={detailsRef} className="min-w-0">
|
|
1610
|
+
<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">
|
|
1427
1611
|
{props.saving ? (
|
|
1428
1612
|
<LoaderCircleIcon className="size-3 animate-spin" />
|
|
1429
1613
|
) : attached ? (
|
|
@@ -1435,13 +1619,13 @@ function BrowserProfileMenu(props: {
|
|
|
1435
1619
|
{attached ? props.session?.name : (props.identity?.name ?? "Temporary")}
|
|
1436
1620
|
</span>
|
|
1437
1621
|
{attached || props.identity ? (
|
|
1438
|
-
<span className="shrink-0 text-og-subtle">· {versionLabel}</span>
|
|
1622
|
+
<span className="shrink-0 text-og-fg-subtle">· {versionLabel}</span>
|
|
1439
1623
|
) : null}
|
|
1440
|
-
<ChevronDownIcon className="size-3 shrink-0 text-og-subtle" />
|
|
1624
|
+
<ChevronDownIcon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
1441
1625
|
</summary>
|
|
1442
|
-
<div className="absolute right-
|
|
1626
|
+
<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">
|
|
1443
1627
|
<div className="flex items-start gap-2">
|
|
1444
|
-
<span className="grid size-7 shrink-0 place-items-center rounded-og-sm bg-og-surface-2 text-og-muted">
|
|
1628
|
+
<span className="grid size-7 shrink-0 place-items-center rounded-og-sm bg-og-surface-2 text-og-fg-muted">
|
|
1445
1629
|
{attached ? (
|
|
1446
1630
|
<MonitorIcon className="size-3.5" />
|
|
1447
1631
|
) : (
|
|
@@ -1452,7 +1636,7 @@ function BrowserProfileMenu(props: {
|
|
|
1452
1636
|
<p className="truncate text-og-control font-medium text-og-fg">
|
|
1453
1637
|
{attached ? props.session?.name : (props.identity?.name ?? "Temporary browser")}
|
|
1454
1638
|
</p>
|
|
1455
|
-
<p className="mt-0.5 text-og-xs leading-4 text-og-subtle">
|
|
1639
|
+
<p className="mt-0.5 text-og-xs leading-4 text-og-fg-subtle">
|
|
1456
1640
|
{attached
|
|
1457
1641
|
? "This session drives the existing Chrome profile and its current login state."
|
|
1458
1642
|
: props.identity?.status === "archived"
|
|
@@ -1467,7 +1651,7 @@ function BrowserProfileMenu(props: {
|
|
|
1467
1651
|
</div>
|
|
1468
1652
|
{props.identity && revisions.length > 0 ? (
|
|
1469
1653
|
<div className="mt-3 border-t border-og-border pt-2">
|
|
1470
|
-
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1654
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1471
1655
|
Saved versions
|
|
1472
1656
|
</p>
|
|
1473
1657
|
<div className="mt-1 max-h-40 overflow-y-auto">
|
|
@@ -1487,19 +1671,19 @@ function BrowserProfileMenu(props: {
|
|
|
1487
1671
|
onClick={() => openVersion(props.identity!.id, revision.id)}
|
|
1488
1672
|
className="flex min-w-0 flex-1 items-center gap-2 px-2 py-1.5 text-left disabled:opacity-50"
|
|
1489
1673
|
>
|
|
1490
|
-
<span className="grid size-4 place-items-center text-og-muted">
|
|
1674
|
+
<span className="grid size-4 place-items-center text-og-fg-muted">
|
|
1491
1675
|
{isCurrent ? <CheckIcon className="size-3.5 text-og-accent" /> : null}
|
|
1492
1676
|
</span>
|
|
1493
1677
|
<span className="min-w-0 flex-1">
|
|
1494
1678
|
<span className="block text-og-control text-og-fg">
|
|
1495
1679
|
Version {revision.ordinal}
|
|
1496
1680
|
</span>
|
|
1497
|
-
<span className="block truncate text-[10px] text-og-subtle">
|
|
1681
|
+
<span className="block truncate text-[10px] text-og-fg-subtle">
|
|
1498
1682
|
{materialization.label}
|
|
1499
1683
|
</span>
|
|
1500
1684
|
</span>
|
|
1501
1685
|
{isCurrent ? (
|
|
1502
|
-
<span className="text-[10px] text-og-subtle">Current</span>
|
|
1686
|
+
<span className="text-[10px] text-og-fg-subtle">Current</span>
|
|
1503
1687
|
) : null}
|
|
1504
1688
|
</button>
|
|
1505
1689
|
<button
|
|
@@ -1514,7 +1698,7 @@ function BrowserProfileMenu(props: {
|
|
|
1514
1698
|
onClick={() =>
|
|
1515
1699
|
void props.onUpdate(props.identity!, { defaultRevisionId: revision.id })
|
|
1516
1700
|
}
|
|
1517
|
-
className="mr-1 shrink-0 rounded-og-sm px-2 py-1 text-[10px] text-og-subtle transition hover:bg-og-surface-3 hover:text-og-fg disabled:cursor-default disabled:opacity-70"
|
|
1701
|
+
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"
|
|
1518
1702
|
>
|
|
1519
1703
|
{isDefault ? "Default" : "Make default"}
|
|
1520
1704
|
</button>
|
|
@@ -1522,7 +1706,7 @@ function BrowserProfileMenu(props: {
|
|
|
1522
1706
|
);
|
|
1523
1707
|
})}
|
|
1524
1708
|
</div>
|
|
1525
|
-
<p className="mt-1 px-2 text-[10px] leading-4 text-og-subtle">
|
|
1709
|
+
<p className="mt-1 px-2 text-[10px] leading-4 text-og-fg-subtle">
|
|
1526
1710
|
Changing the default affects only browsers opened later. Saved browser data can be
|
|
1527
1711
|
copied; a website may still expire or re-verify its own session.
|
|
1528
1712
|
</p>
|
|
@@ -1530,13 +1714,13 @@ function BrowserProfileMenu(props: {
|
|
|
1530
1714
|
) : props.identity &&
|
|
1531
1715
|
props.history?.identityId === props.identity.id &&
|
|
1532
1716
|
props.history.loading ? (
|
|
1533
|
-
<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">
|
|
1717
|
+
<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">
|
|
1534
1718
|
<LoaderCircleIcon className="size-3 animate-spin" /> Loading saved versions…
|
|
1535
1719
|
</p>
|
|
1536
1720
|
) : null}
|
|
1537
1721
|
{props.identity && props.authConnections.length > 0 ? (
|
|
1538
1722
|
<div className="mt-3 border-t border-og-border pt-2">
|
|
1539
|
-
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1723
|
+
<p className="px-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
1540
1724
|
Login health
|
|
1541
1725
|
</p>
|
|
1542
1726
|
<div className="mt-1 space-y-1">
|
|
@@ -1552,7 +1736,7 @@ function BrowserProfileMenu(props: {
|
|
|
1552
1736
|
<span className="block truncate text-og-control text-og-fg">
|
|
1553
1737
|
{connection.name}
|
|
1554
1738
|
</span>
|
|
1555
|
-
<span className="block truncate text-[10px] text-og-subtle">
|
|
1739
|
+
<span className="block truncate text-[10px] text-og-fg-subtle">
|
|
1556
1740
|
{connection.accountLabel} · {health.label}
|
|
1557
1741
|
</span>
|
|
1558
1742
|
</span>
|
|
@@ -1585,7 +1769,7 @@ function BrowserProfileMenu(props: {
|
|
|
1585
1769
|
if (name.trim()) void save(name);
|
|
1586
1770
|
}}
|
|
1587
1771
|
>
|
|
1588
|
-
<label className="text-og-xs font-medium text-og-muted" htmlFor={nameInputId}>
|
|
1772
|
+
<label className="text-og-xs font-medium text-og-fg-muted" htmlFor={nameInputId}>
|
|
1589
1773
|
Profile name
|
|
1590
1774
|
</label>
|
|
1591
1775
|
<div className="mt-1 flex gap-1.5">
|
|
@@ -1595,12 +1779,12 @@ function BrowserProfileMenu(props: {
|
|
|
1595
1779
|
onInput={(event) => setName(event.currentTarget.value)}
|
|
1596
1780
|
maxLength={200}
|
|
1597
1781
|
placeholder="Work"
|
|
1598
|
-
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"
|
|
1782
|
+
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"
|
|
1599
1783
|
/>
|
|
1600
1784
|
<button
|
|
1601
1785
|
type="submit"
|
|
1602
1786
|
disabled={!name.trim() || props.saving}
|
|
1603
|
-
className="inline-flex h-8 items-center gap-1 rounded-og-sm bg-og-
|
|
1787
|
+
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"
|
|
1604
1788
|
>
|
|
1605
1789
|
{props.saving ? (
|
|
1606
1790
|
<LoaderCircleIcon className="size-3.5 animate-spin" />
|
|
@@ -1613,9 +1797,9 @@ function BrowserProfileMenu(props: {
|
|
|
1613
1797
|
</form>
|
|
1614
1798
|
)
|
|
1615
1799
|
) : (
|
|
1616
|
-
<p className="mt-3 rounded-og-sm bg-og-surface-2 px-2.5 py-2 text-og-xs text-og-subtle">
|
|
1800
|
+
<p className="mt-3 rounded-og-sm bg-og-surface-2 px-2.5 py-2 text-og-xs text-og-fg-subtle">
|
|
1617
1801
|
{attached
|
|
1618
|
-
? "Chrome keeps this profile's state directly;
|
|
1802
|
+
? "Chrome keeps this profile's state directly; Opengeni does not copy it automatically."
|
|
1619
1803
|
: props.identity?.status === "archived"
|
|
1620
1804
|
? "Restore this profile before saving another version."
|
|
1621
1805
|
: "This browser cannot save reusable profile state."}
|
|
@@ -1630,7 +1814,7 @@ function BrowserProfileMenu(props: {
|
|
|
1630
1814
|
status: props.identity!.status === "active" ? "archived" : "active",
|
|
1631
1815
|
})
|
|
1632
1816
|
}
|
|
1633
|
-
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"
|
|
1817
|
+
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"
|
|
1634
1818
|
>
|
|
1635
1819
|
{props.identity.status === "active" ? (
|
|
1636
1820
|
<ArchiveIcon className="size-3.5" />
|
|
@@ -1641,8 +1825,9 @@ function BrowserProfileMenu(props: {
|
|
|
1641
1825
|
</button>
|
|
1642
1826
|
) : null}
|
|
1643
1827
|
{canSave ? (
|
|
1644
|
-
<p className="mt-2 text-[10px] leading-4 text-og-subtle">
|
|
1645
|
-
Saving
|
|
1828
|
+
<p className="mt-2 text-[10px] leading-4 text-og-fg-subtle">
|
|
1829
|
+
Saving restarts this browser. Save unfinished work on its pages first; unsaved form
|
|
1830
|
+
entries may be lost. Other open browsers are unchanged.
|
|
1646
1831
|
</p>
|
|
1647
1832
|
) : null}
|
|
1648
1833
|
</div>
|
|
@@ -1695,7 +1880,7 @@ function MenuButton(props: { children: ReactNode; onClick: () => void; disabled?
|
|
|
1695
1880
|
type="button"
|
|
1696
1881
|
onClick={props.onClick}
|
|
1697
1882
|
disabled={props.disabled}
|
|
1698
|
-
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"
|
|
1883
|
+
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"
|
|
1699
1884
|
>
|
|
1700
1885
|
{props.children}
|
|
1701
1886
|
</button>
|
|
@@ -1713,7 +1898,7 @@ function BrowserTabs(props: {
|
|
|
1713
1898
|
}) {
|
|
1714
1899
|
const pages = props.targets.filter((target) => target.kind === "page" || target.kind === "popup");
|
|
1715
1900
|
return (
|
|
1716
|
-
<div className="flex h-9 shrink-0 items-end gap-px overflow-x-auto border-b border-og-border bg-og-surface-
|
|
1901
|
+
<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">
|
|
1717
1902
|
{pages.map((target) => (
|
|
1718
1903
|
<div
|
|
1719
1904
|
key={target.id}
|
|
@@ -1721,7 +1906,7 @@ function BrowserTabs(props: {
|
|
|
1721
1906
|
"group flex h-8 min-w-24 max-w-48 items-center rounded-t-og-sm border border-b-0 border-transparent px-2",
|
|
1722
1907
|
target.id === props.selectedTargetId
|
|
1723
1908
|
? "border-og-border bg-og-bg text-og-fg"
|
|
1724
|
-
: "text-og-muted hover:bg-og-surface-1",
|
|
1909
|
+
: "text-og-fg-muted hover:bg-og-surface-1",
|
|
1725
1910
|
)}
|
|
1726
1911
|
>
|
|
1727
1912
|
<button
|
|
@@ -1749,7 +1934,7 @@ function BrowserTabs(props: {
|
|
|
1749
1934
|
type="button"
|
|
1750
1935
|
onClick={props.onOpen}
|
|
1751
1936
|
disabled={props.mutating}
|
|
1752
|
-
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"
|
|
1937
|
+
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"
|
|
1753
1938
|
aria-label="New tab"
|
|
1754
1939
|
>
|
|
1755
1940
|
<PlusIcon className="size-3.5" />
|
|
@@ -1763,34 +1948,73 @@ function BrowserAddressBar(props: {
|
|
|
1763
1948
|
target: BrowserTarget | null;
|
|
1764
1949
|
loading: boolean;
|
|
1765
1950
|
onNavigate: (url: string) => void;
|
|
1951
|
+
onHistory: (direction: "back" | "forward") => Promise<void>;
|
|
1766
1952
|
onReload: () => void;
|
|
1767
1953
|
}) {
|
|
1768
1954
|
const [draft, setDraft] = useState(props.target?.url ?? "");
|
|
1769
1955
|
const focusedRef = useRef(false);
|
|
1956
|
+
const historyPendingRef = useRef(false);
|
|
1957
|
+
const [historyPending, setHistoryPending] = useState(false);
|
|
1770
1958
|
useEffect(() => {
|
|
1771
1959
|
if (!focusedRef.current) setDraft(props.target?.url ?? "");
|
|
1772
1960
|
}, [props.target?.id, props.target?.url]);
|
|
1773
1961
|
const submit = (event: FormEvent) => {
|
|
1774
1962
|
event.preventDefault();
|
|
1963
|
+
if (historyPendingRef.current) return;
|
|
1775
1964
|
const normalized = normalizeBrowserAddress(draft);
|
|
1776
1965
|
if (normalized) props.onNavigate(normalized);
|
|
1777
1966
|
};
|
|
1967
|
+
const navigateHistory = async (direction: "back" | "forward") => {
|
|
1968
|
+
if (!props.target || props.loading || historyPendingRef.current) return;
|
|
1969
|
+
historyPendingRef.current = true;
|
|
1970
|
+
setHistoryPending(true);
|
|
1971
|
+
try {
|
|
1972
|
+
await props.onHistory(direction);
|
|
1973
|
+
} finally {
|
|
1974
|
+
historyPendingRef.current = false;
|
|
1975
|
+
setHistoryPending(false);
|
|
1976
|
+
}
|
|
1977
|
+
};
|
|
1778
1978
|
return (
|
|
1779
1979
|
<form
|
|
1780
1980
|
onSubmit={submit}
|
|
1981
|
+
aria-busy={historyPending || undefined}
|
|
1781
1982
|
className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-bg px-2"
|
|
1782
1983
|
>
|
|
1984
|
+
{(["back", "forward"] as const).map((direction) => (
|
|
1985
|
+
<button
|
|
1986
|
+
key={direction}
|
|
1987
|
+
type="button"
|
|
1988
|
+
onClick={() => void navigateHistory(direction)}
|
|
1989
|
+
disabled={!props.target || props.loading}
|
|
1990
|
+
aria-disabled={historyPending || undefined}
|
|
1991
|
+
className={cn(
|
|
1992
|
+
"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",
|
|
1993
|
+
historyPending && "opacity-50",
|
|
1994
|
+
)}
|
|
1995
|
+
aria-label={direction === "back" ? "Back" : "Forward"}
|
|
1996
|
+
title={direction === "back" ? "Back" : "Forward"}
|
|
1997
|
+
>
|
|
1998
|
+
{direction === "back" ? (
|
|
1999
|
+
<ArrowLeftIcon className="size-3.5" />
|
|
2000
|
+
) : (
|
|
2001
|
+
<ArrowRightIcon className="size-3.5" />
|
|
2002
|
+
)}
|
|
2003
|
+
</button>
|
|
2004
|
+
))}
|
|
1783
2005
|
<button
|
|
1784
2006
|
type="button"
|
|
1785
2007
|
onClick={props.onReload}
|
|
1786
|
-
disabled={!props.target || props.loading}
|
|
1787
|
-
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"
|
|
2008
|
+
disabled={!props.target || props.loading || historyPending}
|
|
2009
|
+
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"
|
|
1788
2010
|
aria-label="Reload"
|
|
1789
2011
|
>
|
|
1790
|
-
<RefreshCwIcon
|
|
2012
|
+
<RefreshCwIcon
|
|
2013
|
+
className={cn("size-3.5", (props.loading || historyPending) && "animate-spin")}
|
|
2014
|
+
/>
|
|
1791
2015
|
</button>
|
|
1792
|
-
<div className="flex h-7 min-w-0 flex-1 items-center gap-2 rounded-og-sm border border-og-border bg-og-surface-
|
|
1793
|
-
<Globe2Icon className="size-3 shrink-0 text-og-subtle" />
|
|
2016
|
+
<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">
|
|
2017
|
+
<Globe2Icon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
1794
2018
|
<input
|
|
1795
2019
|
value={draft}
|
|
1796
2020
|
onChange={(event) => setDraft(event.target.value)}
|
|
@@ -1803,11 +2027,12 @@ function BrowserAddressBar(props: {
|
|
|
1803
2027
|
onKeyDown={(event) => {
|
|
1804
2028
|
if (event.key !== "Enter") return;
|
|
1805
2029
|
event.preventDefault();
|
|
2030
|
+
if (historyPendingRef.current) return;
|
|
1806
2031
|
const normalized = normalizeBrowserAddress(draft);
|
|
1807
2032
|
if (normalized) props.onNavigate(normalized);
|
|
1808
2033
|
}}
|
|
1809
2034
|
disabled={!props.target}
|
|
1810
|
-
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"
|
|
2035
|
+
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"
|
|
1811
2036
|
aria-label="Address"
|
|
1812
2037
|
spellCheck={false}
|
|
1813
2038
|
placeholder="Search or enter address"
|
|
@@ -1818,16 +2043,35 @@ function BrowserAddressBar(props: {
|
|
|
1818
2043
|
}
|
|
1819
2044
|
|
|
1820
2045
|
function BrowserViewport(props: {
|
|
1821
|
-
|
|
2046
|
+
focusHandoffRef: { current: string | null };
|
|
2047
|
+
focusScope: string;
|
|
2048
|
+
frame: BrowserViewportFrame | null;
|
|
1822
2049
|
connectionState: string;
|
|
1823
2050
|
supportsLiveFrames: boolean;
|
|
1824
2051
|
connectionError: Error | null;
|
|
2052
|
+
controlUnavailable: boolean;
|
|
1825
2053
|
observation: ReturnType<typeof useBrowserSession>["observation"];
|
|
1826
2054
|
mutating: boolean;
|
|
1827
2055
|
clipboardEnabled: boolean;
|
|
2056
|
+
inputBatchAttachment?: {
|
|
2057
|
+
browserSessionId: string;
|
|
2058
|
+
controllerGeneration: string;
|
|
2059
|
+
targetId: string;
|
|
2060
|
+
fencedInputBatches?: true | undefined;
|
|
2061
|
+
focusedInputObservations?: true | undefined;
|
|
2062
|
+
expiresAt: string;
|
|
2063
|
+
} | null;
|
|
1828
2064
|
activityLabel?: string | undefined;
|
|
1829
|
-
onAction: (
|
|
2065
|
+
onAction: (
|
|
2066
|
+
action: BrowserAction | BrowserActionBatch,
|
|
2067
|
+
frame: BrowserFrameInputFence | null,
|
|
2068
|
+
) => Promise<BrowserActionReceipt>;
|
|
1830
2069
|
onReadClipboard: () => Promise<{ text: string }>;
|
|
2070
|
+
onObserveForInput: () => Promise<BrowserObservation>;
|
|
2071
|
+
onSelectFromObservation: (
|
|
2072
|
+
action: BrowserAction,
|
|
2073
|
+
observation: BrowserObservation,
|
|
2074
|
+
) => Promise<BrowserActionReceipt>;
|
|
1831
2075
|
onReconnect: () => void;
|
|
1832
2076
|
reconnectLabel?: string | undefined;
|
|
1833
2077
|
reconnectMessage?: string | undefined;
|
|
@@ -1835,39 +2079,71 @@ function BrowserViewport(props: {
|
|
|
1835
2079
|
}) {
|
|
1836
2080
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
1837
2081
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
2082
|
+
const [selectPopup, setSelectPopup] = useState<{
|
|
2083
|
+
observation: BrowserObservation | null;
|
|
2084
|
+
anchor: { x: number; y: number } | null;
|
|
2085
|
+
} | null>(null);
|
|
2086
|
+
const inputSequenceRef = useRef(0);
|
|
2087
|
+
const dismissSelectPopup = useCallback(() => {
|
|
2088
|
+
inputSequenceRef.current += 1;
|
|
2089
|
+
setSelectPopup({ observation: null, anchor: null });
|
|
2090
|
+
}, []);
|
|
1838
2091
|
const composingRef = useRef(false);
|
|
1839
2092
|
const pointerStartRef = useRef<PointerStart | null>(null);
|
|
1840
2093
|
const lastClickRef = useRef<{
|
|
1841
2094
|
at: number;
|
|
1842
2095
|
x: number;
|
|
1843
2096
|
y: number;
|
|
1844
|
-
frame:
|
|
2097
|
+
frame: BrowserViewportFrame;
|
|
1845
2098
|
} | null>(null);
|
|
1846
2099
|
const wheelRef = useRef<{
|
|
1847
2100
|
x: number;
|
|
1848
2101
|
y: number;
|
|
1849
2102
|
deltaX: number;
|
|
1850
2103
|
deltaY: number;
|
|
1851
|
-
frame:
|
|
2104
|
+
frame: BrowserViewportFrame;
|
|
1852
2105
|
timer: ReturnType<typeof setTimeout> | null;
|
|
1853
2106
|
} | null>(null);
|
|
1854
2107
|
const pendingTextRef = useRef<{
|
|
1855
2108
|
text: string;
|
|
1856
|
-
frame:
|
|
2109
|
+
frame: BrowserViewportFrame | null;
|
|
1857
2110
|
timer: ReturnType<typeof setTimeout>;
|
|
1858
2111
|
} | null>(null);
|
|
1859
2112
|
const actionRef = useRef(props.onAction);
|
|
1860
2113
|
const readClipboardRef = useRef(props.onReadClipboard);
|
|
2114
|
+
const observeInputRef = useRef(props.onObserveForInput);
|
|
1861
2115
|
const errorRef = useRef(props.onError);
|
|
1862
2116
|
const actionTailRef = useRef<Promise<void>>(Promise.resolve());
|
|
1863
2117
|
const actionQueueEpochRef = useRef(0);
|
|
1864
|
-
const
|
|
2118
|
+
const queuedTypingRef = useRef<{
|
|
2119
|
+
actions: BrowserAction[];
|
|
2120
|
+
frame: BrowserFrameInputFence;
|
|
2121
|
+
epoch: number;
|
|
2122
|
+
} | null>(null);
|
|
2123
|
+
const queuedFrameRef = useRef<BrowserViewportFrame | null>(null);
|
|
2124
|
+
const currentFrameRef = useRef<BrowserViewportFrame | null>(props.frame);
|
|
2125
|
+
const paintedFrameRef = useRef<BrowserViewportFrame | null>(null);
|
|
2126
|
+
const [paintedFrame, setPaintedFrame] = useState<BrowserViewportFrame | null>(null);
|
|
1865
2127
|
const decodingFrameRef = useRef(false);
|
|
1866
2128
|
const mountedRef = useRef(true);
|
|
1867
2129
|
actionRef.current = props.onAction;
|
|
1868
2130
|
readClipboardRef.current = props.onReadClipboard;
|
|
2131
|
+
observeInputRef.current = props.onObserveForInput;
|
|
1869
2132
|
errorRef.current = props.onError;
|
|
1870
2133
|
|
|
2134
|
+
const clearBufferedInput = useCallback(() => {
|
|
2135
|
+
dismissSelectPopup();
|
|
2136
|
+
if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
|
|
2137
|
+
if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
|
|
2138
|
+
wheelRef.current = null;
|
|
2139
|
+
pendingTextRef.current = null;
|
|
2140
|
+
queuedTypingRef.current = null;
|
|
2141
|
+
pointerStartRef.current = null;
|
|
2142
|
+
lastClickRef.current = null;
|
|
2143
|
+
composingRef.current = false;
|
|
2144
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
2145
|
+
}, [dismissSelectPopup]);
|
|
2146
|
+
|
|
1871
2147
|
const paintQueuedFrames = useCallback(() => {
|
|
1872
2148
|
if (decodingFrameRef.current) return;
|
|
1873
2149
|
decodingFrameRef.current = true;
|
|
@@ -1880,15 +2156,22 @@ function BrowserViewport(props: {
|
|
|
1880
2156
|
|
|
1881
2157
|
let objectUrl: string | null = null;
|
|
1882
2158
|
try {
|
|
1883
|
-
const blob = new Blob([frame.data
|
|
2159
|
+
const blob = new Blob([frame.data], {
|
|
1884
2160
|
type: frame.mediaType,
|
|
1885
2161
|
});
|
|
1886
2162
|
if (typeof createImageBitmap === "function") {
|
|
1887
2163
|
const bitmap = await createImageBitmap(blob);
|
|
1888
2164
|
try {
|
|
1889
2165
|
const canvas = canvasRef.current;
|
|
1890
|
-
if (
|
|
2166
|
+
if (
|
|
2167
|
+
mountedRef.current &&
|
|
2168
|
+
canvas &&
|
|
2169
|
+
currentFrameRef.current &&
|
|
2170
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2171
|
+
) {
|
|
1891
2172
|
paintCanvas(canvas, bitmap, frame.width, frame.height);
|
|
2173
|
+
paintedFrameRef.current = frame;
|
|
2174
|
+
setPaintedFrame(frame);
|
|
1892
2175
|
}
|
|
1893
2176
|
} finally {
|
|
1894
2177
|
bitmap.close();
|
|
@@ -1898,11 +2181,23 @@ function BrowserViewport(props: {
|
|
|
1898
2181
|
objectUrl = URL.createObjectURL(blob);
|
|
1899
2182
|
const image = await loadImage(objectUrl);
|
|
1900
2183
|
const canvas = canvasRef.current;
|
|
1901
|
-
if (
|
|
2184
|
+
if (
|
|
2185
|
+
mountedRef.current &&
|
|
2186
|
+
canvas &&
|
|
2187
|
+
currentFrameRef.current &&
|
|
2188
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2189
|
+
) {
|
|
1902
2190
|
paintCanvas(canvas, image, frame.width, frame.height);
|
|
2191
|
+
paintedFrameRef.current = frame;
|
|
2192
|
+
setPaintedFrame(frame);
|
|
1903
2193
|
}
|
|
1904
2194
|
} catch (cause) {
|
|
1905
|
-
if (
|
|
2195
|
+
if (
|
|
2196
|
+
mountedRef.current &&
|
|
2197
|
+
currentFrameRef.current &&
|
|
2198
|
+
sameBrowserDocument(frame, currentFrameRef.current)
|
|
2199
|
+
)
|
|
2200
|
+
errorRef.current(cause);
|
|
1906
2201
|
} finally {
|
|
1907
2202
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1908
2203
|
}
|
|
@@ -1914,53 +2209,135 @@ function BrowserViewport(props: {
|
|
|
1914
2209
|
})();
|
|
1915
2210
|
}, []);
|
|
1916
2211
|
|
|
1917
|
-
|
|
2212
|
+
useLayoutEffect(() => {
|
|
2213
|
+
const keyboardInput = inputRef.current;
|
|
2214
|
+
const focusHandoff = props.focusHandoffRef;
|
|
1918
2215
|
mountedRef.current = true;
|
|
2216
|
+
if (focusHandoff.current === props.focusScope) {
|
|
2217
|
+
keyboardInput?.focus({ preventScroll: true });
|
|
2218
|
+
}
|
|
2219
|
+
focusHandoff.current = null;
|
|
1919
2220
|
return () => {
|
|
2221
|
+
// Only a same-target replacement may inherit focus the keyboard sink owned.
|
|
2222
|
+
if (document.activeElement === keyboardInput) {
|
|
2223
|
+
focusHandoff.current = props.focusScope;
|
|
2224
|
+
}
|
|
1920
2225
|
mountedRef.current = false;
|
|
2226
|
+
actionQueueEpochRef.current += 1;
|
|
1921
2227
|
queuedFrameRef.current = null;
|
|
2228
|
+
clearBufferedInput();
|
|
1922
2229
|
};
|
|
1923
|
-
}, []);
|
|
2230
|
+
}, [clearBufferedInput, props.focusHandoffRef, props.focusScope]);
|
|
1924
2231
|
|
|
1925
|
-
|
|
1926
|
-
if (!props.frame)
|
|
2232
|
+
useLayoutEffect(() => {
|
|
2233
|
+
if (currentFrameRef.current && !props.frame) {
|
|
2234
|
+
actionQueueEpochRef.current += 1;
|
|
2235
|
+
clearBufferedInput();
|
|
2236
|
+
}
|
|
2237
|
+
currentFrameRef.current = props.frame;
|
|
2238
|
+
if (!props.frame) {
|
|
2239
|
+
queuedFrameRef.current = null;
|
|
2240
|
+
paintedFrameRef.current = null;
|
|
2241
|
+
setPaintedFrame(null);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
1927
2244
|
queuedFrameRef.current = props.frame;
|
|
1928
2245
|
paintQueuedFrames();
|
|
1929
|
-
}, [paintQueuedFrames, props.frame]);
|
|
1930
|
-
|
|
1931
|
-
useEffect(
|
|
1932
|
-
() => () => {
|
|
1933
|
-
if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
|
|
1934
|
-
if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
|
|
1935
|
-
},
|
|
1936
|
-
[],
|
|
1937
|
-
);
|
|
2246
|
+
}, [clearBufferedInput, paintQueuedFrames, props.frame]);
|
|
1938
2247
|
|
|
1939
2248
|
const enqueue = useCallback(
|
|
1940
2249
|
(
|
|
1941
2250
|
action: BrowserAction,
|
|
1942
|
-
frame:
|
|
2251
|
+
frame: BrowserFrameInputFence | null,
|
|
1943
2252
|
after?: (receipt: BrowserActionReceipt) => Promise<void>,
|
|
2253
|
+
selectAnchor?: { x: number; y: number },
|
|
1944
2254
|
) => {
|
|
2255
|
+
const inputSequence = ++inputSequenceRef.current;
|
|
2256
|
+
setSelectPopup({ observation: null, anchor: null });
|
|
2257
|
+
// Detach image bytes even when the caller supplied a complete painted frame.
|
|
2258
|
+
frame = frame
|
|
2259
|
+
? {
|
|
2260
|
+
browserSessionId: frame.browserSessionId,
|
|
2261
|
+
controllerGeneration: frame.controllerGeneration,
|
|
2262
|
+
targetId: frame.targetId,
|
|
2263
|
+
targetGeneration: frame.targetGeneration,
|
|
2264
|
+
documentGeneration: frame.documentGeneration,
|
|
2265
|
+
frameId: frame.frameId,
|
|
2266
|
+
}
|
|
2267
|
+
: null;
|
|
1945
2268
|
const epoch = actionQueueEpochRef.current;
|
|
2269
|
+
const attachment = props.inputBatchAttachment;
|
|
2270
|
+
const canBatchTyping =
|
|
2271
|
+
action.type === "type" &&
|
|
2272
|
+
!after &&
|
|
2273
|
+
frame &&
|
|
2274
|
+
attachment?.fencedInputBatches === true &&
|
|
2275
|
+
attachment.browserSessionId === frame.browserSessionId &&
|
|
2276
|
+
attachment.controllerGeneration === frame.controllerGeneration &&
|
|
2277
|
+
attachment.targetId === frame.targetId &&
|
|
2278
|
+
Date.parse(attachment.expiresAt) > Date.now();
|
|
2279
|
+
const previous = queuedTypingRef.current;
|
|
2280
|
+
if (
|
|
2281
|
+
canBatchTyping &&
|
|
2282
|
+
frame &&
|
|
2283
|
+
previous &&
|
|
2284
|
+
previous.epoch === epoch &&
|
|
2285
|
+
sameFrameFence(previous.frame, frame) &&
|
|
2286
|
+
previous.actions.length < 16
|
|
2287
|
+
) {
|
|
2288
|
+
// Keep every existing text/input event boundary. Only share transport.
|
|
2289
|
+
previous.actions.push(action);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
const typing = canBatchTyping && frame ? { actions: [action], frame, epoch } : null;
|
|
2293
|
+
// Every non-typing action is an ordering barrier, including keys and paste.
|
|
2294
|
+
queuedTypingRef.current = typing;
|
|
1946
2295
|
actionTailRef.current = actionTailRef.current
|
|
1947
2296
|
.catch(() => undefined)
|
|
1948
2297
|
.then(async () => {
|
|
1949
|
-
if (epoch !== actionQueueEpochRef.current) return;
|
|
1950
|
-
|
|
2298
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2299
|
+
if (queuedTypingRef.current === typing) queuedTypingRef.current = null;
|
|
2300
|
+
const dispatchedAction =
|
|
2301
|
+
typing && typing.actions.length > 1
|
|
2302
|
+
? { type: "batch" as const, actions: typing.actions, fenceEachAction: true as const }
|
|
2303
|
+
: action;
|
|
2304
|
+
// Read after the epoch guard: a queued render callback can retain every
|
|
2305
|
+
// frame from that render even when its input fence contains no bytes.
|
|
2306
|
+
const receipt = await actionRef.current(dispatchedAction, frame);
|
|
2307
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
2308
|
+
// Alt+Down explicitly opens a native dropdown from the keyboard.
|
|
2309
|
+
// Existing controllers return focused-input metadata for clicks only,
|
|
2310
|
+
// so use the ordinary authorized read for this explicit gesture.
|
|
2311
|
+
const keyboardSelect =
|
|
2312
|
+
dispatchedAction.type === "press" && dispatchedAction.key === "Alt+ArrowDown";
|
|
2313
|
+
if ((selectAnchor || keyboardSelect) && inputSequence === inputSequenceRef.current) {
|
|
2314
|
+
const observation = keyboardSelect
|
|
2315
|
+
? await observeInputRef.current()
|
|
2316
|
+
: receipt.observation;
|
|
2317
|
+
if (
|
|
2318
|
+
mountedRef.current &&
|
|
2319
|
+
epoch === actionQueueEpochRef.current &&
|
|
2320
|
+
inputSequence === inputSequenceRef.current &&
|
|
2321
|
+
observation
|
|
2322
|
+
) {
|
|
2323
|
+
setSelectPopup({ observation, anchor: selectAnchor ?? null });
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
1951
2326
|
await after?.(receipt);
|
|
1952
2327
|
})
|
|
1953
2328
|
.catch((cause) => {
|
|
2329
|
+
if (!mountedRef.current || epoch !== actionQueueEpochRef.current) return;
|
|
1954
2330
|
// Do not replay or continue input collected behind a failed request.
|
|
1955
2331
|
actionQueueEpochRef.current += 1;
|
|
2332
|
+
clearBufferedInput();
|
|
1956
2333
|
errorRef.current(cause);
|
|
1957
2334
|
});
|
|
1958
2335
|
},
|
|
1959
|
-
[],
|
|
2336
|
+
[clearBufferedInput, props.inputBatchAttachment],
|
|
1960
2337
|
);
|
|
1961
2338
|
|
|
1962
2339
|
const point = useCallback(
|
|
1963
|
-
(frame:
|
|
2340
|
+
(frame: BrowserViewportFrame, clientX: number, clientY: number) =>
|
|
1964
2341
|
browserPoint(canvasRef.current, frame, clientX, clientY),
|
|
1965
2342
|
[],
|
|
1966
2343
|
);
|
|
@@ -1977,19 +2354,40 @@ function BrowserViewport(props: {
|
|
|
1977
2354
|
lastClickRef.current = null;
|
|
1978
2355
|
}, []);
|
|
1979
2356
|
|
|
2357
|
+
const flushPendingWheel = useCallback(() => {
|
|
2358
|
+
const batch = wheelRef.current;
|
|
2359
|
+
if (!batch) return;
|
|
2360
|
+
if (batch.timer) clearTimeout(batch.timer);
|
|
2361
|
+
wheelRef.current = null;
|
|
2362
|
+
enqueue(
|
|
2363
|
+
{
|
|
2364
|
+
type: "pointer",
|
|
2365
|
+
action: "scroll",
|
|
2366
|
+
x: batch.x,
|
|
2367
|
+
y: batch.y,
|
|
2368
|
+
deltaX: batch.deltaX,
|
|
2369
|
+
deltaY: batch.deltaY,
|
|
2370
|
+
},
|
|
2371
|
+
batch.frame,
|
|
2372
|
+
);
|
|
2373
|
+
}, [enqueue]);
|
|
2374
|
+
|
|
1980
2375
|
const pointerDown = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1981
|
-
|
|
2376
|
+
const frame = paintedFrameRef.current;
|
|
2377
|
+
if (!frame || event.button !== 0) return;
|
|
1982
2378
|
// Keep the hidden keyboard sink focused after the browser performs the
|
|
1983
2379
|
// pointerdown default action for the canvas. Without preventing that
|
|
1984
2380
|
// default, Chrome immediately moves focus back to the document and all
|
|
1985
2381
|
// subsequent typing/paste is silently lost.
|
|
1986
2382
|
event.preventDefault();
|
|
2383
|
+
dismissSelectPopup();
|
|
2384
|
+
flushPendingWheel();
|
|
1987
2385
|
flushPendingText();
|
|
1988
2386
|
pointerStartRef.current = {
|
|
1989
2387
|
x: event.clientX,
|
|
1990
2388
|
y: event.clientY,
|
|
1991
2389
|
pointerId: event.pointerId,
|
|
1992
|
-
frame
|
|
2390
|
+
frame,
|
|
1993
2391
|
};
|
|
1994
2392
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1995
2393
|
inputRef.current?.focus({ preventScroll: true });
|
|
@@ -2033,13 +2431,22 @@ function BrowserViewport(props: {
|
|
|
2033
2431
|
return;
|
|
2034
2432
|
}
|
|
2035
2433
|
lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
|
|
2036
|
-
|
|
2434
|
+
const viewportBounds = canvasRef.current?.parentElement?.getBoundingClientRect();
|
|
2435
|
+
enqueue(
|
|
2436
|
+
{ type: "pointer", action: "click", x: to.x, y: to.y },
|
|
2437
|
+
start.frame,
|
|
2438
|
+
undefined,
|
|
2439
|
+
viewportBounds
|
|
2440
|
+
? { x: event.clientX - viewportBounds.left, y: event.clientY - viewportBounds.top + 8 }
|
|
2441
|
+
: undefined,
|
|
2442
|
+
);
|
|
2037
2443
|
};
|
|
2038
2444
|
|
|
2039
2445
|
const contextMenu = (event: MouseEvent<HTMLCanvasElement>) => {
|
|
2040
2446
|
event.preventDefault();
|
|
2041
|
-
const frame =
|
|
2447
|
+
const frame = paintedFrameRef.current;
|
|
2042
2448
|
if (!frame) return;
|
|
2449
|
+
flushPendingWheel();
|
|
2043
2450
|
flushPendingText();
|
|
2044
2451
|
flushPendingClick();
|
|
2045
2452
|
const at = point(frame, event.clientX, event.clientY);
|
|
@@ -2047,41 +2454,47 @@ function BrowserViewport(props: {
|
|
|
2047
2454
|
};
|
|
2048
2455
|
|
|
2049
2456
|
const wheel = (event: WheelEvent<HTMLCanvasElement>) => {
|
|
2050
|
-
|
|
2457
|
+
dismissSelectPopup();
|
|
2458
|
+
const frame = paintedFrameRef.current;
|
|
2051
2459
|
if (!frame) return;
|
|
2052
2460
|
const at = point(frame, event.clientX, event.clientY);
|
|
2053
2461
|
if (!at) return;
|
|
2054
2462
|
flushPendingText();
|
|
2055
2463
|
flushPendingClick();
|
|
2056
2464
|
event.preventDefault();
|
|
2057
|
-
|
|
2058
|
-
if (
|
|
2465
|
+
let pending = wheelRef.current;
|
|
2466
|
+
if (
|
|
2467
|
+
pending &&
|
|
2468
|
+
(!sameBrowserDocument(pending.frame, frame) ||
|
|
2469
|
+
Math.hypot(pending.x - at.x, pending.y - at.y) > 6)
|
|
2470
|
+
) {
|
|
2471
|
+
flushPendingWheel();
|
|
2472
|
+
pending = null;
|
|
2473
|
+
}
|
|
2059
2474
|
wheelRef.current = {
|
|
2060
2475
|
x: at.x,
|
|
2061
2476
|
y: at.y,
|
|
2062
|
-
deltaX: (pending
|
|
2063
|
-
deltaY: (pending
|
|
2477
|
+
deltaX: (pending?.deltaX ?? 0) + event.deltaX,
|
|
2478
|
+
deltaY: (pending?.deltaY ?? 0) + event.deltaY,
|
|
2064
2479
|
frame,
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
wheelRef.current = null;
|
|
2068
|
-
if (batch)
|
|
2069
|
-
enqueue(
|
|
2070
|
-
{
|
|
2071
|
-
type: "pointer",
|
|
2072
|
-
action: "scroll",
|
|
2073
|
-
x: batch.x,
|
|
2074
|
-
y: batch.y,
|
|
2075
|
-
deltaX: batch.deltaX,
|
|
2076
|
-
deltaY: batch.deltaY,
|
|
2077
|
-
},
|
|
2078
|
-
batch.frame,
|
|
2079
|
-
);
|
|
2080
|
-
}, 45),
|
|
2480
|
+
// Retain the first event's deadline so a continuous gesture keeps moving.
|
|
2481
|
+
timer: pending?.timer ?? setTimeout(flushPendingWheel, 45),
|
|
2081
2482
|
};
|
|
2082
2483
|
};
|
|
2083
2484
|
|
|
2084
2485
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
2486
|
+
// Candidate selection/editing belongs to the local IME. Forward only the
|
|
2487
|
+
// committed text; Enter must not submit the remote page during composition.
|
|
2488
|
+
// Safari can end composition before keydown while retaining keyCode 229.
|
|
2489
|
+
if (
|
|
2490
|
+
composingRef.current ||
|
|
2491
|
+
event.nativeEvent.isComposing ||
|
|
2492
|
+
event.nativeEvent.keyCode === 229
|
|
2493
|
+
) {
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
dismissSelectPopup();
|
|
2497
|
+
flushPendingWheel();
|
|
2085
2498
|
const command = event.metaKey || event.ctrlKey;
|
|
2086
2499
|
if (
|
|
2087
2500
|
props.clipboardEnabled &&
|
|
@@ -2097,26 +2510,31 @@ function BrowserViewport(props: {
|
|
|
2097
2510
|
event.preventDefault();
|
|
2098
2511
|
flushPendingText();
|
|
2099
2512
|
flushPendingClick();
|
|
2100
|
-
enqueue({ type: "press", key },
|
|
2513
|
+
enqueue({ type: "press", key }, paintedFrameRef.current);
|
|
2101
2514
|
};
|
|
2102
2515
|
|
|
2516
|
+
const finishCopy = useCallback(async () => {
|
|
2517
|
+
const clipboard = await readClipboardRef.current();
|
|
2518
|
+
if (!mountedRef.current) return;
|
|
2519
|
+
if (clipboard.text.length === 0) return;
|
|
2520
|
+
if (!(await copyTextToClipboard(clipboard.text))) {
|
|
2521
|
+
throw new Error("Browser text could not be copied to the local clipboard");
|
|
2522
|
+
}
|
|
2523
|
+
}, []);
|
|
2524
|
+
|
|
2103
2525
|
const copy = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
2104
2526
|
if (!props.clipboardEnabled) return;
|
|
2105
2527
|
event.preventDefault();
|
|
2528
|
+
flushPendingWheel();
|
|
2106
2529
|
flushPendingText();
|
|
2107
2530
|
flushPendingClick();
|
|
2108
|
-
enqueue({ type: "clipboard", operation: "copy" },
|
|
2109
|
-
const clipboard = await readClipboardRef.current();
|
|
2110
|
-
if (clipboard.text.length === 0) return;
|
|
2111
|
-
if (!(await copyTextToClipboard(clipboard.text))) {
|
|
2112
|
-
throw new Error("Browser text could not be copied to the local clipboard");
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2531
|
+
enqueue({ type: "clipboard", operation: "copy" }, paintedFrameRef.current, finishCopy);
|
|
2115
2532
|
};
|
|
2116
2533
|
|
|
2117
2534
|
const paste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
2118
2535
|
if (!props.clipboardEnabled) return;
|
|
2119
2536
|
event.preventDefault();
|
|
2537
|
+
flushPendingWheel();
|
|
2120
2538
|
flushPendingText();
|
|
2121
2539
|
flushPendingClick();
|
|
2122
2540
|
enqueue(
|
|
@@ -2125,16 +2543,18 @@ function BrowserViewport(props: {
|
|
|
2125
2543
|
operation: "paste",
|
|
2126
2544
|
text: event.clipboardData.getData("text/plain"),
|
|
2127
2545
|
},
|
|
2128
|
-
|
|
2546
|
+
paintedFrameRef.current,
|
|
2129
2547
|
);
|
|
2130
2548
|
};
|
|
2131
2549
|
|
|
2132
2550
|
const input = (value: string, nativeComposing = false) => {
|
|
2551
|
+
dismissSelectPopup();
|
|
2133
2552
|
if (composingRef.current || nativeComposing) return;
|
|
2134
2553
|
if (!value) return;
|
|
2554
|
+
flushPendingWheel();
|
|
2135
2555
|
flushPendingClick();
|
|
2136
2556
|
const pending = pendingTextRef.current;
|
|
2137
|
-
if (pending && sameOptionalBrowserFrame(pending.frame,
|
|
2557
|
+
if (pending && sameOptionalBrowserFrame(pending.frame, paintedFrameRef.current)) {
|
|
2138
2558
|
clearTimeout(pending.timer);
|
|
2139
2559
|
pending.text += value;
|
|
2140
2560
|
pending.timer = setTimeout(flushPendingText, 16);
|
|
@@ -2142,7 +2562,7 @@ function BrowserViewport(props: {
|
|
|
2142
2562
|
flushPendingText();
|
|
2143
2563
|
pendingTextRef.current = {
|
|
2144
2564
|
text: value,
|
|
2145
|
-
frame:
|
|
2565
|
+
frame: paintedFrameRef.current,
|
|
2146
2566
|
timer: setTimeout(flushPendingText, 16),
|
|
2147
2567
|
};
|
|
2148
2568
|
}
|
|
@@ -2158,7 +2578,11 @@ function BrowserViewport(props: {
|
|
|
2158
2578
|
input(event.currentTarget.value || event.data);
|
|
2159
2579
|
};
|
|
2160
2580
|
|
|
2161
|
-
const showCanvas =
|
|
2581
|
+
const showCanvas =
|
|
2582
|
+
!props.controlUnavailable &&
|
|
2583
|
+
props.frame !== null &&
|
|
2584
|
+
paintedFrame !== null &&
|
|
2585
|
+
sameBrowserDocument(props.frame, paintedFrame);
|
|
2162
2586
|
return (
|
|
2163
2587
|
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
|
2164
2588
|
<canvas
|
|
@@ -2178,6 +2602,7 @@ function BrowserViewport(props: {
|
|
|
2178
2602
|
/>
|
|
2179
2603
|
<textarea
|
|
2180
2604
|
ref={inputRef}
|
|
2605
|
+
disabled={props.controlUnavailable}
|
|
2181
2606
|
defaultValue=""
|
|
2182
2607
|
onInput={(event) =>
|
|
2183
2608
|
input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
|
|
@@ -2189,6 +2614,7 @@ function BrowserViewport(props: {
|
|
|
2189
2614
|
onPaste={paste}
|
|
2190
2615
|
className="pointer-events-none absolute left-1/2 top-1/2 size-px resize-none overflow-hidden opacity-0"
|
|
2191
2616
|
aria-label="Browser keyboard input"
|
|
2617
|
+
aria-keyshortcuts="Alt+ArrowDown"
|
|
2192
2618
|
autoCapitalize="off"
|
|
2193
2619
|
autoCorrect="off"
|
|
2194
2620
|
spellCheck={false}
|
|
@@ -2199,12 +2625,28 @@ function BrowserViewport(props: {
|
|
|
2199
2625
|
connectionState={props.connectionState}
|
|
2200
2626
|
supportsLiveFrames={props.supportsLiveFrames}
|
|
2201
2627
|
error={props.connectionError}
|
|
2628
|
+
controlUnavailable={props.controlUnavailable}
|
|
2202
2629
|
onAction={(action) => enqueue(action, null)}
|
|
2203
2630
|
onReconnect={props.onReconnect}
|
|
2204
2631
|
reconnectLabel={props.reconnectLabel}
|
|
2205
2632
|
reconnectMessage={props.reconnectMessage}
|
|
2206
2633
|
/>
|
|
2207
2634
|
) : null}
|
|
2635
|
+
{showCanvas ? (
|
|
2636
|
+
<BrowserSelectControl
|
|
2637
|
+
manualFallback={props.inputBatchAttachment?.focusedInputObservations !== true}
|
|
2638
|
+
activation={selectPopup}
|
|
2639
|
+
onDismiss={() => inputRef.current?.focus({ preventScroll: true })}
|
|
2640
|
+
observe={async () => {
|
|
2641
|
+
flushPendingText();
|
|
2642
|
+
flushPendingWheel();
|
|
2643
|
+
await actionTailRef.current;
|
|
2644
|
+
if (!mountedRef.current) throw new Error("The browser page changed.");
|
|
2645
|
+
return await props.onObserveForInput();
|
|
2646
|
+
}}
|
|
2647
|
+
act={props.onSelectFromObservation}
|
|
2648
|
+
/>
|
|
2649
|
+
) : null}
|
|
2208
2650
|
{props.mutating ? (
|
|
2209
2651
|
<div className="pointer-events-none absolute right-3 top-3 flex items-center gap-1.5 rounded-full border border-white/10 bg-black/65 px-2.5 py-1 text-[11px] text-white/80 backdrop-blur">
|
|
2210
2652
|
<LoaderCircleIcon className="size-3 animate-spin" /> {props.activityLabel ?? "Acting"}
|
|
@@ -2219,47 +2661,52 @@ function SemanticBrowserFallback(props: {
|
|
|
2219
2661
|
connectionState: string;
|
|
2220
2662
|
supportsLiveFrames: boolean;
|
|
2221
2663
|
error: Error | null;
|
|
2664
|
+
controlUnavailable: boolean;
|
|
2222
2665
|
onAction: (action: BrowserAction) => void;
|
|
2223
2666
|
onReconnect: () => void;
|
|
2224
2667
|
reconnectLabel?: string | undefined;
|
|
2225
2668
|
reconnectMessage?: string | undefined;
|
|
2226
2669
|
}) {
|
|
2227
2670
|
const controlFailure = interactionControlFailureFromError(props.error);
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2671
|
+
const { controlUnavailable } = props;
|
|
2672
|
+
const generationLoss = Boolean(props.reconnectMessage);
|
|
2230
2673
|
const nodes = semanticNodes(
|
|
2231
2674
|
props.observation?.semantic?.kind === "snapshot" ? props.observation.semantic.roots : [],
|
|
2232
2675
|
);
|
|
2233
2676
|
const interactive = nodes.filter((node) => node.actions.includes("click")).slice(0, 8);
|
|
2234
2677
|
return (
|
|
2235
2678
|
<div className="absolute inset-0 grid place-items-center bg-og-bg p-6">
|
|
2236
|
-
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-
|
|
2679
|
+
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-lg">
|
|
2237
2680
|
<div className="flex items-center gap-2">
|
|
2238
2681
|
{props.error ? (
|
|
2239
|
-
<CircleAlertIcon className="size-4 text-og-
|
|
2240
|
-
) :
|
|
2241
|
-
<ZapIcon className="size-4 text-og-muted" />
|
|
2682
|
+
<CircleAlertIcon className="size-4 text-og-danger" />
|
|
2683
|
+
) : props.connectionState === "semantic" ? (
|
|
2684
|
+
<ZapIcon className="size-4 text-og-fg-muted" />
|
|
2242
2685
|
) : (
|
|
2243
|
-
<LoaderCircleIcon className="size-4 animate-spin text-og-muted" />
|
|
2686
|
+
<LoaderCircleIcon className="size-4 animate-spin text-og-fg-muted" />
|
|
2244
2687
|
)}
|
|
2245
2688
|
<p className="text-og-menu font-medium text-og-fg">
|
|
2246
2689
|
{generationLoss
|
|
2247
2690
|
? "Chrome reconnected—open a fresh browser/desktop."
|
|
2248
|
-
:
|
|
2249
|
-
? "
|
|
2250
|
-
: props.
|
|
2251
|
-
?
|
|
2252
|
-
|
|
2691
|
+
: controlUnavailable
|
|
2692
|
+
? "Browser controls unavailable"
|
|
2693
|
+
: props.error
|
|
2694
|
+
? props.supportsLiveFrames
|
|
2695
|
+
? "Live view disconnected"
|
|
2696
|
+
: "Browser unavailable"
|
|
2697
|
+
: props.connectionState === "semantic"
|
|
2698
|
+
? "Semantic browser"
|
|
2699
|
+
: browserConnectionLabel(props.connectionState)}
|
|
2253
2700
|
</p>
|
|
2254
2701
|
</div>
|
|
2255
2702
|
{props.error || props.reconnectMessage ? (
|
|
2256
|
-
<p className="mt-2 text-og-control leading-5 text-og-muted">
|
|
2703
|
+
<p className="mt-2 text-og-control leading-5 text-og-fg-muted">
|
|
2257
2704
|
{props.reconnectMessage ?? controlFailure?.message ?? props.error?.message}
|
|
2258
2705
|
</p>
|
|
2259
2706
|
) : null}
|
|
2260
|
-
{interactive.length > 0 ? (
|
|
2707
|
+
{interactive.length > 0 && !controlUnavailable ? (
|
|
2261
2708
|
<div className="mt-3 border-t border-og-border pt-3">
|
|
2262
|
-
<p className="mb-2 text-og-xs text-og-subtle">
|
|
2709
|
+
<p className="mb-2 text-og-xs text-og-fg-subtle">
|
|
2263
2710
|
{props.supportsLiveFrames
|
|
2264
2711
|
? "Page controls remain available"
|
|
2265
2712
|
: "Available page controls"}
|
|
@@ -2283,11 +2730,11 @@ function SemanticBrowserFallback(props: {
|
|
|
2283
2730
|
</div>
|
|
2284
2731
|
</div>
|
|
2285
2732
|
) : null}
|
|
2286
|
-
{props.error &&
|
|
2733
|
+
{props.error && (!generationLoss || props.reconnectLabel) ? (
|
|
2287
2734
|
<button
|
|
2288
2735
|
type="button"
|
|
2289
2736
|
onClick={props.onReconnect}
|
|
2290
|
-
className="mt-3 text-og-control font-medium
|
|
2737
|
+
className="mt-3 text-og-control font-medium og-link"
|
|
2291
2738
|
>
|
|
2292
2739
|
{props.reconnectLabel ??
|
|
2293
2740
|
(controlFailure?.retryable === false ? "Try again" : "Reconnect")}
|
|
@@ -2303,6 +2750,7 @@ function BrowserStatusBar(props: {
|
|
|
2303
2750
|
profile: BrowserIdentity | null;
|
|
2304
2751
|
target: BrowserTarget | null;
|
|
2305
2752
|
observation: ReturnType<typeof useBrowserSession>["observation"];
|
|
2753
|
+
inputFailure: ReturnType<typeof useBrowserSession>["inputFailure"];
|
|
2306
2754
|
connectionState: string;
|
|
2307
2755
|
refreshing: boolean;
|
|
2308
2756
|
diagnosticsOpen: boolean;
|
|
@@ -2314,7 +2762,7 @@ function BrowserStatusBar(props: {
|
|
|
2314
2762
|
(props.observation?.diagnostics.pageErrorCount ?? 0);
|
|
2315
2763
|
const failed = props.observation?.diagnostics.failedRequestCount ?? 0;
|
|
2316
2764
|
return (
|
|
2317
|
-
<div className="flex h-7 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-
|
|
2765
|
+
<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">
|
|
2318
2766
|
<span
|
|
2319
2767
|
className={cn(
|
|
2320
2768
|
"size-1.5 rounded-full",
|
|
@@ -2330,6 +2778,11 @@ function BrowserStatusBar(props: {
|
|
|
2330
2778
|
? "Semantic"
|
|
2331
2779
|
: browserConnectionLabel(props.connectionState)}
|
|
2332
2780
|
</span>
|
|
2781
|
+
{props.inputFailure ? (
|
|
2782
|
+
<span className="text-og-status-failed">
|
|
2783
|
+
{props.inputFailure.state === "failed" ? "Input failed" : "Input result unknown"}
|
|
2784
|
+
</span>
|
|
2785
|
+
) : null}
|
|
2333
2786
|
<span>{props.profile?.name ?? "Temporary browser"}</span>
|
|
2334
2787
|
<span className="min-w-0 flex-1 truncate">{props.target?.title}</span>
|
|
2335
2788
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
@@ -2395,13 +2848,13 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2395
2848
|
id="browser-diagnostics-drawer"
|
|
2396
2849
|
role="region"
|
|
2397
2850
|
aria-label="Browser diagnostics"
|
|
2398
|
-
className="absolute
|
|
2851
|
+
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"
|
|
2399
2852
|
>
|
|
2400
|
-
<div className="flex h-11 shrink-0 items-center gap-2 border-b border-og-border px-3">
|
|
2401
|
-
<BugIcon className="size-4 text-og-muted" />
|
|
2853
|
+
<div className="flex min-h-11 shrink-0 items-center gap-2 border-b border-og-border px-3 py-2">
|
|
2854
|
+
<BugIcon className="size-4 shrink-0 text-og-fg-muted" />
|
|
2402
2855
|
<div className="min-w-0 flex-1">
|
|
2403
2856
|
<p className="text-og-control font-semibold text-og-fg">Browser diagnostics</p>
|
|
2404
|
-
<p className="truncate text-og-xs text-og-subtle">
|
|
2857
|
+
<p className="truncate text-og-xs text-og-fg-subtle">
|
|
2405
2858
|
{props.target?.title || shortUrl(props.target?.url ?? "") || "Current tab"}
|
|
2406
2859
|
</p>
|
|
2407
2860
|
</div>
|
|
@@ -2409,7 +2862,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2409
2862
|
type="button"
|
|
2410
2863
|
onClick={props.onRefresh}
|
|
2411
2864
|
disabled={props.state.loading}
|
|
2412
|
-
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"
|
|
2865
|
+
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"
|
|
2413
2866
|
aria-label="Refresh browser diagnostics"
|
|
2414
2867
|
>
|
|
2415
2868
|
<RefreshCwIcon className={cn("size-3.5", props.state.loading && "animate-spin")} />
|
|
@@ -2417,7 +2870,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2417
2870
|
<button
|
|
2418
2871
|
type="button"
|
|
2419
2872
|
onClick={props.onClose}
|
|
2420
|
-
className="grid size-7 place-items-center rounded-og-sm text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
2873
|
+
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"
|
|
2421
2874
|
aria-label="Close browser diagnostics"
|
|
2422
2875
|
>
|
|
2423
2876
|
<XIcon className="size-3.5" />
|
|
@@ -2428,7 +2881,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2428
2881
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-runtime-title">
|
|
2429
2882
|
<h3
|
|
2430
2883
|
id="browser-runtime-title"
|
|
2431
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2884
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2432
2885
|
>
|
|
2433
2886
|
Runtime
|
|
2434
2887
|
</h3>
|
|
@@ -2459,18 +2912,19 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2459
2912
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-page-title">
|
|
2460
2913
|
<h3
|
|
2461
2914
|
id="browser-page-title"
|
|
2462
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2915
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2463
2916
|
>
|
|
2464
|
-
|
|
2917
|
+
Tab diagnostics
|
|
2465
2918
|
</h3>
|
|
2919
|
+
<p className="mt-1 text-og-xs text-og-fg-subtle">Includes earlier pages in this tab.</p>
|
|
2466
2920
|
<dl className="mt-2 grid grid-cols-2 gap-2">
|
|
2467
|
-
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount
|
|
2468
|
-
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount
|
|
2469
|
-
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount
|
|
2470
|
-
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount
|
|
2921
|
+
<DiagnosticCount label="Console errors" value={diagnostics?.consoleErrorCount} />
|
|
2922
|
+
<DiagnosticCount label="Page errors" value={diagnostics?.pageErrorCount} />
|
|
2923
|
+
<DiagnosticCount label="Failed requests" value={diagnostics?.failedRequestCount} />
|
|
2924
|
+
<DiagnosticCount label="Downloads" value={diagnostics?.downloadCount} />
|
|
2471
2925
|
</dl>
|
|
2472
2926
|
{session?.failureCode ? (
|
|
2473
|
-
<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-
|
|
2927
|
+
<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">
|
|
2474
2928
|
Browser state: {session.failureCode}
|
|
2475
2929
|
</p>
|
|
2476
2930
|
) : null}
|
|
@@ -2492,25 +2946,25 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2492
2946
|
<div className="flex items-center justify-between gap-2">
|
|
2493
2947
|
<h3
|
|
2494
2948
|
id="browser-events-title"
|
|
2495
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
2949
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2496
2950
|
>
|
|
2497
2951
|
Recent events
|
|
2498
2952
|
</h3>
|
|
2499
2953
|
{props.state.batch?.truncated ? (
|
|
2500
|
-
<span className="text-[10px] text-og-subtle">Newest 100</span>
|
|
2954
|
+
<span className="text-[10px] text-og-fg-subtle">Newest 100</span>
|
|
2501
2955
|
) : null}
|
|
2502
2956
|
</div>
|
|
2503
2957
|
{props.state.loading && !props.state.batch ? (
|
|
2504
|
-
<div className="flex items-center gap-2 py-6 text-og-control text-og-muted">
|
|
2958
|
+
<div className="flex items-center gap-2 py-6 text-og-control text-og-fg-muted">
|
|
2505
2959
|
<LoaderCircleIcon className="size-3.5 animate-spin" /> Loading diagnostics…
|
|
2506
2960
|
</div>
|
|
2507
2961
|
) : props.state.error ? (
|
|
2508
2962
|
<div className="py-5">
|
|
2509
|
-
<p className="text-og-control text-og-
|
|
2963
|
+
<p className="text-og-control text-og-danger">{props.state.error.message}</p>
|
|
2510
2964
|
<button
|
|
2511
2965
|
type="button"
|
|
2512
2966
|
onClick={props.onRefresh}
|
|
2513
|
-
className="mt-2 text-og-control font-medium
|
|
2967
|
+
className="mt-2 text-og-control font-medium og-link"
|
|
2514
2968
|
>
|
|
2515
2969
|
Try again
|
|
2516
2970
|
</button>
|
|
@@ -2522,7 +2976,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2522
2976
|
key={entry.sequence}
|
|
2523
2977
|
className="rounded-og-sm border border-og-border bg-og-bg p-2.5"
|
|
2524
2978
|
>
|
|
2525
|
-
<div className="flex items-center gap-2 text-[10px] text-og-subtle">
|
|
2979
|
+
<div className="flex items-center gap-2 text-[10px] text-og-fg-subtle">
|
|
2526
2980
|
<span
|
|
2527
2981
|
className={cn(
|
|
2528
2982
|
"font-semibold uppercase tracking-wide",
|
|
@@ -2537,7 +2991,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2537
2991
|
{entry.message}
|
|
2538
2992
|
</p>
|
|
2539
2993
|
{entry.url || entry.filename || entry.method || entry.status ? (
|
|
2540
|
-
<p className="mt-1 truncate font-og-mono text-[10px] text-og-subtle">
|
|
2994
|
+
<p className="mt-1 truncate font-og-mono text-[10px] text-og-fg-subtle">
|
|
2541
2995
|
{[entry.method, entry.status, entry.filename, entry.url]
|
|
2542
2996
|
.filter((value) => value !== null)
|
|
2543
2997
|
.join(" · ")}
|
|
@@ -2547,7 +3001,7 @@ function BrowserDiagnosticsDrawer(props: {
|
|
|
2547
3001
|
))}
|
|
2548
3002
|
</ol>
|
|
2549
3003
|
) : (
|
|
2550
|
-
<p className="py-6 text-og-control text-og-muted">No diagnostics for this tab.</p>
|
|
3004
|
+
<p className="py-6 text-og-control text-og-fg-muted">No diagnostics for this tab.</p>
|
|
2551
3005
|
)}
|
|
2552
3006
|
</section>
|
|
2553
3007
|
</div>
|
|
@@ -2594,35 +3048,35 @@ function BrowserDownloadsPanel(props: {
|
|
|
2594
3048
|
return (
|
|
2595
3049
|
<section className="border-b border-og-border p-3" aria-labelledby="browser-downloads-title">
|
|
2596
3050
|
<div className="flex items-center gap-2">
|
|
2597
|
-
<DownloadIcon className="size-3.5 text-og-muted" />
|
|
3051
|
+
<DownloadIcon className="size-3.5 text-og-fg-muted" />
|
|
2598
3052
|
<h3
|
|
2599
3053
|
id="browser-downloads-title"
|
|
2600
|
-
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-subtle"
|
|
3054
|
+
className="text-[10px] font-semibold uppercase tracking-[0.14em] text-og-fg-subtle"
|
|
2601
3055
|
>
|
|
2602
3056
|
Downloads
|
|
2603
3057
|
</h3>
|
|
2604
3058
|
{props.refreshing ? (
|
|
2605
|
-
<LoaderCircleIcon className="ml-auto size-3 animate-spin text-og-muted" />
|
|
3059
|
+
<LoaderCircleIcon className="ml-auto size-3 animate-spin text-og-fg-muted" />
|
|
2606
3060
|
) : null}
|
|
2607
3061
|
</div>
|
|
2608
3062
|
|
|
2609
3063
|
{props.loading ? (
|
|
2610
|
-
<div className="flex items-center gap-2 py-5 text-og-control text-og-muted">
|
|
3064
|
+
<div className="flex items-center gap-2 py-5 text-og-control text-og-fg-muted">
|
|
2611
3065
|
<LoaderCircleIcon className="size-3.5 animate-spin" /> Loading downloads…
|
|
2612
3066
|
</div>
|
|
2613
3067
|
) : props.error ? (
|
|
2614
3068
|
<div className="py-4">
|
|
2615
|
-
<p className="text-og-control text-og-
|
|
3069
|
+
<p className="text-og-control text-og-danger">{props.error.message}</p>
|
|
2616
3070
|
<button
|
|
2617
3071
|
type="button"
|
|
2618
3072
|
onClick={props.onRefresh}
|
|
2619
|
-
className="mt-2 text-og-control font-medium
|
|
3073
|
+
className="mt-2 text-og-control font-medium og-link"
|
|
2620
3074
|
>
|
|
2621
3075
|
Try again
|
|
2622
3076
|
</button>
|
|
2623
3077
|
</div>
|
|
2624
3078
|
) : props.downloads.length === 0 ? (
|
|
2625
|
-
<p className="py-4 text-og-control text-og-muted">No files downloaded yet.</p>
|
|
3079
|
+
<p className="py-4 text-og-control text-og-fg-muted">No files downloaded yet.</p>
|
|
2626
3080
|
) : (
|
|
2627
3081
|
<ol className="mt-2 space-y-2">
|
|
2628
3082
|
{props.downloads.map((download) => {
|
|
@@ -2635,9 +3089,9 @@ function BrowserDownloadsPanel(props: {
|
|
|
2635
3089
|
className="rounded-og-sm border border-og-border bg-og-bg p-2.5"
|
|
2636
3090
|
>
|
|
2637
3091
|
<div className="flex min-w-0 items-start gap-2">
|
|
2638
|
-
<span className="mt-0.5 grid size-6 shrink-0 place-items-center rounded bg-og-surface-2 text-og-muted">
|
|
3092
|
+
<span className="mt-0.5 grid size-6 shrink-0 place-items-center rounded bg-og-surface-2 text-og-fg-muted">
|
|
2639
3093
|
{isSaved ? (
|
|
2640
|
-
<FileCheck2Icon className="size-3.5 text-og-status-
|
|
3094
|
+
<FileCheck2Icon className="size-3.5 text-og-status-idle" />
|
|
2641
3095
|
) : (
|
|
2642
3096
|
<DownloadIcon className="size-3.5" />
|
|
2643
3097
|
)}
|
|
@@ -2649,7 +3103,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2649
3103
|
>
|
|
2650
3104
|
{download.filename}
|
|
2651
3105
|
</p>
|
|
2652
|
-
<p className="mt-0.5 text-[10px] text-og-subtle">
|
|
3106
|
+
<p className="mt-0.5 text-[10px] text-og-fg-subtle">
|
|
2653
3107
|
{downloadStatusLabel(download)}
|
|
2654
3108
|
</p>
|
|
2655
3109
|
</div>
|
|
@@ -2671,7 +3125,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2671
3125
|
}}
|
|
2672
3126
|
disabled={isSaving}
|
|
2673
3127
|
aria-label={`Workspace path for ${download.filename}`}
|
|
2674
|
-
className="h-7 min-w-0 flex-1 rounded-og-sm border border-og-border bg-og-surface-
|
|
3128
|
+
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"
|
|
2675
3129
|
/>
|
|
2676
3130
|
<button
|
|
2677
3131
|
type="button"
|
|
@@ -2697,7 +3151,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2697
3151
|
: "Save"}
|
|
2698
3152
|
</button>
|
|
2699
3153
|
</div>
|
|
2700
|
-
<label className="mt-1.5 flex w-fit items-center gap-1.5 text-[10px] text-og-subtle">
|
|
3154
|
+
<label className="mt-1.5 flex w-fit items-center gap-1.5 text-[10px] text-og-fg-subtle">
|
|
2701
3155
|
<input
|
|
2702
3156
|
type="checkbox"
|
|
2703
3157
|
checked={overwrite[download.id] ?? false}
|
|
@@ -2714,7 +3168,7 @@ function BrowserDownloadsPanel(props: {
|
|
|
2714
3168
|
Replace an existing workspace file
|
|
2715
3169
|
</label>
|
|
2716
3170
|
{errors[download.id] ? (
|
|
2717
|
-
<p className="mt-1.5 break-words text-[10px] text-og-
|
|
3171
|
+
<p className="mt-1.5 break-words text-[10px] text-og-danger">
|
|
2718
3172
|
{errors[download.id]}
|
|
2719
3173
|
</p>
|
|
2720
3174
|
) : null}
|
|
@@ -2757,7 +3211,7 @@ function defaultDownloadDestination(download: BrowserDownload): string {
|
|
|
2757
3211
|
function DiagnosticFact(props: { label: string; value: string }) {
|
|
2758
3212
|
return (
|
|
2759
3213
|
<>
|
|
2760
|
-
<dt className="text-og-subtle">{props.label}</dt>
|
|
3214
|
+
<dt className="text-og-fg-subtle">{props.label}</dt>
|
|
2761
3215
|
<dd className="min-w-0 truncate text-og-fg" title={props.value}>
|
|
2762
3216
|
{props.value}
|
|
2763
3217
|
</dd>
|
|
@@ -2765,17 +3219,17 @@ function DiagnosticFact(props: { label: string; value: string }) {
|
|
|
2765
3219
|
);
|
|
2766
3220
|
}
|
|
2767
3221
|
|
|
2768
|
-
function DiagnosticCount(props: { label: string; value: number }) {
|
|
3222
|
+
function DiagnosticCount(props: { label: string; value: number | undefined }) {
|
|
2769
3223
|
return (
|
|
2770
3224
|
<div className="rounded-og-sm border border-og-border bg-og-bg px-2.5 py-2">
|
|
2771
|
-
<dt className="text-[10px] text-og-subtle">{props.label}</dt>
|
|
3225
|
+
<dt className="text-[10px] text-og-fg-subtle">{props.label}</dt>
|
|
2772
3226
|
<dd
|
|
2773
3227
|
className={cn(
|
|
2774
3228
|
"mt-0.5 text-og-sm font-semibold",
|
|
2775
|
-
props.value ? "text-og-fg" : "text-og-muted",
|
|
3229
|
+
props.value ? "text-og-fg" : "text-og-fg-muted",
|
|
2776
3230
|
)}
|
|
2777
3231
|
>
|
|
2778
|
-
{props.value}
|
|
3232
|
+
{props.value ?? "Unavailable"}
|
|
2779
3233
|
</dd>
|
|
2780
3234
|
</div>
|
|
2781
3235
|
);
|
|
@@ -2806,9 +3260,9 @@ function diagnosticKindLabel(kind: BrowserDiagnosticBatch["entries"][number]["ki
|
|
|
2806
3260
|
}
|
|
2807
3261
|
|
|
2808
3262
|
function diagnosticTone(level: BrowserDiagnosticBatch["entries"][number]["level"]): string {
|
|
2809
|
-
if (level === "error") return "text-og-
|
|
3263
|
+
if (level === "error") return "text-og-danger";
|
|
2810
3264
|
if (level === "warning") return "text-og-status-waiting";
|
|
2811
|
-
return "text-og-muted";
|
|
3265
|
+
return "text-og-fg-muted";
|
|
2812
3266
|
}
|
|
2813
3267
|
|
|
2814
3268
|
function formatDiagnosticTime(value: string): string {
|
|
@@ -2822,7 +3276,7 @@ function BrowserNotice(props: { icon: ReactNode; text: string; className?: strin
|
|
|
2822
3276
|
return (
|
|
2823
3277
|
<div
|
|
2824
3278
|
className={cn(
|
|
2825
|
-
"grid h-full place-items-center bg-og-bg text-og-control text-og-muted",
|
|
3279
|
+
"grid h-full place-items-center bg-og-bg text-og-control text-og-fg-muted",
|
|
2826
3280
|
props.className,
|
|
2827
3281
|
)}
|
|
2828
3282
|
>
|
|
@@ -2858,7 +3312,7 @@ function loadImage(url: string): Promise<HTMLImageElement> {
|
|
|
2858
3312
|
|
|
2859
3313
|
function browserPoint(
|
|
2860
3314
|
canvas: HTMLCanvasElement | null,
|
|
2861
|
-
frame:
|
|
3315
|
+
frame: BrowserViewportFrame | null,
|
|
2862
3316
|
clientX: number,
|
|
2863
3317
|
clientY: number,
|
|
2864
3318
|
): { x: number; y: number } | null {
|
|
@@ -2905,18 +3359,24 @@ function frameMatchesSelectedTarget(
|
|
|
2905
3359
|
);
|
|
2906
3360
|
}
|
|
2907
3361
|
|
|
2908
|
-
function sameFrameFence(left:
|
|
3362
|
+
function sameFrameFence(left: BrowserFrameInputFence, right: BrowserFrameInputFence): boolean {
|
|
3363
|
+
return sameBrowserDocument(left, right) && left.frameId === right.frameId;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
function sameBrowserDocument(left: BrowserFrameInputFence, right: BrowserFrameInputFence): boolean {
|
|
2909
3367
|
return (
|
|
2910
3368
|
left.browserSessionId === right.browserSessionId &&
|
|
2911
3369
|
left.controllerGeneration === right.controllerGeneration &&
|
|
2912
3370
|
left.targetId === right.targetId &&
|
|
2913
3371
|
left.targetGeneration === right.targetGeneration &&
|
|
2914
|
-
left.documentGeneration === right.documentGeneration
|
|
2915
|
-
left.frameId === right.frameId
|
|
3372
|
+
left.documentGeneration === right.documentGeneration
|
|
2916
3373
|
);
|
|
2917
3374
|
}
|
|
2918
3375
|
|
|
2919
|
-
function sameOptionalBrowserFrame(
|
|
3376
|
+
function sameOptionalBrowserFrame(
|
|
3377
|
+
left: BrowserViewportFrame | null,
|
|
3378
|
+
right: BrowserViewportFrame | null,
|
|
3379
|
+
): boolean {
|
|
2920
3380
|
return left === null || right === null ? left === right : sameFrameFence(left, right);
|
|
2921
3381
|
}
|
|
2922
3382
|
|