From 69d0c7dcd758a50bddae04596052b181cd6a7f52 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Sat, 19 Sep 2026 22:46:45 +1000 Subject: [PATCH 01/55] Upgrade Hex1b and improve dashboard terminal navigation and metadata Adopt the published Hex1b alpha with bundled workers, themed overlay scrollbars, retained command marks, and live terminal metadata. Simplify vendored assets and add a continuously cycling terminal feature sample. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6bb79b75-08ac-4fcb-a432-921948a3a205 --- Directory.Packages.props | 6 +- NuGet.config | 7 + .../Terminals/Terminals.AppHost/AppHost.cs | 13 +- .../Scripts/terminal-features.cs | 72 ++ .../Components/Controls/TerminalTitle.razor | 35 + .../Controls/TerminalTitle.razor.cs | 105 ++ .../Controls/TerminalTitle.razor.css | 110 ++ .../Controls/TerminalTitle.razor.js | 92 ++ .../Components/Controls/TerminalView.razor | 2 +- .../Components/Controls/TerminalView.razor.cs | 17 +- .../Controls/TerminalView.razor.css | 14 +- .../Components/Controls/TerminalView.razor.js | 165 ++- .../Dialogs/InteractionsTerminalDialog.razor | 14 +- .../InteractionsTerminalDialog.razor.cs | 8 + .../InteractionsTerminalDialog.razor.css | 18 +- .../Components/Layout/TerminalDock.razor | 8 +- .../Components/Layout/TerminalDock.razor.css | 2 + .../Components/Pages/TerminalWindow.razor | 20 +- .../Components/Pages/TerminalWindow.razor.cs | 5 + .../Components/Pages/TerminalWindow.razor.css | 11 +- .../Resources/TerminalStrings.Designer.cs | 24 + .../Resources/TerminalStrings.resx | 13 + .../Resources/xlf/TerminalStrings.cs.xlf | 20 + .../Resources/xlf/TerminalStrings.de.xlf | 20 + .../Resources/xlf/TerminalStrings.es.xlf | 20 + .../Resources/xlf/TerminalStrings.fr.xlf | 20 + .../Resources/xlf/TerminalStrings.it.xlf | 20 + .../Resources/xlf/TerminalStrings.ja.xlf | 20 + .../Resources/xlf/TerminalStrings.ko.xlf | 20 + .../Resources/xlf/TerminalStrings.pl.xlf | 20 + .../Resources/xlf/TerminalStrings.pt-BR.xlf | 20 + .../Resources/xlf/TerminalStrings.ru.xlf | 20 + .../Resources/xlf/TerminalStrings.tr.xlf | 20 + .../Resources/xlf/TerminalStrings.zh-Hans.xlf | 20 + .../Resources/xlf/TerminalStrings.zh-Hant.xlf | 20 + src/Aspire.Dashboard/package-lock.json | 128 +- src/Aspire.Dashboard/package.json | 5 +- .../scripts/terminal-assets.mjs | 41 + .../scripts/update-terminal-assets.mjs | 23 +- .../scripts/verify-terminal-assets.mjs | 27 +- src/Aspire.Dashboard/wwwroot/js/README.md | 120 +- src/Aspire.Dashboard/wwwroot/js/app.js | 4 +- .../wwwroot/js/hex1b-web-terminal/README.md | 811 ------------- .../dist/backend-selection.d.ts | 7 - .../dist/backend-selection.d.ts.map | 1 - .../dist/backend-selection.js | 23 - .../dist/backend-selection.js.map | 1 - .../hex1b-web-terminal/dist/command-mark.d.ts | 14 - .../dist/command-mark.d.ts.map | 1 - .../hex1b-web-terminal/dist/command-mark.js | 26 - .../dist/command-mark.js.map | 1 - .../dist/fonts/cascadia-mono-nf/README.md | 29 - .../dist/history-state.d.ts | 27 - .../dist/history-state.d.ts.map | 1 - .../hex1b-web-terminal/dist/history-state.js | 194 --- .../dist/history-state.js.map | 1 - .../hex1b-web-terminal/dist/hyperlinks.d.ts | 10 - .../dist/hyperlinks.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/hyperlinks.js | 28 - .../hex1b-web-terminal/dist/hyperlinks.js.map | 1 - .../js/hex1b-web-terminal/dist/index.d.ts | 7 - .../js/hex1b-web-terminal/dist/index.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/index.js | 6 - .../js/hex1b-web-terminal/dist/index.js.map | 1 - .../js/hex1b-web-terminal/dist/index.min.js | 2 + .../hex1b-web-terminal/dist/input-policy.d.ts | 42 - .../dist/input-policy.d.ts.map | 1 - .../hex1b-web-terminal/dist/input-policy.js | 143 --- .../dist/input-policy.js.map | 1 - .../dist/link-detection-worker.d.ts | 3 - .../dist/link-detection-worker.d.ts.map | 1 - .../dist/link-detection-worker.js | 100 -- .../dist/link-detection-worker.js.map | 1 - .../dist/link-detection.d.ts | 29 - .../dist/link-detection.d.ts.map | 1 - .../hex1b-web-terminal/dist/link-detection.js | 290 ----- .../dist/link-detection.js.map | 1 - .../hex1b-web-terminal/dist/link-options.d.ts | 19 - .../dist/link-options.d.ts.map | 1 - .../hex1b-web-terminal/dist/link-options.js | 108 -- .../dist/link-options.js.map | 1 - .../dist/link-presentation.d.ts | 41 - .../dist/link-presentation.d.ts.map | 1 - .../dist/link-presentation.js | 125 -- .../dist/link-presentation.js.map | 1 - .../js/hex1b-web-terminal/dist/link-text.d.ts | 18 - .../dist/link-text.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/link-text.js | 92 -- .../hex1b-web-terminal/dist/link-text.js.map | 1 - .../hex1b-web-terminal/dist/link-types.d.ts | 72 -- .../dist/link-types.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/link-types.js | 2 - .../hex1b-web-terminal/dist/link-types.js.map | 1 - .../dist/link-worker-protocol.d.ts | 31 - .../dist/link-worker-protocol.d.ts.map | 1 - .../dist/link-worker-protocol.js | 10 - .../dist/link-worker-protocol.js.map | 1 - .../hex1b-web-terminal/dist/mouse-input.d.ts | 35 - .../dist/mouse-input.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/mouse-input.js | 427 ------- .../dist/mouse-input.js.map | 1 - .../js/hex1b-web-terminal/dist/protocol.d.ts | 19 - .../hex1b-web-terminal/dist/protocol.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/protocol.js | 337 ------ .../hex1b-web-terminal/dist/protocol.js.map | 1 - .../dist/render-backend.d.ts | 31 - .../dist/render-backend.d.ts.map | 1 - .../hex1b-web-terminal/dist/render-backend.js | 5 - .../dist/render-backend.js.map | 1 - .../dist/renderer-options.d.ts | 3 - .../dist/renderer-options.d.ts.map | 1 - .../dist/renderer-options.js | 6 - .../dist/renderer-options.js.map | 1 - .../js/hex1b-web-terminal/dist/renderer.d.ts | 102 -- .../hex1b-web-terminal/dist/renderer.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/renderer.js | 473 -------- .../hex1b-web-terminal/dist/renderer.js.map | 1 - .../dist/selection-input.d.ts | 36 - .../dist/selection-input.d.ts.map | 1 - .../dist/selection-input.js | 69 -- .../dist/selection-input.js.map | 1 - .../hex1b-web-terminal/dist/selection-ui.d.ts | 24 - .../dist/selection-ui.d.ts.map | 1 - .../hex1b-web-terminal/dist/selection-ui.js | 113 -- .../dist/selection-ui.js.map | 1 - .../dist/terminal-font.d.ts | 23 - .../dist/terminal-font.d.ts.map | 1 - .../hex1b-web-terminal/dist/terminal-font.js | 85 -- .../dist/terminal-font.js.map | 1 - .../dist/terminal-sizing.d.ts | 8 - .../dist/terminal-sizing.d.ts.map | 1 - .../dist/terminal-sizing.js | 38 - .../dist/terminal-sizing.js.map | 1 - .../dist/terminal-theme.d.ts | 2 - .../dist/terminal-theme.d.ts.map | 1 - .../hex1b-web-terminal/dist/terminal-theme.js | 39 - .../dist/terminal-theme.js.map | 1 - .../dist/terminal-worker.d.ts | 2 - .../dist/terminal-worker.d.ts.map | 1 - .../dist/terminal-worker.js | 331 ------ .../dist/terminal-worker.js.map | 1 - .../js/hex1b-web-terminal/dist/types.d.ts | 437 ------- .../js/hex1b-web-terminal/dist/types.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/types.js | 2 - .../js/hex1b-web-terminal/dist/types.js.map | 1 - .../hex1b-web-terminal/dist/validation.d.ts | 3 - .../dist/validation.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/validation.js | 7 - .../hex1b-web-terminal/dist/validation.js.map | 1 - .../hex1b-web-terminal/dist/web-terminal.d.ts | 61 - .../dist/web-terminal.d.ts.map | 1 - .../hex1b-web-terminal/dist/web-terminal.js | 1052 ----------------- .../dist/web-terminal.js.map | 1 - .../dist/webgl2-backend.d.ts | 38 - .../dist/webgl2-backend.d.ts.map | 1 - .../hex1b-web-terminal/dist/webgl2-backend.js | 456 ------- .../dist/webgl2-backend.js.map | 1 - .../dist/webgpu-backend.d.ts | 24 - .../dist/webgpu-backend.d.ts.map | 1 - .../hex1b-web-terminal/dist/webgpu-backend.js | 234 ---- .../dist/webgpu-backend.js.map | 1 - .../hex1b-web-terminal/dist/wire-types.d.ts | 263 ----- .../dist/wire-types.d.ts.map | 1 - .../js/hex1b-web-terminal/dist/wire-types.js | 2 - .../hex1b-web-terminal/dist/wire-types.js.map | 1 - .../js/hex1b-web-terminal/package.json | 42 - .../Controls/TerminalTitleTests.cs | 122 ++ .../Controls/TerminalViewTests.cs | 23 + .../InteractionsTerminalDialogTests.cs | 20 + .../JavaScript/TerminalTitle.test.mjs | 125 ++ .../JavaScript/TerminalView.test.mjs | 307 ++++- .../Layout/TerminalDockTests.cs | 31 + .../Pages/TerminalWindowTests.cs | 24 + .../Shared/TerminalSetupHelpers.cs | 8 + .../Integration/Playwright/TerminalTests.cs | 9 +- .../Terminal/TerminalWebSocketTests.cs | 73 ++ .../Infrastructure.Tests/NuGetConfigTests.cs | 17 +- 177 files changed, 1999 insertions(+), 7327 deletions(-) create mode 100644 playground/Terminals/Terminals.AppHost/Scripts/terminal-features.cs create mode 100644 src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor create mode 100644 src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs create mode 100644 src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css create mode 100644 src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js create mode 100644 src/Aspire.Dashboard/scripts/terminal-assets.mjs delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/README.md delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/fonts/cascadia-mono-nf/README.md delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js.map create mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.min.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js.map delete mode 100644 src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/package.json create mode 100644 tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs create mode 100644 tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs diff --git a/Directory.Packages.props b/Directory.Packages.props index 3b8f4885962..f303c2ecc29 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -119,9 +119,9 @@ - - - + + + diff --git a/NuGet.config b/NuGet.config index 2bf6bd51597..d24112e407d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -14,6 +14,8 @@ + + @@ -28,6 +30,11 @@ + + + + + diff --git a/playground/Terminals/Terminals.AppHost/AppHost.cs b/playground/Terminals/Terminals.AppHost/AppHost.cs index 3fda6aec877..9ac3efb029d 100644 --- a/playground/Terminals/Terminals.AppHost/AppHost.cs +++ b/playground/Terminals/Terminals.AppHost/AppHost.cs @@ -1,14 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// WithTerminal() is currently experimental (ASPIRETERMINAL001). Suppressed here -// because this playground project intentionally exercises the experimental API. -#pragma warning disable ASPIRETERMINAL001 +// This playground intentionally exercises the experimental terminal and file-based app APIs. +#pragma warning disable ASPIRETERMINAL001, ASPIRECSHARPAPPS001 using Terminals.AppHost; var builder = DistributedApplication.CreateBuilder(args); +builder.AddCSharpApp("terminal-features", "Scripts/terminal-features.cs") + .WithTerminal(options => + { + options.Columns = 120; + options.Rows = 32; + options.ShowTerminalHost = true; + }); + // A multi-replica project that calls `WithTerminal()` so each replica gets its // own pseudo-terminal and the dashboard can attach to any of them via // `/api/terminal?resource=repl&replica=`. The replica index is forwarded as diff --git a/playground/Terminals/Terminals.AppHost/Scripts/terminal-features.cs b/playground/Terminals/Terminals.AppHost/Scripts/terminal-features.cs new file mode 100644 index 00000000000..485238beb6c --- /dev/null +++ b/playground/Terminals/Terminals.AppHost/Scripts/terminal-features.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Run directly with `dotnet run --file terminal-features.cs`, or open the terminal-features +// resource in the Terminals playground. No terminal library is needed: these are ordinary OSC +// escape sequences terminated by BEL (\u0007). +// Title/directory: https://iterm2.com/documentation-escape-codes.html +// Progress: https://learn.microsoft.com/windows/terminal/tutorials/progress-bar-sequences +// Command marks: https://iterm2.com/documentation-shell-integration.html + +using var stopping = new CancellationTokenSource(); +Console.CancelKeyPress += (_, e) => +{ + e.Cancel = true; + stopping.Cancel(); +}; + +// Report illustrative paths (including a space to exercise URI decoding) without touching the filesystem. +var sourceDirectory = new Uri(Path.Combine(Environment.CurrentDirectory, "demo source") + Path.DirectorySeparatorChar).AbsoluteUri; +var outputDirectory = new Uri(Path.Combine(Environment.CurrentDirectory, "demo output") + Path.DirectorySeparatorChar).AbsoluteUri; + +Console.WriteLine("Terminal feature driver. Watch the title bar and scrollbar. Press Ctrl+C to stop."); + +try +{ + for (var cycle = 1; ; cycle++) + { + Console.WriteLine($"\u001b]2;Terminal demo - cycle {cycle}\u0007"); + Console.WriteLine($"\u001b]7;{sourceDirectory}\u0007"); + Console.WriteLine("\u001b]133;A\u0007$ \u001b]133;B\u0007terminal-feature-demo"); + Console.WriteLine("\u001b]133;C\u0007"); + + Console.WriteLine("\u001b]9;4;3\u0007Indeterminate progress: preparing work..."); + await Task.Delay(2000, stopping.Token); + + Console.WriteLine($"\u001b]2;Building - cycle {cycle}\u0007"); + for (var percentage = 0; percentage <= 100; percentage += 10) + { + Console.WriteLine($"\u001b]9;4;1;{percentage}\u0007Determinate progress: {percentage}%"); + Console.WriteLine($" Cycle {cycle}: processing item {percentage + 1}"); + Console.WriteLine(" More output for scrollback, selection and command-marker navigation."); + await Task.Delay(400, stopping.Token); + } + + Console.WriteLine($"\u001b]7;{outputDirectory}\u0007"); + Console.WriteLine("\u001b]2;Warning - retrying\u0007"); + Console.WriteLine("\u001b]9;4;4;60\u0007Warning progress at 60% (simulated)."); + await Task.Delay(2000, stopping.Token); + + Console.WriteLine("\u001b]2;Error - demonstration only\u0007"); + Console.WriteLine("\u001b]9;4;2;75\u0007Error progress at 75% (simulated)."); + await Task.Delay(2000, stopping.Token); + + Console.WriteLine("\u001b]2;Complete\u0007"); + Console.WriteLine("\u001b]9;4;1;100\u0007Complete at 100%."); + // Alternate completion status so retained command markers include successful and failed commands. + Console.WriteLine($"\u001b]133;D;{cycle % 2}\u0007"); + await Task.Delay(2000, stopping.Token); + + Console.WriteLine("\u001b]9;4;0\u0007Progress cleared."); + Console.WriteLine("\u001b]2;\u0007Title cleared: the title bar should show its fallback name."); + await Task.Delay(2000, stopping.Token); + } +} +catch (OperationCanceledException) when (stopping.IsCancellationRequested) +{ + // Ctrl+C stops the continuous demo without reporting a failed workload. +} +finally +{ + Console.WriteLine("\u001b]9;4;0\u0007\u001b]2;\u0007Driver stopped."); +} diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor new file mode 100644 index 00000000000..b5745bd81d3 --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor @@ -0,0 +1,35 @@ +@namespace Aspire.Dashboard.Components.Controls + + diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs new file mode 100644 index 00000000000..aba29e59b7f --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Extensions; +using Aspire.Dashboard.Utils; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.JSInterop; + +namespace Aspire.Dashboard.Components.Controls; + +/// Displays the workload title, directory and progress without changing terminal state. +public partial class TerminalTitle : IAsyncDisposable +{ + private readonly string _directoryButtonId = $"terminal-directory-{Guid.NewGuid():N}"; + private ElementReference _metadataElement; + private IJSObjectReference? _jsModule; + private Task? _initializationTask; + private bool _disposed; + + /// Gets or sets the terminal's latest presentation state. + [Parameter] + public TerminalToolbarState? State { get; set; } + + /// Gets or sets the title used before the workload reports one or after it clears it. + [Parameter] + public string FallbackTitle { get; set; } = string.Empty; + + [Inject] + public required IStringLocalizer Loc { get; init; } + + [Inject] + public required IStringLocalizer ControlsLoc { get; init; } + + [Inject] + public required IJSRuntime JS { get; init; } + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _initializationTask = InitializeAsync(); + } + + return _initializationTask ?? Task.CompletedTask; + } + + private async Task InitializeAsync() + { + _jsModule = await JS.InvokeAsync("import", "./Components/Controls/TerminalTitle.razor.js"); + if (!_disposed) + { + await _jsModule.InvokeVoidAsync("observePath", _metadataElement); + } + } + + /// Releases the path measurement observers. + public async ValueTask DisposeAsync() + { + _disposed = true; + try + { + if (_initializationTask is not null) + { + await _initializationTask; + } + if (_jsModule is not null) + { + await _jsModule.InvokeVoidAsync("disconnectPath", _metadataElement); + } + } + catch (JSDisconnectedException) + { + // The browser may already be gone when the component is disposed. + } + catch (OperationCanceledException) + { + // The browser may already be gone when the component is disposed. + } + finally + { + if (_jsModule is not null) + { + await JSInteropHelpers.SafeDisposeAsync(_jsModule); + } + } + } + + private Dictionary DirectoryCopyAttributes => FluentUIExtensions.GetClipboardCopyAdditionalAttributes( + State?.WorkingDirectory, + ControlsLoc[nameof(Resources.ControlsStrings.GridValueCopyToClipboard)], + ControlsLoc[nameof(Resources.ControlsStrings.GridValueCopied)], + ("aria-label", Loc[nameof(Resources.TerminalStrings.TerminalCopyWorkingDirectory), State?.WorkingDirectory ?? string.Empty].Value)); + + private string DisplayTitle => State is { Title.Length: > 0 } ? State.Title : FallbackTitle; + + private double? ProgressValue => State?.ProgressState is "indeterminate" ? null : State?.ProgressPercentage; + + private string ProgressLabel => Loc[State?.ProgressState switch + { + "error" => nameof(Resources.TerminalStrings.TerminalProgressError), + "warning" => nameof(Resources.TerminalStrings.TerminalProgressWarning), + _ => nameof(Resources.TerminalStrings.TerminalProgress) + }]; +} diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css new file mode 100644 index 00000000000..859b0a27541 --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css @@ -0,0 +1,110 @@ +.terminal-metadata { + display: flex; + align-items: center; + gap: 12px; + min-width: 0; + flex: 1; + color: var(--colorNeutralForeground1); + font-size: var(--fontSizeBase200); +} + +.terminal-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.terminal-title:empty { + display: none; +} + +.terminal-directory-container { + margin-inline-start: auto; + max-width: min(32rem, 60%); + min-width: 0; +} + +.terminal-directory-container ::deep .terminal-directory { + max-width: 100%; + min-width: 0; + font-family: var(--fontFamilyMonospace); + font-size: inherit; +} + +.terminal-directory-container ::deep .terminal-directory::part(content) { + min-width: 0; +} + +.terminal-directory-content { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; + /* Let the shared clipboard handler receive the button as the click target. */ + pointer-events: none; +} + +.terminal-directory-text { + position: relative; + min-width: 0; + white-space: nowrap; + overflow: hidden; +} + +.terminal-directory-measure { + visibility: hidden; +} + +.terminal-directory-display { + position: absolute; + inset: 0; +} + +.terminal-directory-copy-icons { + flex: 0 0 16px; + width: 16px; + height: 16px; + opacity: 0; +} + +.terminal-directory-container:hover .terminal-directory-copy-icons, +.terminal-directory-container:has(:focus-visible) .terminal-directory-copy-icons { + opacity: 1; +} + +.terminal-progress[data-state] { + display: flex; + align-items: center; + gap: 3px; + padding-inline-end: 12px; + border-inline-end: 1px solid var(--terminal-titlebar-border-color, var(--colorNeutralStroke1)); + flex-shrink: 0; + font-variant-numeric: tabular-nums; + white-space: nowrap; +} + +.terminal-progress[data-state="error"] { + color: var(--error); + --colorBrandStroke1: var(--error); +} + +.terminal-progress-percentage { + /* Reserve three digits, the percent sign and any localized separating space. */ + inline-size: 5ch; + text-align: end; +} + +.terminal-progress[data-state="warning"] { + color: var(--warning); + --colorBrandStroke1: var(--warning); +} + +@media (forced-colors: active) { + .terminal-progress[data-state] { + color: CanvasText; + --colorBrandStroke1: CanvasText; + --colorNeutralStroke2: Canvas; + } +} diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js new file mode 100644 index 00000000000..bc948f04b0a --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +const observations = new WeakMap(); +const ellipsis = "\u2026"; + +export function compactPath(path, fits) { + if (fits(path)) { + return path; + } + + // Preserve roots and separator runs, e.g. /home/user/src/, C:\work\src, + // and \\server\share\src. A backslash in a POSIX path such as /home/a\b/src + // is part of the name, not a separator. + const windows = /^[a-z]:[\\/]|^\\\\/i.test(path) || (!path.includes("/") && path.includes("\\")); + const root = path.match(windows ? /^(?:[a-z]:[\\/]+|[\\/]+)/i : /^\/+/)?.[0] ?? ""; + const segments = path.slice(root.length).match(windows ? /[^\\/]+[\\/]*/gu : /[^/]+\/*/gu) ?? []; + const separator = windows ? path.match(/[\\/]/)?.[0] ?? "\\" : "/"; + let left = Math.floor((segments.length - 1) / 2); + let right = left + 1; + while (left >= 0 && right < segments.length) { + const candidate = root + segments.slice(0, left).join("") + ellipsis + separator + segments.slice(right).join(""); + if (fits(candidate)) { + return candidate; + } + if (left > 0 && (left >= segments.length - right || right === segments.length - 1)) { + left--; + } else { + right++; + } + } + + // A single name can itself exceed the available space. Omit it entirely + // rather than showing a misleading fragment of that name. + for (const candidate of [root + ellipsis, ellipsis, ""]) { + if (fits(candidate)) { + return candidate; + } + } + return ""; +} + +export function observePath(element) { + disconnectPath(element); + const context = document.createElement("canvas").getContext("2d"); + if (!context) { + throw new Error("Terminal path measurement requires a 2D canvas context."); + } + let text = null; + let disposed = false; + const refresh = () => { + if (disposed) { + return; + } + const current = element.querySelector(".terminal-directory-text"); + if (current !== text) { + if (text) { + resize.unobserve(text); + } + text = current; + if (text) { + resize.observe(text); + } + } + if (!text) { + return; + } + context.font = getComputedStyle(text).font; + const path = element.getAttribute("data-directory") ?? ""; + // clientWidth rounds to whole pixels and can reject a complete path that actually fits. + const width = text.getBoundingClientRect().width; + text.querySelector(".terminal-directory-display").textContent = + compactPath(path, candidate => context.measureText(candidate).width <= width); + }; + const resize = new ResizeObserver(refresh); + const mutations = new MutationObserver(refresh); + mutations.observe(element, { attributes: true, attributeFilter: ["data-directory"] }); + document.fonts.addEventListener("loadingdone", refresh); + document.fonts.ready.then(refresh); + observations.set(element, () => { + disposed = true; + resize.disconnect(); + mutations.disconnect(); + document.fonts.removeEventListener("loadingdone", refresh); + }); + refresh(); +} + +export function disconnectPath(element) { + observations.get(element)?.(); + observations.delete(element); +} diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor index c1bc46bb994..95185128f5e 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor +++ b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor @@ -16,7 +16,7 @@ @if (!Chromeless) {
- @(ResourceName ?? Loc[nameof(Resources.TerminalStrings.TerminalTitle)].Value) + @if (ShowOpenInWindow && !string.IsNullOrEmpty(ResourceName)) { Gets the selected font size, or the initial preference before the first state notification. public int? FontSize => _state.FontPx > 0 ? _state.FontPx : InitialFontSize; + /// Gets the current presentation state for host-owned terminal chrome. + public TerminalToolbarState ToolbarState => _state; + /// Gets or sets whether the footer offers fixed-resolution presets. Defaults to true. /// The font stepper remains available on surfaces sized by a splitter or dialog. [Parameter] @@ -108,7 +111,7 @@ public sealed partial class TerminalView : ComponentBase, IAsyncDisposable [Parameter] public bool AutoFit { get; set; } - /// Raised when the terminal's role, dimensions, font or connection state changes. + /// Raised when the terminal's metadata, role, dimensions, font or connection state changes. [Parameter] public EventCallback OnToolbarStateChanged { get; set; } @@ -535,7 +538,7 @@ public sealed record TerminalViewOptions public required string FocusControlsHint { get; init; } } -/// A generation-tagged snapshot of terminal role, sizing and connection state. +/// A generation-tagged snapshot of terminal metadata, role, sizing and connection state. public sealed record TerminalToolbarState { /// The unique JS-side view identifier. @@ -546,6 +549,16 @@ public sealed record TerminalToolbarState public string Status { get; init; } = "connecting"; /// Whether a connected frame has been presented. public bool Connected { get; init; } + /// The workload-reported title, or empty when unset. + public string Title { get; init; } = string.Empty; + /// The decoded working directory reported by the shell, or null when unset. + public string? WorkingDirectory { get; init; } + /// The original working directory URI, displayed as text only. + public string? WorkingDirectoryUri { get; init; } + /// The reported progress state: none, normal, error, indeterminate or warning. + public string ProgressState { get; init; } = "none"; + /// The reported percentage, or null for hidden or indeterminate progress. + public int? ProgressPercentage { get; init; } /// Whether this view owns resize authority. public bool IsPrimary { get; init; } /// Whether requesting resize authority is available. diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.css b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.css index 78eef5bf4bd..b289e6c1bc6 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.css +++ b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.css @@ -13,6 +13,7 @@ } .terminal-frame { + --terminal-background: #0d1117; display: flex; flex-direction: column; width: 100%; @@ -20,7 +21,7 @@ min-width: 0; min-height: 0; box-sizing: border-box; - background: #0d1117; + background: var(--terminal-background); border: 2px solid #3a4250; border-radius: 6px; overflow: hidden; @@ -88,6 +89,7 @@ } .terminal-titlebar { + --terminal-titlebar-border-color: #30363d; display: flex; align-items: center; flex: 0 0 30px; @@ -96,19 +98,11 @@ padding-inline-end: 8px; box-sizing: border-box; background: linear-gradient(180deg, #1a2029 0%, #161b22 100%); - border-bottom: 1px solid #30363d; + border-bottom: 1px solid var(--terminal-titlebar-border-color); color: #8b949e; font: 12px ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; } -.terminal-title { - flex: 1; - min-width: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - .terminal-titlebar ::deep .terminal-open-window { color: inherit; } diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.js b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.js index de6f9a224fa..91e0abc3e3b 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.js +++ b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor.js @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { WebTerminal, MIN_FONT_SIZE, MAX_FONT_SIZE, InputRoute } from "../../js/hex1b-web-terminal/dist/index.js"; +import { WebTerminal, MIN_FONT_SIZE, MAX_FONT_SIZE, InputRoute, createDefaultScrollbarRenderer, renderDefaultScrollbarTooltip } from "../../js/hex1b-web-terminal/dist/index.min.js"; const terminals = new Map(); const rememberedFontSizes = new Map(); @@ -20,6 +20,78 @@ const SIZE_PRESETS = [ { value: "132x50", label: "132×50", cols: 132, rows: 50 }, ]; +function scrollbarConfiguration(state) { + // Read Dashboard colors outside the terminal's intentionally dark scope. + // Resolve variables/system colors to concrete canvas colors before passing + // them to the snapshotted built-in painter. + const probe = document.createElement("span"); + probe.style.cssText = "position:absolute;visibility:hidden;pointer-events:none;forced-color-adjust:none"; + document.body.append(probe); + try { + const color = value => { + probe.style.color = value; + return getComputedStyle(probe).color; + }; + const forced = state.forcedColors.matches; + const terminalStyle = getComputedStyle(state.element); + const tooltipStyle = { + background: color(forced ? "Canvas" : "var(--aspire-popup-background)"), + color: color(forced ? "CanvasText" : "var(--colorNeutralForeground1)"), + borderColor: color(forced ? "CanvasText" : "var(--colorNeutralStroke1)"), + borderRadius: "var(--aspire-popup-radius)", + boxShadow: forced ? "none" : getComputedStyle(probe).getPropertyValue("--aspire-popup-shadow"), + font: "var(--fontSizeBase200)/var(--lineHeightBase200) var(--fontFamilyBase)", + padding: "8px 12px", + }; + const renderScrollbar = createDefaultScrollbarRenderer({ + // The overlay belongs to the dark terminal surface, not the page. + // Resolve the light thumb in the terminal's dark theme scope, even on a light page. + track: { + color: forced ? color("Canvas") : terminalStyle.getPropertyValue("--terminal-background").trim(), + opacity: forced || state.moreContrast.matches ? 1 : 0.35, + }, + thumb: { + color: forced ? color("CanvasText") : terminalStyle.getPropertyValue("--colorNeutralForeground1").trim(), + }, + markers: { + color: color(forced ? "Highlight" : "var(--colorBrandForeground1)"), + errorColor: color(forced ? "LinkText" : "var(--error)"), + opacity: forced || state.moreContrast.matches ? 1 : 0.65, + }, + }); + return { + placement: "overlay", + markers: true, + tooltip(context) { + // Preserve upstream text escaping, async detail states and positioning. + const element = renderDefaultScrollbarTooltip(context); + Object.assign(element.style, tooltipStyle); + return element; + }, + render(frame) { + // Hex1b paints a thumb outline after pointer release because the scrollbar + // retains focus. Suppress only that paint; its DOM :focus-visible outline + // still identifies keyboard focus, and real interaction state is unchanged. + return renderScrollbar({ + ...frame, + interaction: { ...frame.interaction, focused: false }, + }); + }, + }; + } finally { + probe.remove(); + } +} + +function updateScrollbar(state) { + if (state.disposed) { + return; + } + state.scrollbar = scrollbarConfiguration(state); + // setScrollbar replaces, rather than merges, the configuration. + state.client?.setScrollbar(state.scrollbar); +} + function isCurrent(state, generation) { return !state.disposed && state.generation === generation; } @@ -89,6 +161,8 @@ function cancelReconnect(state) { } function releaseClient(state) { + state.inspectionObserver?.disconnect(); + state.inspectionObserver = null; if (state.client?.element.contains(document.activeElement)) { requestFocus(state); } @@ -100,6 +174,34 @@ function releaseClient(state) { client?.dispose(); } +function configureTerminalChrome(client) { + // This pinned Hex1b release has no options/parts for its history chrome or + // pointer focus outline. Keep keyboard focus, errors and selection feedback; + // remove these overrides when upstream exposes controls for them. + const shadow = client.element.shadowRoot; + const status = shadow?.querySelector(".inspection-message"); + if (!status || !shadow.querySelector(".return-live")) { + throw new Error("Hex1b history chrome could not be configured."); + } + const style = document.createElement("style"); + style.textContent = ` + .return-live { display: none !important; } + .inspection-message[data-aspire-history-position] { display: none !important; } + :host([data-aspire-pointer-input="true"]) .scrollbar-accessibility:focus-visible { outline: none; } + `; + shadow.append(style); + const update = () => { + // Hex1b emits "42 rows above live"; other status text includes selection + // feedback and navigation errors and must not be suppressed. + status.toggleAttribute("data-aspire-history-position", + status.dataset.level !== "error" && /^\d+ rows above live$/.test(status.textContent)); + }; + const observer = new MutationObserver(update); + observer.observe(status, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ["data-level"] }); + update(); + return observer; +} + function scheduleReconnect(state, generation) { if (!isCurrent(state, generation) || state.ended || state.reconnectTimer !== null) { return; @@ -387,6 +489,27 @@ async function mountClient(state, generation, controller) { label: state.options.label, sizing: state.sizing, readOnly: state.readOnly, + scrollbar: state.scrollbar, + padding: 3, + onTitleChange(title) { + if (current()) { + state.title = title; + notifyToolbar(state); + } + }, + onWorkingDirectoryChange(directory) { + if (current()) { + state.workingDirectory = directory.path; + state.workingDirectoryUri = directory.uri; + notifyToolbar(state); + } + }, + onProgressChange(progress) { + if (current()) { + state.progress = progress; + notifyToolbar(state); + } + }, onInput: input => inputPolicy(state, input), onClose(details) { if (current()) { @@ -447,6 +570,9 @@ async function mountClient(state, generation, controller) { return; } state.client = client; + state.inspectionObserver = configureTerminalChrome(client); + // Theme/accessibility settings can change while awaiting the first frame. + client.setScrollbar(state.scrollbar); // Selection notifications can precede mount completion, before the handle is available. clearInvalidatedSelection(state); // Policy can change while mount is waiting for its first frame. @@ -527,10 +653,15 @@ export function initTerminal(element, wsUrl, dotNetRef, options, selectionTempla readOnly: !!options.readOnly, autoFit: !!options.autoFit, client: null, + inspectionObserver: null, controller: null, disposed: false, ended: false, connected: false, + title: "", + workingDirectory: null, + workingDirectoryUri: null, + progress: { state: "none", percentage: null }, peer: { id: null, primaryId: null, isPrimary: false }, geometry: null, sizing: { mode: "auto", fontSize }, @@ -547,7 +678,27 @@ export function initTerminal(element, wsUrl, dotNetRef, options, selectionTempla focusOrigin: document.activeElement, failurePending: false, listeners: new AbortController(), + forcedColors: window.matchMedia("(forced-colors: active)"), + moreContrast: window.matchMedia("(prefers-contrast: more)"), }; + updateScrollbar(state); + state.themeObserver = new MutationObserver(() => updateScrollbar(state)); + state.themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] }); + for (const media of [state.forcedColors, state.moreContrast]) { + media.addEventListener("change", () => updateScrollbar(state), { signal: state.listeners.signal }); + } + // A programmatically focused scrollbar can remain :focus-visible after a mouse + // drag. Track modality before Hex1b consumes events, without moving actual focus. + element.addEventListener("pointerdown", () => { + if (state.client) { + state.client.element.dataset.aspirePointerInput = "true"; + } + }, { capture: true, signal: state.listeners.signal }); + element.addEventListener("keydown", () => { + if (state.client) { + delete state.client.element.dataset.aspirePointerInput; + } + }, { capture: true, signal: state.listeners.signal }); footer.addEventListener("click", event => focusAfterMouseControl(state, event), { signal: state.listeners.signal }); footer.addEventListener("keydown", event => { @@ -576,6 +727,12 @@ export function reconnectTerminal(id, wsUrl) { if (!state || (state.ended && state.wsUrl === wsUrl)) { return state?.generation ?? 0; } + if (state.wsUrl !== wsUrl) { + state.title = ""; + state.workingDirectory = null; + state.workingDirectoryUri = null; + state.progress = { state: "none", percentage: null }; + } state.wsUrl = wsUrl; state.ended = false; state.attempts = 0; @@ -597,6 +754,7 @@ export function disposeTerminal(id) { cancelAnimationFrame(state.toolbarFrame); } state.observer.disconnect(); + state.themeObserver.disconnect(); state.listeners.abort(); releaseClient(state); state.dotNetRef = null; @@ -713,6 +871,11 @@ export function getToolbarState(id) { generation: state.generation, status: !connected ? "connecting" : isPrimary ? "primary" : state.peer.primaryId === null ? "no-primary" : "viewer", connected, isPrimary, canTakeControl, + title: state.title, + workingDirectory: state.workingDirectory, + workingDirectoryUri: state.workingDirectoryUri, + progressState: state.progress.state, + progressPercentage: state.progress.percentage, sizeMode: state.sizing.mode === "auto" ? "font" : "fixed", sizeKey: state.sizing.mode === "auto" ? state.geometry ? `${state.geometry.columns}x${state.geometry.rows}` : "" diff --git a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor index 2ec8ddbd38b..5364e3eb8a8 100644 --- a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor +++ b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor @@ -7,10 +7,16 @@ @((MarkupString)Content.Message)
- +
+ +
+
+ +
diff --git a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.cs b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.cs index 47d48dc3f3f..c5131f1d316 100644 --- a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.cs +++ b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Aspire.Dashboard.Components.Controls; using Aspire.Dashboard.Model.Interaction; using Microsoft.AspNetCore.Components; using Microsoft.FluentUI.AspNetCore.Components; @@ -10,6 +11,7 @@ namespace Aspire.Dashboard.Components.Dialogs; public partial class InteractionsTerminalDialog { private InteractionsTerminalDialogViewModel? _content; + private TerminalToolbarState _terminalState = new(); [Parameter] public InteractionsTerminalDialogViewModel Content { get; set; } = default!; @@ -25,6 +27,10 @@ protected override void OnParametersSet() { if (_content != Content) { + if (!string.Equals(_content?.TerminalId, Content.TerminalId, StringComparison.Ordinal)) + { + _terminalState = new(); + } _content?.OnInteractionUpdated = null; _content = Content; _content.OnInteractionUpdated = () => InvokeAsync(StateHasChanged); @@ -33,6 +39,8 @@ protected override void OnParametersSet() private Task CancelAsync() => Dialog.CloseAsync(DialogResult.Ok("cancel")); + private void OnTerminalToolbarStateChanged(TerminalToolbarState state) => _terminalState = state; + public void Dispose() { _content?.OnInteractionUpdated = null; diff --git a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.css b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.css index f661d1594cd..5d30801dad9 100644 --- a/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.css +++ b/src/Aspire.Dashboard/Components/Dialogs/InteractionsTerminalDialog.razor.css @@ -2,9 +2,10 @@ margin-bottom: 1rem; } -/* The terminal measures its container; an explicit height is needed even before it has any output. - Chromeless presentation keeps the dialog's title and the terminal's footer without a second title bar. */ +/* The terminal measures its container; an explicit height is needed even before it has any output. */ .interaction-terminal-container { + display: flex; + flex-direction: column; width: 100%; height: min(420px, 60vh); min-width: 0; @@ -12,3 +13,16 @@ border: var(--strokeWidthThin) solid var(--colorNeutralStroke1); border-radius: var(--aspire-control-radius); } + +.interaction-terminal-titlebar { + display: flex; + align-items: center; + flex: 0 0 30px; + padding-inline: 8px; + background: var(--colorNeutralBackground2); +} + +.interaction-terminal-body { + flex: 1; + min-height: 0; +} diff --git a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor index 524d75ccb6b..f21ca6f11ec 100644 --- a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor +++ b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor @@ -62,7 +62,13 @@ } } -
+
+ @if (_activeTerminalId is { } activeId && IsPaneActive(activeId) && !_detachedTerminalIds.Contains(activeId) && + _terminalViews.TryGetValue(activeId, out var activeView)) + { + + } +
@if (_popupBlocked) { @Loc[nameof(Resources.TerminalStrings.TerminalDockDetachBlocked)] diff --git a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css index 13e0956acf2..dbf73dd3d37 100644 --- a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css +++ b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css @@ -96,6 +96,8 @@ .terminal-dock-filler { flex: 1 1 auto; + min-width: 0; + padding-inline: 8px; } .terminal-dock-body { diff --git a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor index a334dded8d9..35fd35d57fa 100644 --- a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor +++ b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor @@ -6,7 +6,7 @@ @using Aspire.Dashboard.Components.Controls @using Aspire.Dashboard.Components.Layout -@_title +@(string.IsNullOrEmpty(_terminalState.Title) ? _title : _terminalState.Title) @* Without dashboard navigation or a page toolbar, the terminal fills the window and fits its available space. *@
@@ -20,11 +20,17 @@ } else if (_windowReady) { - +
+ +
+
+ +
}
diff --git a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.cs b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.cs index 514c9abf223..578b97edc89 100644 --- a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Aspire.Dashboard.Components.Controls; using Aspire.DashboardService.Proto.V1; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Localization; @@ -26,6 +27,7 @@ public sealed partial class TerminalWindow : ComponentBase, IAsyncDisposable { private string? _endpoint; private string _title = string.Empty; + private TerminalToolbarState _terminalState = new(); private bool _ended; private bool _disposed; private (string? TerminalId, string? ResourceName, int ReplicaIndex, string? WindowOwner, string? WindowGeneration)? _routeIdentity; @@ -97,6 +99,7 @@ protected override async Task OnParametersSetAsync() } _routeIdentity = routeIdentity; + _terminalState = new(); var generation = ++_watchGeneration; _ended = false; _windowTrackingFailed = false; @@ -125,6 +128,8 @@ protected override async Task OnParametersSetAsync() _watchTask = Task.Run(() => WatchTerminalsAsync(terminalId, generation, cancellationToken), cancellationToken); } + private void OnTerminalToolbarStateChanged(TerminalToolbarState state) => _terminalState = state; + protected override async Task OnAfterRenderAsync(bool firstRender) { if (_disposed) diff --git a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css index 5c1f77cc913..b640a019136 100644 --- a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css +++ b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css @@ -5,16 +5,25 @@ position: fixed; inset: 0; display: flex; + flex-direction: column; background: #0d1117; overflow: hidden; } -.terminal-window ::deep .aspire-terminal-host { +.terminal-window-body { flex: 1; min-width: 0; min-height: 0; } +.terminal-window-titlebar { + display: flex; + align-items: center; + flex: 0 0 30px; + padding-inline: 8px; + background: var(--colorNeutralBackground2); +} + .terminal-window-ended { flex: 1; display: flex; diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs index aa2bc6eabc6..5e68f3c2407 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs @@ -45,6 +45,30 @@ public static System.Globalization.CultureInfo Culture { } } + public static string TerminalProgress { + get { + return ResourceManager.GetString("TerminalProgress", resourceCulture); + } + } + + public static string TerminalCopyWorkingDirectory { + get { + return ResourceManager.GetString("TerminalCopyWorkingDirectory", resourceCulture); + } + } + + public static string TerminalProgressError { + get { + return ResourceManager.GetString("TerminalProgressError", resourceCulture); + } + } + + public static string TerminalProgressWarning { + get { + return ResourceManager.GetString("TerminalProgressWarning", resourceCulture); + } + } + public static string TerminalTitle { get { return ResourceManager.GetString("TerminalTitle", resourceCulture); diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.resx b/src/Aspire.Dashboard/Resources/TerminalStrings.resx index 8574e3dfb3a..9f3f831494a 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.resx +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.resx @@ -117,6 +117,19 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Terminal progress + + + Copy working directory: {0} + {0} is the working directory path. + + + Error + + + Warning + Terminal Default terminal title when no resource name is available. diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf index f0a54380a69..443dedf8b6b 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf index 83fdce17caa..c56a734dc18 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf index decbfadc3e9..5d46cd79fb0 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf index 2c6896a9146..3dd729d9d73 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf index 9b467f87c85..839e388ed11 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf index d63444d61bf..500b55d2956 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf index 4fda386df5b..36eed236e93 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf index 96f059136bb..0d410f5ed30 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf index 87847edcafb..cdde1e08400 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf index 69567a565f9..5df6c7d4331 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf index 4e540dc1529..c9a07ca4477 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf index ff3ec04ac92..4f6df34306e 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf index 3a2dd811a09..5f235a672af 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy working directory: {0} + Copy working directory: {0} + {0} is the working directory path. + The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. The terminal disconnected and automatic retries have stopped. Check that the resource is running, then retry. @@ -127,6 +132,21 @@ The terminal could not connect or initialize its renderer. Check that the resource is running and your browser supports WebGPU or WebGL2, then reconnect to try again. You can still view this resource's console logs. + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal diff --git a/src/Aspire.Dashboard/package-lock.json b/src/Aspire.Dashboard/package-lock.json index 02849df0d25..103c45068a6 100644 --- a/src/Aspire.Dashboard/package-lock.json +++ b/src/Aspire.Dashboard/package-lock.json @@ -6,17 +6,137 @@ "": { "name": "aspire-dashboard-assets", "dependencies": { - "@hex1b/web-terminal": "0.168.0" + "@hex1b/web-terminal": "0.169.0-alpha.1608.1.d6a20d4" + }, + "devDependencies": { + "terser": "5.51.2" } }, "node_modules/@hex1b/web-terminal": { - "version": "0.168.0", - "resolved": "https://registry.npmjs.org/@hex1b/web-terminal/-/web-terminal-0.168.0.tgz", - "integrity": "sha512-NojZVe3O74TvCcua6YMY18JdV5xZbKFGRkyWuZYau8bUbgxkXSe9UzpRPeDOmTyLcPlGkO69Kc36PxMbbFbGSw==", + "version": "0.169.0-alpha.1608.1.d6a20d4", + "resolved": "https://registry.npmjs.org/@hex1b/web-terminal/-/web-terminal-0.169.0-alpha.1608.1.d6a20d4.tgz", + "integrity": "sha512-bXG2P36R9ZQum6NkYuTEAkU9UVkTPFRR3OLVnG2IWF2NS1mGwpAKsWGkbgEpKnV2Ck4RDAapslm2ersCoGYwqw==", "license": "MIT", "engines": { "node": ">=22" } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } } } } diff --git a/src/Aspire.Dashboard/package.json b/src/Aspire.Dashboard/package.json index f8bbf01d2b1..1069de3f454 100644 --- a/src/Aspire.Dashboard/package.json +++ b/src/Aspire.Dashboard/package.json @@ -8,6 +8,9 @@ "test": "node --test ../../tests/Aspire.Dashboard.Components.Tests/JavaScript/*.test.mjs" }, "dependencies": { - "@hex1b/web-terminal": "0.168.0" + "@hex1b/web-terminal": "0.169.0-alpha.1608.1.d6a20d4" + }, + "devDependencies": { + "terser": "5.51.2" } } diff --git a/src/Aspire.Dashboard/scripts/terminal-assets.mjs b/src/Aspire.Dashboard/scripts/terminal-assets.mjs new file mode 100644 index 00000000000..f9b756b8a66 --- /dev/null +++ b/src/Aspire.Dashboard/scripts/terminal-assets.mjs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import assert from "node:assert/strict"; +import { readFile, readdir } from "node:fs/promises"; +import { minify } from "terser"; + +const dashboard = new URL("../", import.meta.url); +const installed = new URL("node_modules/@hex1b/web-terminal/", dashboard); +const copiedAssets = [ + "LICENSE", + "dist/fonts/cascadia-mono-nf/CascadiaMonoNF.woff2", + "dist/fonts/cascadia-mono-nf/LICENSE.txt", +]; + +export async function prepareTerminalAssets() { + const manifest = JSON.parse(await readFile(new URL("package.json", dashboard), "utf8")); + const packageInfo = JSON.parse(await readFile(new URL("package.json", installed), "utf8")); + assert.equal(packageInfo.version, manifest.dependencies["@hex1b/web-terminal"], + "Run npm ci first; the installed package must match the exact manifest version."); + + const files = await readdir(new URL("dist/", installed), { recursive: true }); + assert.deepEqual(files.filter(name => name.endsWith(".js")).sort(), ["index.js"], + "Expected the upstream single-module distribution before replacing checked-in assets."); + + // Minify the published bundle, not upstream source. Module mode preserves + // public exports and import.meta.url, which both embedded workers and fonts use. + const { code } = await minify(await readFile(new URL("dist/index.js", installed), "utf8"), { + module: true, + sourceMap: false, + format: { + comments: /^!|@preserve|@license/, + preamble: `// @hex1b/web-terminal ${packageInfo.version}; minified with Terser. See ../LICENSE.`, + }, + }); + assert.ok(code, "Minification must produce the terminal bundle."); + const assets = new Map(await Promise.all(copiedAssets.map(async name => + [name, await readFile(new URL(name, installed))]))); + assets.set("dist/index.min.js", Buffer.from(code + "\n")); + return { version: packageInfo.version, assets }; +} diff --git a/src/Aspire.Dashboard/scripts/update-terminal-assets.mjs b/src/Aspire.Dashboard/scripts/update-terminal-assets.mjs index fab8d30b91e..b4452994671 100644 --- a/src/Aspire.Dashboard/scripts/update-terminal-assets.mjs +++ b/src/Aspire.Dashboard/scripts/update-terminal-assets.mjs @@ -1,23 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { cp, mkdir, readFile, rm } from "node:fs/promises"; +import { mkdir, rm, writeFile } from "node:fs/promises"; +import { prepareTerminalAssets } from "./terminal-assets.mjs"; const dashboard = new URL("../", import.meta.url); -const source = new URL("node_modules/@hex1b/web-terminal/", dashboard); const destination = new URL("wwwroot/js/hex1b-web-terminal/", dashboard); -const manifest = JSON.parse(await readFile(new URL("package.json", dashboard), "utf8")); -const installed = JSON.parse(await readFile(new URL("package.json", source), "utf8")); -if (installed.version !== manifest.dependencies["@hex1b/web-terminal"]) { - throw new Error("Run npm ci before updating terminal assets; the installed package must match the exact manifest version."); -} +// Validate and prepare every output before deleting the previous acquisition. +const { version, assets } = await prepareTerminalAssets(); await rm(destination, { recursive: true, force: true }); -await mkdir(destination, { recursive: true }); -// The module worker and font URLs are relative to the emitted modules. Keep -// the complete tree, including maps, declarations, font provenance and licenses. -await cp(new URL("dist/", source), new URL("dist/", destination), { recursive: true }); -for (const name of ["LICENSE", "README.md", "package.json"]) { - await cp(new URL(name, source), new URL(name, destination)); +for (const [name, content] of assets) { + const file = new URL(name, destination); + await mkdir(new URL(".", file), { recursive: true }); + await writeFile(file, content); } -console.log(`Vendored @hex1b/web-terminal ${installed.version}.`); +console.log(`Vendored ${assets.size} runtime/license files for @hex1b/web-terminal ${version}.`); diff --git a/src/Aspire.Dashboard/scripts/verify-terminal-assets.mjs b/src/Aspire.Dashboard/scripts/verify-terminal-assets.mjs index 828f0a26ead..23260576a00 100644 --- a/src/Aspire.Dashboard/scripts/verify-terminal-assets.mjs +++ b/src/Aspire.Dashboard/scripts/verify-terminal-assets.mjs @@ -3,33 +3,24 @@ import assert from "node:assert/strict"; import { readFile, readdir } from "node:fs/promises"; -import { join, relative } from "node:path"; +import { join, relative, sep } from "node:path"; import { fileURLToPath } from "node:url"; +import { prepareTerminalAssets } from "./terminal-assets.mjs"; const dashboard = new URL("../", import.meta.url); -const installed = new URL("node_modules/@hex1b/web-terminal/", dashboard); const vendored = new URL("wwwroot/js/hex1b-web-terminal/", dashboard); -const manifest = JSON.parse(await readFile(new URL("package.json", dashboard), "utf8")); -const packageInfo = JSON.parse(await readFile(new URL("package.json", installed), "utf8")); -assert.equal(packageInfo.version, manifest.dependencies["@hex1b/web-terminal"], - "Run npm ci before verifying terminal assets; the installed package must match the exact manifest version."); +const { version, assets } = await prepareTerminalAssets(); async function listFiles(root) { const entries = await readdir(root, { recursive: true, withFileTypes: true }); return entries.filter(entry => entry.isFile()) - .map(entry => relative(root, join(entry.parentPath, entry.name))) + .map(entry => relative(root, join(entry.parentPath, entry.name)).split(sep).join("/")) .sort(); } -const installedDist = fileURLToPath(new URL("dist/", installed)); -const vendoredDist = fileURLToPath(new URL("dist/", vendored)); -const files = await listFiles(installedDist); -assert.deepEqual(await listFiles(vendoredDist), files, - "The vendored dist tree must contain every installed file and no stale extras."); -for (const name of files) { - assert.deepEqual(await readFile(join(vendoredDist, name)), await readFile(join(installedDist, name)), name); +assert.deepEqual(await listFiles(fileURLToPath(vendored)), [...assets.keys()].sort(), + "Only the minified runtime, font and required licenses should be vendored."); +for (const [name, content] of assets) { + assert.deepEqual(await readFile(new URL(name, vendored)), content, name); } -for (const name of ["LICENSE", "README.md", "package.json"]) { - assert.deepEqual(await readFile(new URL(name, vendored)), await readFile(new URL(name, installed)), name); -} -console.log(`Verified ${files.length} dist files and package metadata/licenses for @hex1b/web-terminal ${packageInfo.version}.`); +console.log(`Verified ${assets.size} runtime/license files for @hex1b/web-terminal ${version}.`); diff --git a/src/Aspire.Dashboard/wwwroot/js/README.md b/src/Aspire.Dashboard/wwwroot/js/README.md index bc85e81a8e8..3684c08f4ee 100644 --- a/src/Aspire.Dashboard/wwwroot/js/README.md +++ b/src/Aspire.Dashboard/wwwroot/js/README.md @@ -18,8 +18,10 @@ If we ever want to show more chart types than those, we'll need to change the bu ## Hex1b web terminal -`hex1b-web-terminal/` vendors `@hex1b/web-terminal` **0.168.0**, -paired with the Hex1b NuGet package **0.168.0**. The client and server use the evolving +`hex1b-web-terminal/` vendors `@hex1b/web-terminal` **0.169.0-alpha.1608.1.d6a20d4**, +paired with the Hex1b, Hex1b.McpServer, and Hex1b.Tool NuGet packages at the same version, +published by [run 35429880293](https://github.com/mitchdenny/hex1b/actions/runs/35429880293) +from commit `d6a20d4f0674cbeb5b9d153a93ddabad0717bdf6`. The client and server use the evolving HWT1 presentation transport and must be updated together. Do not substitute a different client based only on a similar version number. @@ -31,9 +33,12 @@ npm run update-terminal-assets npm test ``` -`update-terminal-assets` copies the package and then verifies that every emitted -file matches the installed package byte-for-byte, including metadata/licenses, -and that no stale files remain in `dist/`. To rerun that acquisition-only check +`update-terminal-assets` first requires the installed distribution to contain +exactly one JavaScript file, `dist/index.js`, before replacing checked-in assets. +It minifies the bundle and copies the runtime assets, then verifies that every emitted +font/license file matches the installed package byte-for-byte, that the minified +bundle matches reproducible Terser output, and that no stale files remain. +To rerun that acquisition-only check without copying, use `npm run verify-terminal-assets` after `npm ci`. This check intentionally requires `node_modules`; ordinary regression tests do not. @@ -41,13 +46,24 @@ Review and commit the manifest, lockfile, and generated asset changes together. This is a manual acquisition step: ordinary .NET builds use the checked-in files and do not run npm or download frontend packages. -The update script copies the **complete `dist/` tree**, preserving relative ES -module, module-worker, source-map, declaration, and font paths. It also retains -the package metadata, README, and MIT license. The bundled Cascadia Mono NF font -has its own SIL Open Font License and provenance under -`dist/fonts/cascadia-mono-nf/`. Do not flatten, selectively bundle, or edit these -vendored files. `TerminalView.razor.js` imports only the public `dist/index.js` -entry point, not the package's internal protocol/renderer modules. +The published package contains one unminified runtime bundle. The acquisition +script minifies it with the pinned Terser dependency in module mode, producing +`dist/index.min.js` with the public API and both workers. It does not rebuild +upstream source or split the workers. Only **four files** are vendored: this +minified bundle, the package's MIT license, the unmodified Cascadia Mono NF +WOFF2 font and its SIL Open Font License (including copyright notice). +Declarations, maps, package metadata and package/font READMEs are not deployed. +Version provenance remains in the bundle header and the Dashboard's manifest +and lockfile. + +The font comes from Microsoft's [Cascadia Code v2407.24 release](https://github.com/microsoft/cascadia-code/releases/tag/v2407.24), +path `woff2/CascadiaMonoNF.woff2`. Its relative path under +`dist/fonts/cascadia-mono-nf/` is preserved. Do not hand-edit generated assets. +`TerminalView.razor.js` imports only the minified public entry point. + +The temporary `nuget-hex1b` source in the repository's `NuGet.config` maps only +`Hex1b`, `Hex1b.McpServer`, and `Hex1b.Tool` to nuget.org while this alpha awaits +mirroring. Other packages continue to use the existing feeds. The terminal uses `renderer: "auto"`: WebGPU is preferred, with the package's WebGL2 compatibility backend used when WebGPU's secure context, API, adapter, @@ -70,13 +86,83 @@ and `/api/terminal` and `/api/apphost-terminal` WebSockets in the deployment CSP localized error if mounting fails. The component import and socket endpoint resolve beneath `NavigationManager.BaseUri`. -The package import, worker entry, and bundled font resolve relative to their -modules, so deployments under a PathBase retain the prefix throughout the -asset tree. No blob worker, eval, CDN, or cross-origin font permission is +Both module workers load that same `dist/index.min.js` URL using the fragments +`#hex1b-terminal-worker` and `#hex1b-link-detection-worker`; the path and query +string are preserved. The bundled font resolves relative to the module, so +deployments under a PathBase retain the prefix throughout the asset tree. +No blob worker, eval, CDN, or cross-origin font permission is needed. The dashboard's existing `script-src 'self'` also allows same-origin workers through the CSP worker-source fallback; its production `default-src 'self'` covers the font and same-origin connections. +### Terminal metadata + +Workload-reported titles (OSC 0/2), working directories (OSC 7) and progress +(OSC 9;4) flow through the public client callbacks to the Dashboard title bar. +The resource view, active dock pane, detached window and interaction dialog +share the same title/directory/progress presentation. Titles and directory URIs +are treated as untrusted text, not HTML or navigable links. The decoded directory +is displayed at the right of the title bar as a copy button. Clicking anywhere +on the path copies the full value using the Dashboard's shared client-side +clipboard handler; the copy icon appears on hover or keyboard focus without +changing the layout. +Long paths omit whole middle segments to retain leading and trailing context, while +the clipboard retains the full decoded path. Cleared titles fall +back to the surface's original name. Progress supports determinate, +indeterminate, error and warning states, and is hidden when disconnected. +It appears before the title, reserving a stable percentage width only for +determinate states. Error and warning labels appear in the progress tooltip +and accessible name rather than as inline text. +These values require the application or shell to emit the corresponding OSC +sequences; the Dashboard does not infer them from output. + +### Scrollbar and retained command marks + +The pinned client has no public option for suppressing its legacy "rows above +live" status and "Return to live" button. The adapter installs a small +shadow-DOM style/observer shim for those two elements only, leaving errors and +selection feedback intact. The observer is disconnected on reconnect/disposal. +Remove the shim when the upstream client offers a history-chrome option. +The overlay scrollbar and keyboard navigation still provide history navigation. + +The terminal uses Hex1b's default Canvas2D **overlay** scrollbar, not a native +HTML scrollbar or a reserved gutter. The mount requests 3 CSS pixels of internal +padding on every side. `createDefaultScrollbarRenderer` keeps the +upstream capsule thumb, marker drawing, gestures, hit testing and auto-hide. +The painter adapter suppresses its additional canvas focus ring after pointer +release. A scoped shadow-DOM override also hides the track's DOM focus outline +after pointer input, restoring the upstream `:focus-visible` outline on keyboard +input without changing actual focus. The modality listeners are removed on disposal. +The track uses the terminal frame's background at 35% opacity. The thumb uses +the light foreground token from the terminal's dark theme scope, rather than +the Dashboard page's foreground, so it remains contrasting in either page theme. +Markers use the Dashboard's brand foreground and error tokens at 65% opacity, +restoring full opacity for increased contrast and forced colors. The track remains +translucent and dark in the light Dashboard theme; terminal cell colors are not changed. + +Marker and tooltip colors are resolved outside the terminal's dark theme scope. Dashboard theme +changes and the `forced-colors` and `prefers-contrast` media queries recreate the +snapshotted painter and replace the complete overlay configuration. Forced +colors use resolved system colors, and increased contrast makes the track +opaque. Hex1b owns reduced-motion behavior. Theme observers and media listeners +are removed when the view is disposed. + +Marker hover previews decorate `renderDefaultScrollbarTooltip` with Aspire's +popup background, border, radius, shadow and UI typography. Their colors are +resolved in the same outer Dashboard theme scope as the markers, with system +colors in forced-color mode. Hex1b still owns safe text rendering, detail loading, +positioning and tooltip lifetime. + +The existing HMP-to-HWT mirror has its own 10,000-row scrollback capacity. +Hex1b now negotiates retained text and OSC 133 command-mark checkpoints by +default, restoring producer-backed history and marks on late attachment and +reconnect when both peers support them. The built-in scrollbar exposes mark +navigation without a Dashboard mode chooser or custom tooltip UI. Marks follow +retained content and disappear on eviction; unavailable marker rows are not row +zero. Browser-owned bookmarks remain per-view and do not survive reconnect. + +### View lifecycle + Each reconnect aborts the previous mount and creates a new client. Mounting is deferred while initially hidden; once connected, changing the Console/Terminal view retains the client, selection, and producer-backed history. Disposal closes @@ -158,7 +244,7 @@ run in CI through `Infrastructure.Tests` using the existing `NodeCommand` helper. They verify focused shadow-DOM input isolation from dashboard shortcuts, cancellation, reconnect generations, visibility, role-gated sizing, failure state, PathBase asset URLs, deployment asset presence, and exact version parity -between `Directory.Packages.props`, the npm manifest/lockfile, and the vendored -package. Complete installed-package byte comparison belongs to the separate +between `Directory.Packages.props`, the npm manifest/lockfile, and the minified +bundle header. Reproducible minification and installed font/license byte comparison belong to the separate acquisition verification command above. Neither suite substitutes for a browser WebGPU/WebGL2 rendering test or multi-peer server/CLI integration tests. diff --git a/src/Aspire.Dashboard/wwwroot/js/app.js b/src/Aspire.Dashboard/wwwroot/js/app.js index 9bd82f70891..67e15371e38 100644 --- a/src/Aspire.Dashboard/wwwroot/js/app.js +++ b/src/Aspire.Dashboard/wwwroot/js/app.js @@ -261,9 +261,11 @@ window.copyTextToClipboard = function (id, text, precopy, postcopy) { checkmarkIcon.style.display = ''; } }) - .catch(() => { + .catch(error => { if (tooltipDiv) { tooltipDiv.innerText = 'Could not access clipboard'; + } else { + console.warn("Dashboard clipboard copy failed.", error); } }); diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/README.md b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/README.md deleted file mode 100644 index 7f109df39a1..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/README.md +++ /dev/null @@ -1,811 +0,0 @@ -# @hex1b/web-terminal - -The first-party GPU-rendered browser terminal for Hex1b. It renders server-authoritative -cells and graphics in a module worker, with local input routing, producer-backed -history and selection, clipboard actions, and primary/secondary view sizing. -There are no runtime package dependencies. - -**Experimental and paired with Hex1b:** this client speaks the evolving HWT1 -transport implemented by the matching Hex1b server. HWT1 and internal browser -modules are not a supported third-party protocol or renderer API. The bootstrap -npm version `0.1.0` must be paired with the server build from the **same -implementation commit**; it is not compatible merely by version number with the -historical Hex1b NuGet `0.1.0`. Subsequent normal CI releases coordinate npm and -NuGet versions; use matching builds from the same release. - -## Install and mount - -```sh -npm install @hex1b/web-terminal -``` - -Give the container a nonzero width and height. Mount resolves after a connected -frame has been presented, or rejects on initialization failure or a 30-second -first-frame timeout. - -```html -
-``` - -```ts -import { WebTerminal } from "@hex1b/web-terminal"; - -const container = document.getElementById("terminal"); -if (!container) throw new Error("Missing terminal container"); - -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", // Your matching Hex1b HWT1 WebSocket endpoint. - sizing: { mode: "auto", fontSize: 16 }, - onStatus(message, level) { - console.log(level, message); - } -}); - -terminal.focus(); -// On component teardown: -// terminal.dispose(); -``` - -`url` accepts a string or URL; relative URLs resolve against the page, and -`http:`/`https:` become `ws:`/`wss:`. An optional `AbortSignal` cancels mounting -or disposes a mounted view. Disposal removes only the appended element and its -connection, not the container or server-side shared terminal. - -### Connection closure and workload completion - -Use `onClose(details)` to observe the browser's actual WebSocket close event. -`TerminalCloseDetails` contains readonly `code`, `reason`, and `wasClean` fields. -The callback runs once with the view already disconnected, **even if the socket -closes before the first HWT frame or authoritative HMP peer state**. A pending -mount rejects after the callback, so capture any host state before calling mount. -The details object is frozen. Reasons are untrusted text; do not render them as HTML. - -```ts -import { WebTerminal, type TerminalCloseDetails } from "@hex1b/web-terminal"; - -const container = document.getElementById("terminal"); -if (!container) throw new Error("Missing terminal container"); -let closed: TerminalCloseDetails | undefined; -try { - const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - onClose(details) { - closed = details; - console.log("View closed", details.code, details.reason, details.wasClean); - } - }); - terminal.focus(); -} catch (error) { - // closed is set for a transport close, but not for local initialization failure. - console.error("Mount failed", closed, error); -} -``` - -Transport loss is **not workload completion**. Code 1006 means the browser did not -receive a close frame; even code 1000 and `wasClean: true` only describe transport -closure, not successful process exit. Hosts can define an application close-code -contract and send it when their authoritative producer reports completion, -including before any HWT frame exists. Hex1b does not assign workload meaning to -close codes or reason strings. HTTP upgrade failures generally surface as 1006; -browsers do not expose the rejected HTTP response body or status through this API. - -The client never retries automatically. The host decides whether to mount a new -view after transport loss or leave an ended tab/dialog visible. Abort, explicit -disposal, mount timeout, and local initialization/renderer failures do not -synthesize `onClose`; no callback runs after disposal. Callback exceptions are -reported to the host, not swallowed or retried, and do not leave mounting pending. -The API does not retain the producer after exit or promise a final rendered frame. - -### Browser and deployment requirements - -Use a browser with WebGPU or WebGL2, module workers, transferable OffscreenCanvas, -worker animation frames, ResizeObserver, and CSS Font Loading. WebGPU requires -HTTPS or localhost; WebGL2 rendering also works on ordinary HTTP origins. -Clipboard API access still requires a secure context, browser permission and, -for relevant actions, a user gesture. There is no Canvas2D terminal-rendering fallback. -Renderer selection does not change transport security: use HTTPS/WSS to protect -terminal input and output. - -### Renderer selection - -Set the mount-time `renderer` option to `"auto"` (the default), `"webgpu"`, or -`"webgl2"`. Auto prefers WebGPU and uses WebGL2 if the secure context, API, -adapter, device acquisition, or presentation context is unavailable. Explicit -modes require that backend and report an error rather than falling back. -Shader, font, validation, and unexpected initialization errors are not -compatibility fallbacks. Runtime GPU/context loss terminates the view with an -error; it does not switch backends behind the caller's back. - -```ts -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - renderer: "webgl2", // Use WebGL2 even if WebGPU is available. - onStats(stats) { - console.log(stats.renderer, stats.rendererFallbackReason); - } -}); -``` - -`stats.renderer` identifies the active backend after initialization. -`stats.rendererFallbackReason` explains an automatic fallback and is absent -for explicit selections and successful WebGPU initialization. Both backends -share glyph rasterization, frame preparation, clipping, and image ordering. -WebGPU preference is not a performance guarantee; compare representative -workloads on your target browsers and devices. - -### Module and worker deployment - -The package contains browser ES modules, not a single bundle. For bare static -hosting, copy **all of `dist/`**, preserving its directory structure, and import -`/web-terminal/index.js` from a module script. `dist/web-terminal.js` also remains -available for relative static imports. Package consumers should import only from -`@hex1b/web-terminal`; internal protocol/renderer modules are not public exports. - -The worker is created with -`new Worker(new URL("./terminal-worker.js", import.meta.url), { type: "module" })`. -The default font is resolved relative to its module, not the host page. -Bundlers differ in whether they discover and rewrite assets inside dependencies; -this package does not claim universal or individually verified bundler support. -The reliable static deployment layout is the complete emitted tree described -above. - -If your bundler does not handle the dependency's worker URL, explicitly provide -the entry point you deployed: - -```ts -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - workerUrl: "/web-terminal/terminal-worker.js" -}); -``` - -`workerUrl` accepts a nonempty string or URL and resolves relative strings against -the page, not the package module. It still creates a **module** worker. Deploy its -complete relative module tree, or provide a separately bundled worker entry from -the same package build. The override does not automatically copy fonts: retain -the default font asset URL or provide explicit `font.faces` URLs as shown below. -The browser's worker origin and CSP restrictions still apply. - -Configure your server's JavaScript and WOFF2 MIME types and CSP to allow these -workers, fonts, and the intended WebSocket endpoint. - -## Configuration and state - -`WebTerminalOptions` includes: - -| Option | Meaning | -| --- | --- | -| `workerUrl` | Optional module-worker entry; useful when worker assets are deployed separately. | -| `linkDetectionWorkerUrl` | Optional detection module-worker entry (`string \| URL`), parallel to `workerUrl`. | -| `links` | Opt-in per-view text detection and OSC 8 interaction policy; `false` disables all local links. | -| `onLinkDetectionError` | Feature-local detection diagnostics; also reported through `onStatus`. | -| `scale` | GPU backing scale `0.5`–`3`, or `"auto"` (default, bounded device pixel ratio). | -| `renderer` | `"auto"` (prefer WebGPU), `"webgpu"`, or `"webgl2"`; selected once per mount. | -| `font` | One family and optional downloadable font faces; see below. | -| `sizing` | `{ mode: "auto", fontSize?: number }` or `{ mode: "fixed", columns, rows, fontSize?: number }`. | -| `readOnly` | Initial per-view input policy; change it later with `setReadOnly(boolean)`. | -| `label` | Accessible label for the terminal's hidden keyboard input. | -| `onTitleChange` | Initial authoritative workload title, then distinct presented changes; see below. | -| `onClose` | Native WebSocket close details, including pre-mount transport failure; not workload completion. | -| `onProgressChange`, `onShellIntegrationChange` | Initial authoritative activity, then distinct presented changes for host-owned chrome. | -| `onWorkingDirectoryChange`, `onCommandMarkChange` | Initial authoritative OSC 7 directory and latest OSC 133 marker, then distinct presented changes; see below. | -| `inputBindings`, `onInput`, `actions` | Per-view input policy and custom actions. | -| `onSelectionUI` | Synchronous, cancelable UI notification hook. | - -Font size is an integer from 8–32, defaulting to 16. Import `MIN_FONT_SIZE` and -`MAX_FONT_SIZE` from `@hex1b/web-terminal` for sizing controls. Requested fixed grids allow -20–300 columns and 10–100 rows. The producer still owns actual grid geometry. -`resize()`, `setSizing()`, and automatic resize requests require primary -ownership. `requestPrimary()` explicitly requests ownership; inspect `peer` or -`onRoleChange` to observe the result. - -The handle exposes `geometry`, `peer`, `connected`, `readOnly`, `title`, `progress`, `shellIntegration`, -`workingDirectory`, `commandMark`, `stats`, `screenText`, -`sizing`, `viewport`, `selection`, `inputBindings`, and `inputContext`. -Metrics start empty; check optional fields before using them. History may be -unavailable, and selection can be unavailable, none, pending, valid, or -invalidated. Narrow `viewport.available` and `selection.status` before using -their state-specific values. `screenText` reflects the presented viewport, not -an independently reconstructed ANSI buffer. - -Callbacks include `onGeometry`, `onRoleChange`, `onTitleChange`, `onSizingChange`, `onStats`, -`onProgressChange`, `onShellIntegrationChange`, `onWorkingDirectoryChange`, `onCommandMarkChange`, -`onViewportChange`, `onSelectionChange`, `onStatus`, and `onInputError`. - -### Live read-only views - -Call `terminal.setReadOnly(true)` to disable application input on an already -mounted view. `terminal.readOnly`, `inputContext.readOnly`, and selection UI -notifications reflect the new policy. Use `setReadOnly(false)` to re-enable input; -neither call remounts, reconnects, releases the peer's primary role, or changes -the server's current grid. A writable primary resumes automatic sizing requests. -Mutating the original `options.readOnly` after mount has no effect. - -Read-only blocks keyboard/text/IME input, application mouse reports, direct -`paste()`/`pasteClipboard()`, the paste paths of `runAction()`, `resize()`, -`setSizing()`, `requestPrimary()`, and automatic resize requests. Explicit input -methods throw when disabled; DOM application input is not forwarded. Routing -overrides cannot bypass this policy. Custom actions can still run local operations, -but any terminal input method they call remains gated. - -Active pointer capture and queued mouse movement, pending composition, queued -resize, and pending clipboard pastes are cancelled on policy change. Quickly -re-enabling input does not revive a previously pending paste. Commands already -dispatched cannot be recalled. Output, local selection gestures, history -navigation, resync, and copying remain available, including a copy already in -progress. UI notifications follow their usual coalescing rules. - -```ts -import { WebTerminal } from "@hex1b/web-terminal"; - -const container = document.getElementById("terminal"); -const inputEnabled = document.querySelector("#input-enabled"); -if (!container || !inputEnabled) throw new Error("Missing terminal controls"); - -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - readOnly: !inputEnabled.checked -}); -inputEnabled.addEventListener("change", () => terminal.setReadOnly(!inputEnabled.checked)); -``` - -**Client policy is not authorization.** Enforce it independently on each server -view with `Hwt1PresentationAdapter.IsReadOnly`, initially or at runtime. For -example, in the host's existing connection setup (C# snippet): - -```csharp -var presentation = new Hwt1PresentationAdapter { IsReadOnly = true }; -// Attach this presentation to the view's terminal and drive its existing transport loops. -// Only trusted host policy should grant writes: -presentation.IsReadOnly = false; -``` - -The adapter ignores producer-mutating browser input, resize, and primary requests -while read-only, but still processes acknowledgements, resync, history, selection, -and copy. This is **per presentation**, not a producer-wide input lock: direct -terminal automation and other authorized viewers continue. A policy change does -not retract a command the adapter already accepted. The host must update both its -server policy and browser UX; client changes do not authorize themselves, and the -server property does not automatically change client UI. - -### Workload titles - -The read-only `terminal.title` is the current presented workload title. An empty -string means unset or explicitly cleared; choose your own fallback. The optional -`onTitleChange(title)` callback runs once with the first authoritative presented -value, **including `""`, before mount resolves**. The getter is updated before -the callback. Later notifications report only distinct presented values. Identical -updates, same-title resyncs, cursor blinking, and statistics do not notify again. -Intermediate workload changes can coalesce; this is not an event for every OSC -sequence. - -```ts -import { WebTerminal } from "@hex1b/web-terminal"; - -const container = document.getElementById("terminal"); -const header = document.getElementById("terminal-header"); -if (!container || !header) throw new Error("Missing terminal elements"); -const resourceName = "Build service"; - -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - onTitleChange(title) { - header.textContent = title || resourceName; - } -}); -console.log(terminal.title); -``` - -The callback uses elements and fallback text captured **before** mounting, not -the still-pending `terminal` result. The component does not change `document.title`, -your header, or the input's accessible `label` automatically. There is no title -subscription method or DOM title event. - -Titles are normalized by the core to at most 4,096 UTF-16 code units, with C0, -DEL, and C1 controls removed, malformed surrogates replaced with U+FFFD, and -truncation at a Unicode scalar boundary. They remain **untrusted text**: markup -and bidi characters are preserved. Use `textContent`, not `innerHTML`; apply your -own presentation and bidi policies. - -OSC 0 and OSC 2 set or explicitly clear the window title; OSC 1 is icon-only. -Use `ESC ] 0 ; text BEL` or `ESC ] 2 ; text BEL`; `ESC \` (ST) may replace BEL. -The UTF-8 input path also accepts Unicode C1 OSC (`U+009D`) and ST (`U+009C`). -Semicolons within `text` are literal. Existing OSC 22/23 saved-title extensions -update the same state, including after a late HMP1 attachment. -RIS, soft reset, screen clearing, and buffer switching preserve the title and -existing saved-title behavior. History inspection retains the current workload -title rather than a title associated with an old row. - -Disconnect and disposal retain the last known title without a synthetic clear. -Disposal (including abort) stops title callbacks. Attach a new view to reconnect; -it receives its own initial current title. Preliminary disconnected relay frames -do not trigger the initial notification. Callbacks run directly like the other -state callbacks; thrown host errors are not swallowed or retried. - -The required title field needs the matching server build. Missing or malformed -title metadata fails the connection; an older server is not silently treated as -an empty title. -The per-title bound is not a limit on all parser buffering or saved-stack depth. -An HMP1 snapshot with too much saved title state fails its 16 MiB replay limit -rather than silently discarding saved titles. - -### Application progress and shell activity - -`terminal.progress` exposes OSC 9;4 state as `{ state, percentage }`. -The states are `"none"`, `"normal"`, `"error"`, `"indeterminate"`, and `"warning"`. -Normal/error/warning percentages are integers from 0 through 100. None and -indeterminate have `percentage: null`; none means the host should hide its indicator. -This is application-reported progress, not inferred from output or CPU activity. -It is independent of `ProgressWidget`, which draws inside terminal cells. - -`terminal.shellIntegration` exposes OSC 133 as `{ phase, lastExitCode }`. -The phases are `"unknown"`, `"prompt"` (A), `"commandLine"` (B), -`"executing"` (C), and `"finished"` (D). B means input after the prompt, **not** -command execution. Unknown does not mean idle. `lastExitCode` is a signed -32-bit integer, or null when no status was reported; null is not success. -A/B/C preserve the last reported result, and D replaces it, including clearing -it to null when the shell omits its status. No command text, history, or output -locations are retained by these APIs. - -`terminal.workingDirectory` exposes OSC 7 state as `{ uri, host, path }`, all -`null` until the first report. `uri` is the raw reported `file://` URI; `host` -and `path` are derived from it (`host` is `""` for a local/unqualified -authority). A malformed or non-`file` URI leaves the previous value unchanged. - -`terminal.commandMark` exposes the single most-recently-reported OSC 133 marker -as `{ phase, exitCode, rawParameters } | null` — `null` until the first marker. -`phase` uses the same enum as `shellIntegration.phase`. `exitCode` is non-null -only on a `finished` (D) marker. `rawParameters` is the verbatim -`key=value[;key=value...]` text trailing the marker (for example a -`cmdline_url` extension on marker C), or `null` when none was present; use the -exported `parseCommandMarkParameters(rawParameters)` helper to parse it into a -`Map`, or `getCmdlineUrl(mark)` as a shortcut for the `cmdline_url` entry. This -is **not** a command-mark history — only the latest marker is exposed, mirroring -`shellIntegration`. A host that wants its own history should accumulate -distinct values from `onCommandMarkChange` itself. - -All four getters return defensive copies. Their callbacks receive the first -authoritative presented state before mount resolves, then distinct presented -changes. All four getters are updated before their corresponding activity -callback. Callbacks use the same direct, synchronous host-callback convention -as title changes; host exceptions are not swallowed or retried. - -Frames coalesce: the browser might see only Finished for a fast command, or -miss an entire command whose final state is unchanged. These callbacks are -**current-state notifications, not a lossless start/finish event stream**. -Resync/replay never invent commands, unchanged state does not notify again, -and a new mount receives its own baseline. - -This example creates optional chrome outside the terminal: - -```ts -import { WebTerminal, getCmdlineUrl } from "@hex1b/web-terminal"; - -const status = document.createElement("span"); -const cwd = document.createElement("span"); -const progress = document.createElement("progress"); -progress.max = 100; -progress.hidden = true; -const container = document.createElement("div"); -container.style.cssText = "width:800px;height:480px"; -document.body.append(status, cwd, progress, container); - -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - onProgressChange(value) { - progress.hidden = value.state === "none"; - progress.dataset.state = value.state; // Host CSS can distinguish error/warning. - if (value.percentage === null) progress.removeAttribute("value"); - else progress.value = value.percentage; - }, - onShellIntegrationChange(value) { - status.textContent = value.phase + - (value.lastExitCode === null ? "" : ` (last exit ${value.lastExitCode})`); - }, - onWorkingDirectoryChange(value) { - cwd.textContent = value.path ?? ""; - }, - onCommandMarkChange(value) { - const cmdlineUrl = getCmdlineUrl(value); - if (cmdlineUrl) console.log("Command link:", cmdlineUrl); - }, - onStats(stats) { - if (!stats.connected) { - progress.hidden = true; - status.textContent = "Disconnected"; - } - } -}); -console.log(terminal.progress, terminal.shellIntegration, terminal.workingDirectory, terminal.commandMark); -``` - -No title, document chrome, or progress UI is changed automatically by the -component. The sample endpoint must be supplied by your application. -Callbacks can run before the `terminal` variable is assigned; use their -arguments during initial mounting. - -RIS resets progress to None and shell integration to Unknown. Soft reset, -screen clearing, resize, and buffer switches preserve them. OSC 9;4 state 0 -clears only progress; a shell completion does not implicitly clear it. -Disconnect, process exit, and disposal retain the last reported values -without inventing a completion or progress clear. Check `connected` before -showing active chrome, and remount to reconnect. Disposal stops callbacks. -Snapshots and historical viewports carry current activity, not activity at -the time a particular row was printed. - -The core accepts BEL, ESC-backslash ST, and decoded Unicode C1 terminators -through its UTF-8 input path. Determinate progress requires unsigned decimal -0-100; clear/indeterminate allow an omitted percentage and ignore its optional -value. OSC 133 supports the basic A/B/C forms and D with an optional signed -decimal exit status (an empty field also means no status). Missing required, -malformed, overflowed, excess, or unsupported arguments do not change state. -The raw-output presentation path still forwards the original sequences to -supporting outer terminals. Required activity metadata needs the matching -server build; invalid/missing wire fields fail the connection, not silently -fall back to default state. - -## Input and clipboard - -Import `InputRoute`, `TerminalAction`, and `defaultInputBindings` to inspect and -customize routing. Defaults preserve browser shortcuts, forward terminal keys, -copy with Cmd+C or Ctrl+Shift+C, and use a local right-click to copy or paste -when application mouse capture does not own that gesture. IME composition and -paste are forwarded through the producer's mode-aware input encoder. - -Overrides match first. Reuse a default binding's ID to replace it, or specify -`{ id: "clipboard.context-click", remove: true }` to remove that default. -`match`, `when`, and `onInput` must finish synchronously. Actions may be async. - -```ts -import { InputRoute, TerminalAction, type WebTerminalOptions } from "@hex1b/web-terminal"; - -const options: WebTerminalOptions = { - url: "/ws/terminal", - inputBindings: [ - { - id: "history.previous-page", - match: input => input.type === "key" && input.key === "PageUp" && input.shift, - action: TerminalAction.ScrollLines, - args: -20 - }, - { id: "clipboard.context-click", remove: true } - ], - onInput(input) { - if (input.type === "key" && input.meta) return InputRoute.Browser; - return InputRoute.Continue; - } -}; -``` - -Named actions are `copySelection`, `pasteClipboard`, `copyOrPaste`, -`clearSelection`, `scrollToLive`, and `scrollLines`. -`terminal.runAction(TerminalAction.ScrollLines, -20)` shares the same -implementation as bindings and UI controls. Custom `actions` receive -`(context, args, input)`; their argument/result types are `unknown`, so custom -handlers validate their own data. Built-in action names cannot be overridden. - -You can also call `scrollLines()`, `scrollToLive()`, `clearSelection()`, -`copySelection({ clear: true })`, `paste(text)`, or `pasteClipboard()` directly. -Copy uses authoritative producer selection text, not rendered cells. Clipboard -actions reject if selection/input/focus changes before their asynchronous work -can be applied safely. Errors are surfaced rather than silently reported as -successful copies or pastes. - -### Hyperlinks - -Hold Ctrl or Cmd and click an OSC 8 hyperlink to open its destination in a new -tab. Hovering shows the destination and activation hint; holding the modifier -also shows a pointer cursor. Links work in live output, scrollback, and read-only -views. Plain clicks and drags retain their existing selection/application -behavior, and explicit input-policy routes or actions take precedence. -Shift and Alt/Option continue to reserve selection gestures. - -Only absolute `http:`, `https:`, and `mailto:` destinations are activated -(`mailto:` handling depends on the browser). New tabs use `noopener,noreferrer`. -Script, data, file, relative, and custom-scheme URLs are not activated. -This is the default when `links` is omitted: text detection is off and legacy -allowlisted OSC 8 navigation is preserved. - -### Opt-in text links and host actions - -Detection is browser-local and per view. It does not create server hyperlinks, -emit OSC/SGR, change copied text, or modify HWT1. Detected text **never opens a -browser, application, or file automatically**. Register actions at mount time, -even if detection starts disabled; `setLinks` does not register actions. - -This example creates a plain-text preview, not a navigation or file-access UI: - -```ts -import { WebTerminal, linkAction, type TerminalLinkOptions } from "@hex1b/web-terminal"; - -const container = document.createElement("div"); -container.style.cssText = "width:800px;height:480px"; -const preview = document.createElement("pre"); -document.body.append(container, preview); - -const links: TerminalLinkOptions = { - osc8: { action: "previewUri" }, // Optional: replace legacy navigation as well. - detection: { - activation: "modifierClick", - decoration: "always", - underlineStyle: "solid", - rules: [ - { id: "web", builtin: "url", action: "previewUri" }, - { id: "files", builtin: "absolutePath", action: "remoteFile" }, - { id: "home", builtin: "homePath", action: "remoteFile" }, - { id: "uris", builtin: "uri", action: "previewUri" }, - { - id: "issues", pattern: /\bPROJ-(?\d+)\b/gu, - kind: "custom", text: "logicalLine", action: "issue", - resolve(match) { - const number = match.groups.number; - return number ? { target: number, data: { label: match.text } } : null; - } - } - ] - } -}; - -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - links, - actions: { - previewUri: linkAction((_context, activation, _input) => { - preview.textContent = `URI preview: ${activation.target}`; - }), - remoteFile: linkAction((context, activation) => { - preview.textContent = `Remote path: ${activation.target}\n` + - `Remote cwd: ${context.terminal.workingDirectory.path ?? "unknown"}`; - }), - issue: linkAction((_context, activation) => { - preview.textContent = `Issue ${activation.target}: ${activation.text}`; - }) - }, - onLinkDetectionError(error) { - console.warn(error.code, error.ruleId, error.revision, error.message); - }, - onStatus(message, level) { - console.log(level, message); - } -}); - -// Replace the entire configuration, disabling only the "home" rule. -if (links.detection) { - terminal.setLinks({ - ...links, - detection: { - ...links.detection, - rules: links.detection.rules.map(rule => - rule.id === "home" ? { ...rule, enabled: false } : rule) - } - }); -} -terminal.setLinks({ detection: false }); // Reset to legacy OSC 8, no detection. -terminal.setLinks(false); // Disable every local link interaction. -terminal.setLinks(links); // Re-enable the original configuration. -``` - -`setLinks(options)` replaces, rather than merges, the complete configuration. -Omitted fields reset to defaults. Validation occurs before replacing active -state; malformed rules, duplicate IDs, unsupported regex flags, and missing -named actions throw. `enabled: false` disables an individual rule. -`osc8: false` disables OSC 8 interactions while allowing configured detection; -`osc8: { action }` delegates OSC 8 activation to a consumer action. An omitted -`osc8` keeps legacy allowlisted navigation, even when detection is configured. - -Actions use the existing `actions` registry, not an `onLinkClick` callback. -`linkAction((context, activation, input) => unknown)` returns an -`InputActionHandler` that validates and types its activation argument. -`context` is `TerminalInputContext`; `input` is a readonly `TerminalInput` or -`undefined`. Async action completion uses the existing dispatcher. A rule or -OSC 8 action can also be an inline handler. Built-in terminal action names -cannot serve as link actions; use a registered custom action or callback. - -`TerminalLinkActivation` contains `source` (`"detected"` or `"osc8"`), `ruleId` -(`null` for OSC 8), `kind` (`"uri"`, `"path"`, or `"custom"`), matched `text`, -resolved `target`, visible end-exclusive cell `ranges`, presented `revision`, -and optional consumer `data`. Core activation fields and ranges are frozen; -consumer-owned `data` is not deep-frozen. Targets, OSC 8 destinations, and data remain untrusted: authorize any -navigation or remote operation in your application and display text with -`textContent`, not `innerHTML`. A custom OSC 8 action can receive schemes outside -the legacy allowlist; this is not permission to open them. - -#### Rules, text modes, and resolution - -Rules compete in array order; the first accepted match owns its cells. Presets -recognize HTTP/HTTPS URLs (`url`), general including opaque URIs (`uri`), POSIX -and Windows drive-absolute paths (`absolutePath`), and literal `~/...` -(`homePath`). Paths are lexical, whitespace-delimited **remote terminal paths**, -not browser-local files. There is no `~` expansion, percent decoding, existence -check, home-directory inference, or resolution against the page URL. Use custom -rules for quoted/spaced paths, UNC paths, or `file:line:column` suffix grammars. - -Custom rules supply `pattern: RegExp`, `kind`, and an action. `text` applies to -built-ins and custom rules: - -| `text` | Match input | -| --- | --- | -| `"logicalLine"` (default) | Displayed rows joined only across authoritative soft wraps. | -| `"physicalRow"` | Each displayed physical row independently. | -| `"viewport"` | Displayed text with soft wraps joined and hard breaks retained as `\n`. | - -An optional synchronous `resolve(match)` returns `null` to reject a match, or -`{ target, action?, data? }` to transform its destination, override the action, -and attach local data. Without a resolver, `target` is the recognized text. -`match` exposes matched `text`, UTF-16 `index`, `captures` (excluding the full -match), named `groups`, and `chunk: { text, mode, start, end }`. Boundary values -are `"complete"`, `"clipped"`, or `"unknown"`. The full regex match determines -the highlight; a resolver cannot replace its range. Regexes scan all matches, -with or without `g`, without changing the caller's `lastIndex`; sticky `y` is -unsupported. Empty matches have no clickable cells. - -#### Visible-only limits and styling - -Only the currently displayed text is scanned, including history **while it is -displayed**. There is no off-screen fetch, unseen-history scan, independent -reflow, or reconstruction of missing text. HWT1 already carries the soft-wrap -flag but does not carry wide-wrap padding markers. Such blanks must remain -spaces, so some Unicode targets wrapped at a wide glyph will not match. -Candidates depending on uncertain/clipped edges or unavailable continuations -are not active. Complete visible delimiters are important; false negatives -are intentional rather than activating truncated destinations. - -Cell mapping respects wide/combining characters and rejects partial-grapheme -matches. Hidden cells and graphics placeholders are barriers, not text to -silently remove. Authoritative OSC 8 spans reserve cells **even when disabled -or blocked**; an overlapping detected candidate is rejected in full. - -Underline visibility and appearance are independent: - -| Option | Values | Default | -|---|---|---| -| `decoration` | `"always"`, `"hover"`, `"none"` | `"always"` | -| `underlineStyle` | `"solid"`, `"dashed"` | `"solid"` | - -For example, use `decoration: "hover", underlineStyle: "dashed"` for dashed -underlines only while the pointer is over a detected link. Hover decorates -the whole match, including its visible wrapped spans; no modifier key is needed -to reveal it. `"none"` retains hit-testing/activation without inferred underlines. -Change either option at runtime by passing the updated configuration to `setLinks`. -Decorations are local. Existing SGR underline style and color are preserved; -disabling links cannot erase application-authored underlines. - -`activation` defaults to `"modifierClick"` (Ctrl/Cmd+click); `"click"` is an -explicit alternative that takes ownership only on a link. Input policy retains -first refusal. Activation occurs on release, is canceled by dragging or stale -content/configuration, and does not forward the consumed gesture to the -workload. Read-only views may still invoke local link actions. - -#### Detection isolation and deployment - -Regex scanning uses a separate, lazily created detection module worker so a -pathological regex cannot stall the rendering worker. Work is bounded by -internal text, rule, match, and time budgets; oversized work is diagnosed, not -silently presented as complete. These are implementation limits, not public -scheduling options. Disabling detection or disposing the view releases its -detection worker. - -Current internal limits (not benchmark-derived performance guarantees): - -| Budget | Limit | -| --- | --- | -| Configured rules | 32 | -| Regex source length | 8,192 UTF-16 code units | -| One text chunk | 65,536 UTF-16 code units | -| Total scan text | 262,144 UTF-16 code units | -| Mapped cells | 262,144 | -| Matches | 2,048 per rule and 2,048 visible resolved matches | -| Estimated result payload | 262,144 budget units (bounds capture amplification) | -| Cache entries / estimated retained payload | 2,048 entries / 1,048,576 budget units, including keys and all matched/captured/group text | -| Per-rule timeout | 250 ms, including worker startup | - -Payload budgets count UTF-16 text units plus estimated overhead (16 units per -match and 8 per capture/group), including empty captures. They bound estimated -payload size, not exact JavaScript heap bytes. - -The cell budget does not override the chunk budget: oversized logical-line or -viewport chunks are rejected, not split into apparently complete targets. - -The first displayed row's start is treated as unknown, as are full right -edges. A visible delimiter is required when a candidate would otherwise -depend on an uncertain edge. Budget diagnostics remain feature-local; a regex -timeout disables its rule until `setLinks` reconfigures detection. - -**Resolvers are trusted synchronous main-thread JavaScript and cannot be -preempted by the regex watchdog.** Keep them fast, side-effect-free, and -nonblocking; they can run repeatedly during detection. Promises are invalid. -Timeouts and resolver failures disable the affected rule until reconfiguration. -`onLinkDetectionError` receives -`{ code: "timeout" | "limit" | "resolver" | "worker", ruleId: string | null, revision, message }`; -errors also use `onStatus`. Detection failure leaves the terminal running. -Activation failures instead use existing `onInputError`/status handling and -never fall back to navigation. - -Deploy the complete package tree, including the detection worker and its -relative dependencies. If your bundler requires explicit worker entries: - -```ts -const terminal = await WebTerminal.mount(container, { - url: "/ws/terminal", - workerUrl: "/web-terminal/terminal-worker.js", - linkDetectionWorkerUrl: "/web-terminal/link-detection-worker.js", - links: { detection: false } -}); -``` - -`linkDetectionWorkerUrl` accepts `string | URL`, resolves relative strings -against the page like `workerUrl`, and is a mount-time override. Without it the -entry resolves relative to the package module. Worker origin/CSP restrictions -still apply. Rules and matched text are not sent to an external service. -See the [opt-in demo](../../samples/WebTerminalDemo/README.md#try-local-link-previews). - -## Selection UI hooks - -`onSelectionUI` receives a typed `SelectionUIEvent`, also dispatched as the -`selectionui` DOM event on `terminal.element`. Its frozen detail includes -selection, viewport, geometry, canvas size, connection/read-only state, and: - -- `overlay`: a stable light-DOM host for custom UI; style your controls and set - `pointer-events: auto` on interactive descendants. -- `rects`: selection rectangles in overlay-local CSS pixels. -- `runAction`: the same typed action API as the terminal handle. -- `signal`: cleanup lifetime, aborted on disposal. - -Call `event.preventDefault()` **synchronously** to replace the default Copy -button. This does not remove selection highlights or transfer ownership of -selection/clipboard state. The callback must return `undefined`, not a Promise. -Events coalesce meaningful changes; `refreshSelectionUI()` re-notifies hosts -after external styling or policy changes. External DOM listeners may also -cancel the default UI. - -Inspection UI inherits the embedding page's `--cp-*` theme tokens and otherwise -uses its own light/dark defaults. Shadow parts include `selection-highlights`, -`selection-highlight`, and `selection-copy-button`. - -## Fonts and licenses - -The default is the bundled **Cascadia Mono NF** variable WOFF2 font. The package -includes the unmodified font, SIL Open Font License, and provenance in -`dist/fonts/cascadia-mono-nf/`; no sample assets are required. - -```ts -const font = { - family: "My Terminal Font", - faces: [{ url: "/fonts/my-terminal.woff2", weight: "100 900", style: "normal" }] -}; -``` - -Custom face URLs resolve against the host page before the configuration reaches -the worker. Page-loaded fonts are not inherited by workers: supply face URLs, -a locally installed family, or a generic family such as `monospace`. A generic -family cannot have downloadable faces. Font loading failures reject rather -than silently selecting a different font. - -Hex1b's code is MIT licensed (`LICENSE`); the bundled font uses its separate -SIL Open Font License. - -## Build, test, and pack - -From this package directory, with Node.js 22 or later: - -```sh -npm ci -npm run build -npm test -npm pack -``` - -The strict TypeScript build emits JavaScript, declarations, declaration maps, -and source maps (with embedded sources), then copies fonts into `dist/`. -`npm test` runs zero-dependency `node:test` tests against those emitted modules -and strict public-consumer declaration checks in NodeNext and bundler modes. -`npm run typecheck` validates sources without emitting. - -`prepack` rebuilds for `npm pack` and manual `npm publish` from this directory. -Only `dist/`, this README, the MIT license, and package metadata are shipped. -A prepared tarball is self-contained and can be published with -`npm publish ./hex1b-web-terminal-.tgz --ignore-scripts`; it does not -need development sources or build scripts. The package name is always -`@hex1b/web-terminal`. CI publishes main/release builds to npmjs; PR builds -provide the tarball as the `npm-web-terminal` workflow artifact and do not -publish it to a registry. No registry is pinned in `package.json`. diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts deleted file mode 100644 index 02f0242848f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { RenderBackend } from "./render-backend.js"; -import type { TerminalRendererPreference } from "./types.js"; -export declare function createRenderBackend(canvas: OffscreenCanvas, onFatal: (error: Error) => void, preference?: TerminalRendererPreference): Promise<{ - backend: RenderBackend; - fallbackReason?: string; -}>; -//# sourceMappingURL=backend-selection.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts.map deleted file mode 100644 index 4b86fdfaaf3..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"backend-selection.d.ts","sourceRoot":"","sources":["../src/backend-selection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIzD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAChG,UAAU,GAAE,0BAAmC,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAe/G"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js deleted file mode 100644 index 13e05af37b9..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js +++ /dev/null @@ -1,23 +0,0 @@ -import { RendererUnavailableError } from "./render-backend.js"; -import { normalizeRenderer } from "./renderer-options.js"; -import { WebGpuBackend } from "./webgpu-backend.js"; -import { WebGl2Backend } from "./webgl2-backend.js"; -export async function createRenderBackend(canvas, onFatal, preference = "auto") { - const requested = normalizeRenderer(preference); - if (requested === "webgl2") - return { backend: await WebGl2Backend.create(canvas, onFatal) }; - try { - return { backend: await WebGpuBackend.create(canvas, onFatal) }; - } - catch (error) { - if (requested !== "auto" || !(error instanceof RendererUnavailableError)) - throw error; - try { - return { backend: await WebGl2Backend.create(canvas, onFatal), fallbackReason: error.message }; - } - catch (fallbackError) { - throw new AggregateError([error, fallbackError], `WebGPU unavailable (${error.message}); WebGL2 initialization failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`); - } - } -} -//# sourceMappingURL=backend-selection.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js.map deleted file mode 100644 index 37e56ea0142..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/backend-selection.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"backend-selection.js","sourceRoot":"","sources":["../src/backend-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAuB,EAAE,OAA+B,EAChG,aAAyC,MAAM;IAC/C,MAAM,SAAS,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAChD,IAAI,SAAS,KAAK,QAAQ;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAC5F,IAAI,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAClE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,SAAS,KAAK,MAAM,IAAI,CAAC,CAAC,KAAK,YAAY,wBAAwB,CAAC;YAAE,MAAM,KAAK,CAAC;QACtF,IAAI,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QACjG,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACvB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,aAAa,CAAC,EAC7C,uBAAuB,KAAK,CAAC,OAAO,oCAClC,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { RendererUnavailableError } from \"./render-backend.js\";\nimport type { RenderBackend } from \"./render-backend.js\";\nimport { normalizeRenderer } from \"./renderer-options.js\";\nimport { WebGpuBackend } from \"./webgpu-backend.js\";\nimport { WebGl2Backend } from \"./webgl2-backend.js\";\nimport type { TerminalRendererPreference } from \"./types.js\";\n\nexport async function createRenderBackend(canvas: OffscreenCanvas, onFatal: (error: Error) => void,\n preference: TerminalRendererPreference = \"auto\"): Promise<{ backend: RenderBackend; fallbackReason?: string }> {\n const requested = normalizeRenderer(preference);\n if (requested === \"webgl2\") return { backend: await WebGl2Backend.create(canvas, onFatal) };\n try {\n return { backend: await WebGpuBackend.create(canvas, onFatal) };\n } catch (error) {\n if (requested !== \"auto\" || !(error instanceof RendererUnavailableError)) throw error;\n try {\n return { backend: await WebGl2Backend.create(canvas, onFatal), fallbackReason: error.message };\n } catch (fallbackError) {\n throw new AggregateError([error, fallbackError],\n `WebGPU unavailable (${error.message}); WebGL2 initialization failed: ${\n fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`);\n }\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts deleted file mode 100644 index f83bbb7c517..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { TerminalCommandMark } from "./types.js"; -/** - * Parses a {@link TerminalCommandMark.rawParameters} string into key/value pairs, mirroring the - * server's `TerminalCommandMark.Parameters`: segments are split on `;`, each split on the first - * `=`; segments without `=` are ignored. Values are not decoded (e.g. percent-decoding is left - * to the caller for keys that use it, such as `cmdline_url`). Returns an empty map for null/"". - */ -export declare function parseCommandMarkParameters(rawParameters: string | null): ReadonlyMap; -/** - * Gets the raw (still percent-encoded) `cmdline_url` parameter from a command mark, or null when - * absent. This is a Contour-originated, non-universal extension to OSC 133;C. - */ -export declare function getCmdlineUrl(mark: TerminalCommandMark | null): string | null; -//# sourceMappingURL=command-mark.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts.map deleted file mode 100644 index 2ba331a2437..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"command-mark.d.ts","sourceRoot":"","sources":["../src/command-mark.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CASpG;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAE7E"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js deleted file mode 100644 index eefeb6547f0..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Parses a {@link TerminalCommandMark.rawParameters} string into key/value pairs, mirroring the - * server's `TerminalCommandMark.Parameters`: segments are split on `;`, each split on the first - * `=`; segments without `=` are ignored. Values are not decoded (e.g. percent-decoding is left - * to the caller for keys that use it, such as `cmdline_url`). Returns an empty map for null/"". - */ -export function parseCommandMarkParameters(rawParameters) { - const result = new Map(); - if (!rawParameters) - return result; - for (const segment of rawParameters.split(";")) { - const separator = segment.indexOf("="); - if (separator < 0) - continue; - result.set(segment.slice(0, separator), segment.slice(separator + 1)); - } - return result; -} -/** - * Gets the raw (still percent-encoded) `cmdline_url` parameter from a command mark, or null when - * absent. This is a Contour-originated, non-universal extension to OSC 133;C. - */ -export function getCmdlineUrl(mark) { - return mark ? parseCommandMarkParameters(mark.rawParameters).get("cmdline_url") ?? null : null; -} -//# sourceMappingURL=command-mark.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js.map deleted file mode 100644 index bdc6b524f28..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/command-mark.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"command-mark.js","sourceRoot":"","sources":["../src/command-mark.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,aAA4B;IACrE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,IAAI,CAAC,aAAa;QAAE,OAAO,MAAM,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,SAAS,GAAG,CAAC;YAAE,SAAS;QAC5B,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAgC;IAC5D,OAAO,IAAI,CAAC,CAAC,CAAC,0BAA0B,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACjG,CAAC","sourcesContent":["import type { TerminalCommandMark } from \"./types.js\";\n\n/**\n * Parses a {@link TerminalCommandMark.rawParameters} string into key/value pairs, mirroring the\n * server's `TerminalCommandMark.Parameters`: segments are split on `;`, each split on the first\n * `=`; segments without `=` are ignored. Values are not decoded (e.g. percent-decoding is left\n * to the caller for keys that use it, such as `cmdline_url`). Returns an empty map for null/\"\".\n */\nexport function parseCommandMarkParameters(rawParameters: string | null): ReadonlyMap {\n const result = new Map();\n if (!rawParameters) return result;\n for (const segment of rawParameters.split(\";\")) {\n const separator = segment.indexOf(\"=\");\n if (separator < 0) continue;\n result.set(segment.slice(0, separator), segment.slice(separator + 1));\n }\n return result;\n}\n\n/**\n * Gets the raw (still percent-encoded) `cmdline_url` parameter from a command mark, or null when\n * absent. This is a Contour-originated, non-universal extension to OSC 133;C.\n */\nexport function getCmdlineUrl(mark: TerminalCommandMark | null): string | null {\n return mark ? parseCommandMarkParameters(mark.rawParameters).get(\"cmdline_url\") ?? null : null;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/fonts/cascadia-mono-nf/README.md b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/fonts/cascadia-mono-nf/README.md deleted file mode 100644 index cacff43753b..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/fonts/cascadia-mono-nf/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Cascadia Mono NF - -The web terminal's default font is Microsoft's Cascadia Mono with Nerd Font -symbols and without programming ligatures. The font file is unmodified. - -- Upstream: -- Release: [v2407.24](https://github.com/microsoft/cascadia-code/releases/tag/v2407.24) -- Upstream release path: `woff2/CascadiaMonoNF.woff2` -- Retrieved from Aspire's copy at commit - [`1dd4584e3df56f5544a3e7f5fda8aa767f92318e`](https://github.com/microsoft/aspire/tree/1dd4584e3df56f5544a3e7f5fda8aa767f92318e/src/Aspire.Dashboard/wwwroot/fonts/cascadia-mono-nf) -- Size: 976,460 bytes -- SHA-256: `bd42b0c992de9c42d8a770112e8140d67d6437798be39d49e57f4954e2f6e8e2` - -## License and redistribution - -Copyright (c) 2019 - Present, Microsoft Corporation, -with Reserved Font Name Cascadia Code. - -The font is licensed under the **SIL Open Font License 1.1**, not Hex1b's code -license. The full, unmodified license and copyright notice are in -[`LICENSE.txt`](LICENSE.txt). Include that notice and license whenever -redistributing the font, including future embedded-resource or npm packaging. -OFL permits bundling and embedding alongside software; the font cannot be sold -on its own. Modified versions must respect the reserved-font-name and other -OFL conditions. - -To update, take the unmodified WOFF2 from an official upstream release, retain -the accompanying license, and update the version, provenance, size, and hash -above. Do not subset or patch the asset as an incidental packaging step. diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts deleted file mode 100644 index a29e024d576..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** Commands contain producer identities; text and ranges are never inferred from painted cells. */ -export declare class HistoryState { - #private; - constructor(send: (command: TerminalCommand) => void, change?: () => void); - get viewport(): HistoryViewport; - get selection(): HistorySelectionState; - accept(history: HistoryMetadata | null, revision: number): boolean; - begin(point: TerminalPoint, { mode, extend }: { - mode: SelectionMode; - extend?: boolean; - }): void; - extend(point: TerminalPoint): void; - scroll(delta: number, endpoint?: TerminalPoint): void; - live(): void; - clear(): void; - endGesture(cancelled?: boolean): void; - cancelCopy(error: unknown): void; - copy(): Promise; - disconnect(): void; -} -import type { SelectionMode, TerminalPoint, TerminalSelection, TerminalViewport } from "./types.js"; -import type { HistoryMetadata, TerminalCommand } from "./wire-types.js"; -type OmitEach = T extends unknown ? Omit : never; -type HistoryViewport = OmitEach; -type HistorySelectionState = OmitEach; -export {}; -//# sourceMappingURL=history-state.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts.map deleted file mode 100644 index deb455afaa7..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"history-state.d.ts","sourceRoot":"","sources":["../src/history-state.ts"],"names":[],"mappings":"AAIA,mGAAmG;AACnG,qBAAa,YAAY;;gBAaX,IAAI,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,EAAE,MAAM,GAAE,MAAM,IAAe;IAKnF,IAAI,QAAQ,IAAI,eAAe,CAK9B;IAED,IAAI,SAAS,IAAI,qBAAqB,CAYrC;IAED,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAuDlE,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,MAAc,EAAE,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IAStG,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAalC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,GAAG,IAAI;IAcrD,IAAI;IAQJ,KAAK;IAQL,UAAU,CAAC,SAAS,UAAQ;IAc5B,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAEhC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBvB,UAAU;CAIX;AACD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACpG,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAExE,KAAK,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AACjF,KAAK,eAAe,GAAG,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,QAAQ,CAAC,CAAC;AAC3E,KAAK,qBAAqB,GAAG,QAAQ,CAAC,iBAAiB,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC,CAAC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js deleted file mode 100644 index a625549f08e..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js +++ /dev/null @@ -1,194 +0,0 @@ -const unavailable = "Text history is not available for this terminal view"; -const expired = "Selection expired: its text was changed, evicted, reset, or resized. Select the text again."; -const changedBeforeCopy = "Selection changed before copy completed. Copy the new selection again."; -/** Commands contain producer identities; text and ranges are never inferred from painted cells. */ -export class HistoryState { - #send; - #change; - #history = null; - #revision = 0; - #nextRequest = 0; - #selectionRequest = 0; - #viewportRequest = 0; - #pendingMode = "character"; - #pendingAction = "clear"; - #pendingCopy; - #deferredEndpoint; - constructor(send, change = () => { }) { - this.#send = send; - this.#change = change; - } - get viewport() { - if (!this.#history) - return { available: false, following: true, pending: false }; - const { selection, copy, ...viewport } = this.#history; - return { ...viewport, rowIds: [...viewport.rowIds], available: true, revision: this.#revision, - pending: viewport.requestId < this.#viewportRequest }; - } - get selection() { - if (!this.#history) - return { status: "unavailable", mode: "character", ranges: [], text: null, message: unavailable }; - const selection = this.#history.selection; - if (selection.requestId < this.#selectionRequest || this.#deferredEndpoint) { - return { requestId: this.#selectionRequest, status: "pending", mode: this.#pendingMode, - canExtend: this.#pendingAction !== "clear", - ranges: this.#pendingAction === "clear" ? [] : selection.ranges.map(range => ({ ...range })), text: null, - message: "Resolving selection…" }; - } - return { ...selection, canExtend: selection.status === "valid", - ranges: selection.ranges.map(range => ({ ...range })), revision: this.#revision, - message: selection.status === "invalidated" ? expired : "" }; - } - accept(history, revision) { - if (revision <= this.#revision) - return false; - const previousGeneration = this.#history?.generation; - this.#history = history; - this.#revision = revision; - if (!history || (previousGeneration && previousGeneration !== history.generation) || - history.selection.status === "invalidated") { - this.endGesture(true); - this.#rejectCopy(new Error(expired)); - } - const pending = this.#pendingCopy; - if (pending && history?.copy?.requestId === pending.requestId) { - if (history.copy.status !== "valid" || history.selection.status !== "valid" || - history.selection.requestId !== pending.selectionRequestId || - history.generation !== pending.generation || this.#selectionRequest > pending.selectionRequestId || - history.copy.text !== history.selection.text) { - this.#rejectCopy(new Error(expired)); - } - else { - clearTimeout(pending.timer); - this.#pendingCopy = undefined; - pending.resolve(history.copy.text); - } - } - if (this.#deferredEndpoint && !this.viewport.pending) { - const point = this.#deferredEndpoint; - this.#deferredEndpoint = undefined; - this.extend(point); - } - this.#change(); - return true; - } - #requireHistory() { - if (!this.#history) - throw new Error(unavailable); - return this.#history; - } - #pointRowId(point) { - const history = this.#requireHistory(); - if (!point || !Number.isInteger(point.x) || point.x < 0 || point.x > 1023 || - !Number.isInteger(point.y) || point.y < 0 || - typeof history.rowIds[point.y] !== "string" || !history.rowIds[point.y].length) { - throw new Error("Terminal viewport is not ready for that selection point. Wait for the next frame and try again."); - } - return history.rowIds[point.y]; - } - #selectionChanged(mode = this.selection.mode, action = "extend") { - this.#rejectCopy(new Error(changedBeforeCopy)); - this.#pendingMode = mode; - this.#pendingAction = action; - this.#selectionRequest = ++this.#nextRequest; - return this.#selectionRequest; - } - begin(point, { mode, extend = false }) { - const rowId = this.#pointRowId(point); - this.#deferredEndpoint = undefined; - const requestId = this.#selectionChanged(mode, extend ? "extend" : "start"); - this.#send({ type: "selection", action: extend ? "extend" : "start", mode, requestId, - generation: this.#requireHistory().generation, rowId, column: point.x }); - this.#change(); - } - extend(point) { - this.#pointRowId(point); - if (this.viewport.pending) { - this.#rejectCopy(new Error(changedBeforeCopy)); - this.#pendingMode = this.selection.mode; - this.#pendingAction = "extend"; - this.#deferredEndpoint = { ...point }; - this.#change(); - return; - } - this.begin(point, { mode: this.selection.mode, extend: true }); - } - scroll(delta, endpoint) { - this.#requireHistory(); - if (!Number.isSafeInteger(delta) || delta < -2147483648 || delta > 2147483647) - throw new RangeError("Scroll delta must be a signed 32-bit integer"); - if (endpoint) - this.#pointRowId(endpoint); - if (!delta && !endpoint) - return; - if (endpoint) - this.#deferredEndpoint = undefined; - else - this.#flushDeferredEndpoint(); - const requestId = endpoint ? this.#selectionChanged() : ++this.#nextRequest; - this.#viewportRequest = requestId; - this.#send({ type: "viewport", requestId, delta, - ...(endpoint ? { extend: { row: endpoint.y, column: endpoint.x } } : {}) }); - this.#change(); - } - live() { - this.#requireHistory(); - this.#flushDeferredEndpoint(); - this.#viewportRequest = ++this.#nextRequest; - this.#send({ type: "viewport", live: true, requestId: this.#viewportRequest }); - this.#change(); - } - clear() { - this.#requireHistory(); - this.#deferredEndpoint = undefined; - const requestId = this.#selectionChanged(this.selection.mode, "clear"); - this.#send({ type: "selection", action: "clear", requestId }); - this.#change(); - } - endGesture(cancelled = false) { - if (cancelled && this.#deferredEndpoint) { - this.#deferredEndpoint = undefined; - this.#change(); - } - } - #flushDeferredEndpoint() { - if (!this.#deferredEndpoint) - return; - const endpoint = this.#deferredEndpoint; - this.#deferredEndpoint = undefined; - // Preserve command order if another scroll overtakes the pending presentation. - this.scroll(0, endpoint); - } - cancelCopy(error) { this.#rejectCopy(error); } - copy() { - const history = this.#requireHistory(); - const selection = this.selection; - if (selection.status !== "valid") - throw new Error(selection.message || "Select text before copying."); - this.#rejectCopy(new Error("A newer copy request replaced this request.")); - const requestId = ++this.#nextRequest; - const { promise, resolve, reject } = Promise.withResolvers(); - const timer = setTimeout(() => this.#rejectCopy(new Error("Timed out resolving selection. Copy again.")), 10000); - this.#pendingCopy = { requestId, selectionRequestId: selection.requestId, generation: history.generation, - resolve, reject, timer }; - try { - this.#send({ type: "copy", requestId, selectionRequestId: selection.requestId, generation: history.generation }); - } - catch (error) { - this.#rejectCopy(error); - } - return promise; - } - #rejectCopy(error) { - if (!this.#pendingCopy) - return; - clearTimeout(this.#pendingCopy.timer); - this.#pendingCopy.reject(error); - this.#pendingCopy = undefined; - } - disconnect() { - this.#rejectCopy(new Error("Terminal view disconnected before copy completed.")); - this.#deferredEndpoint = undefined; - } -} -//# sourceMappingURL=history-state.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js.map deleted file mode 100644 index a2236812fc1..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/history-state.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"history-state.js","sourceRoot":"","sources":["../src/history-state.ts"],"names":[],"mappings":"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","sourcesContent":["const unavailable = \"Text history is not available for this terminal view\";\nconst expired = \"Selection expired: its text was changed, evicted, reset, or resized. Select the text again.\";\nconst changedBeforeCopy = \"Selection changed before copy completed. Copy the new selection again.\";\n\n/** Commands contain producer identities; text and ranges are never inferred from painted cells. */\nexport class HistoryState {\n #send: (command: TerminalCommand) => void;\n #change: () => void;\n #history: HistoryMetadata | null = null;\n #revision = 0;\n #nextRequest = 0;\n #selectionRequest = 0;\n #viewportRequest = 0;\n #pendingMode: SelectionMode = \"character\";\n #pendingAction = \"clear\";\n #pendingCopy: PendingCopy | undefined;\n #deferredEndpoint: TerminalPoint | undefined;\n\n constructor(send: (command: TerminalCommand) => void, change: () => void = () => {}) {\n this.#send = send;\n this.#change = change;\n }\n\n get viewport(): HistoryViewport {\n if (!this.#history) return { available: false, following: true, pending: false };\n const { selection, copy, ...viewport } = this.#history;\n return { ...viewport, rowIds: [...viewport.rowIds], available: true, revision: this.#revision,\n pending: viewport.requestId < this.#viewportRequest };\n }\n\n get selection(): HistorySelectionState {\n if (!this.#history) return { status: \"unavailable\", mode: \"character\", ranges: [], text: null, message: unavailable };\n const selection = this.#history.selection;\n if (selection.requestId < this.#selectionRequest || this.#deferredEndpoint) {\n return { requestId: this.#selectionRequest, status: \"pending\", mode: this.#pendingMode,\n canExtend: this.#pendingAction !== \"clear\",\n ranges: this.#pendingAction === \"clear\" ? [] : selection.ranges.map(range => ({ ...range })), text: null,\n message: \"Resolving selection…\" };\n }\n return { ...selection, canExtend: selection.status === \"valid\",\n ranges: selection.ranges.map(range => ({ ...range })), revision: this.#revision,\n message: selection.status === \"invalidated\" ? expired : \"\" };\n }\n\n accept(history: HistoryMetadata | null, revision: number): boolean {\n if (revision <= this.#revision) return false;\n const previousGeneration = this.#history?.generation;\n this.#history = history;\n this.#revision = revision;\n if (!history || (previousGeneration && previousGeneration !== history.generation) ||\n history.selection.status === \"invalidated\") {\n this.endGesture(true);\n this.#rejectCopy(new Error(expired));\n }\n const pending = this.#pendingCopy;\n if (pending && history?.copy?.requestId === pending.requestId) {\n if (history.copy.status !== \"valid\" || history.selection.status !== \"valid\" ||\n history.selection.requestId !== pending.selectionRequestId ||\n history.generation !== pending.generation || this.#selectionRequest > pending.selectionRequestId ||\n history.copy.text !== history.selection.text) {\n this.#rejectCopy(new Error(expired));\n } else {\n clearTimeout(pending.timer);\n this.#pendingCopy = undefined;\n pending.resolve(history.copy.text);\n }\n }\n if (this.#deferredEndpoint && !this.viewport.pending) {\n const point = this.#deferredEndpoint;\n this.#deferredEndpoint = undefined;\n this.extend(point);\n }\n this.#change();\n return true;\n }\n\n #requireHistory(): HistoryMetadata {\n if (!this.#history) throw new Error(unavailable);\n return this.#history;\n }\n\n #pointRowId(point: TerminalPoint): string {\n const history = this.#requireHistory();\n if (!point || !Number.isInteger(point.x) || point.x < 0 || point.x > 1023 ||\n !Number.isInteger(point.y) || point.y < 0 ||\n typeof history.rowIds[point.y] !== \"string\" || !history.rowIds[point.y].length) {\n throw new Error(\"Terminal viewport is not ready for that selection point. Wait for the next frame and try again.\");\n }\n return history.rowIds[point.y];\n }\n\n #selectionChanged(mode = this.selection.mode, action = \"extend\") {\n this.#rejectCopy(new Error(changedBeforeCopy));\n this.#pendingMode = mode;\n this.#pendingAction = action;\n this.#selectionRequest = ++this.#nextRequest;\n return this.#selectionRequest;\n }\n\n begin(point: TerminalPoint, { mode, extend = false }: { mode: SelectionMode; extend?: boolean }): void {\n const rowId = this.#pointRowId(point);\n this.#deferredEndpoint = undefined;\n const requestId = this.#selectionChanged(mode, extend ? \"extend\" : \"start\");\n this.#send({ type: \"selection\", action: extend ? \"extend\" : \"start\", mode, requestId,\n generation: this.#requireHistory().generation, rowId, column: point.x });\n this.#change();\n }\n\n extend(point: TerminalPoint): void {\n this.#pointRowId(point);\n if (this.viewport.pending) {\n this.#rejectCopy(new Error(changedBeforeCopy));\n this.#pendingMode = this.selection.mode;\n this.#pendingAction = \"extend\";\n this.#deferredEndpoint = { ...point };\n this.#change();\n return;\n }\n this.begin(point, { mode: this.selection.mode, extend: true });\n }\n\n scroll(delta: number, endpoint?: TerminalPoint): void {\n this.#requireHistory();\n if (!Number.isSafeInteger(delta) || delta < -2147483648 || delta > 2147483647) throw new RangeError(\"Scroll delta must be a signed 32-bit integer\");\n if (endpoint) this.#pointRowId(endpoint);\n if (!delta && !endpoint) return;\n if (endpoint) this.#deferredEndpoint = undefined;\n else this.#flushDeferredEndpoint();\n const requestId = endpoint ? this.#selectionChanged() : ++this.#nextRequest;\n this.#viewportRequest = requestId;\n this.#send({ type: \"viewport\", requestId, delta,\n ...(endpoint ? { extend: { row: endpoint.y, column: endpoint.x } } : {}) });\n this.#change();\n }\n\n live() {\n this.#requireHistory();\n this.#flushDeferredEndpoint();\n this.#viewportRequest = ++this.#nextRequest;\n this.#send({ type: \"viewport\", live: true, requestId: this.#viewportRequest });\n this.#change();\n }\n\n clear() {\n this.#requireHistory();\n this.#deferredEndpoint = undefined;\n const requestId = this.#selectionChanged(this.selection.mode, \"clear\");\n this.#send({ type: \"selection\", action: \"clear\", requestId });\n this.#change();\n }\n\n endGesture(cancelled = false) {\n if (cancelled && this.#deferredEndpoint) {\n this.#deferredEndpoint = undefined;\n this.#change();\n }\n }\n\n #flushDeferredEndpoint() {\n if (!this.#deferredEndpoint) return;\n const endpoint = this.#deferredEndpoint;\n this.#deferredEndpoint = undefined;\n // Preserve command order if another scroll overtakes the pending presentation.\n this.scroll(0, endpoint);\n }\n cancelCopy(error: unknown): void { this.#rejectCopy(error); }\n\n copy(): Promise {\n const history = this.#requireHistory();\n const selection = this.selection;\n if (selection.status !== \"valid\") throw new Error(selection.message || \"Select text before copying.\");\n this.#rejectCopy(new Error(\"A newer copy request replaced this request.\"));\n const requestId = ++this.#nextRequest;\n const { promise, resolve, reject } = Promise.withResolvers();\n const timer = setTimeout(() => this.#rejectCopy(new Error(\"Timed out resolving selection. Copy again.\")), 10000);\n this.#pendingCopy = { requestId, selectionRequestId: selection.requestId, generation: history.generation,\n resolve, reject, timer };\n try {\n this.#send({ type: \"copy\", requestId, selectionRequestId: selection.requestId, generation: history.generation });\n } catch (error) {\n this.#rejectCopy(error);\n }\n return promise;\n }\n\n #rejectCopy(error: unknown): void {\n if (!this.#pendingCopy) return;\n clearTimeout(this.#pendingCopy.timer);\n this.#pendingCopy.reject(error);\n this.#pendingCopy = undefined;\n }\n\n disconnect() {\n this.#rejectCopy(new Error(\"Terminal view disconnected before copy completed.\"));\n this.#deferredEndpoint = undefined;\n }\n}\nimport type { SelectionMode, TerminalPoint, TerminalSelection, TerminalViewport } from \"./types.js\";\nimport type { HistoryMetadata, TerminalCommand } from \"./wire-types.js\";\n\ntype OmitEach = T extends unknown ? Omit : never;\ntype HistoryViewport = OmitEach;\ntype HistorySelectionState = OmitEach;\ninterface PendingCopy {\n requestId: number; selectionRequestId: number; generation: string;\n resolve: (text: string) => void; reject: (error: unknown) => void;\n timer: ReturnType;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts deleted file mode 100644 index a14995dac28..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { TerminalPoint } from "./types.js"; -import type { HyperlinkRange } from "./wire-types.js"; -/** OSC 8 destinations are untrusted output, not page-relative navigation. */ -export declare function hyperlinkUri(uri: string): string | null; -export declare class Hyperlinks { - #private; - update(ranges: readonly HyperlinkRange[]): void; - at(point: TerminalPoint): string | null; -} -//# sourceMappingURL=hyperlinks.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts.map deleted file mode 100644 index 2fe5440ec12..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hyperlinks.d.ts","sourceRoot":"","sources":["../src/hyperlinks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIvD;AAED,qBAAa,UAAU;;IAGrB,MAAM,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAa/C,EAAE,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI;CAIxC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js deleted file mode 100644 index df786e6b4c7..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js +++ /dev/null @@ -1,28 +0,0 @@ -/** OSC 8 destinations are untrusted output, not page-relative navigation. */ -export function hyperlinkUri(uri) { - if (/[\u0000-\u0020\u007f]/u.test(uri) || !URL.canParse(uri)) - return null; - const url = new URL(uri); - return ["https:", "http:", "mailto:"].includes(url.protocol) ? url.href : null; -} -export class Hyperlinks { - #rows = new Map(); - update(ranges) { - this.#rows.clear(); - const destinations = new Map(); - for (const range of ranges) { - if (!destinations.has(range.uri)) - destinations.set(range.uri, hyperlinkUri(range.uri)); - const uri = destinations.get(range.uri); - if (!uri) - continue; - const row = this.#rows.get(range.row) ?? []; - row.push({ ...range, uri }); - this.#rows.set(range.row, row); - } - } - at(point) { - return this.#rows.get(point.y)?.find(range => point.x >= range.startColumn && point.x < range.endColumn)?.uri ?? null; - } -} -//# sourceMappingURL=hyperlinks.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js.map deleted file mode 100644 index 8907ef54eb7..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/hyperlinks.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"hyperlinks.js","sourceRoot":"","sources":["../src/hyperlinks.ts"],"names":[],"mappings":"AAGA,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACjF,CAAC;AAED,MAAM,OAAO,UAAU;IACrB,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAE5C,MAAM,CAAC,MAAiC;QACtC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;QACtD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;gBAAE,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;YACvF,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,EAAE,CAAC,KAAoB;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAC3C,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC;IAC5E,CAAC;CACF","sourcesContent":["import type { TerminalPoint } from \"./types.js\";\nimport type { HyperlinkRange } from \"./wire-types.js\";\n\n/** OSC 8 destinations are untrusted output, not page-relative navigation. */\nexport function hyperlinkUri(uri: string): string | null {\n if (/[\\u0000-\\u0020\\u007f]/u.test(uri) || !URL.canParse(uri)) return null;\n const url = new URL(uri);\n return [\"https:\", \"http:\", \"mailto:\"].includes(url.protocol) ? url.href : null;\n}\n\nexport class Hyperlinks {\n #rows = new Map();\n\n update(ranges: readonly HyperlinkRange[]): void {\n this.#rows.clear();\n const destinations = new Map();\n for (const range of ranges) {\n if (!destinations.has(range.uri)) destinations.set(range.uri, hyperlinkUri(range.uri));\n const uri = destinations.get(range.uri);\n if (!uri) continue;\n const row = this.#rows.get(range.row) ?? [];\n row.push({ ...range, uri });\n this.#rows.set(range.row, row);\n }\n }\n\n at(point: TerminalPoint): string | null {\n return this.#rows.get(point.y)?.find(range =>\n point.x >= range.startColumn && point.x < range.endColumn)?.uri ?? null;\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts deleted file mode 100644 index fbc53840e57..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { WebTerminal } from "./web-terminal.js"; -export { InputRoute, TerminalAction, defaultInputBindings } from "./input-policy.js"; -export { MIN_FONT_SIZE, MAX_FONT_SIZE } from "./terminal-sizing.js"; -export { parseCommandMarkParameters, getCmdlineUrl } from "./command-mark.js"; -export { linkAction } from "./link-options.js"; -export type * from "./types.js"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts.map deleted file mode 100644 index f7076cc5fda..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,mBAAmB,YAAY,CAAC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js deleted file mode 100644 index e01d1630aa9..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js +++ /dev/null @@ -1,6 +0,0 @@ -export { WebTerminal } from "./web-terminal.js"; -export { InputRoute, TerminalAction, defaultInputBindings } from "./input-policy.js"; -export { MIN_FONT_SIZE, MAX_FONT_SIZE } from "./terminal-sizing.js"; -export { parseCommandMarkParameters, getCmdlineUrl } from "./command-mark.js"; -export { linkAction } from "./link-options.js"; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js.map deleted file mode 100644 index 3e980691b6c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,0BAA0B,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export { WebTerminal } from \"./web-terminal.js\";\nexport { InputRoute, TerminalAction, defaultInputBindings } from \"./input-policy.js\";\nexport { MIN_FONT_SIZE, MAX_FONT_SIZE } from \"./terminal-sizing.js\";\nexport { parseCommandMarkParameters, getCmdlineUrl } from \"./command-mark.js\";\nexport { linkAction } from \"./link-options.js\";\nexport type * from \"./types.js\";\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.min.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.min.js new file mode 100644 index 00000000000..a631eab5d4e --- /dev/null +++ b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.min.js @@ -0,0 +1,2 @@ +// @hex1b/web-terminal 0.169.0-alpha.1608.1.d6a20d4; minified with Terser. See ../LICENSE. +var e=Object.defineProperty,t=Object.getOwnPropertyNames,i=(e,i,r)=>function(){if(r)throw r[0];try{return e&&(i=(0,e[t(e)[0]])(e=0)),i}catch(e){throw r=[e],e}};function r(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function n(e){return r(e)&&"string"==typeof e.message?e.message:String(e)}var s=i({"src/validation.ts"(){}});function o(){if("function"==typeof crypto.randomUUID)return crypto.randomUUID();const e=crypto.getRandomValues(new Uint8Array(16));e[6]=15&e[6]|64,e[8]=63&e[8]|128;const t=Array.from(e,e=>e.toString(16).padStart(2,"0")).join("");return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}var a,l,c=i({"src/random-id.ts"(){}});function h(e,t=import.meta.url){if(e??={family:"Cascadia Mono NF",faces:[{url:a,weight:"200 700"}]},"object"!=typeof e||Array.isArray(e)||"string"!=typeof e.family||!e.family.trim()||e.family.length>256||/[\u0000-\u001f\u007f]/u.test(e.family))throw new TypeError("font.family must be a single, nonempty font family name");const i=e.faces??[];if(!Array.isArray(i)||i.length>16)throw new TypeError("font.faces must contain at most 16 font sources");return{family:e.family.trim(),faces:i.map(e=>{if(!e||"string"!=typeof e.url||!e.url.trim())throw new TypeError("Each font face requires a URL");for(const t of["weight","style"])if(void 0!==e[t]&&("string"!=typeof e[t]||e[t].length>64))throw new TypeError(`Font face ${t} must be a CSS descriptor string`);return{url:new URL(e.url,t).href,weight:e.weight??"400",style:e.style??"normal"}})}}async function d(e){const{family:t,faces:i}=h(e),n=globalThis,s="fonts"in n&&(r(o=n.fonts)&&"function"==typeof o.add&&"function"==typeof o.delete)?n.fonts:globalThis.document?.fonts;var o;if(!s||"function"!=typeof FontFace)throw new Error("Terminal font loading requires the CSS Font Loading API in the rendering context");const a=l.has(t.toLowerCase()),c=a?t.toLowerCase():JSON.stringify(t);if(a&&i.length)throw new TypeError("A generic font family cannot have downloadable faces");const d=i.length?i:a?[]:[{local:t}],u=[];for(const e of d)try{const i=new FontFace(t,"local"in e?`local(${JSON.stringify(e.local)})`:`url(${JSON.stringify(e.url)})`,{weight:e.weight??"400",style:e.style??"normal"});u.push(await i.load())}catch(i){throw new Error(`Could not load terminal font "${t}" from ${"url"in e?e.url:"local fonts"}`,{cause:i})}for(const e of u)s.add(e);return{family:t,cssFamily:c,dispose(){for(const e of u)s.delete(e)}}}var u=i({"src/terminal-font.ts"(){s(),a=new URL("./fonts/cascadia-mono-nf/CascadiaMonoNF.woff2",import.meta.url).href,l=new Set(["monospace","serif","sans-serif","system-ui"])}});function p(e="auto"){if("auto"===e||"webgpu"===e||"webgl2"===e)return e;throw new TypeError('renderer must be "auto", "webgpu", or "webgl2"')}var f,m=i({"src/renderer-options.ts"(){}});function g(e){return"string"==typeof e&&/^[1-9][0-9]{0,18}$/u.test(e)&&BigInt(e)<=9223372036854775807n}function w(e){if("string"!=typeof e||!/^(?:custom|command):[A-Za-z0-9-]{1,100}$/u.test(e))throw new TypeError("Invalid terminal marker ID")}var y,v,b,k=i({"src/marker-state.ts"(){c(),f=class{#e;#t;#i=Object.freeze([]);#r=new Map;#n=[];#s=0;#o=0;#a=!1;constructor(e,t){this.#e=e,this.#t=t}get markers(){return this.#i}accept(e,t){if(t<=this.#o)return;this.#o=t;const i=(e?.markers??[]).map(e=>Object.freeze({...e,...this.#r.get(e.id)})),r=!this.#a||JSON.stringify(i)!==JSON.stringify(this.#i);this.#a=!0,this.#i=Object.freeze(i);const n=e?.markerResult,s=this.#n[0];n&&s&&n.requestId===s.command.requestId&&(this.#n.shift(),clearTimeout(s.timer),n.success?s.resolve(n):s.reject(new Error(n.error||"Marker operation failed")),this.#l());const o=new Set(i.map(e=>e.id));for(const e of this.#n)"add"===e.command.action&&o.add(e.command.id);for(const e of this.#r.keys())o.has(e)||this.#r.delete(e);r&&this.#t()}#c(e){const t=Promise.withResolvers();return this.#n.push({command:{type:"marker",...e,requestId:++this.#s},resolve:t.resolve,reject:t.reject}),1===this.#n.length&&this.#l(),t.promise}#l(){const e=this.#n[0];if(e)try{this.#e(e.command),e.timer=setTimeout(()=>{this.#h(new Error("Timed out waiting for the marker operation; resync before retrying."))},1e4)}catch(t){this.#n.shift(),e.reject(t instanceof Error?t:new Error(String(t))),this.#l()}}async add(e){const t=e?.position;if(!t||!g(t.generation)||!g(t.rowId)||!Number.isInteger(t.column)||t.column<0||t.column>1024)throw new TypeError("A marker requires a valid presented generation, row ID, and column");if(void 0!==e.label&&("string"!=typeof e.label||e.label.length>4096))throw new TypeError("Marker label must be at most 4096 characters");if(void 0!==e.color&&("string"!=typeof e.color||e.color.length>256||!CSS.supports("color",e.color)))throw new TypeError("Marker color must be a CSS color");const i=`custom:${o()}`;this.#r.set(i,{...void 0===e.label?{}:{label:e.label},...void 0===e.color?{}:{color:e.color}});try{await this.#c({action:"add",id:i,...t});const e=this.#i.find(e=>e.id===i);if(!e)throw new Error("Marker registration succeeded without a retained marker");return e}catch(e){throw this.#r.delete(i),e}}async remove(e){w(e),await this.#c({action:"remove",id:e}),this.#r.delete(e)}async jump(e){w(e),await this.#c({action:"jump",id:e})}async details(e){w(e);const t=await this.#c({action:"details",id:e});if(!t.details)throw new Error("Command marker details were not returned");return Object.freeze({...t.details})}disconnect(e=new Error("Terminal disconnected before the marker operation completed.")){this.#h(e),this.#r.clear()}#h(e){for(const t of this.#n.splice(0))clearTimeout(t.timer),t.reject(e)}}}});function x(e,t,i=0,r=Number.MAX_SAFE_INTEGER){if("number"!=typeof e||!Number.isSafeInteger(e)||er)throw new Error(`Invalid ${t}: ${String(e)}`);return e}function E(e,t,i){if(!Array.isArray(e)||e.length>i)throw new Error(`Invalid ${t}`)}function T(e){if("string"!=typeof e||!e.length||e.length>1024)throw new Error("Invalid image key")}function C(e,t){if("string"!=typeof e||!/^[1-9][0-9]{0,18}$/u.test(e)||BigInt(e)>9223372036854775807n)throw new Error(`Invalid ${t}`)}function S(e){if(!r(e)||"string"!=typeof e.phase||!["unknown","prompt","commandLine","executing","finished"].includes(e.phase))throw new Error("Invalid terminal command mark");if(null!==e.exitCode&&(x(e.exitCode,"terminal command mark exit code",-2147483648,2147483647),"finished"!==e.phase))throw new Error("Non-finished terminal command mark has an exit code");if(null!==e.rawParameters&&("string"!=typeof e.rawParameters||e.rawParameters.length>y.commandMarkParameterUnits))throw new Error("Invalid terminal command mark parameters")}function I(e){if("string"!=typeof e.status||!["none","valid","invalidated"].includes(e.status)||("valid"===e.status?"string"!=typeof e.text||e.text.length>y.metadataBytes:null!==e.text))throw new Error("Invalid selection text")}function M(e){if(!r(e)||1!==e.version||"boolean"!=typeof e.full)throw new Error("Unsupported frame metadata version");if(x(e.revision,"revision",1),x(e.baseRevision,"base revision"),"string"!=typeof e.title||e.title.length>y.titleUnits||/[\u0000-\u001f\u007f-\u009f\ud800-\udfff]/u.test(e.title))throw new Error("Invalid terminal title");const t=e.progress;if(!r(t)||"string"!=typeof t.state||!["none","normal","error","indeterminate","warning"].includes(t.state))throw new Error("Invalid terminal progress");if("none"===t.state||"indeterminate"===t.state){if(null!==t.percentage)throw new Error("Unexpected terminal progress percentage")}else x(t.percentage,"terminal progress percentage",0,100);const i=e.shellIntegration;if(!r(i)||"string"!=typeof i.phase||!["unknown","prompt","commandLine","executing","finished"].includes(i.phase))throw new Error("Invalid terminal shell integration");if(null!==i.lastExitCode&&x(i.lastExitCode,"terminal shell exit code",-2147483648,2147483647),"unknown"===i.phase&&null!==i.lastExitCode)throw new Error("Unknown terminal shell phase has an exit code");const n=e.workingDirectory;if(!r(n))throw new Error("Invalid terminal working directory");const s=[n.uri,n.host,n.path];if(s.every(e=>null===e));else if(s.some(e=>"string"!=typeof e||e.length>y.metadataBytes))throw new Error("Invalid terminal working directory");const o=e.commandMark;null!==o&&S(o);const a=x(e.columns,"columns",1,1024),l=x(e.rows,"rows",1,512);if("number"!=typeof e.mouseTracking||![0,9,1e3,1002,1003].includes(e.mouseTracking))throw new Error("Unsupported mouse tracking mode");if(!r(e.peer)||"boolean"!=typeof e.peer.isPrimary)throw new Error("Invalid peer state");for(const t of["id","primaryId"]){const i=e.peer[t];if(null!==i&&("string"!=typeof i||!i.length||i.length>256))throw new Error(`Invalid peer ${t}`)}if(null!==e.peer.id&&e.peer.isPrimary!==(e.peer.id===e.peer.primaryId))throw new Error("Inconsistent primary peer state");x(a*l,"cell count",1,y.cells),function(e,t,i){if(null===e)return;if(!r(e))throw new Error("Missing history metadata");if(C(e.generation,"history generation"),"main"!==e.buffer&&"alternate"!==e.buffer)throw new Error("Invalid history buffer");const n=x(e.totalRows,"history total rows",i,2147483647),s=x(e.liveTop,"history live top",0,n-i);if(s!==n-i)throw new Error("Inconsistent history extent");if(x(e.top,"history top",0,s),"boolean"!=typeof e.following||e.following&&e.top!==e.liveTop)throw new Error("Invalid history following state");if(x(e.requestId,"viewport request id"),void 0!==e.viewportError&&null!==e.viewportError&&("string"!=typeof e.viewportError||e.viewportError.length>4096))throw new Error("Invalid viewport error");if(E(e.rowIds,"viewport row ids",i),e.rowIds.length!==i||new Set(e.rowIds).size!==i)throw new Error("Invalid viewport row ids");for(const t of e.rowIds)C(t,"viewport row id");const o=e.selection;if(!r(o)||"string"!=typeof o.status||!["none","valid","invalidated"].includes(o.status))throw new Error("Invalid selection status");if(x(o.requestId,"selection request id"),"string"!=typeof o.mode||!["character","word","line","rectangle"].includes(o.mode))throw new Error("Invalid selection mode");E(o.ranges,"selection ranges",i);let a=-1;for(const e of o.ranges){if(!r(e))throw new Error("Invalid selection range");const n=x(e.row,"selection range row",a+1,i-1),s=x(e.startColumn,"selection start column",0,t-1);x(e.endColumn,"selection end column",s+1,t),a=n}if(I(o),"valid"!==o.status&&o.ranges.length)throw new Error("Inactive selection has highlight ranges");if(null!==e.copy){if(!r(e.copy))throw new Error("Missing copy metadata");x(e.copy.requestId,"copy request id",1),I(e.copy)}if(void 0!==e.markers){E(e.markers,"history markers",y.metadataBytes/48);const t=new Set;for(const i of e.markers){if(!r(i)||"string"!=typeof i.id)throw new Error("Invalid history marker");if(w(i.id),t.has(i.id))throw new Error("Duplicate history marker ID");if(t.add(i.id),"command"!==i.source&&"custom"!==i.source)throw new Error("Invalid marker source");if(!i.id.startsWith(`${i.source}:`))throw new Error("Marker source disagrees with its ID");if("main"!==i.buffer&&"alternate"!==i.buffer)throw new Error("Invalid marker buffer");if(x(i.column,"marker column",0,1024),null!==i.row&&(x(i.row,"marker row",0,n-1),i.buffer!==e.buffer))throw new Error("Inactive-buffer marker has a resolved row");if("command"===i.source)S({...i,rawParameters:null});else if(void 0!==i.phase&&null!==i.phase)throw new Error("Custom marker has a shell phase")}}if(void 0!==e.markerPage&&null!==e.markerPage){const t=e.markerPage;if(!r(t)||"string"!=typeof t.revision||!t.revision.length||t.revision.length>64)throw new Error("Invalid marker page revision");const i=x(t.total,"marker inventory count",1,Math.floor(y.metadataBytes/48)),n=x(t.offset,"marker page offset",0,i-1);if(!Array.isArray(e.markers)||!e.markers.length||n+e.markers.length>i)throw new Error("Invalid marker page length")}if(void 0!==e.markerResult&&null!==e.markerResult){const t=e.markerResult;if(!r(t)||"boolean"!=typeof t.success)throw new Error("Invalid marker result");if(x(t.requestId,"marker request ID",1),void 0!==t.error&&null!==t.error&&("string"!=typeof t.error||t.error.length>4096))throw new Error("Invalid marker error");if(void 0!==t.markerId&&null!==t.markerId){if("string"!=typeof t.markerId)throw new Error("Invalid result marker ID");w(t.markerId)}void 0!==t.details&&null!==t.details&&S(t.details)}}(e.history,a,l),E(e.hyperlinks,"hyperlinks",a*l);let c=0;for(const t of e.hyperlinks){if(!r(t))throw new Error("Invalid hyperlink");const e=x(t.row,"hyperlink row",0,l-1),i=x(t.startColumn,"hyperlink start column",0,a-1),n=x(t.endColumn,"hyperlink end column",i+1,a);if(e*a+iy.metadataBytes)throw new Error("Invalid hyperlink URI")}if(10!==e.cellWidth||20!==e.cellHeight)throw new Error("This spike requires server geometry of 10 × 20 logical pixels");for(const t of["defaultBackground","defaultForeground"])void 0!==e[t]&&x(e[t],t,0,4294967295);if(!r(e.cursor)||"boolean"!=typeof e.cursor.visible)throw new Error("Invalid cursor");x(e.cursor.x,"cursor x",-1,1024),x(e.cursor.y,"cursor y",-1,512);if("string"==typeof e.cursor.shape&&(e.cursor.shape=["Default","BlinkingBlock","SteadyBlock","BlinkingUnderline","SteadyUnderline","BlinkingBar","SteadyBar"].indexOf(e.cursor.shape)),x(e.cursor.shape,"cursor shape",0,6),E(e.images,"images",y.images),E(e.retainedImages,"retained image keys",y.images),E(e.placements,"placements",y.placements),E(e.warnings,"warnings",256),e.warnings.some(e=>"string"!=typeof e))throw new Error("Invalid warning");if(!r(e.stats))throw new Error("Invalid server metrics");for(const t of["workloadBytes","outputBatches","captureMs","elapsedMs"]){const i=e.stats[t];if("number"!=typeof i||!Number.isFinite(i)||i<0)throw new Error(`Invalid server metric ${t}`)}const h=new Set;for(const t of e.retainedImages){if(T(t),h.has(t))throw new Error("Duplicate retained image key");h.add(t)}const d=new Set;let u=0;for(const t of e.images){if(!r(t))throw new Error("Invalid image");if(T(t.key),d.has(t.key)||!h.has(t.key))throw new Error("Inconsistent new image keys");d.add(t.key);const e=x(t.width,"image width",1,16384),i=x(t.height,"image height",1,16384),n=x(t.byteLength,"image byte length",1,y.frameBytes);if("rgba"!==t.format&&"png"!==t.format)throw new Error("Unsupported image format");if("rgba"===t.format&&n!==e*i*4)throw new Error("RGBA image size mismatch");if(u+=e*i*4,u>y.textureBytes)throw new Error("New images exceed decoded texture budget")}for(const t of e.placements){if(!r(t))throw new Error("Invalid placement");if(T(t.key),!h.has(t.key))throw new Error("Placement references an unretained image");if("kgp"!==t.kind&&"sixel"!==t.kind)throw new Error("Invalid placement kind");for(const e of["x","y","width","height","sourceX","sourceY","sourceWidth","sourceHeight","clipX","clipY","clipWidth","clipHeight","z"]){const i=t[e];if("number"!=typeof i||!Number.isFinite(i)||Math.abs(i)>2147483648)throw new Error(`Invalid placement ${e}`)}for(const e of["width","height","sourceWidth","sourceHeight","clipWidth","clipHeight"]){const i=t[e];if("number"!=typeof i||i<0)throw new Error(`Negative placement ${e}`)}}}var L,A=i({"src/protocol.ts"(){s(),k(),y=Object.freeze({commandBytes:65536,frameBytes:100663296,metadataBytes:8388608,titleUnits:4096,commandMarkParameterUnits:8192,cells:262144,images:4096,placements:16384,textureBytes:268435456}),v=new TextDecoder("utf-8",{fatal:!0}),b=new TextEncoder}});function R(e,t){if("function"!=typeof e&&("string"!=typeof e||!t.has(e)))throw new TypeError("Link action must be a registered custom action or callback")}function P(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function O(e,t){if(!1===e)return!1;if(void 0===e)return{};if(!P(e))throw new TypeError("Invalid links configuration");const i={};if(!1===e.osc8)i.osc8=!1;else if(void 0!==e.osc8){if(!P(e.osc8))throw new TypeError("Invalid OSC 8 configuration");R(e.osc8.action,t),i.osc8={action:e.osc8.action}}const r=e.detection;if(!1===r)i.detection=!1;else if(void 0!==r){if(!P(r)||!Array.isArray(r.rules))throw new TypeError("Invalid detection configuration");if(r.rules.length>L.rules)throw new TypeError("Too many link rules");if(void 0!==r.activation&&!["modifierClick","click"].includes(r.activation))throw new TypeError("Invalid link activation");if(void 0!==r.decoration&&!["always","hover","none"].includes(r.decoration))throw new TypeError("Invalid link decoration");if(void 0!==r.underlineStyle&&!["solid","dashed"].includes(r.underlineStyle))throw new TypeError("Invalid link underline style");const e=new Set,n=r.rules.map(i=>{if(!P(i)||"string"!=typeof i.id||!i.id.length||i.id.length>256||e.has(i.id))throw new TypeError("Link rule IDs must be nonempty and unique");if(e.add(i.id),void 0!==i.enabled&&"boolean"!=typeof i.enabled)throw new TypeError("Invalid enabled flag");if(void 0!==i.text&&!["physicalRow","logicalLine","viewport"].includes(i.text))throw new TypeError("Invalid link text mode");if(void 0!==i.resolve&&"function"!=typeof i.resolve)throw new TypeError("Invalid resolver");R(i.action,t);const r={id:i.id,action:i.action,enabled:i.enabled??!0,text:i.text??"logicalLine",resolve:i.resolve};if(void 0!==i.builtin){if(!["url","uri","absolutePath","homePath"].includes(i.builtin)||void 0!==i.pattern||void 0!==i.kind)throw new TypeError("Invalid builtin rule");return{...r,builtin:i.builtin}}if(!(i.pattern instanceof RegExp&&["uri","path","custom"].includes(i.kind)))throw new TypeError("Custom rules require a RegExp and kind");if(i.pattern.source.length>L.pattern||/[^dgimsuv]/u.test(i.pattern.flags))throw new TypeError("Unsupported regex flags or oversized pattern (sticky is not supported)");return{...r,kind:i.kind,pattern:new RegExp(i.pattern.source,i.pattern.flags)}});i.detection={rules:n,activation:r.activation??"modifierClick",decoration:r.decoration??"always",underlineStyle:r.underlineStyle??"solid"}}return i}function U(e){if("function"!=typeof e)throw new TypeError("Expected a link action callback");return(t,i,r)=>{if(!function(e){if(!P(e))return!1;const t=e;return!(!(["detected","osc8"].includes(t.source)&&["uri","path","custom"].includes(t.kind)&&"string"==typeof t.text&&"string"==typeof t.target&&Number.isSafeInteger(t.revision))||t.revision<0||!Array.isArray(t.ranges)||0===t.ranges.length)&&("osc8"===t.source?null===t.ruleId&&"uri"===t.kind:"string"==typeof t.ruleId&&0!==t.ruleId.length)&&t.ranges.every(e=>P(e)&&Number.isSafeInteger(e.row)&&e.row>=0&&Number.isSafeInteger(e.startColumn)&&e.startColumn>=0&&Number.isSafeInteger(e.endColumn)&&e.endColumn>e.startColumn)}(i))throw new TypeError("Expected a terminal link activation");return e(t,i,r)}}var z=i({"src/link-options.ts"(){L=Object.freeze({rules:32,pattern:8192,chunk:65536,totalText:262144,cells:262144,matches:2048,resultText:262144,cacheEntries:2048,cacheText:1048576,timeoutMs:250})}});function D(e){let t=e.text.length+16;for(const i of e.captures)t+=8+(i?.length??0);for(const[i,r]of Object.entries(e.groups))t+=8+i.length+(r?.length??0);return t}var B,q,N,j,$,F,G=i({"src/link-worker-protocol.ts"(){}}),W=i({"src/render-backend.ts"(){B=16,q=class extends Error{}}}),_=i({"src/webgpu-backend.ts"(){W(),N=class{constructor(e,t,i,r,n){this.device=e,this.texture=t,this.bindGroup=i,this.width=r,this.height=n}device;texture;bindGroup;width;height;writePixels(e,t,i,r=0,n=0){this.device.queue.writeTexture({texture:this.texture,origin:[r,n]},e,{bytesPerRow:4*t,rowsPerImage:i},[t,i])}writeBitmap(e){this.device.queue.copyExternalImageToTexture({source:e},{texture:this.texture,premultipliedAlpha:!1},[this.width,this.height])}destroy(){this.texture.destroy()}},j=class e{constructor(e){this.device=e,this.format=navigator.gpu.getPreferredCanvasFormat(),this.uniform=e.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST}),this.sampler=e.createSampler({minFilter:"linear",magFilter:"linear"})}device;kind="webgpu";instanceBufferBytes=0;instanceBuffer;disposed=!1;context;format;uniform;sampler;pipeline;static async create(t,i){if(!globalThis.isSecureContext)throw new q("WebGPU requires HTTPS or localhost");if(!navigator.gpu)throw new q("WebGPU is unavailable in this browser worker");const r=await navigator.gpu.requestAdapter();if(!r)throw new q("No WebGPU adapter is available");let n,s;try{n=await r.requestDevice()}catch(e){if(e instanceof DOMException&&"OperationError"===e.name)throw new q(`Could not acquire a WebGPU device: ${e.message}`,{cause:e});throw e}try{s=new e(n);const r=s;return n.lost.then(e=>{r.disposed||i(new Error(`WebGPU device lost: ${e.message||e.reason}`))}),n.addEventListener("uncapturederror",e=>{r.disposed||i(new Error(`WebGPU error: ${e.error.message}`))}),await s.initialize(t),s}catch(e){throw s?s.dispose():n.destroy(),e}}get maxTextureDimension2D(){return this.device.limits.maxTextureDimension2D}get maxCanvasDimension2D(){return this.maxTextureDimension2D}async initialize(e){const t=this.device.createShaderModule({code:"\nstruct Viewport { size: vec2f, padding: vec2f }\n@group(0) @binding(0) var viewport: Viewport;\n@group(0) @binding(1) var image: texture_2d;\n@group(0) @binding(2) var imageSampler: sampler;\n\nstruct VertexOut {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n @location(1) color: vec4f,\n @location(2) @interpolate(flat) mode: f32,\n}\n\n@vertex fn vertex(\n @builtin(vertex_index) index: u32,\n @location(0) rect: vec4f,\n @location(1) uvRect: vec4f,\n @location(2) color: vec4f,\n @location(3) mode: f32,\n) -> VertexOut {\n let corners = array(\n vec2f(0, 0), vec2f(1, 0), vec2f(0, 1),\n vec2f(0, 1), vec2f(1, 0), vec2f(1, 1)\n );\n let corner = corners[index];\n let position = rect.xy + corner * rect.zw;\n var out: VertexOut;\n out.position = vec4f(position / viewport.size * vec2f(2, -2) + vec2f(-1, 1), 0, 1);\n out.uv = mix(uvRect.xy, uvRect.zw, corner);\n out.color = color;\n out.mode = mode;\n return out;\n}\n\n@fragment fn fragment(in: VertexOut) -> @location(0) vec4f {\n // Explicit LOD avoids derivative-uniformity requirements across solid/mask/image batches.\n let texel = textureSampleLevel(image, imageSampler, in.uv, 0);\n if (in.mode < 0.5) { return in.color; }\n if (in.mode < 1.5) { return vec4f(in.color.rgb, in.color.a * texel.a); }\n return texel * in.color;\n}"});this.pipeline=await this.device.createRenderPipelineAsync({layout:"auto",vertex:{module:t,entryPoint:"vertex",buffers:[{arrayStride:4*B,stepMode:"instance",attributes:[{shaderLocation:0,offset:0,format:"float32x4"},{shaderLocation:1,offset:16,format:"float32x4"},{shaderLocation:2,offset:32,format:"float32x4"},{shaderLocation:3,offset:48,format:"float32"}]}]},fragment:{module:t,entryPoint:"fragment",targets:[{format:this.format,blend:{color:{srcFactor:"src-alpha",dstFactor:"one-minus-src-alpha"},alpha:{srcFactor:"one",dstFactor:"one-minus-src-alpha"}}}]},primitive:{topology:"triangle-list"}});const i=e.getContext("webgpu");if(!i)throw new q("Could not create an OffscreenCanvas WebGPU context");this.context=i,i.configure({device:this.device,format:this.format,alphaMode:"opaque"})}createTexture(e,t,i){if(e>this.maxTextureDimension2D||t>this.maxTextureDimension2D)throw new Error(`${i} exceeds the GPU texture dimension limit`);if(!this.pipeline)throw new Error("WebGPU backend is not initialized");const r=this.device.createTexture({label:i,size:[e,t],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT});try{const i=this.device.createBindGroup({layout:this.pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:this.uniform}},{binding:1,resource:r.createView()},{binding:2,resource:this.sampler}]});return new N(this.device,r,i,e,t)}catch(e){throw r.destroy(),e}}resize(e,t){this.device.queue.writeBuffer(this.uniform,0,new Float32Array([e,t,0,0]))}submit(e,t,i,r){if(this.disposed||!this.context||!this.pipeline)throw new Error("WebGPU backend is not initialized");const n=t*B*4;(!this.instanceBuffer||n>this.instanceBufferBytes)&&(this.instanceBuffer?.destroy(),this.instanceBufferBytes=Math.max(256,e.byteLength),this.instanceBuffer=this.device.createBuffer({size:this.instanceBufferBytes,usage:GPUBufferUsage.VERTEX|GPUBufferUsage.COPY_DST})),n&&this.device.queue.writeBuffer(this.instanceBuffer,0,e,0,t*B);const s=this.device.createCommandEncoder(),o=s.beginRenderPass({colorAttachments:[{view:this.context.getCurrentTexture().createView(),clearValue:{r:r[0],g:r[1],b:r[2],a:1},loadOp:"clear",storeOp:"store"}]});o.setPipeline(this.pipeline),o.setVertexBuffer(0,this.instanceBuffer);for(const e of i){if(!(e.resource instanceof N))throw new Error("Texture belongs to a different rendering backend");o.setBindGroup(0,e.resource.bindGroup),o.draw(6,e.count,0,e.start)}o.end(),this.device.queue.submit([s.finish()])}async idle(){await this.device.queue.onSubmittedWorkDone()}dispose(){this.disposed||(this.disposed=!0,this.instanceBuffer?.destroy(),this.uniform.destroy(),this.context?.unconfigure(),this.device.destroy())}}}});function H(e){return e instanceof Error?e:new Error(String(e))}function K(e){return Number.isSafeInteger(e)&&e>0}function V(e){e.pixelStorei(e.UNPACK_ALIGNMENT,1),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!1),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,e.NONE),e.pixelStorei(e.UNPACK_ROW_LENGTH,0),e.pixelStorei(e.UNPACK_SKIP_PIXELS,0),e.pixelStorei(e.UNPACK_SKIP_ROWS,0)}var X=i({"src/webgl2-backend.ts"(){W(),$=class{constructor(e,t,i,r,n,s,o){this.owner=e,this.gl=t,this.texture=i,this.width=r,this.height=n,this.assertActive=s,this.release=o}owner;gl;texture;width;height;assertActive;release;destroyed=!1;bind(){if(this.assertActive(),this.destroyed)throw new Error("WebGL2 texture is destroyed");this.gl.bindTexture(this.gl.TEXTURE_2D,this.texture),V(this.gl)}writePixels(e,t,i,r=0,n=0){if(!K(t)||!K(i)||!Number.isSafeInteger(r)||!Number.isSafeInteger(n)||r<0||n<0||r+t>this.width||n+i>this.height||e.byteLength{const t=e.statusMessage;this.contextLost(new Error("WebGL2 context lost"+(t?`: ${t}`:"")))};static async create(t,i){const r=t.getContext("webgl2",{alpha:!1,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!1,preserveDrawingBuffer:!1});if(!r)throw new q("Could not create an OffscreenCanvas WebGL2 context");const n=new e(t,r,i);try{return n.initialize(),n}catch(e){throw n.dispose(),e}}get maxTextureDimension2D(){return this.textureLimit}get maxCanvasDimension2D(){return this.canvasLimit}get instanceBufferBytes(){return this.bufferBytes}initialize(){const e=this.gl;this.canvas.addEventListener("webglcontextlost",this.onContextLost),this.checkErrors("initialization"),this.textureLimit=e.getParameter(e.MAX_TEXTURE_SIZE);const t=e.getParameter(e.MAX_VIEWPORT_DIMS);if(this.canvasLimit=Math.min(this.textureLimit,e.getParameter(e.MAX_RENDERBUFFER_SIZE),t[0],t[1]),!K(this.textureLimit)||!K(this.canvasLimit))throw new Error("Invalid WebGL2 texture or viewport limits");const i=this.compile(e.VERTEX_SHADER,"#version 300 es\nprecision highp float;\nprecision highp int;\n\nlayout(location = 0) in vec4 rect;\nlayout(location = 1) in vec4 uvRect;\nlayout(location = 2) in vec4 color;\nlayout(location = 3) in float mode;\nuniform vec2 viewport;\nout vec2 fragmentUv;\nout vec4 fragmentTint;\nflat out float fragmentMode;\n\nvoid main() {\n const vec2 corners[6] = vec2[6](\n vec2(0, 0), vec2(1, 0), vec2(0, 1),\n vec2(0, 1), vec2(1, 0), vec2(1, 1)\n );\n vec2 corner = corners[gl_VertexID];\n vec2 position = rect.xy + corner * rect.zw;\n gl_Position = vec4(position / viewport * vec2(2, -2) + vec2(-1, 1), 0, 1);\n fragmentUv = mix(uvRect.xy, uvRect.zw, corner);\n fragmentTint = color;\n fragmentMode = mode;\n}"),r=this.compile(e.FRAGMENT_SHADER,"#version 300 es\nprecision highp float;\nprecision highp int;\n\nuniform highp sampler2D image;\nin vec2 fragmentUv;\nin vec4 fragmentTint;\nflat in float fragmentMode;\nout vec4 fragmentColor;\n\nvoid main() {\n vec4 texel = textureLod(image, fragmentUv, 0.0);\n if (fragmentMode < 0.5) {\n fragmentColor = fragmentTint;\n } else if (fragmentMode < 1.5) {\n fragmentColor = vec4(fragmentTint.rgb, fragmentTint.a * texel.a);\n } else {\n fragmentColor = texel * fragmentTint;\n }\n}"),n=e.createProgram();if(!n)throw new Error("Could not allocate a WebGL2 shader program");if(this.program=n,e.attachShader(n,i),e.attachShader(n,r),e.linkProgram(n),!e.getProgramParameter(n,e.LINK_STATUS))throw new Error(`WebGL2 shader link failed: ${e.getProgramInfoLog(n)||"No diagnostic available"}`);for(const t of this.shaders)e.detachShader(n,t),e.deleteShader(t);this.shaders.clear();const s=e.getUniformLocation(n,"viewport"),o=e.getUniformLocation(n,"image");if(null===s||null===o)throw new Error("WebGL2 shader uniforms are unavailable");this.viewport=s;const a=e.createBuffer();if(!a)throw new Error("Could not allocate a WebGL2 instance buffer");this.instanceBuffer=a;const l=e.createVertexArray();if(!l)throw new Error("Could not allocate a WebGL2 vertex array");this.vertexArray=l,e.useProgram(n),e.uniform1i(o,0),e.activeTexture(e.TEXTURE0),e.bindVertexArray(l),e.bindBuffer(e.ARRAY_BUFFER,a);for(let t=0;t<4;t++)e.enableVertexAttribArray(t),e.vertexAttribDivisor(t,1);e.enable(e.BLEND),e.blendEquationSeparate(e.FUNC_ADD,e.FUNC_ADD),e.blendFuncSeparate(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA,e.ONE,e.ONE_MINUS_SRC_ALPHA),e.disable(e.DEPTH_TEST),e.disable(e.STENCIL_TEST),e.disable(e.CULL_FACE),e.disable(e.SCISSOR_TEST),e.disable(e.DITHER),this.resize(this.canvas.width,this.canvas.height)}compile(e,t){const i=this.gl,r=i.createShader(e);if(!r)throw new Error("Could not allocate a WebGL2 shader");if(this.shaders.add(r),i.shaderSource(r,t),i.compileShader(r),!i.getShaderParameter(r,i.COMPILE_STATUS))throw new Error(`WebGL2 shader compilation failed: ${i.getShaderInfoLog(r)||"No diagnostic available"}`);return r}assertActive(){if(this.disposed)throw new Error("WebGL2 backend is disposed");if(this.lostError)throw this.lostError}contextLost(e){if(!this.disposed&&!this.lostError){this.lostError=e;for(const t of this.fences)this.settleFence(t,e);this.onFatal(e)}}checkErrors(e){this.assertActive();const t=this.gl,i=t.getError();if(i===t.CONTEXT_LOST_WEBGL||t.isContextLost()){const e=new Error("WebGL2 context lost");throw this.contextLost(e),e}if(i!==t.NO_ERROR){const r=i===t.OUT_OF_MEMORY?"OUT_OF_MEMORY":i===t.INVALID_ENUM?"INVALID_ENUM":i===t.INVALID_VALUE?"INVALID_VALUE":i===t.INVALID_OPERATION?"INVALID_OPERATION":i===t.INVALID_FRAMEBUFFER_OPERATION?"INVALID_FRAMEBUFFER_OPERATION":`0x${i.toString(16)}`;throw new Error(`WebGL2 ${e} failed: ${r}`)}}createTexture(e,t,i){if(this.assertActive(),!K(e)||!K(t)||e>this.textureLimit||t>this.textureLimit)throw new Error(`${i} has invalid dimensions or exceeds the WebGL2 texture dimension limit`);const r=this.gl,n=r.createTexture();if(!n)throw this.checkErrors("texture allocation"),new Error(`Could not allocate WebGL2 texture: ${i}`);try{r.bindTexture(r.TEXTURE_2D,n),V(r),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MIN_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MAG_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_S,r.CLAMP_TO_EDGE),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_T,r.CLAMP_TO_EDGE),r.texImage2D(r.TEXTURE_2D,0,r.RGBA8,e,t,0,r.RGBA,r.UNSIGNED_BYTE,null),this.checkErrors(`texture allocation (${i})`);const s=new $(this,r,n,e,t,()=>this.assertActive(),e=>this.textures.delete(e));return this.textures.add(s),s}catch(e){throw r.deleteTexture(n),e}}resize(e,t){if(this.assertActive(),!Number.isFinite(e)||!Number.isFinite(t)||e<=0||t<=0)throw new Error("Invalid WebGL2 logical viewport dimensions");if(!K(this.canvas.width)||!K(this.canvas.height)||this.canvas.width>this.canvasLimit||this.canvas.height>this.canvasLimit)throw new Error("Canvas exceeds the WebGL2 drawing buffer dimension limit");const i=this.gl;if(i.drawingBufferWidth!==this.canvas.width||i.drawingBufferHeight!==this.canvas.height)throw this.checkErrors("drawing buffer resize"),new Error("WebGL2 could not allocate the requested canvas drawing buffer");i.viewport(0,0,this.canvas.width,this.canvas.height),i.useProgram(this.program),i.uniform2f(this.viewport,e,t),this.checkErrors("resize")}submit(e,t,i,r){if(this.assertActive(),!Number.isSafeInteger(t)||t<0||t*B>e.length)throw new Error("Invalid WebGL2 instance data");for(const e of i){if(!(e.resource instanceof $)||e.resource.owner!==this)throw new Error("Texture belongs to a different rendering backend");if(e.resource.destroyed)throw new Error("WebGL2 texture is destroyed");if(!Number.isSafeInteger(e.start)||!Number.isSafeInteger(e.count)||e.start<0||e.count<0||e.start+e.count>t)throw new Error("Invalid WebGL2 batch instance range")}const n=this.gl,s=B*Float32Array.BYTES_PER_ELEMENT,o=t*s;if(n.bindVertexArray(this.vertexArray),n.bindBuffer(n.ARRAY_BUFFER,this.instanceBuffer),!this.bufferBytes||o>this.bufferBytes){const t=Math.max(256,e.byteLength);n.bufferData(n.ARRAY_BUFFER,t,n.DYNAMIC_DRAW),this.checkErrors("instance buffer allocation"),this.bufferBytes=t}o&&n.bufferSubData(n.ARRAY_BUFFER,0,e,0,t*B),n.useProgram(this.program),n.activeTexture(n.TEXTURE0),n.clearColor(r[0],r[1],r[2],1),n.clear(n.COLOR_BUFFER_BIT);for(const e of i){if(!e.count)continue;const t=e.resource;n.bindTexture(n.TEXTURE_2D,t.texture);const i=e.start*s;n.vertexAttribPointer(0,4,n.FLOAT,!1,s,i),n.vertexAttribPointer(1,4,n.FLOAT,!1,s,i+16),n.vertexAttribPointer(2,4,n.FLOAT,!1,s,i+32),n.vertexAttribPointer(3,1,n.FLOAT,!1,s,i+48),n.drawArraysInstanced(n.TRIANGLES,0,6,e.count)}this.checkErrors("frame submission")}async idle(){this.checkErrors("GPU completion");const e=this.gl,t=e.fenceSync(e.SYNC_GPU_COMMANDS_COMPLETE,0);if(!t)throw this.checkErrors("GPU fence allocation"),new Error("Could not allocate a WebGL2 GPU fence");return new Promise((i,r)=>{const n={sync:t,resolve:i,reject:r};this.fences.add(n);try{e.flush(),this.checkErrors("GPU fence submission"),this.pollFence(n)}catch(e){this.settleFence(n,H(e))}})}pollFence(e){if(this.fences.has(e))try{this.assertActive();const t=this.gl;if(t.isContextLost()){const e=new Error("WebGL2 context lost");throw this.contextLost(e),e}const i=t.clientWaitSync(e.sync,0,0);if(i===t.ALREADY_SIGNALED||i===t.CONDITION_SATISFIED)this.checkErrors("GPU completion"),this.settleFence(e);else{if(i!==t.TIMEOUT_EXPIRED)throw this.checkErrors("GPU fence wait"),new Error("WebGL2 GPU fence wait failed");e.timer=setTimeout(()=>{e.timer=void 0,this.pollFence(e)},0)}}catch(t){this.settleFence(e,H(t))}}settleFence(e,t){this.fences.delete(e)&&(void 0!==e.timer&&clearTimeout(e.timer),this.gl.deleteSync(e.sync),t?e.reject(t):e.resolve())}dispose(){if(this.disposed)return;this.disposed=!0,this.canvas.removeEventListener("webglcontextlost",this.onContextLost);for(const e of this.fences)this.settleFence(e,new Error("WebGL2 backend is disposed"));for(const e of this.textures)e.destroy();const e=this.gl;this.instanceBuffer&&e.deleteBuffer(this.instanceBuffer),this.vertexArray&&e.deleteVertexArray(this.vertexArray),this.program&&e.deleteProgram(this.program);for(const t of this.shaders)e.deleteShader(t);this.shaders.clear(),this.instanceBuffer=void 0,this.vertexArray=void 0,this.program=void 0,this.bufferBytes=0,e.isContextLost()||e.getExtension("WEBGL_lose_context")?.loseContext()}}}});var Y,J,Z,Q,ee,te,ie,re=i({"src/backend-selection.ts"(){W(),m(),_(),X()}});function ne(e){return[(255&e)/255,(e>>>8&255)/255,(e>>>16&255)/255,(e>>>24&255)/255]}function se(e){return`${5&e.attributes}/${e.width}/${e.text}`}function oe(e){return 1109742===e.text.codePointAt(0)}function ae(e,t,i,r={x:0,y:0,rowHeight:0}){let{x:n,y:s,rowHeight:o}=r;const a=[];for(const[r,l]of e){const e=Math.ceil(l.width*Y*i)+4,c=Math.ceil(J*i)+4;if(e>t||c>t)return null;if(n+e>t&&(n=0,s+=o,o=0),s+c>t)return null;a.push({key:r,cell:l,x:n,y:s,width:e,height:c}),n+=e,o=Math.max(o,c)}return{placements:a,shelf:{x:n,y:s,rowHeight:o}}}var le,ce,he=i({"src/renderer.ts"(){A(),u(),re(),W(),Y=10,J=20,Z=1048576,Q=1048576,ee=B,te=[1,1,1,1],ie=class e{canvas;scale;backingScale;backend;fallbackReason;fontConfiguration;fontMetrics;images;glyphs;imageUploadBytes;imagePayloadBytes;glyphUploadBytes;atlasRebuilds;textureBytes;instances;disposed;columns;rows;font;rasterCanvas;raster;atlas;glyphKeyUnits=0;shelf={x:0,y:0,rowHeight:0};canvasLimited=!1;width=0;height=0;quadCount=0;batches=[];static async create(t,i,r,n,s="auto"){const o=h(n);if(!Number.isFinite(i)||i<.5||i>3)throw new Error("Invalid backing scale");const{backend:a,fallbackReason:l}=await async function(e,t,i="auto"){const r=p(i);if("webgl2"===r)return{backend:await F.create(e,t)};try{return{backend:await j.create(e,t)}}catch(i){if("auto"!==r||!(i instanceof q))throw i;try{return{backend:await F.create(e,t),fallbackReason:i.message}}catch(e){throw new AggregateError([i,e],`WebGPU unavailable (${i.message}); WebGL2 initialization failed: ${e instanceof Error?e.message:String(e)}`)}}}(t,r,s),c=new e(t,i,a,o);c.fallbackReason=l;try{return await c.initialize(),c}catch(e){throw c.dispose(),e}}constructor(e,t,i,r){if(!Number.isFinite(t)||t<.5||t>3)throw new Error("Invalid backing scale");this.canvas=e,this.scale=t,this.backingScale=t,this.backend=i,this.fontConfiguration=r,this.fontMetrics=new Map,this.images=new Map,this.glyphs=new Map,this.imageUploadBytes=0,this.imagePayloadBytes=0,this.glyphUploadBytes=0,this.atlasRebuilds=0,this.textureBytes=0,this.instances=new Float32Array(4096*ee),this.disposed=!1,this.columns=0,this.rows=0}async initialize(){this.font=await d(this.fontConfiguration),this.rasterCanvas=new OffscreenCanvas(1,1);const e=this.rasterCanvas.getContext("2d",{willReadFrequently:!0});if(!e)throw new Error("Worker glyph rasterization is unavailable");this.raster=e,this.resetAtlas(Math.min(2048,this.backend.maxTextureDimension2D))}createTexture(e,t,i){return this.backend.createTexture(e,t,i)}resetAtlas(e){this.atlas?.destroy(),this.atlas=this.createTexture(e,e,"Glyph atlas"),this.glyphs.clear(),this.glyphKeyUnits=0,this.shelf={x:0,y:0,rowHeight:0}}resize(e,t,i){const r=e*Y,n=t*J,s=this.backend.maxCanvasDimension2D,o=Math.min(this.scale,i?i.width/r:1/0,i?i.height/n:1/0);this.canvasLimited=r*o>s||n*o>s,this.backingScale=Math.min(o,s/r,s/n);const a=Math.max(1,Math.min(s,Math.ceil(r*this.backingScale))),l=Math.max(1,Math.min(s,Math.ceil(n*this.backingScale)));this.columns===e&&this.rows===t&&this.canvas.width===a&&this.canvas.height===l||(this.columns=e,this.rows=t,this.width=r,this.height=n,this.canvas.width=a,this.canvas.height=l,this.backend.resize(r,n))}async updateImages(e,t){const i=new Set(t),r=new Map(e.map(e=>[e.key,e]));let n=0;for(const e of i){const t=r.get(e)||this.images.get(e);if(!t)throw new Error(`Missing retained image resource: ${e}`);n+=t.width*t.height*4}if(n>y.textureBytes)throw new Error("Retained images exceed the 256 MiB texture budget");for(const[e,t]of this.images)i.has(e)&&!r.has(e)||(t.destroy(),this.textureBytes-=t.width*t.height*4,this.images.delete(e));for(const t of e){const e=this.createTexture(t.width,t.height,`Image ${t.key}`);try{if("rgba"===t.format)e.writePixels(t.bytes,t.width,t.height);else{const i=new DataView(t.bytes.buffer,t.bytes.byteOffset,t.bytes.byteLength);if(i.byteLength<33||2303741511!==i.getUint32(0)||218765834!==i.getUint32(4)||13!==i.getUint32(8)||1229472850!==i.getUint32(12)||i.getUint32(16)!==t.width||i.getUint32(20)!==t.height)throw new Error(`PNG header dimensions do not match resource ${t.key}`);const r=await createImageBitmap(new Blob([t.bytes],{type:"image/png"}),{premultiplyAlpha:"none",colorSpaceConversion:"none"});try{if(r.width!==t.width||r.height!==t.height)throw new Error("Decoded PNG dimension mismatch");e.writeBitmap(r)}finally{r.close()}}this.images.set(t.key,e),this.textureBytes+=t.width*t.height*4,this.imageUploadBytes+=t.width*t.height*4,this.imagePayloadBytes+=t.byteLength}catch(t){throw e.destroy(),t}}}prepareGlyphs(e){const t=new Map;for(const i of e)!(i&&i.width&&i.text.trim())||64&i.attributes||oe(i)||t.set(se(i),i);const i=e=>[...e.keys()].reduce((e,t)=>e+t.length,0);if(t.size>16384||i(t)>Q)throw new Error("Visible glyph metadata exceeds the bounded glyph cache");const r=new Map([...t].filter(([e])=>!this.glyphs.has(e)));if(!r.size)return;let n=this.glyphs.size+r.size<=16384&&this.glyphKeyUnits+i(r)<=Q?ae(r,this.atlas.width,this.scale,this.shelf):null;if(!n){let e=this.atlas.width;const i=Math.min(4096,this.backend.maxTextureDimension2D);for(;!(n=ae(t,e,this.scale))&&e=Z)throw new Error("Frame exceeds bounded quad budget");const p=this.quadCount*ee;if(p+ee>this.instances.length){const e=new Float32Array(Math.min(2*this.instances.length,Z*ee));e.set(this.instances),this.instances=e}const f=a[2]-a[0],m=a[3]-a[1];this.instances.set([c,h,d-c,u-h,a[0]+(c-t)/r*f,a[1]+(h-i)/n*m,a[0]+(d-t)/r*f,a[1]+(u-i)/n*m,...s,o,0,0,0],p);const g=this.batches[this.batches.length-1];g?.resource===e?g.count++:this.batches.push({resource:e,start:this.quadCount,count:1}),this.quadCount++}solid(e,t,i,r,n){this.quad(this.atlas,e,t,i,r,n)}placement(e){const t=this.images.get(e.key);if(!t)throw new Error(`Placement texture is missing: ${e.key}`);const{sourceX:i,sourceY:r,sourceWidth:n,sourceHeight:s}=e;if(!(n&&s&&e.width&&e.height))return;const o=e.x+Math.max(0,-i)/n*e.width,a=e.y+Math.max(0,-r)/s*e.height,l=e.x+Math.min(n,t.width-i)/n*e.width,c=e.y+Math.min(s,t.height-r)/s*e.height,h=Math.max(o,e.clipX),d=Math.max(a,e.clipY),u=Math.min(l,e.clipX+e.clipWidth),p=Math.min(c,e.clipY+e.clipHeight);this.quad(t,e.x,e.y,e.width,e.height,te,2,[i/t.width,r/t.height,(i+n)/t.width,(r+s)/t.height],[h,d,Math.max(0,u-h),Math.max(0,p-d)])}decorations(e,t,i,r,n){128&e.attributes&&this.solid(t,i+10,r,1,n),256&e.attributes&&this.solid(t,i+1,r,1,n);const s=e.underlineStyle||(8&e.attributes?1:0);this.underline(s,t,i,r,ne(e.underlineColor))}underline(e,t,i,r,n){if(1===e)this.solid(t,i+18,r,1,n);else if(2===e)this.solid(t,i+16,r,1,n),this.solid(t,i+18,r,1,n);else if(3===e)for(let e=0;e({placement:e,order:t,z:"sixel"===e.kind?-1:e.z})).sort((e,t)=>e.z-t.z||e.order-t.order);for(const e of s)e.z<-1073741824&&this.placement(e.placement);for(let t=0;t=-1073741824&&e.z<0&&this.placement(e.placement);for(let t=0;t=0&&this.placement(e.placement);const o=t.cursor,a=0===o.shape||o.shape%2==1;if(o.visible&&(!a||i)&&o.x>=0&&o.x=0&&o.ye!==s?.rowIds[t]))return!1;if(t.hyperlinks.length!==r.hyperlinks.length||t.hyperlinks.some((e,t)=>{const i=r.hyperlinks[t];return e.row!==i.row||e.startColumn!==i.startColumn||e.endColumn!==i.endColumn||e.uri!==i.uri}))return!1;if(e.length!==i.length)return!1;for(let t=0;tNumber.isSafeInteger(e)&&e>=0)||!Array.isArray(r)||!["solid","dashed"].includes(s))throw new Error("Invalid link decorations");const o=this.presented?.metadata;if(!this.enabled||n||!o||e!==o.revision||t!==this.generation||i<=this.lastSerial)return!1;if(r.length>o.columns*o.rows||r.some(e=>!e||!Number.isInteger(e.row)||e.row<0||e.row>=o.rows||!Number.isInteger(e.startColumn)||!Number.isInteger(e.endColumn)||e.startColumn<0||e.endColumn<=e.startColumn||e.endColumn>o.columns))throw new Error("Invalid link decoration ranges");const a=new Uint8Array(o.columns*o.rows),l="dashed"===s?5:1;for(const e of r)a.fill(l,e.row*o.columns+e.startColumn,e.row*o.columns+e.endColumn);return this.lastSerial=i,this.decorations=this.pending={revision:e,generation:t,serial:i,mask:a},!0}submission(){return{mask:this.decorations?.mask,acknowledgement:this.pending}}acknowledge(e,t){if(e&&e===this.pending&&!t&&this.enabled&&e.generation===this.generation&&e.revision===this.presented?.metadata.revision)return this.pending=void 0,{revision:e.revision,generation:e.generation,serial:e.serial}}}}}),qe=i({"src/marker-pages.ts"(){A(),ue=class{#d;get pending(){return void 0!==this.#d}reset(){this.#d=void 0}accept(e){if(!e?.markerPage)return this.reset(),e;const{markers:t=[],markerPage:i,...r}=e,n=JSON.stringify(r);0===i.offset&&(this.#d={revision:i.revision,total:i.total,signature:n,markers:[],ids:new Set,bytes:0});const s=this.#d;if(!s||s.revision!==i.revision||s.total!==i.total||s.markers.length!==i.offset||s.signature!==n)throw new Error("Marker pages do not describe the same contiguous history snapshot");if(s.bytes+=(new TextEncoder).encode(JSON.stringify(t)).byteLength,s.bytes>y.metadataBytes)throw new Error("Retained marker inventory exceeds the 8 MiB client budget");for(const e of t){if(s.ids.has(e.id))throw new Error("Duplicate marker ID across inventory pages");s.ids.add(e.id),s.markers.push(e)}return s.markers.length{be=!1,we||ye||me||ge||Oe.pending||Ue||(Me=async function(){if(!ke||!Te||!pe)return;ke=!1,ye=!0;const e=Ie;try{e&&Pe.prepare(Ce,Te);const t=Pe.submission();pe.resize(Te.columns,Te.rows,Re);const i=Math.floor(performance.now()/600)%2==0,r=pe.render(Ce,Te,i,t.mask);if(await pe.idle(),me||ge)return;if(Ee=i,ze.presentations++,ze.rendererCpuMs=r.cpuMs,ze.quads=r.quads,ze.drawCalls=r.drawCalls,ze.warnings=pe.canvasLimited?[...Te.warnings,`Canvas resolution capped by the GPU; terminal grid remains ${Te.columns}x${Te.rows}`]:Te.warnings,e){Ie=void 0,ve=!1,ze.frames++,e.full&&ze.fullFrames++,ze.revision=e.revision,ze.changedCells+=e.changedCells,ze.lastChangedCells=e.changedCells,ze.captureMs=Te.stats.captureMs,ze.workloadBytes=Te.stats.workloadBytes,ze.outputBatches=Te.stats.outputBatches,ze.serverElapsedMs=Te.stats.elapsedMs,ze.columns=Te.columns,ze.rows=Te.rows,ze.mouseTracking=Te.mouseTracking,ze.peer=Te.peer,ze.history=Te.history;const t=function(e,t,i){const r=[];for(let n=0;ny.frameBytes)throw new Error("Invalid or oversized binary frame");const t=new DataView(e);let i=0;const r=e=>{if(e<0||e>t.byteLength-i)throw new Error("Truncated HWT1 frame")},n=()=>{r(4);const e=t.getUint32(i,!0);return i+=4,e};if(827610952!==n())throw new Error("Unsupported frame magic (expected HWT1)");const s=x(n(),"metadata length",2,y.metadataBytes);r(s);const o=JSON.parse(v.decode(new Uint8Array(e,i,s)));i+=s,M(o);const a=o.columns*o.rows,l=x(n(),"changed cell count",0,a);if(l>Math.floor((t.byteLength-i)/22))throw new Error("Truncated cell records");if(o.full&&l!==a)throw new Error("Incomplete full frame");const c=[],h=new Set;for(let s=0;s=a||h.has(s))throw new Error("Invalid or duplicate cell index");h.add(s);const o=n(),l=n(),d=n(),u=t.getUint16(i,!0),p=t.getUint8(i+2),f=t.getUint8(i+3),m=t.getUint16(i+4,!0);if(i+=6,f>5)throw new Error("Unsupported underline style");r(m);const g=v.decode(new Uint8Array(e,i,m));i+=m,c.push({index:s,foreground:o,background:l,underlineColor:d,attributes:u,width:p,underlineStyle:f,text:g})}if(o.images.reduce((e,t)=>e+t.byteLength,0)!==t.byteLength-i)throw new Error("Image payload length mismatch");const d=o.images.map(t=>{const r=new Uint8Array(e,i,t.byteLength);return i+=t.byteLength,{...t,bytes:r}});return{metadata:o,cells:c,images:d}}(e),i=t.metadata;if(!i.full&&(Ue||i.baseRevision!==Se||i.revision<=Se||!Te||i.columns!==Te.columns||i.rows!==Te.rows))return ze.discardedFrames++,ve=!1,Oe.reset(),Ue=!0,ke=!1,Ie=void 0,$e({type:"ack",revision:i.revision}),$e({type:"resync"}),void je(`Revision mismatch (local ${Se}, base ${i.baseRevision}); requesting a full frame`);if(await Me,me||ge)return;if(await pe.idle(),await pe.updateImages(t.images,i.retainedImages),me||ge)return;const r=performance.now(),n=i.full?new Array(i.columns*i.rows):Ce.slice();for(const e of t.cells)n[e.index]=e;pe.prepareGlyphs(n),Ce=n,Te=i,Se=i.revision;const s=Oe.pending&&!i.full,o=Oe.accept(i.history);if(Ue=!1,Ie={revision:i.revision,full:i.full||s&&!!Ie?.full,changedCells:t.cells.length+(s?Ie?.changedCells??0:0)},xe=Ce.some(e=>e&&16&e.attributes&&!(64&e.attributes))||i.cursor.visible&&(0===i.cursor.shape||i.cursor.shape%2==1),ze.preparationCpuMs=performance.now()-r,void 0===o)return ke=!1,ve=!1,void $e({type:"ack",revision:i.revision});i.history=o,ke=!0}finally{we=!1,ke&&We()}}}var He=i({"src/terminal-worker.ts"(){A(),he(),Be(),qe(),s(),me=!1,ge=!1,we=!1,ye=!1,ve=!1,be=!1,ke=!1,xe=!1,Ee=!0,Ce=[],Se=0,Me=Promise.resolve(),Pe=new ce,Oe=new ue,Ue=!1,ze={revision:0,fullFrames:0,frames:0,presentations:0,changedCells:0,lastChangedCells:0,discardedFrames:0,imageCount:0,textureBytes:0,atlasGlyphs:0,atlasBytes:0,bytesReceived:0,imageUploadBytes:0,imagePayloadBytes:0,gpu:"initializing",connected:!1,warnings:[],fps:0,receivedKBps:0,workloadMBps:0,captureMs:0,rendererCpuMs:0,preparationCpuMs:0,workloadBytes:0,outputBatches:0,serverElapsedMs:0},De={time:performance.now(),presentations:0,bytes:0,workload:0},self.addEventListener("error",e=>{e.preventDefault(),Ge(e.error||new Error(e.message))}),self.addEventListener("unhandledrejection",e=>{e.preventDefault(),Ge(e.reason)}),self.addEventListener("message",e=>{const t=e.data;if("init"===t.type)(async function(e){if(pe||fe)throw new Error("Worker is already initialized");if("function"!=typeof self.requestAnimationFrame)throw new Error("This browser does not support requestAnimationFrame in a dedicated OffscreenCanvas worker");if(je("Loading terminal font and initializing renderer..."),pe=await ie.create(e.canvas,e.scale,Ge,e.font,e.renderer),me||ge)return void pe?.dispose();ze.gpu="ready",ze.backingScale=e.scale,Fe();const t="webgpu"===pe.backend.kind?"WebGPU":"WebGL2";pe.fallbackReason&&je(`Using WebGL2: ${pe.fallbackReason}`),je(`${t} ready. Attaching terminal view...`);const i=new URL(e.url);if(!["ws:","wss:"].includes(i.protocol))throw new Error("The terminal WebSocket URL must use ws: or wss:");(fe=new WebSocket(i)).binaryType="arraybuffer",fe.addEventListener("open",()=>{me||ge||(ze.connected=!0,self.postMessage({type:"connected"}),je(`Connected · ${t} worker · server-authoritative cells and graphics`,"ready"),Fe())}),fe.addEventListener("message",e=>{e.data instanceof ArrayBuffer?_e(e.data).catch(Ge):Ge(new Error(`Expected binary HWT1 frame, received ${String(e.data).slice(0,200)}`))}),fe.addEventListener("close",e=>{ze.connected=!1,me||ge||(ze.gpu="stopped",ze.fps=0,ge=!0,clearInterval(Le),clearInterval(Ae),pe?.dispose(),self.postMessage({type:"closed",details:{code:e.code,reason:e.reason,wasClean:e.wasClean}}),je(`View disconnected (${e.code}${e.reason?`: ${e.reason}`:""}). Attach another view to reconnect.`,"error"),Fe())}),Le=setInterval(()=>{const e=performance.now(),t=(e-De.time)/1e3;ze.fps=(ze.presentations-De.presentations)/t,ze.receivedKBps=(ze.bytesReceived-De.bytes)/t/1e3,ze.workloadMBps=Math.max(0,ze.workloadBytes-De.workload)/t/1e6,De={time:e,presentations:ze.presentations,bytes:ze.bytesReceived,workload:ze.workloadBytes},Fe()},1e3),Ae=setInterval(()=>{const e=Math.floor(performance.now()/600)%2==0;xe&&e!==Ee&&We()},100)})(t).catch(Ge);else if("stop"===t.type)ge=!0,clearInterval(Le),clearInterval(Ae),fe?.close(1e3,"View detached"),pe?.dispose(),self.close();else if("viewport"!==t.type||me||ge)if("command"!==t.type||me||ge)if("linkDetection"!==t.type||me||ge){if("linkDecorations"===t.type&&!me&&!ge)try{Pe.accept(t.revision,t.generation,t.serial,t.ranges,we||ve||!!Ie,t.underlineStyle)&&We()}catch(e){Ge(e)}}else try{if(!Pe.configure(t.enabled,t.generation))return;if(!we&&!ye&&!Ie){const e=Pe.snapshot();e&&self.postMessage({type:"linkSnapshot",generation:Pe.generation,snapshot:e})}We()}catch(e){Ge(e)}else $e(t.command);else{if(!Number.isFinite(t.width)||!Number.isFinite(t.height)||t.width<0||t.height<0)return void Ge(new Error("Invalid mounted viewport dimensions"));if(Re?.width===t.width&&Re?.height===t.height)return;Re={width:t.width,height:t.height},We()}})}}),Ke={};function Ve(e){let t=e.length;const i={")":"(","]":"[","}":"{"};for(;t>0;){const r=e[t-1];if(!".,;:!?".includes(r)){if(i[r]){const n=e.slice(0,t);if(n.split(r).length>n.split(i[r]).length){t--;continue}}break}t--}return e.slice(0,t)}function Xe(e){try{const t="builtin"in e.rule?e.rule.builtin:void 0;let i;if("source"in e.rule){const t=e.rule.flags.replaceAll("g","");i=new RegExp(e.rule.source,`${t}g`)}else{const t={url:String.raw`https?:\/\/[^\s\0<>"'\x60]+`,uri:String.raw`[A-Za-z][A-Za-z0-9+.-]*:[^\s\0<>"'\x60]+`,absolutePath:String.raw`(?:[A-Za-z]:[\\/]|/)[^\s\0<>"'\x60]*`,homePath:String.raw`~/[^\s\0<>"'\x60]+`};i=new RegExp(t[e.rule.builtin],"giu")}let r=0,n=0,s=0,o=0;const a=[];for(const l of e.chunks){if(s+=l.text.length,l.text.length>L.chunk||s>L.totalText)return{id:e.id,error:"limit",message:"Link scan text limit exceeded"};i.lastIndex=0;const c=[];let h;for(;null!==(h=i.exec(l.text));){if(++n>L.totalText||r>=L.matches)return{id:e.id,error:"limit",message:"Link match limit exceeded"};if(!h[0].length){const e=l.text.codePointAt(i.lastIndex);i.lastIndex+=(i.unicode||i.unicodeSets)&&void 0!==e&&e>65535?2:1;continue}let s=h[0];if(t){const e=l.text[h.index-1];if(void 0!==e&&!/[\s([{"'<>=]/u.test(e))continue;if("uri"===t&&/^[a-z]:[\\/]/iu.test(s))continue;if(s=Ve(s),!s||"absolutePath"===t&&("/"===s||/^[a-z]:[\\/]$/iu.test(s)))continue;if("url"===t)try{if(!new URL(s).hostname)continue}catch{continue}}const a={index:h.index,text:s,captures:h.slice(1),groups:{...h.groups}};if(o+=D(a),o>L.resultText)return{id:e.id,error:"limit",message:"Link result text limit exceeded"};c.push(a),r++}a.push({key:l.key,matches:c})}return{id:e.id,results:a}}catch(t){return{id:e.id,error:"worker",message:t instanceof Error?t.message:String(t)}}}((t,i)=>{for(var r in i)e(t,r,{get:i[r],enumerable:!0})})(Ke,{scanLinks:()=>Xe});var Ye=i({"src/link-detection-worker.ts"(){z(),G(),"undefined"!=typeof self&&"function"==typeof self.postMessage&&self.addEventListener("message",e=>{self.postMessage(Xe(e.data))})}});var Je=class{x=0;y=0;reset(){this.x=this.y=0}take(e,t,i){const r=t.height/i;if(!(r>0))return{x:0,y:0};const n=1===e.deltaMode?1:2===e.deltaMode?i:1/r;this.x+=e.deltaX*n,this.y+=e.deltaY*n;const s=Math.trunc(this.x),o=Math.trunc(this.y);return this.x-=s,this.y-=o,{x:Math.max(-32,Math.min(32,s)),y:Math.max(-32,Math.min(32,o))}}},Ze=class{owner=null;mode="character";endpoint=null;begin(e,t,{tracking:i,historical:r,readOnly:n,selection:s}){if(this.owner)return null;const o=r||n||!i||e.shiftKey;if(o&&0!==e.button)return null;if(this.owner=o?"local":"app",!o)return{owner:"app"};const a=!!e.shiftKey&&!!s&&["valid","pending"].includes(s.status)&&!1!==s.canExtend;return this.mode=a&&s?s.mode:e.altKey?"rectangle":e.detail>=3?"line":2===e.detail?"word":"character",this.endpoint={x:t.x,y:t.y},{owner:this.owner,mode:this.mode,extend:a}}move(e){return"local"!==this.owner?null:(this.endpoint={x:e.x,y:e.y},{...this.endpoint})}scrollPoint(e){if("local"===this.owner)return{x:"rectangle"===this.mode&&this.endpoint?this.endpoint.x:e.x,y:e.y}}wheelOwner(e,{tracking:t,historical:i,readOnly:r}){return this.owner??(i||r||!t||e.shiftKey?"local":"app")}end(){this.owner=null}};s();var Qe=Object.freeze({Continue:"continue",Consume:"consume",Application:"application",Browser:"browser"}),et=Object.freeze({CopySelection:"copySelection",PasteClipboard:"pasteClipboard",CopyOrPaste:"copyOrPaste",ClearSelection:"clearSelection",ScrollToLive:"scrollToLive",ScrollLines:"scrollLines"}),tt=new Set(["l","t","v","w","+","-","=","0"]),it=new Set(["F1","F3","F5","F6","F7","F10","F11","F12"]),rt=new Set(["Tab","Enter","Backspace","Delete","Escape","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert",...Array.from({length:12},(e,t)=>`F${t+1}`)]),nt=new Set(Object.values(Qe));function st(){return[{id:"clipboard.copy-key",match:e=>"key"===e.type&&"c"===e.key.toLowerCase()&&!e.alt&&(e.meta&&!e.ctrl||e.ctrl&&e.shift&&!e.meta),action:et.CopySelection},{id:"browser.shortcuts",match:e=>"key"===e.type&&(e.meta||it.has(e.key)||e.ctrl&&("Tab"===e.key||"F4"===e.key||tt.has(e.key.toLowerCase())||e.shift&&1===e.key.length)||e.alt&&!e.ctrl),route:Qe.Browser},{id:"terminal.keys",match:e=>"key"===e.type&&(rt.has(e.key)||e.ctrl&&1===e.key.length),route:Qe.Application},{id:"clipboard.context-click",match:(e,t)=>"pointer"===e.type&&"right"===e.button&&!e.meta&&(!t.mouseCaptured||e.shift||t.historical||t.readOnly),action:et.CopyOrPaste}]}function ot(e,t){if("function"!=typeof e||"AsyncFunction"===e.constructor.name)throw new TypeError(`${t} must be a synchronous function`)}var at=class{#u;#p;#f;constructor({inputBindings:e=[],onInput:t,actions:i={}}={}){if(!Array.isArray(e))throw new TypeError("inputBindings must be an array");if(!i||"object"!=typeof i||Array.isArray(i))throw new TypeError("actions must be an object");this.#f=new Set(Object.values(et));for(const[e,t]of Object.entries(i)){if(!e||this.#f.has(e)||"function"!=typeof t)throw new TypeError(`Invalid or reserved action: ${e}`);this.#f.add(e)}void 0!==t&&ot(t,"onInput"),this.#p=t;const r=st(),n=new Set,s=e.map(e=>{if(!e||"string"!=typeof e.id||!e.id||n.has(e.id))throw new TypeError("Each input binding needs a unique, nonempty id");if(n.add(e.id),!0===e.remove){if(!r.some(t=>t.id===e.id))throw new TypeError(`Cannot remove unknown default binding: ${e.id}`);if(Object.keys(e).some(e=>!["id","remove"].includes(e)))throw new TypeError(`Removed binding ${e.id} must contain only id and remove`);return null}return ot(e.match,`Binding ${e.id}.match`),void 0!==e.when&&ot(e.when,`Binding ${e.id}.when`),this.#m(e),Object.freeze({...e})}).filter(e=>null!==e);this.#u=[...s,...r.filter(e=>!n.has(e.id))]}get bindings(){return this.#u.map(e=>({...e}))}#m(e){if(lt(e))return{route:e};if(!r(e)||"function"==typeof e.then)throw new TypeError("Input routing must synchronously return a route or action");const t=Object.hasOwn(e,"action"),i=Object.hasOwn(e,"route");if(t===i)throw new TypeError("Specify exactly one action or route");if(i){if(!lt(e.route))throw new TypeError(`Unknown input route: ${String(e.route)}`);return{route:e.route}}if(!(function(e){return"function"==typeof e}(e.action)||"string"==typeof e.action&&this.#f.has(e.action)))throw new TypeError(`Unknown terminal action: ${String(e.action)}`);return{action:e.action,args:e.args}}resolve(e,t){const i=this.#p?.(e,t);if(void 0!==i){const e=this.#m(i);if(e.route!==Qe.Continue)return e}for(const i of this.#u){const r=i.match(e,t);if("boolean"!=typeof r)throw new TypeError(`Binding ${i.id}.match must return a boolean`);if(!r)continue;if(i.when){const r=i.when(t,e);if("boolean"!=typeof r)throw new TypeError(`Binding ${i.id}.when must return a boolean`);if(!r)continue}const n=this.#m(i);if(n.route!==Qe.Continue)return n}return{route:Qe.Continue}}};function lt(e){return"string"==typeof e&&[...nt].some(t=>t===e)}function ct(e){return{ctrl:!!e.ctrlKey,alt:!!e.altKey,shift:!!e.shiftKey,meta:!!e.metaKey}}var ht=[["left",1],["middle",4],["right",2]],dt=["left","middle","right"];function ut(e,t,i,r={}){const n=new AbortController,s={signal:n.signal};let o=1,a=1,l=0,c=null;const h=new Set;let d,u,p,f;const m=new Je,g=new Ze;let w,y,v,b,k,x,E,T,C,S,I,M={count:0,time:0,x:-1,y:-1};const L=e.title,A=e.style.cursor,R=()=>({tracking:l,...r.state?.()});function P(t,i=!1){return function(e,t,i,r,n=!1){if(!(t.width>0&&t.height>0))return null;const s=Math.floor((e.clientX-t.left)*i/t.width),o=Math.floor((e.clientY-t.top)*r/t.height);return!n&&(s<0||o<0||s>=i||o>=r)?null:{x:Math.max(0,Math.min(i-1,s)),y:Math.max(0,Math.min(r-1,o)),shift:!!e.shiftKey,alt:!!e.altKey,ctrl:!!e.ctrlKey}}(t,e.getBoundingClientRect(),o,a,i)}function O(t=S){if(!r.hyperlink)return;S=t;const i=C&&P(C),n=i?r.hyperlink(i):null;e.title=n?`${n.target}\n${"click"===n.activation?"Click":"Ctrl/Cmd+click"} to activate link`:L,e.style.cursor=n&&t&&("click"===n.activation||t.ctrlKey||t.metaKey)&&!t.altKey&&!t.shiftKey?"pointer":A,T&&T.link.id!==n?.id&&(T.dragged=!0),I!==n?.id&&(I=n?.id,r.hoverHyperlink?.(n))}function U(){if(clearTimeout(v),v=void 0,"local"!==g.owner||!y)return;const t=e.getBoundingClientRect(),i=y.clientY=t.bottom?y.clientY-t.bottom+1:0;i&&(v=setTimeout(()=>{if(v=void 0,!y)return;const e=P(y,!0);if("local"===g.owner&&e){b&&(b.dragged=!0),M.count=0;const n=Math.sign(i)*Math.min(8,Math.max(1,Math.ceil(Math.abs(i)/(t.height/a))));r.scroll?.(n,g.scrollPoint(e)),U()}},60))}function z(){void 0!==f&&cancelAnimationFrame(f),f=void 0,u&&t(u),u=void 0}function D(e,i){for(const[r,n]of ht){const s=0!==(e.buttons&n);s!==h.has(r)&&(z(),s?h.add(r):h.delete(r),(s||9!==l)&&t({type:"mouse",action:s?"down":"up",button:r,...i}),p=void 0)}}function B(i=!0,n=!0){if(r.end?.(n),clearTimeout(v),v=void 0,y=void 0,b=void 0,k=void 0,T=void 0,g.end(),x=void 0,i?z():(void 0!==f&&cancelAnimationFrame(f),f=void 0,u=void 0),i&&9!==l&&d)for(const e of h)t({type:"mouse",action:"up",button:e,...d});h.clear();const s=c;c=null,null!==s&&e.hasPointerCapture(s)&&e.releasePointerCapture(s),p=void 0,m.reset(),w=void 0}return e.addEventListener("pointerdown",t=>{if(t.defaultPrevented&&null===c)return;if("mouse"!==t.pointerType||![0,1,2].includes(t.button))return;const n=P(t);if(!n)return;if(null!==c)return T&&(T.dragged=!0),x!==Qe.Browser&&t.preventDefault(),void("app"!==g.owner&&x!==Qe.Application||c!==t.pointerId||D(t,n));i();const s={type:"pointer",button:dt[t.button],point:Object.freeze({...n}),...ct(t)},o=r.resolve?.(s)??{route:Qe.Continue};let a=void 0!==o.action?Qe.Consume:o.route;if(a===Qe.Continue&&0===t.button&&!t.altKey&&!t.shiftKey){const e=r.hyperlink?.(n);e&&("click"===e.activation||t.ctrlKey||t.metaKey)&&(T={link:e,input:s,x:t.clientX,y:t.clientY,dragged:!1},a=Qe.Consume)}if(E=2===t.button?a:void 0,T&&(E=Qe.Consume),C=t,O(t),a!==Qe.Continue)return k=b=void 0,M.count=0,x=a,c=t.pointerId,d=n,e.setPointerCapture(c),a!==Qe.Browser&&t.preventDefault(),a===Qe.Application?z():(void 0!==f&&cancelAnimationFrame(f),f=void 0,u=void 0),a===Qe.Application&&D(t,n),void(void 0!==o.action&&r.execute?.(o,s));if(t.preventDefault(),t.metaKey)return;const l=performance.now();M.count=l-M.time<500&&n.x===M.x&&n.y===M.y?M.count%3+1:1,M={count:M.count,time:l,x:n.x,y:n.y};const h=g.begin({button:t.button,shiftKey:t.shiftKey,altKey:t.altKey,detail:t.detail||M.count},n,R());h&&(c=t.pointerId,y=t,d=n,k=void 0,e.setPointerCapture(c),"local"===h.owner?(void 0!==f&&cancelAnimationFrame(f),f=void 0,u=void 0,p=void 0,b={point:n,mode:h.mode,extend:h.extend,dragged:!1},r.begin?.(n,h)):D(t,n))},s),e.addEventListener("pointermove",e=>{if("mouse"!==e.pointerType)return;if(null===c&&e.buttons)return;if(null!==c&&c!==e.pointerId)return;C=e,O(e),T&&Math.hypot(e.clientX-T.x,e.clientY-T.y)>=4&&(T.dragged=!0);const t=P(e,null!==c);if(!t)return;if(d=t,x&&x!==Qe.Application)return;if("local"===g.owner){y=e;const i=g.endpoint,n=g.move(t);return n&&i&&(n.x!==i.x||n.y!==i.y)&&(M.count=0,b&&(b.dragged=!0),r.extend?.(n)),void U()}if(!l||!x&&null===g.owner&&(R().historical||R().readOnly||e.shiftKey))return;if(c===e.pointerId&&D(e,t),e.metaKey||1003!==l&&(1002!==l||!h.size))return;const i={type:"mouse",action:"move",button:ht.find(([e])=>h.has(e))?.[0]??"none",...t},n=JSON.stringify(i);n!==p&&(p=n,u=i,void 0===f&&(f=requestAnimationFrame(z)))},s),e.addEventListener("pointerup",e=>{if(c!==e.pointerId)return;if(T){e.preventDefault();const t=T,i=P(e),n=0===e.button&&0===e.buttons&&!t.dragged&&Math.hypot(e.clientX-t.x,e.clientY-t.y)<4&&i&&r.hyperlink?.(i)?.id===t.link.id;return 0===e.buttons?B(!1,!1):t.dragged=!0,void(n&&r.openHyperlink?.(t.link,t.input))}const t=P(e,!0)??d;if(x)return x===Qe.Application&&t&&D(e,t),void(0===e.buttons&&B(x===Qe.Application));if("local"===g.owner){if(t&&g.endpoint&&(t.x!==g.endpoint.x||t.y!==g.endpoint.y)){b&&(b.dragged=!0);const e=g.move(t);e&&r.extend?.(e)}const e=b;return B(!1,!1),void(k=e)}t&&D(e,t),h.size||B()},s),e.addEventListener("click",e=>{const t=k;if(k=void 0,!t||t.dragged||!Number.isInteger(e.detail)||e.detail<1)return;M.count=e.detail;const i=e.detail>=3?"line":2===e.detail?"word":"character";t.extend||"rectangle"===t.mode||i===t.mode||r.begin?.(t.point,{mode:i,extend:!1})},s),e.addEventListener("pointercancel",()=>B(),s),e.addEventListener("pointerleave",()=>{C=void 0,O()},s),e.addEventListener("lostpointercapture",()=>{null!==c&&B()},s),window.addEventListener("blur",()=>{B(),C=void 0,O()},s),window.addEventListener("keydown",e=>O(e),{...s,capture:!0}),window.addEventListener("keyup",e=>O(e),{...s,capture:!0}),e.addEventListener("contextmenu",e=>{if(void 0!==E&&E!==Qe.Continue){const t=E;return E=void 0,void(t!==Qe.Browser&&e.preventDefault())}(l||"local"===g.owner)&&e.preventDefault()},s),e.addEventListener("wheel",i=>{T&&(T.dragged=!0);const n={type:"wheel",deltaX:i.deltaX,deltaY:i.deltaY,deltaMode:i.deltaMode,point:P(i),...ct(i)},s=null===c?r.resolve?.(n)??{route:Qe.Continue}:{route:x??Qe.Continue};if(s.route===Qe.Browser)return;if(void 0!==s.action||s.route===Qe.Consume)return i.preventDefault(),void(void 0!==s.action&&r.execute?.(s,n));if(s.route!==Qe.Application&&(i.ctrlKey||i.metaKey))return;const o=s.route===Qe.Application?"app":g.wheelOwner(i,R());if("app"===o&&9===l)return;const h=P("local"===g.owner&&y?y:i,"local"===g.owner);if(!h)return;i.preventDefault(),z();const d=e.getBoundingClientRect();w!==o&&m.reset(),w=o;const{x:u,y:p}=m.take(i,d,a);if("local"===o)return void(p&&(b&&(b.dragged=!0),M.count=0,r.scroll?.(p,g.scrollPoint(h))));const f=[[p,"wheelUp","wheelDown"],[u,"wheelLeft","wheelRight"]];for(const[e,i,r]of f)e&&t({type:"mouse",action:"wheel",button:e<0?i:r,count:Math.min(32,Math.abs(e)),...h})},{...s,passive:!1}),{update(e,t,i){o===e&&a===t&&l===i||(d&&(d={...d,x:Math.min(d.x,e-1),y:Math.min(d.y,t-1)}),o===e&&a===t||B(0!==i),o=e,a=t,l=i),O()},refresh(){O()},cancel(){B()},dispose(){B(!1),n.abort(),e.title=L,e.style.cursor=A}}}function pt(e){const t=new URL(import.meta.url);return t.hash=`hex1b-${e}-worker`,t}c(),u(),m();var ft=8,mt=32;function gt(e,t){if(!Number.isInteger(e)||e<1||e>300||!Number.isInteger(t)||t<1||t>100)throw new RangeError("Requested grid must be 1-300 columns by 1-100 rows");return{columns:e,rows:t}}function wt(e={mode:"auto"},t=16){if(!e||!["auto","fixed"].includes(e.mode))throw new TypeError("Sizing mode must be 'auto' or 'fixed'");const i=e.fontSize??t;if(!Number.isInteger(i)||i<8||i>32)throw new RangeError("Font size must be an integer from 8 to 32");return"fixed"===e.mode?{mode:"fixed",fontSize:i,...gt(e.columns,e.rows)}:{mode:"auto",fontSize:i}}var yt="Text history is not available for this terminal view",vt="Selection expired: its text was changed, evicted, reset, or resized. Select the text again.",bt="Selection changed before copy completed. Copy the new selection again.",kt=class{#e;#t;#g=null;#o=0;#w=0;#y=0;#v=0;#b="character";#k="clear";#x;#E;constructor(e,t=()=>{}){this.#e=e,this.#t=t}get viewport(){if(!this.#g)return{available:!1,following:!0,pending:!1};const{selection:e,copy:t,markers:i,markerResult:r,markerPage:n,viewportError:s,...o}=this.#g;return{...o,rowIds:[...o.rowIds],available:!0,revision:this.#o,...s&&o.requestId>=this.#v?{navigationError:s}:{},pending:o.requestId({...e})),text:null,message:"Resolving selection…"}:{...e,canExtend:"valid"===e.status,ranges:e.ranges.map(e=>({...e})),revision:this.#o,message:"invalidated"===e.status?vt:""}}accept(e,t){if(t<=this.#o)return!1;const i=this.#g?.generation;this.#g=e,this.#o=t,(!e||i&&i!==e.generation||"invalidated"===e.selection.status)&&(this.endGesture(!0),this.#T(new Error(vt)));const r=this.#x;if(r&&e?.copy?.requestId===r.requestId&&("valid"!==e.copy.status||"valid"!==e.selection.status||e.selection.requestId!==r.selectionRequestId||e.generation!==r.generation||this.#y>r.selectionRequestId||e.copy.text!==e.selection.text?this.#T(new Error(vt)):(clearTimeout(r.timer),this.#x=void 0,r.resolve(e.copy.text))),this.#E&&!this.viewport.pending){const e=this.#E;this.#E=void 0,this.extend(e)}return this.#t(),!0}#C(){if(!this.#g)throw new Error(yt);return this.#g}#S(e){const t=this.#C();if(!e||!Number.isInteger(e.x)||e.x<0||e.x>1023||!Number.isInteger(e.y)||e.y<0||"string"!=typeof t.rowIds[e.y]||!t.rowIds[e.y].length)throw new Error("Terminal viewport is not ready for that selection point. Wait for the next frame and try again.");return t.rowIds[e.y]}#I(e=this.selection.mode,t="extend"){return this.#T(new Error(bt)),this.#b=e,this.#k=t,this.#y=++this.#w,this.#y}begin(e,{mode:t,extend:i=!1}){const r=this.#S(e);this.#E=void 0;const n=this.#I(t,i?"extend":"start");this.#e({type:"selection",action:i?"extend":"start",mode:t,requestId:n,generation:this.#C().generation,rowId:r,column:e.x}),this.#t()}extend(e){if(this.#S(e),this.viewport.pending)return this.#T(new Error(bt)),this.#b=this.selection.mode,this.#k="extend",this.#E={...e},void this.#t();this.begin(e,{mode:this.selection.mode,extend:!0})}scroll(e,t){if(this.#C(),!Number.isSafeInteger(e)||e<-2147483648||e>2147483647)throw new RangeError("Scroll delta must be a signed 32-bit integer");if(t&&this.#S(t),!e&&!t)return;t?this.#E=void 0:this.#M();const i=t?this.#I():++this.#w;this.#v=i,this.#e({type:"viewport",requestId:i,delta:e,...t?{extend:{row:t.y,column:t.x}}:{}}),this.#t()}live(){this.#C(),this.#M(),this.#v=++this.#w,this.#e({type:"viewport",live:!0,requestId:this.#v}),this.#t()}scrollTo(e){const t=this.#C();if(!Number.isSafeInteger(e)||e<0||e>2147483647)throw new RangeError("Scroll top must be a nonnegative 32-bit integer");this.#M(),this.#v=++this.#w,this.#e({type:"viewport",requestId:this.#v,top:Math.min(e,t.liveTop),generation:t.generation,originRowId:t.rowIds[0],originTop:t.top}),this.#t()}clear(){this.#C(),this.#E=void 0;const e=this.#I(this.selection.mode,"clear");this.#e({type:"selection",action:"clear",requestId:e}),this.#t()}endGesture(e=!1){e&&this.#E&&(this.#E=void 0,this.#t())}#M(){if(!this.#E)return;const e=this.#E;this.#E=void 0,this.scroll(0,e)}cancelCopy(e){this.#T(e)}copy(){const e=this.#C(),t=this.selection;if("valid"!==t.status)throw new Error(t.message||"Select text before copying.");this.#T(new Error("A newer copy request replaced this request."));const i=++this.#w,{promise:r,resolve:n,reject:s}=Promise.withResolvers(),o=setTimeout(()=>this.#T(new Error("Timed out resolving selection. Copy again.")),1e4);this.#x={requestId:i,selectionRequestId:t.requestId,generation:e.generation,resolve:n,reject:s,timer:o};try{this.#e({type:"copy",requestId:i,selectionRequestId:t.requestId,generation:e.generation})}catch(e){this.#T(e)}return r}#T(e){this.#x&&(clearTimeout(this.#x.timer),this.#x.reject(e),this.#x=void 0)}disconnect(){this.#T(new Error("Terminal view disconnected before copy completed.")),this.#E=void 0}};function xt(e=0){if("number"!=typeof e&&!r(e))throw new TypeError("Padding must be a number or per-edge object");const t=t=>{const i="number"==typeof e?e:void 0===e[t]?0:e[t];if("number"!=typeof i||!Number.isFinite(i)||i<0)throw new RangeError(`Padding ${t} must be finite nonnegative CSS pixels`);return i};return Object.freeze({top:t("top"),right:t("right"),bottom:t("bottom"),left:t("left")})}function Et(e,t,i){return{width:Math.max(0,e.width-t.left-t.right-(i&&"beside"===i.placement?i.width:0)),height:Math.max(0,e.height-t.top-t.bottom)}}function Tt(e,t,i,r,n,s){const o=Et(e,n,s),a=function(e,t,i,r){const n=i&&"auto"===r.mode?r.fontSize/16:1/0;return Math.max(0,Math.min(n,e.width/(t.columns*t.cellWidth),e.height/(t.rows*t.cellHeight)))}(o,t,i,r),l=t.columns*t.cellWidth*a,c=t.rows*t.cellHeight*a,h=Math.min(e.width,n.left)+(o.width-l)/2,d=Math.min(e.height,n.top)+(o.height-c)/2,u=s?Math.min(s.width,"overlay"===s.placement?l:Math.max(0,e.width-n.right-h-l)):0;return Object.freeze({width:e.width,height:e.height,content:Object.freeze({left:h,top:d,width:l,height:c}),scrollbar:s?Object.freeze({left:h+l-("overlay"===s.placement?u:0),top:d,width:u,height:c}):null,padding:n,cellWidth:l/t.columns,cellHeight:c/t.rows})}k(),s();var Ct=/\b(?:var|env|attr|light-dark|currentcolor|inherit|initial|unset|revert|revert-layer|default|context-fill|context-stroke|accentcolor|accentcolortext|activetext|buttonborder|buttonface|buttontext|canvas|canvastext|field|fieldtext|graytext|highlight|highlighttext|linktext|mark|marktext|selecteditem|selecteditemtext|visitedtext|activeborder|activecaption|appworkspace|background|buttonhighlight|buttonshadow|captiontext|inactiveborder|inactivecaption|inactivecaptiontext|infobackground|infotext|menu|menutext|scrollbar|threeddarkshadow|threedface|threedhighlight|threedlightshadow|threedshadow|window|windowframe|windowtext)\b/i;function St(e,t){if(null===e||"object"!=typeof e||"[object Object]"!==Object.prototype.toString.call(e))throw new TypeError(`${t} must be an options object`)}function It(e){const t=function(e={}){let t;St(e,"scrollbar.appearance");const i=(e,i)=>{if(void 0===e)return;if("string"!=typeof e||e.length>256||!e.trim()||/\\|\/\*|--/.test(e)||Ct.test(e))throw new TypeError(`${i} must be a concrete CSS color of 1-256 characters`);const r=e.trim();if(void 0===t&&(t="undefined"!=typeof OffscreenCanvas?new OffscreenCanvas(1,1).getContext("2d"):"undefined"!=typeof document?document.createElement("canvas").getContext("2d"):null),!t)throw new TypeError(`${i} requires a browser canvas to validate CSS colors`);t.fillStyle="#010203",t.fillStyle=r;const n=t.fillStyle;if(t.fillStyle="#040506",t.fillStyle=r,n!==t.fillStyle)throw new TypeError(`${i} must be a valid concrete CSS color`);return r},r=(e,t,r)=>{void 0!==e&&St(e,r);const{color:n,opacity:s=t}=e??{};if("number"!=typeof s||!Number.isFinite(s)||s<0||s>1)throw new RangeError(`${r}.opacity must be a finite number between 0 and 1`);return Object.freeze({color:i(n,`${r}.color`),opacity:s})},{track:n,thumb:s,markers:o}=e,a=r(o,1,"scrollbar.appearance.markers");return Object.freeze({track:r(n,.35,"scrollbar.appearance.track"),thumb:r(s,1,"scrollbar.appearance.thumb"),markers:Object.freeze({...a,errorColor:i(o?.errorColor,"scrollbar.appearance.markers.errorColor")})})}(e);return e=>{if(e.opacity<=0)return;const{context:i,track:r,thumb:n,colors:s}=e,o=i.globalAlpha*e.opacity;i.save();try{if(i.globalAlpha=o*t.track.opacity,i.fillStyle=t.track.color??s.track,r.width>0&&r.height>0&&i.fillRect(r.left,r.top,r.width,r.height),n.width>0&&n.height>0){const e=Math.min(2,n.width/4),r=n.width-2*e;i.globalAlpha=o*t.thumb.opacity,i.fillStyle=t.thumb.color??s.thumb,i.beginPath(),i.roundRect(n.left+e,n.top,r,n.height,Math.min(r,n.height)/2),i.fill()}i.globalAlpha=o*t.markers.opacity;for(const{marker:r,bounds:n,color:o}of e.markers)i.fillStyle=null!=r.exitCode&&0!==r.exitCode?t.markers.errorColor??o??s.error:t.markers.color??o??s.marker,r.color&&(i.fillStyle=r.color),n.width>0&&n.height>0&&i.fillRect(n.left,n.top,n.width,n.height);if(e.interaction.focused&&!e.interaction.dragging&&n.width>0&&n.height>0){i.globalAlpha=o,i.strokeStyle=t.thumb.color??s.thumb;const e=Math.min(1,n.width/2,n.height/2),r=n.width-e,a=n.height-e;i.lineWidth=e,i.beginPath(),i.roundRect(n.left+e/2,n.top+e/2,r,a,Math.min(r,a)/2),i.stroke()}}finally{i.restore()}}}var Mt=It();function Lt(e){Mt(e)}var At=Object.freeze({track:"#202020",thumb:"#999999",marker:"#aaaaaa",error:"#eeeeee",prompt:"#666666","command-line":"#888888",executing:"#bbbbbb",success:"#999999",custom:"#dddddd"});function Rt(e,t){if("custom"===e.source)return t("custom");if(null!=e.exitCode&&0!==e.exitCode)return t("error");switch(e.phase){case"prompt":return t("prompt");case"commandLine":return t("command-line");case"executing":return t("executing");case"finished":return t(0===e.exitCode?"success":"marker");default:return t("marker")}}function Pt(e){if(!1===e)return!1;if(void 0!==e&&(null===e||"object"!=typeof e||Array.isArray(e)))throw new TypeError("scrollbar must be false or an options object");const t=e??{},i=(e,t,i,r=0,n=1/0)=>{const s=void 0===e?t:e;if(!Number.isFinite(s)||sn)throw new RangeError(`scrollbar.${i} must be a finite number between ${r} and ${n}`);return s};if(void 0!==t.placement&&"overlay"!==t.placement&&"beside"!==t.placement)throw new TypeError("scrollbar.placement must be overlay or beside");if(void 0!==t.markers&&"boolean"!=typeof t.markers)throw new TypeError("scrollbar.markers must be boolean");if(void 0!==t.render&&("function"!=typeof t.render||"AsyncFunction"===t.render.constructor.name))throw new TypeError("scrollbar.render must be a synchronous function");if(void 0!==t.tooltip&&!1!==t.tooltip&&("function"!=typeof t.tooltip||"AsyncFunction"===t.tooltip.constructor.name))throw new TypeError("scrollbar.tooltip must be false or a synchronous function");return Object.freeze({placement:t.placement??"overlay",width:i(t.width,12,"width",4,64),proximity:i(t.proximity,24,"proximity"),hideDelay:i(t.hideDelay,900,"hideDelay"),fadeDuration:i(t.fadeDuration,300,"fadeDuration"),markers:t.markers??!0,...t.render?{render:t.render}:{},...void 0!==t.tooltip?{tooltip:t.tooltip}:{}})}var Ot=(e,t,i)=>Math.max(t,Math.min(i,e)),Ut=(e,t,i,r=0)=>t>=e.left-r&&t=e.top-r&&iUt(e.bounds,t,i,3)).sort((e,t)=>Math.abs(e.bounds.top+e.bounds.height/2-i)-Math.abs(t.bounds.top+t.bounds.height/2-i)||(e.marker.idt.marker.id?1:0))[0]?.marker}var Dt=class{constructor(e){this.options=e,this.motion="function"==typeof matchMedia?matchMedia("(prefers-reduced-motion: reduce)"):void 0;const t=(e,t,i,r=!1)=>{e.addEventListener(t,i,{capture:!0,passive:r}),this.listeners.push(()=>e.removeEventListener?.(t,i,{capture:!0}))};t(e.element,"pointerdown",e=>this.pointerDown(e)),t(e.element,"pointermove",e=>this.pointerMove(e)),t(e.element,"pointerup",e=>this.pointerEnd(e,!1)),t(e.element,"pointercancel",e=>this.pointerEnd(e,!0)),t(e.element,"lostpointercapture",e=>this.pointerEnd(e,!0)),t(e.element,"pointerleave",()=>{this.near=this.hovered=!1,this.clearHover(),this.activity()}),t(e.element,"wheel",e=>this.wheel(e));for(const i of["mousedown","mouseup","click","dblclick","contextmenu"])t(e.element,i,e=>{this.contentPointers.size||(null!==this.pointer||this.hit(e)||this.suppressClick&&("click"===i||"mouseup"===i))&&(this.consume(e),"click"===i&&(this.suppressClick=!1))});if(t(e.accessibility,"keydown",e=>this.keyDown(e)),t(e.accessibility,"focus",()=>{this.focused=!0,this.activity()}),t(e.accessibility,"blur",()=>{this.focused=!1,this.activity()}),this.motion){const e=()=>this.invalidate();this.motion.addEventListener("change",e),this.listeners.push(()=>this.motion?.removeEventListener("change",e))}e.accessibility.setAttribute("role","scrollbar"),e.accessibility.setAttribute("aria-label","Terminal scrollback"),e.accessibility.setAttribute("aria-orientation","vertical"),e.accessibility.style.position="absolute",e.accessibility.style.background="transparent",this.refresh()}options;context;listeners=[];motion;configuration;frameId;fadeTimer;disposed=!1;failed=!1;dormant=!1;near=!1;hovered=!1;focused=!1;lastActivityAt=-1/0;pointer=null;dragOffset=null;contentPointers=new Set;suppressClick=!1;desired=null;queued=null;requestBaseline=0;generation;wheelRemainder=0;hoverPoint=null;refresh(){if(this.disposed)return;const e=this.options.getState();e.configuration!==this.configuration&&(this.cancel(),this.configuration=e.configuration,this.failed=!1,this.lastActivityAt=this.now()),e.viewport.generation!==this.generation&&(this.endGesture(),this.desired=this.queued=null,this.generation=e.viewport.generation),null===this.pointer&&null===this.queued&&e.viewport.available&&!e.viewport.pending&&(e.viewport.top===this.desired||e.viewport.requestId>this.requestBaseline)&&(this.desired=null);const t=this.geometry(e);if(!t)return this.cancel(),this.options.accessibility.hidden=!0,this.options.accessibility.tabIndex=-1,void this.clear();const{track:i}=t,r=this.options.accessibility;r.hidden=!1,r.tabIndex=0,r.matches?.(":focus")&&(this.focused=!0),Object.assign(r.style,{left:`${i.left}px`,top:`${i.top}px`,width:`${i.width}px`,height:`${i.height}px`}),r.setAttribute("aria-valuemin","0"),r.setAttribute("aria-valuemax",String(e.viewport.liveTop)),r.setAttribute("aria-valuenow",String(this.desired??e.viewport.top)),r.setAttribute("aria-valuetext",e.viewport.following?"Live terminal output":`Scrollback row ${this.desired??e.viewport.top}`),r.setAttribute("aria-busy",String(e.viewport.pending)),this.options.onMarkerHover?.(this.hoveredMarker()),this.schedule()}activity(){this.disposed||(this.dormant=!1,this.lastActivityAt=this.now(),void 0!==this.fadeTimer&&clearTimeout(this.fadeTimer),this.fadeTimer=void 0,this.schedule())}invalidate(){this.dormant=!1,this.refresh()}cancel(){this.clearHover(),this.endGesture(),this.contentPointers.clear(),this.desired=this.queued=null,this.near=this.hovered=this.focused=this.suppressClick=!1,this.dormant=!1,this.wheelRemainder=0,void 0!==this.frameId&&cancelAnimationFrame(this.frameId),void 0!==this.fadeTimer&&clearTimeout(this.fadeTimer),this.frameId=this.fadeTimer=void 0,this.options.accessibility.hidden=!0,this.options.accessibility.tabIndex=-1,this.options.accessibility.title="",this.clear()}dispose(){if(!this.disposed){this.disposed=!0,this.cancel();for(const e of this.listeners)e();this.options.accessibility.hidden=!0,this.options.accessibility.tabIndex=-1}}now(){return performance.now()}geometry(e=this.options.getState()){return e.connected&&e.configuration?function(e,t,i,r=null){const n=e.scrollbar;if(!n||n.width<=0||n.height<=0||!t.available||"main"!==t.buffer||t.liveTop<=0)return null;const s=Math.max(1,t.totalRows-t.liveTop),o=Math.min(n.height,Math.max(24,n.height*s/t.totalRows)),a=Object.freeze({left:n.left,top:n.top+(n.height-o)*Ot(r??t.top,0,t.liveTop)/t.liveTop,width:n.width,height:o}),l=i.filter(e=>e.buffer===t.buffer&&null!==e.row&&Number.isFinite(e.row)&&e.row>=0&&e.row{const i=Math.min(3,n.height);return Object.freeze({marker:Object.freeze({...e}),bounds:Object.freeze({left:n.left,top:n.top+(n.height-i)*e.row/Math.max(1,t.totalRows-1),width:n.width,height:i})})});return Object.freeze({track:Object.freeze({...n}),thumb:a,markers:Object.freeze(l)})}(e.layout,e.viewport,e.configuration.markers?e.markers:[],this.desired):null}point(e){const t=this.options.element.getBoundingClientRect(),i=this.options.getState().layout;return{x:t.width>0?(e.clientX-t.left)*i.width/t.width:-1/0,y:t.height>0?(e.clientY-t.top)*i.height/t.height:-1/0}}hit(e){const t=this.geometry(),i=this.point(e);return!!t&&Ut(t.track,i.x,i.y)}consume(e){e.preventDefault(),e.stopImmediatePropagation()}pointerDown(e){if(this.clearHover(),this.suppressClick=!1,!this.hit(e))return void this.contentPointers.add(e.pointerId);if(this.contentPointers.size)return;if(this.consume(e),0!==e.button||null!==this.pointer)return;const t=this.geometry(),i=this.point(e);this.pointer=e.pointerId;try{this.options.element.setPointerCapture(e.pointerId)}catch(e){return this.endGesture(),void this.report(e)}if(this.options.accessibility.setAttribute("data-pointer-active","true"),this.options.accessibility.focus({preventScroll:!0}),this.focused=!0,this.activity(),Ut(t.thumb,i.x,i.y))return void(this.dragOffset=i.y-t.thumb.top);const r=zt(t.markers,i.x,i.y);if(r)return void this.jump(r.id);const n=this.options.getState().viewport;n.available&&this.navigate((this.desired??n.top)+(i.y0&&this.navigate((i.y-this.dragOffset-t.track.top)/r*e.liveTop)}return void this.activity()}if(this.contentPointers.size||0!==e.buttons)return void this.clearHover();const r=this.hoveredMarker();this.hoverPoint={clientX:e.clientX,clientY:e.clientY};const n=this.hoveredMarker();this.options.onMarkerHover?.(n),r?.marker.id!==n?.marker.id&&(this.dormant=!1,this.schedule());const s=this.options.getState().configuration,o=Ut(t.track,i.x,i.y,s?s.proximity:0),a=Ut(t.track,i.x,i.y);o===this.near&&a===this.hovered||(this.near=o,this.hovered=a,this.activity()),a&&this.consume(e)}clearHover(){this.hoverPoint=null,this.options.onMarkerHover?.(null)}hoveredMarker(){if(!this.hoverPoint||null!==this.pointer||this.contentPointers.size)return null;const e=this.geometry();if(!e)return null;const t=this.point(this.hoverPoint);if(!Ut(e.track,t.x,t.y))return null;const i=zt(e.markers,t.x,t.y);return e.markers.find(e=>e.marker.id===i?.id)??null}pointerEnd(e,t){this.contentPointers.delete(e.pointerId),this.pointer===e.pointerId&&(this.consume(e),this.suppressClick=!t,this.endGesture(),t&&(this.desired=this.queued=null),this.activity())}endGesture(){const e=this.pointer;if(this.pointer=this.dragOffset=null,this.options.accessibility.setAttribute("data-pointer-active","false"),null!==e)try{this.options.element.hasPointerCapture(e)&&this.options.element.releasePointerCapture(e)}catch(e){this.report(e)}}wheel(e){if(this.contentPointers.size||!this.hit(e))return;this.consume(e);const{viewport:t,layout:i}=this.options.getState();if(!t.available||!Number.isFinite(e.deltaY))return;const r=Math.max(1,t.totalRows-t.liveTop),n=this.options.element.getBoundingClientRect(),s=i.height>0?n.height/i.height:1;this.wheelRemainder+=e.deltaY*(2===e.deltaMode?r:1===e.deltaMode?1:1/Math.max(1,i.cellHeight*s));const o=Math.trunc(this.wheelRemainder);this.wheelRemainder-=o,0!==o&&this.navigate((this.desired??t.top)+o),this.activity()}keyDown(e){const{viewport:t}=this.options.getState();if(!this.geometry()||!t.available||e.ctrlKey||e.metaKey)return;const i=this.desired??t.top,r=Math.max(1,t.totalRows-t.liveTop);if(e.altKey&&("ArrowDown"===e.key||"ArrowUp"===e.key)){this.consume(e);const t="ArrowDown"===e.key,r=this.geometry().markers.map(e=>e.marker).filter(e=>t?e.row>i:e.row(t?1:-1)*(e.row-i.row)||(e.idi.id?1:0));return r[0]&&this.jump(r[0].id),void this.activity()}if(e.altKey)return;const n={ArrowUp:i-1,ArrowDown:i+1,PageUp:i-r,PageDown:i+r,Home:0,End:t.liveTop};Object.hasOwn(n,e.key)&&(this.consume(e),this.navigate(n[e.key]),this.activity())}jump(e){this.desired=this.queued=null;try{this.options.scrollToMarker(e).catch(e=>this.report(e))}catch(e){this.report(e)}}navigate(e){const t=this.options.getState().viewport;t.available&&Number.isFinite(e)&&(this.requestBaseline=t.requestId,this.desired=this.queued=Math.round(Ot(e,0,t.liveTop)),this.schedule())}schedule(){this.disposed||void 0!==this.frameId||!this.geometry()||this.dormant&&null===this.queued||"function"!=typeof requestAnimationFrame||(this.frameId=requestAnimationFrame(()=>{this.frameId=void 0,this.paint()}))}clear(){this.context&&(this.context.save(),this.context.setTransform(1,0,0,1,0,0),this.context.clearRect(0,0,this.options.canvas.width,this.options.canvas.height),this.context.restore())}paint(){if(this.disposed)return;const e=this.options.getState();if(!this.geometry(e)||!e.configuration)return;if(null!==this.queued){const t=this.queued;this.queued=null;try{e.viewport.available&&t>=e.viewport.liveTop?this.options.scrollToLive():this.options.scrollToRow(t)}catch(e){this.desired=null,this.report(e)}}const t=this.geometry(e);if(this.options.onMarkerHover?.(this.hoveredMarker()),this.disposed||e.configuration!==this.options.getState().configuration)return;const{canvas:i,accessibility:r}=this.options;if(r.setAttribute("aria-valuenow",String(this.desired??e.viewport.top)),!this.failed&&void 0===this.context)try{if(this.context=i.getContext("2d"),!this.context)throw new Error("The scrollbar Canvas2D context is unavailable")}catch(e){this.failed=!0,this.endGesture(),this.report(e)}if(!this.context||this.failed)return;const n=this.context,s=Ot("number"==typeof devicePixelRatio?devicePixelRatio:1,1,3),o=Math.max(1,Math.round(e.layout.width*s)),a=Math.max(1,Math.round(e.layout.height*s));i.width!==o&&(i.width=o),i.height!==a&&(i.height=a),this.clear();const l=this.now(),c=this.near||this.focused||null!==this.pointer,h="overlay"===e.configuration.placement,d=this.motion?.matches??!1,u=function(e,t,i,r,n){if("beside"===r.placement||i)return 1;const s=e-t-r.hideDelay;return s<0?1:n||0===r.fadeDuration?0:Ot(1-s/r.fadeDuration,0,1)}(l,this.lastActivityAt,c,e.configuration,d),p="function"==typeof getComputedStyle?getComputedStyle(this.options.element):null,f=e=>((e,t)=>p?.getPropertyValue?.(e).trim()||t)(`--cp-view-scrollbar-${e}`,At[e]),m=Object.freeze({...e.layout,content:Object.freeze({...e.layout.content}),padding:Object.freeze({...e.layout.padding}),scrollbar:t.track}),g=Object.freeze({...e.viewport,...e.viewport.rowIds?{rowIds:Object.freeze([...e.viewport.rowIds])}:{}}),w=Object.freeze({canvas:i,context:n,layout:m,viewport:g,...t,now:l,opacity:u,pendingTarget:this.desired,markers:Object.freeze(t.markers.map(e=>Object.freeze({...e,color:Rt(e.marker,f)}))),hoveredMarker:this.hoveredMarker(),interaction:Object.freeze({near:this.near,hovered:this.hovered,dragging:null!==this.dragOffset,focused:this.focused,lastActivityAt:this.lastActivityAt,reducedMotion:d}),colors:Object.freeze({track:f("track"),thumb:f("thumb"),marker:f("marker"),error:f("error")})});let y=!1;n.save();try{n.setTransform(s,0,0,s,0,0);const t=(e.configuration.render??Lt)(w);if(void 0!==t&&"boolean"!=typeof t)throw t instanceof Promise&&t.catch(e=>{this.disposed||this.report(e)}),new TypeError("scrollbar.render must synchronously return boolean or undefined");y=t}catch(e){this.failed=!0,this.endGesture(),this.desired=this.queued=null,void 0!==this.frameId&&cancelAnimationFrame(this.frameId),void 0!==this.fadeTimer&&clearTimeout(this.fadeTimer),this.frameId=this.fadeTimer=void 0,this.report(e)}finally{n.restore()}this.failed?this.clear():this.disposed||e.configuration!==this.options.getState().configuration||(this.dormant=!c&&0===u&&!0!==y,!0===y||h&&!c&&u>0&&l>=this.lastActivityAt+e.configuration.hideDelay?this.schedule():h&&!c&&u>0&&void 0===this.fadeTimer&&(this.fadeTimer=setTimeout(()=>{this.fadeTimer=void 0,this.schedule()},Math.min(2147483647,Math.max(0,this.lastActivityAt+e.configuration.hideDelay-l)))))}report(e){this.disposed||this.options.reportError(e)}};function Bt(e){const t=new Map;if(!e)return t;for(const i of e.split(";")){const e=i.indexOf("=");e<0||t.set(i.slice(0,e),i.slice(e+1))}return t}function qt(e){return e?Bt(e.rawParameters).get("cmdline_url")??null:null}function Nt(e){const t=document.createElement("div");t.className="hex1b-scrollbar-tooltip",t.setAttribute("role","tooltip"),t.style.cssText="padding:8px 10px;border:1px solid var(--cp-view-text-muted,#888);border-radius:6px;background:var(--cp-view-surface,#202020);color:var(--cp-view-text,#eee);font:12px/1.4 var(--cp-view-font-family,monospace);white-space:pre-wrap;overflow-wrap:anywhere";const{marker:i,details:r}=e,n=[i.label||("custom"===i.source?"Bookmark":"Terminal command")];if("command"===i.source&&(n.push(`Phase: ${i.phase??"unknown"}`),null!=i.exitCode&&n.push(`Exit code: ${i.exitCode}`),e.loading&&n.push("Loading command details..."),e.error&&n.push(`Command details unavailable: ${e.error}`),r)){const e=qt(r);if(e)try{n.push(decodeURIComponent(e))}catch(t){if(!(t instanceof URIError))throw t;n.push(`Command (invalid URL encoding): ${e}`)}else r.rawParameters?n.push(r.rawParameters):n.push("This shell did not provide command text.")}return t.textContent=n.join("\n"),t}s();var jt=class{constructor(e){this.options=e}options;target=null;result=null;fetching=!1;lifetime;observer;element=null;failed=!1;disposed=!1;update(e,t,i=Nt){if(this.disposed)return;if(!e||!1===i)return this.target=this.result=null,this.failed=!1,void this.clearView();const r=JSON.stringify({tick:e,layout:t});if(this.target?.signature===r&&this.target.renderer===i)return;const n=JSON.stringify([e.marker.id,e.marker.phase,e.marker.exitCode]);this.target?.key===n&&this.target.renderer===i||(this.result=null,this.failed=!1),this.target={tick:e,layout:t,renderer:i,key:n,signature:r},this.render(),this.loadDetails()}dispose(){this.disposed=!0,this.target=this.result=null,this.clearView()}clearView(){this.observer?.disconnect(),this.observer=void 0;const e=this.lifetime;this.lifetime=void 0,this.element=null,this.options.overlay.replaceChildren(),e?.abort()}render(){const e=this.target;if(this.clearView(),!e||this.target!==e||this.failed||this.disposed)return;const t=this.lifetime=new AbortController,i=this.result?.key===e.key?this.result:null,n=Object.freeze({marker:e.tick.marker,anchor:e.tick.bounds,layout:e.layout,details:i?.details??null,error:i?.error??null,loading:"command"===e.tick.marker.source&&!i,signal:t.signal});try{const i=e.renderer(n);if(r(i)&&"function"==typeof i.then&&Promise.resolve(i).catch(e=>{t.signal.aborted||this.options.reportError(e)}),t.signal.aborted)return;if(null!==i&&!(i instanceof HTMLElement))throw new TypeError("scrollbar.tooltip must synchronously return an HTMLElement or null");if(!i)return;this.element=i,Object.assign(i.style,{position:"absolute",boxSizing:"border-box",margin:"0",pointerEvents:"none",maxWidth:`${Math.max(0,Math.min(360,e.layout.width-16))}px`,maxHeight:`${Math.max(0,e.layout.height-16)}px`,overflow:"hidden"}),this.options.overlay.append(i),this.position(),this.observer=new ResizeObserver(()=>this.position()),this.observer.observe(i)}catch(e){this.failed=!0,this.clearView(),this.options.reportError(e)}}position(){if(!this.target||!this.element)return;const{bounds:e}=this.target.tick,{width:t,height:i}=this.target.layout,r=e.left-this.element.offsetWidth-8,n=e.top+e.height/2-this.element.offsetHeight/2;this.element.style.left=`${Math.max(0,Math.min(Math.max(8,r),t-this.element.offsetWidth-8))}px`,this.element.style.top=`${Math.max(0,Math.min(Math.max(8,n),i-this.element.offsetHeight-8))}px`}loadDetails(){const e=this.target;!e||this.fetching||this.failed||this.disposed||"command"!==e.tick.marker.source||this.result?.key===e.key||(this.fetching=!0,Promise.resolve().then(()=>this.disposed||this.target?.key!==e.key?null:this.options.getDetails(e.tick.marker.id)).then(t=>{t&&this.target?.key===e.key&&!this.disposed&&(this.result={key:e.key,details:Object.freeze({...t}),error:null},this.render())},t=>{this.target?.key!==e.key||this.disposed||(this.result={key:e.key,details:null,error:t instanceof Error?t.message:String(t)},this.render())}).finally(()=>{this.fetching=!1,this.loadDetails()}))}},$t="\n --cp-terminal-surface: #ffffff;\n --cp-terminal-text: #242424;\n --cp-terminal-text-muted: #5c5c5c;\n --cp-terminal-border-strong: #919191;\n --cp-terminal-accent: #b11f4b;\n --cp-terminal-accent-soft: rgba(177, 31, 75, 0.08);\n --cp-terminal-danger: #dc2626;\n",Ft="\n --cp-terminal-surface: #292929;\n --cp-terminal-text: #dedede;\n --cp-terminal-text-muted: #919191;\n --cp-terminal-border-strong: #5f5f5f;\n --cp-terminal-accent: #fd8ea1;\n --cp-terminal-accent-soft: rgba(253, 142, 161, 0.14);\n --cp-terminal-danger: #f87171;\n",Gt=`\n :host {\n ${$t}\n --cp-terminal-font-family: "Segoe UI", Aptos, Calibri, -apple-system, BlinkMacSystemFont, sans-serif;\n }\n @media (prefers-color-scheme: dark) { :host { ${Ft} } }\n :host-context([data-theme="light"]) { ${$t} }\n :host-context([data-theme="dark"]) { ${Ft} }\n .viewport {\n ${Object.entries(At).map(([e,t])=>`--cp-view-scrollbar-${e}: var(--cp-scrollbar-${e}, ${t});`).join("\n")}\n --cp-view-surface: var(--cp-surface, var(--cp-terminal-surface));\n --cp-view-text: var(--cp-text, var(--cp-terminal-text));\n --cp-view-text-muted: var(--cp-text-muted, var(--cp-terminal-text-muted));\n --cp-view-border-strong: var(--cp-border-strong, var(--cp-terminal-border-strong));\n --cp-view-accent: var(--cp-accent, var(--cp-terminal-accent));\n --cp-view-accent-soft: var(--cp-accent-soft, var(--cp-terminal-accent-soft));\n --cp-view-danger: var(--cp-danger, var(--cp-terminal-danger));\n --cp-view-font-family: var(--cp-font-family, var(--cp-terminal-font-family));\n }\n`;A(),s();var Wt=["status","mode","requestId","active","pending","canExtend","text","message","copying","copyError"],_t=["available","following","pending","generation","buffer","top","liveTop","totalRows","requestId","navigationError"],Ht=["columns","rows","cellWidth","cellHeight","mouseTracking"],Kt=["row","startColumn","endColumn"],Vt=(e,t,i)=>i.every(i=>e?.[i]===t?.[i]);function Xt(e,t,i){const r=i.width/t.columns,n=i.height/t.rows;return e.map(e=>Object.freeze({left:e.startColumn*r,top:e.row*n,width:(e.endColumn-e.startColumn)*r,height:n}))}var Yt=class{#L;#A;#R;#P;#O;#U;#z;#D;#B;#q=!1;#N=!1;constructor({element:e,overlay:t,button:i,signal:n,getState:s,runAction:o,onSelectionUI:a,reportError:l}){this.#L=e,this.#A=t,this.#R=i,this.#P=n,this.#O=s,this.#U=o,this.#z=l,a&&e.addEventListener("selectionui",e=>{if(!this.#B||e!==this.#B)return;const t=this.#D;try{const e=a(this.#B);if(void 0!==e)throw r(e)&&"function"==typeof e.then&&Promise.resolve(e).catch(e=>{n.aborted||this.#D!==t||l(e)}),new TypeError("onSelectionUI must finish synchronously; use preventDefault() and signal for UI ownership")}catch(t){e.preventDefault(),l(t)}},{signal:n})}refresh(e=!1){this.#N||=e,this.#q||this.#P.aborted||(this.#q=!0,queueMicrotask(()=>{if(this.#q=!1,this.#P.aborted)return;const e=this.#O(),t=this.#N;if(this.#N=!1,!t&&(i=this.#D,r=e,i&&i.connected===r.connected&&i.readOnly===r.readOnly&&Vt(i.selection,r.selection,Wt)&&Vt(i.viewport,r.viewport,_t)&&Vt(i.geometry,r.geometry,Ht)&&Vt(i.canvasSize,r.canvasSize,["width","height"])&&i.selection.ranges.length===r.selection.ranges.length&&i.selection.ranges.every((e,t)=>Vt(e,r.selection.ranges[t],Kt))))return;var i,r;const n=Object.freeze({...e,selection:Object.freeze({...e.selection,ranges:Object.freeze(e.selection.ranges.map(e=>Object.freeze({...e})))}),viewport:Object.freeze({...e.viewport,rowIds:Object.freeze([...e.viewport.rowIds??[]])}),geometry:Object.freeze({...e.geometry}),canvasSize:Object.freeze({...e.canvasSize})});this.#D=n;const s=Object.freeze({...n,overlay:this.#A,signal:this.#P,runAction:this.#U,rects:Object.freeze(Xt(n.selection.ranges,n.geometry,n.canvasSize))}),o=new CustomEvent("selectionui",{cancelable:!0,detail:s});if(this.#B=o,this.#z(null),this.#L.dispatchEvent(o),this.#P.aborted)return;const a=n.selection;this.#R.hidden=o.defaultPrevented||["none","unavailable"].includes(a.status),this.#R.disabled=!n.connected||"valid"!==a.status||a.copying;const l=a.copying?"Copying…":"Copy";this.#R.textContent!==l&&(this.#R.textContent=l)}))}};function Jt(e){if(/[\u0000-\u0020\u007f]/u.test(e)||!URL.canParse(e))return null;const t=new URL(e);return["https:","http:","mailto:"].includes(t.protocol)?t.href:null}var Zt=class{#j=new Map;update(e){this.#j.clear();const t=new Map;for(const i of e){t.has(i.uri)||t.set(i.uri,Jt(i.uri));const e=t.get(i.uri);if(!e)continue;const r=this.#j.get(i.row)??[];r.push({...i,uri:e}),this.#j.set(i.row,r)}}at(e){return this.#j.get(e.y)?.find(t=>e.x>=t.startColumn&&e.xL.cells)throw new RangeError("Link grid exceeds cell limit");const s=[];let o="",a=[],l=new Set,c=0,h=0,d="unknown";const u=e=>e>=0&&!!(1024&(n[(e+1)*i-1]?.attributes??0)),p=e=>{const i=Object.freeze({text:o,mode:t,start:d,end:e}),r=new Set([o.length]);for(const e of Qt.segment(o))r.add(e.index);s.push({chunk:i,spans:a,unsafe:l,boundaries:r,key:JSON.stringify([t,c,d,e,o])}),o="",a=[],l=new Set};for(let e=0;e=1&&s<=2&&t+s<=i&&!(r&&(64&r.attributes||1109742===r.text.codePointAt(0)))&&(2!==s||0===n[e*i+t+1]?.width),d=c?r?.text||" ":"\0";if(h+d.length>L.totalText||o.length+d.length>L.chunk)throw new RangeError("Link text exceeds chunk or viewport limit");const u=o.length;o+=d,h+=d.length,c?a.push({start:u,end:o.length,row:e,startColumn:t,endColumn:t+s}):(l.add(u),l.add(o.length)),c&&2===s&&t++}const s=u(e);if(s||l.add(o.length),"physicalRow"===t||"logicalLine"===t&&!s||e===r-1)p(s?"clipped":"unknown");else if(!s){if(++h>L.totalText||o.length+1>L.chunk)throw new RangeError("Link text exceeds limit");o+="\n"}}return s}function ti(e,t,i){const r=t+i,{chunk:n}=e;if(i<=0||t<0||r>n.text.length||!e.boundaries.has(t)||!e.boundaries.has(r)||0===t&&"complete"!==n.start||r===n.text.length&&"complete"!==n.end||n.text.slice(t,r).includes("\0"))return null;for(const i of e.unsafe)if(i===t||i===r||i>t&&i=r)continue;if(i.startr)return null;const e=s.at(-1);e&&e.row===i.row&&e.endColumn===i.startColumn?e.endColumn=i.endColumn:s.push({row:i.row,startColumn:i.startColumn,endColumn:i.endColumn})}return s.length?s:null}G();var ii=class{#$;#F;#G=null;#W;#_=null;#H=null;#K=[];#V=new Set;#X=new Map;#Y=new Map;#J=0;#Z=0;#Q=0;#o=0;#ee=!1;#te=!1;#ie=!1;constructor(e){this.#$={...e,actions:new Set(e.actions)}}configure(e){const t=O({detection:e},this.#$.actions);this.#ee||(this.#F=!1!==t&&t.detection,this.#V.clear(),this.#te=!1,this.clear())}update(e){if(this.#ee||e.revisionObject.freeze({...t,activation:Object.freeze({...t.activation,revision:e})}))))}clear(){this.#oe(),this.#Z++,this.#H=null,this.#X.clear(),this.#Y.clear(),this.#J=0,this.#ee||this.#re([])}dispose(){this.#ee||(this.clear(),this.#ee=!0)}#re(e){this.#K=Object.freeze([...e]),this.#$.onChange(this.#o,this.#K)}#ne(e,t,i){this.#$.onError(Object.freeze({code:e,ruleId:t,revision:this.#o,message:i instanceof Error?i.message:String(i)}))}#oe(){clearTimeout(this.#W),this.#W=void 0,this.#G&&(this.#G.terminate(),this.#G=null),this.#_=null}#ae(e){const t=this.#_?.rule.id??null;this.#oe(),this.#te=!0,this.#re([]),this.#ne("worker",t,e)}#se(){if(!(this.#ie||this.#ee||this.#_)&&this.#H&&this.#F&&!this.#te){this.#ie=!0;try{const e=this.#H;for(const t of this.#F.rules){if(!t.enabled||this.#V.has(t.id))continue;const i=(e.chunks.get(t.text??"logicalLine")??[]).filter(e=>!this.#X.has(this.#le(t,e)));if(i.length){try{if(!this.#G){const e=new Worker(this.#$.workerUrl??pt("link-detection"),{type:"module",name:"hex1b-link-detection"});this.#G=e,e.addEventListener("message",t=>{this.#G===e&&this.#ce(t.data)}),e.addEventListener("error",t=>{this.#G===e&&(t.preventDefault(),this.#ae(t.message))}),e.addEventListener("messageerror",()=>{this.#G===e&&this.#ae("Invalid detection worker message")})}const r=++this.#Q;this.#_={id:r,work:e,rule:t,keys:new Map(i.map(e=>[e.key,this.#le(t,e)]))},this.#W=setTimeout(()=>{this.#_?.id===r&&(this.#oe(),this.#V.add(t.id),this.#ne("timeout",t.id,`Link rule exceeded ${L.timeoutMs} ms`),this.#se())},L.timeoutMs);const n={id:r,rule:t.builtin?{builtin:t.builtin}:{source:t.pattern.source,flags:t.pattern.flags},chunks:i.map(({chunk:e,key:t})=>({key:t,text:e.text}))};this.#G.postMessage(n)}catch(e){this.#ae(e)}return}}this.#he(e)}finally{this.#ie=!1}}}#le(e,t){return JSON.stringify([e.id,t.key])}#ce(e){const t=this.#_;if(t&&e?.id===t.id&&!this.#ee){if(clearTimeout(this.#W),this.#W=void 0,this.#_=null,e.error)this.#V.add(t.rule.id),this.#ne(e.error,t.rule.id,e.message??"Link scan failed");else{if(!function(e,t){if(!Array.isArray(e.results)||e.results.length!==t.keys.size)return!1;const i=new Set;let r=0,n=0;for(const s of e.results){if(!s||!t.keys.has(s.key)||i.has(s.key)||!Array.isArray(s.matches))return!1;if(i.add(s.key),r+=s.matches.length,r>L.matches)return!1;for(const e of s.matches){if(!e||!Number.isSafeInteger(e.index)||e.index<0||"string"!=typeof e.text||!e.text.length||!Array.isArray(e.captures)||!e.captures.every(e=>void 0===e||"string"==typeof e)||!e.groups||"object"!=typeof e.groups||Array.isArray(e.groups)||!Object.values(e.groups).every(e=>void 0===e||"string"==typeof e))return!1;if(n+=D(e),n>L.resultText)return!1}}return!0}(e,t))return void this.#ae("Malformed detection response");for(const i of e.results){const e=t.keys.get(i.key),r=e.length+i.matches.reduce((e,t)=>e+D(t),0);this.#J+=r-(this.#Y.get(e)??0),this.#Y.set(e,r),this.#X.set(e,i.matches)}}this.#H!==t.work&&this.#de(),this.#se()}}#he(e){if(!this.#F||this.#H!==e)return;const t=[...e.snapshot.hyperlinks],i=[],r=e=>e.some(e=>t.some(t=>e.row===t.row&&e.startColumnt.startColumn));for(const n of this.#F.rules){if(!n.enabled||this.#V.has(n.id))continue;const s=[],o=[];try{for(const t of e.chunks.get(n.text??"logicalLine")??[])for(const a of this.#X.get(this.#le(n,t))??[]){const l=ti(t,a.index,a.text.length);if(!l||r(l))continue;const c=Object.freeze({...a,captures:Object.freeze([...a.captures]),groups:Object.freeze({...a.groups}),chunk:t.chunk}),h=n.resolve?n.resolve(c):{target:a.text};if(this.#H!==e||this.#ee)return;if(null===h)continue;if("object"!=typeof h||void 0===h||"then"in h||"string"!=typeof h.target)throw new TypeError("Resolver must return a synchronous link resolution or null");const d=void 0===h.action?n.action:h.action;if(R(d,this.#$.actions),i.length+s.length>=L.matches)throw new RangeError("Resolved link limit exceeded");const u=Object.freeze({source:"detected",ruleId:n.id,kind:n.builtin?"url"===n.builtin||"uri"===n.builtin?"uri":"path":n.kind,text:a.text,target:h.target,ranges:Object.freeze(l.map(e=>Object.freeze(e))),revision:e.revision,data:h.data});s.push(Object.freeze({id:JSON.stringify([e.identity,n.id,l[0].row,l[0].startColumn,a.index]),action:d,activation:u})),o.push(...l)}i.push(...s),t.push(...o)}catch(t){if(this.#V.add(n.id),this.#ne(t instanceof RangeError?"limit":"resolver",n.id,t),this.#H!==e||this.#ee)return}}this.#de(),this.#re(i)}#de(){for(;this.#X.size>L.cacheEntries||this.#J>L.cacheText;){const e=this.#X.keys().next().value;this.#X.delete(e),this.#J-=this.#Y.get(e),this.#Y.delete(e)}}};function ri(e,t,i){const r=e.querySelector(t);if(!(r instanceof i))throw new Error(`Missing terminal element: ${t}`);return r}z(),s();var ni=class e{element;#$;#ue;#G;#pe;#fe;#me;#ge;#we;#ye=new AbortController;#ve={width:0,height:0};#be=wt();#ke={columns:80,rows:24,cellWidth:10,cellHeight:20,mouseTracking:0};#xe={id:null,primaryId:null,isPrimary:!1};#Ee=!1;#Te=!1;#Ce;#ee=!1;#Se=!1;#Ie;#Me;#Le;#Ae;#Re=Promise.withResolvers();#Pe;#Oe={};#Ue="";#ze="";#De=!1;#Be={state:"none",percentage:null};#qe={phase:"unknown",lastExitCode:null};#Ne={uri:null,host:null,path:null};#je=null;#$e=!1;#g;#Fe;#Ge;#We;#_e;#He;#Ke;#Ve=!1;#Xe=!1;#Ye;#Je;#Ze="";#Qe=0;#et=!1;#tt;#f;#it=!1;#rt=0;#nt;#st;#ot="";#at={width:0,height:0};#lt=new Zt;#K;#ct;#ht=1;#dt=0;#ut=0;#pt;#ft=new Map;#mt=[];#gt=[];#wt=new Map;#yt;#vt;static async mount(t,i){if(!(t instanceof HTMLElement))throw new TypeError("A terminal container HTMLElement is required");if(!i?.url)throw new TypeError("A terminal WebSocket URL is required");if(i.signal?.aborted)throw i.signal.reason;if(!("webgpu"!==p(i.renderer)||window.isSecureContext&&navigator.gpu))throw new Error("The requested WebGPU renderer requires WebGPU over HTTPS or localhost");if(!(window.Worker&&window.ResizeObserver&&window.OffscreenCanvas&&HTMLCanvasElement.prototype.transferControlToOffscreen))throw new Error("WebTerminal requires module workers, ResizeObserver, and a transferable OffscreenCanvas");const r=new e(i);try{return await Promise.all([r.#Re.promise,Promise.resolve().then(()=>r.#l(t))]),r}catch(e){throw r.dispose(),e}}constructor(e){if(this.#$=e,void 0!==e.readOnly&&"boolean"!=typeof e.readOnly)throw new TypeError("readOnly must be a boolean");if(this.#Ce=e.readOnly??!1,this.#ue=p(e.renderer),this.#Ge=xt(e.padding),this.#We=Pt(e.scrollbar),this.#_e=Tt(this.#ve,this.#ke,!1,this.#be,this.#Ge,this.#We),!(void 0===e.workerUrl||e.workerUrl instanceof URL||"string"==typeof e.workerUrl&&e.workerUrl.trim()))throw new TypeError("workerUrl must be a nonempty URL string or URL");if(!(void 0===e.linkDetectionWorkerUrl||e.linkDetectionWorkerUrl instanceof URL||"string"==typeof e.linkDetectionWorkerUrl&&e.linkDetectionWorkerUrl.trim()))throw new TypeError("linkDetectionWorkerUrl must be a nonempty URL string or URL");if(void 0!==e.onLinkDetectionError&&"function"!=typeof e.onLinkDetectionError)throw new TypeError("onLinkDetectionError must be a function");if(void 0!==e.onSelectionUI&&("function"!=typeof e.onSelectionUI||"AsyncFunction"===e.onSelectionUI.constructor.name))throw new TypeError("onSelectionUI must be a synchronous event handler");this.#tt=new at(e),this.#f=new Map(Object.entries(e.actions??{})),this.#K=O(e.links,new Set(this.#f.keys())),this.#ct=new ii({workerUrl:void 0===e.linkDetectionWorkerUrl?void 0:new URL(e.linkDetectionWorkerUrl,location.href),actions:new Set(this.#f.keys()),onChange:(e,t)=>{!this.#ee&&this.#Ee&&e===this.#dt&&(this.#mt=t,this.#bt([]),this.#kt())},onError:e=>{if(!this.#ee)try{this.#$.onStatus?.(`Link detection: ${e.message}`,"error")}finally{this.#$.onLinkDetectionError?.(e)}}}),this.#ct.configure(!!this.#K&&this.#K.detection),this.#g=new kt(e=>this.#e(e),()=>this.#xt()),this.#Fe=new f(e=>this.#e(e),()=>this.#Et()),this.element=document.createElement("div"),this.element.className="hex1b-terminal",this.element.tabIndex=-1,this.element.style.cssText="width:100%;height:100%;min-width:0;min-height:0;contain:strict"}get geometry(){return{...this.#ke}}get peer(){return{...this.#xe}}get connected(){return this.#Ee}get readOnly(){return this.#Ce}get title(){return this.#ze}get progress(){return{...this.#Be}}get shellIntegration(){return{...this.#qe}}get workingDirectory(){return{...this.#Ne}}get commandMark(){return this.#je?{...this.#je}:null}get stats(){return{...this.#Oe}}get screenText(){return this.#Ue}get sizing(){return{...this.#be}}get layout(){return this.#_e}get padding(){return this.#Ge}get scrollbar(){return this.#We}get markers(){return this.#Fe.markers}get inputBindings(){return this.#tt.bindings}get viewport(){const e=this.#g.viewport;return{...e,followTail:e.following,offset:e.available?e.liveTop-e.top:0}}get selection(){const e=this.#g.selection;return{...e,active:"valid"===e.status,pending:"pending"===e.status,copying:this.#et,copyError:this.#Ze}}#l(e){if(this.#$.signal?.aborted)throw this.#$.signal.reason;const t=new URL(this.#$.url,location.href);if("https:"===t.protocol&&(t.protocol="wss:"),"http:"===t.protocol&&(t.protocol="ws:"),!["ws:","wss:"].includes(t.protocol))throw new TypeError("A ws: or wss: URL is required");const i=void 0===this.#$.scale||"auto"===this.#$.scale?Math.min(3,Math.max(.5,window.devicePixelRatio||1)):this.#$.scale;if(!Number.isFinite(i)||i<.5||i>3)throw new RangeError("Backing scale must be 0.5-3 or 'auto'");const r=h(this.#$.font,location.href);this.#be=wt(this.#$.sizing);const s=this.element.attachShadow({mode:"open"});s.innerHTML=`\n \n
\n \n \n \n \n
\n \n
\n \n
\n \n \n \n
`,this.#pe=ri(s,".surface",HTMLDivElement),this.#fe=ri(s,"canvas",HTMLCanvasElement),this.#me=ri(s,"textarea",HTMLTextAreaElement),this.#Ye=ri(s,".highlights",HTMLDivElement),this.#Je=ri(s,".inspection",HTMLDivElement),this.#fe.id=`hex1b-content-${o()}`;const a=ri(s,".scrollbar-accessibility",HTMLDivElement);a.setAttribute("aria-controls",this.#fe.id);const l=document.createElement("div");l.slot="scrollbar-tooltip",l.className="hex1b-scrollbar-tooltip-overlay",l.style.cssText="position:relative;width:100%;height:100%;pointer-events:none",this.element.append(l),this.#Ke=new jt({overlay:l,getDetails:e=>this.getCommandMarkDetails(e),reportError:e=>{this.#Ze=`Scrollbar tooltip: ${n(e)}`,this.#Tt(),this.#$.onStatus?.(this.#Ze,"error")}}),this.#He=new Dt({element:ri(s,".viewport",HTMLDivElement),canvas:ri(s,".scrollbar-canvas",HTMLCanvasElement),accessibility:a,getState:()=>({layout:this.layout,viewport:this.viewport,markers:this.markers,connected:this.#Ee,configuration:this.#We}),scrollToRow:e=>this.scrollToRow(e),scrollToLive:()=>this.scrollToLive(),scrollToMarker:e=>this.scrollToMarker(e),onMarkerHover:e=>this.#Ke?.update(e,this.layout,!!this.#We&&this.#We.tooltip),reportError:e=>{this.#Ze=`Scrollbar: ${n(e)}`,this.#Tt(),this.#$.onStatus?.(this.#Ze,"error")}}),this.#st=document.createElement("div"),this.#st.slot="selection-ui",this.#st.className="hex1b-selection-overlay",this.#st.style.cssText="position:relative;width:100%;height:100%;pointer-events:none",this.element.append(this.#st),this.#nt=new Yt({element:this.element,overlay:this.#st,button:ri(this.#Je,".copy-selection",HTMLButtonElement),signal:this.#ye.signal,getState:()=>({selection:this.selection,viewport:this.viewport,geometry:this.geometry,canvasSize:this.#at,connected:this.#Ee,readOnly:this.#Ce}),runAction:this.runAction.bind(this),onSelectionUI:this.#$.onSelectionUI,reportError:e=>{this.#ot=e?`Selection UI failed: ${n(e)}`:"",this.#st.hidden=!!e,this.#Tt(),e&&this.#$.onStatus?.(this.#ot,"error")}}),this.#nt.refresh(),this.#me.setAttribute("aria-label",this.#$.label||"Terminal input. Click outside to use page controls."),this.#me.disabled=!0,e.append(this.element);const c=e=>{try{this.#Ze="",e()}catch(e){this.#Ze=n(e),this.#xt()}};this.#ge=ut(this.#fe,e=>this.#Ct(e),()=>this.focus(),{state:()=>({historical:!this.viewport.following||this.viewport.pending,readOnly:this.#Ce,selection:this.selection}),begin:(e,t)=>c(()=>this.#g.begin(e,t)),extend:e=>c(()=>this.#g.extend(e)),scroll:(e,t)=>c(()=>{this.#g.scroll(e,t),this.#He?.activity()}),end:e=>this.#g.endGesture(e),resolve:e=>this.#St(e),execute:(e,t)=>this.#It(e,t),hyperlink:e=>this.#Mt(e),hoverHyperlink:e=>{this.#yt=e?.id,this.#K&&this.#K.detection&&"hover"===this.#K.detection.decoration&&this.#kt()},openHyperlink:(e,t)=>this.#Lt(e,t)}),this.#At(),ri(this.#Je,".return-live",HTMLButtonElement).addEventListener("click",()=>{this.runAction(et.ScrollToLive).catch(e=>this.#Rt(e))},{signal:this.#ye.signal}),ri(this.#Je,".copy-selection",HTMLButtonElement).addEventListener("click",()=>{this.runAction(et.CopySelection).catch(e=>this.#Rt(e))},{signal:this.#ye.signal}),ri(s,".viewport",HTMLDivElement).addEventListener("pointerdown",e=>{e.composedPath().includes(this.#st)||e.target instanceof Element&&e.target.closest("button")||e.target===this.#fe||e.target===this.#me||(e.preventDefault(),this.focus())},{signal:this.#ye.signal}),this.#we=new ResizeObserver(e=>{const{width:t,height:i}=e[0].contentRect,r=t!==this.#ve.width||i!==this.#ve.height;this.#ve={width:t,height:i},this.#Pt(),r&&this.#Ot()}),this.#we.observe(e),window.addEventListener("pagehide",()=>this.dispose(),{signal:this.#ye.signal}),this.#$.signal?.addEventListener("abort",()=>this.dispose(),{once:!0,signal:this.#ye.signal}),this.#Pe=setTimeout(()=>this.#Ut(new Error("Timed out waiting for the terminal's first frame")),3e4),this.#G=void 0===this.#$.workerUrl?new Worker(pt("terminal"),{type:"module",name:"Hex1b WebTerminal"}):new Worker(new URL(this.#$.workerUrl,location.href),{type:"module",name:"Hex1b WebTerminal"}),this.#G.addEventListener("message",e=>this.#zt(e.data)),this.#G.addEventListener("error",e=>{e.preventDefault(),this.#Ut(new Error(e.message||"Terminal worker failed"))}),this.#G.addEventListener("messageerror",()=>this.#Ut(new Error("Terminal worker message could not be decoded")));const d=this.#fe.transferControlToOffscreen();this.#Dt({type:"init",canvas:d,url:t.href,scale:i,font:r,renderer:this.#ue},[d]),this.#Bt()}#zt(e){if(!this.#ee)if("connected"===e.type)this.#Ee=!0,this.#me.disabled=!this.#qt();else if("closed"===e.type){if(this.#Te)return;this.#Te=!0,clearTimeout(this.#Pe);try{this.#Nt(),this.#ee||this.#$.onClose?.(Object.freeze({...e.details}))}finally{this.#Re.reject(new Error(`Terminal WebSocket closed (${e.details.code}${e.details.reason?`: ${e.details.reason}`:""}) before mounting completed`))}}else if("status"===e.type)"error"===e.level&&(this.#Nt(),this.#Re.reject(new Error(e.message))),this.#$.onStatus?.(e.message,e.level);else if("geometry"===e.type){this.#dt=e.revision,this.#ft.clear();for(const t of e.hyperlinks){const e=this.#ft.get(t.row)??[];e.push(t),this.#ft.set(t.row,e)}this.#bt([]),this.#vt=void 0;const t=!this.#Se,i=t||["columns","rows","cellWidth","cellHeight","mouseTracking"].some(t=>this.#ke[t]!==e[t]);this.#ke={columns:e.columns,rows:e.rows,cellWidth:e.cellWidth,cellHeight:e.cellHeight,mouseTracking:e.mouseTracking},this.#Se=!0;const r=this.#xe;if(this.#xe=e.peer,this.#me.disabled=!this.#qt(),this.#qt()&&document.activeElement===this.element&&!this.element.shadowRoot?.activeElement&&this.focus(),this.#lt.update(e.hyperlinks),(i||r.isPrimary!==this.#xe.isPrimary)&&this.#Pt(),this.#xe.isPrimary||(clearTimeout(this.#Ie),this.#Ie=void 0,this.#Me=void 0),(t||!r.isPrimary&&this.#xe.isPrimary)&&this.#Ot(!0),this.#Me===`${e.columns}x${e.rows}`&&(this.#Me=void 0),Object.hasOwn(e,"history")&&(this.#Ue=e.text,this.#Fe.accept(e.history,e.revision),this.#g.accept(e.history,e.revision)),e.linkGeneration===this.#ht&&(e.linkSnapshot?this.#jt(e.linkSnapshot):(this.#pt&&(this.#pt={...this.#pt,revision:e.revision}),this.#ct.advance(e.revision))),this.#ge?.update(e.columns,e.rows,e.mouseTracking),i&&this.#$.onGeometry?.(this.geometry),t&&this.#$.onSizingChange?.(this.sizing),r.id===this.#xe.id&&r.primaryId===this.#xe.primaryId&&r.isPrimary===this.#xe.isPrimary||this.#$.onRoleChange?.(this.peer),!this.#ee&&this.#Ee&&(null!==this.#xe.id||this.#xe.isPrimary)){const t=!this.#De||this.#ze!==e.title,i=!this.#$e||this.#Be.state!==e.progress.state||this.#Be.percentage!==e.progress.percentage,r=!this.#$e||this.#qe.phase!==e.shellIntegration.phase||this.#qe.lastExitCode!==e.shellIntegration.lastExitCode,n=!this.#$e||this.#Ne.uri!==e.workingDirectory.uri,s=!this.#$e||this.#je?.phase!==e.commandMark?.phase||this.#je?.exitCode!==e.commandMark?.exitCode||this.#je?.rawParameters!==e.commandMark?.rawParameters;this.#ze=e.title,this.#De=!0,this.#Be={...e.progress},this.#qe={...e.shellIntegration},this.#Ne={...e.workingDirectory},this.#je=e.commandMark?{...e.commandMark}:null,this.#$e=!0,t&&this.#$.onTitleChange?.(this.#ze),!this.#ee&&i&&this.#$.onProgressChange?.(this.progress),!this.#ee&&r&&this.#$.onShellIntegrationChange?.(this.shellIntegration),!this.#ee&&n&&this.#$.onWorkingDirectoryChange?.(this.workingDirectory),!this.#ee&&s&&this.#$.onCommandMarkChange?.(this.commandMark)}}else if("linkSnapshot"===e.type)e.generation===this.#ht&&e.snapshot.revision===this.#dt&&this.#jt(e.snapshot);else if("linkDecorations"===e.type){const t=this.#vt;this.#Ee&&t&&t.serial===e.serial&&t.generation===e.generation&&e.generation===this.#ht&&t.revision===e.revision&&e.revision===this.#dt&&(this.#vt=void 0,this.#bt(t.links),this.#ge?.refresh())}else"history"===e.type?(this.#Ue=e.text,this.#Fe.accept(e.history,e.revision),this.#g.accept(e.history,e.revision)):"stats"===e.type&&(this.#Oe=e.stats,void 0!==e.text&&(this.#Ue=e.text),e.stats.revision>0&&this.#De&&this.#$e&&this.#Ee&&(null!==this.#xe.id||this.#xe.isPrimary)&&(clearTimeout(this.#Pe),this.#Re.resolve(this)),this.#$.onStats?.(this.stats,e.text))}#Pt(){const e=Tt(this.#ve,this.#ke,this.#xe.isPrimary,this.#be,this.#Ge,this.#We),t=JSON.stringify(e)!==JSON.stringify(this.#_e);this.#_e=e;const{width:i,height:r,left:n,top:s}=e.content;this.#pe.style.width=`${i}px`,this.#pe.style.height=`${r}px`,this.#pe.style.left=`${n}px`,this.#pe.style.top=`${s}px`;const o=getComputedStyle(this.#pe);this.#at={width:Number.parseFloat(o.width),height:Number.parseFloat(o.height)},this.#nt?.refresh(),this.#He?.refresh(),t&&!this.#Ve&&(this.#Ve=!0,queueMicrotask(()=>{this.#Ve=!1,this.#ee||this.#$.onLayoutChange?.(this.layout)}));const a=window.devicePixelRatio||1;this.#Dt({type:"viewport",width:Math.ceil(i*a),height:Math.ceil(r*a)})}#$t(){return function(e,t,i){if("fixed"===i.mode)return{columns:i.columns,rows:i.rows};if(e.width<=0||e.height<=0)return null;const r=i.fontSize/16;return{columns:Math.max(1,Math.min(300,Math.floor(e.width/(t.cellWidth*r)))),rows:Math.max(1,Math.min(100,Math.floor(e.height/(t.cellHeight*r))))}}(Et(this.#ve,this.#Ge,this.#We),this.#ke,this.#be)}#Ot(e=!1){("fixed"!==this.#be.mode||e)&&this.#qt()&&this.#xe.isPrimary&&void 0===this.#Ie&&!this.#ee&&(this.#Ie=setTimeout(()=>{this.#Ie=void 0;const e=this.#$t();if(!e||!this.#xe.isPrimary||!this.#qt())return;const t=`${e.columns}x${e.rows}`;e.columns===this.#ke.columns&&e.rows===this.#ke.rows||t===this.#Me||this.resize(e.columns,e.rows)},50))}#Dt(e,t=[]){this.#G?.postMessage(e,t)}#e(e){if(!this.#Ee||this.#ee)throw new Error("Terminal view is not connected");if(this.#Ce&&["input","paste","key","mouse","resize","requestPrimary"].includes(e.type))throw new Error("Terminal view does not accept input");this.#Dt({type:"command",command:e})}#Ct(e){!this.#ee&&this.#qt()&&(!function(e){if(b.encode(JSON.stringify(e)).byteLength>y.commandBytes)throw new RangeError("Terminal input exceeds the 64 KiB message limit. Send a smaller amount of text.")}(e),this.#rt++,["input","paste","key"].includes(e.type)&&this.viewport.available&&(this.#ge?.cancel(),"none"!==this.selection.status&&this.clearSelection(),this.viewport.following&&!this.viewport.pending||this.scrollToLive()),this.#e(e))}#xt(){const e=this.viewport,t=this.selection;if(this.#ge?.refresh(),"invalidated"===t.status&&this.#ge?.cancel(),this.#Ye){this.#Ye.replaceChildren(...t.ranges.map(e=>{const t=document.createElement("span");return t.className="highlight",t.setAttribute("part","selection-highlight"),t.style.cssText=`left:${e.startColumn/this.#ke.columns*100}%;top:${e.row/this.#ke.rows*100}%;width:${(e.endColumn-e.startColumn)/this.#ke.columns*100}%;height:${100/this.#ke.rows}%`,t}));const i=ri(this.#Je,".return-live",HTMLButtonElement);i.hidden=!e.available||e.following&&!e.pending,i.disabled=!this.#Ee,this.#Tt()}this.#ee||this.#nt?.refresh(),this.#He?.refresh(),this.#$.onViewportChange?.(e),this.#$.onSelectionChange?.(t)}scrollLines(e){this.#g.scroll(e),this.#He?.activity()}scrollToRow(e){this.#g.scrollTo(e),this.#He?.activity()}scrollToLive(){this.#g.live(),this.#He?.activity()}scrollToMarker(e){return this.#He?.activity(),this.#Fe.jump(e)}addMarker(e){return this.#Fe.add(e)}removeMarker(e){return this.#Fe.remove(e)}getCommandMarkDetails(e){return this.#Fe.details(e)}#Et(){this.#He?.activity(),this.#Xe||(this.#Xe=!0,queueMicrotask(()=>{this.#Xe=!1,this.#ee||this.#$.onMarkersChange?.(this.markers)}))}setPadding(e){if(this.#ee)throw new Error("Terminal view is disposed");this.#Ge=xt(e),this.#Pt(),this.#Ot(!0)}setScrollbar(e){if(this.#ee)throw new Error("Terminal view is disposed");this.#We=Pt(e),this.#He?.cancel(),this.#Pt(),this.#Ot(!0)}refreshScrollbar(){if(this.#ee)throw new Error("Terminal view is disposed");this.#He?.invalidate()}clearSelection(){this.#Ze="",this.#g.clear()}refreshSelectionUI(){if(this.#ee)throw new Error("Terminal view is disposed");this.#nt?.refresh(!0)}#Tt(){if(!this.#Je)return;const e=this.selection,t=this.viewport,i=ri(this.#Je,".inspection-message",HTMLSpanElement);i.textContent=this.#ot||this.#Ze||t.navigationError||("unavailable"===e.status?"":e.message)||(t.available&&!t.following?t.liveTop-t.top+" rows above live":""),i.hidden=!i.textContent,i.dataset.level=this.#ot||this.#Ze||t.navigationError||"invalidated"===e.status?"error":"info"}#Rt(e){const t=e instanceof Error?e:new Error(String(e));this.#Ze=`Input action failed: ${t.message}`,this.#xt(),this.#$.onInputError?.(t)}async copySelection({clear:e=!1}={}){if("boolean"!=typeof e)throw new TypeError("Copy clear must be a boolean");const t=++this.#Qe,i=this.selection.requestId,r=this.viewport.generation;this.#Ze="";try{if(!this.#Ee||this.#ee)throw new Error("Terminal view is not connected");if(!navigator.clipboard?.write||"function"!=typeof ClipboardItem)throw new Error("Clipboard writing is unavailable. Use a secure browser context with clipboard permission.");const n=this.#g.copy();this.#et=!0,this.#xt();const s=new ClipboardItem({"text/plain":n.then(e=>new Blob([e],{type:"text/plain"}))}),[o]=await Promise.all([n,navigator.clipboard.write([s])]);return e&&t===this.#Qe&&"valid"===this.selection.status&&this.selection.requestId===i&&this.viewport.generation===r&&this.clearSelection(),o}catch(e){throw t===this.#Qe&&(this.#g.cancelCopy(e),this.#Ze=`Copy failed: ${n(e)}`),e}finally{t===this.#Qe&&(this.#et=!1),this.#xt()}}#qt(){return this.#Ee&&this.#Se&&!this.#Ce&&(null!==this.#xe.id||this.#xe.isPrimary)}get inputContext(){return Object.freeze({terminal:this,selection:this.selection,viewport:this.viewport,buffer:this.viewport.buffer??null,mouseCaptured:!this.#Ce&&0!==this.#ke.mouseTracking,historical:!this.viewport.following||this.viewport.pending,readOnly:this.#Ce,connected:this.#Ee,peer:this.peer})}#St(e){try{const t=this.#tt.resolve(Object.freeze(e),this.inputContext);return this.#Ce&&t.route===Qe.Application?{route:"pointer"===e.type||"wheel"===e.type?Qe.Continue:Qe.Consume}:t}catch(e){return this.#Rt(e),{route:Qe.Consume}}}#It(e,t){this.#Ft(e.action,e.args,t).catch(e=>this.#Rt(e))}async runAction(e,t,i){return this.#Ft(e,t,i)}async#Ft(e,t,i){if(this.#ee)throw new Error("Terminal view is disposed");if(this.#Ze="","function"==typeof e)return e(this.inputContext,t,i);const n=this.#f.get(e);if(n)return n(this.inputContext,t,i);switch(e){case et.CopySelection:if(void 0===t)return this.copySelection();if(!r(t))throw new TypeError("Copy options must be an object");if(void 0!==t.clear&&"boolean"!=typeof t.clear)throw new TypeError("Copy clear must be a boolean");return this.copySelection({clear:t.clear});case et.PasteClipboard:return this.pasteClipboard();case et.ClearSelection:return this.clearSelection();case et.ScrollToLive:return this.scrollToLive();case et.ScrollLines:if("number"!=typeof t)throw new RangeError("Scroll delta must be a signed 32-bit integer");return this.scrollLines(t);case et.CopyOrPaste:if(this.#it)throw new Error("A clipboard action is still in progress. Try again when it finishes.");this.#it=!0;try{return this.selection.active||this.selection.pending&&this.selection.canExtend?await this.copySelection({clear:!0}):this.#Ce?void 0:await this.pasteClipboard()}finally{this.#it=!1}default:throw new TypeError(`Unknown terminal action: ${e}`)}}paste(e){if("string"!=typeof e)throw new TypeError("Paste text must be a string");if(!this.#qt())throw new Error("Terminal view does not accept input");e&&this.#Ct({type:"paste",text:e})}async pasteClipboard(){if(!this.#qt())throw new Error("Terminal view does not accept input");if(!navigator.clipboard?.readText)throw new Error("Clipboard reading is unavailable. Use the browser's paste shortcut instead.");const e=this.#rt,t=this.viewport.generation,i=this.selection.requestId,r=document.activeElement,n=await navigator.clipboard.readText();if(!this.#qt()||e!==this.#rt||t!==this.viewport.generation||i!==this.selection.requestId||document.activeElement!==r)throw new Error("Terminal input, selection, focus, or buffer changed while reading the clipboard. Paste again.");return this.paste(n),n}setReadOnly(e){if("boolean"!=typeof e)throw new TypeError("readOnly must be a boolean");if(this.#ee)throw new Error("Terminal view is disposed");if(this.#Ce===e)return;const t=document.activeElement===this.element&&(!this.element.shadowRoot?.activeElement||this.element.shadowRoot.activeElement===this.#me);this.#Ce=e,this.#rt++,this.#Ae?.(),this.#me&&(this.#me.value="",this.#me.disabled=!this.#qt()),this.#ge?.cancel(),this.#ge?.refresh(),clearTimeout(this.#Ie),this.#Ie=void 0,this.#Me=void 0,this.#Ot(!0),t&&this.focus(),this.#nt?.refresh()}setLinks(e){if(this.#ee)throw new Error("Terminal view is disposed");if(void 0===e)throw new TypeError("Link options must be an object or false");const t=O(e,new Set(this.#f.keys()));this.#K=t,this.#ht++,this.#mt=[],this.#bt([]),this.#vt=void 0,this.#pt=void 0,this.#yt=void 0,this.#ct.configure(!!t&&t.detection),this.#ge?.cancel(),this.#ge?.refresh(),this.#Bt()}#Bt(){const e=!(!this.#K||!(this.#K.osc8||this.#K.detection&&this.#K.detection.rules.some(e=>!1!==e.enabled)));this.#Dt({type:"linkDetection",enabled:e,generation:this.#ht})}#jt(e){this.#pt=e,this.#mt=[],this.#bt([]),this.#vt=void 0,this.#ct.update(e),this.#ge?.refresh()}#Gt(e){return`detected/${this.#ht}/${this.#dt}/${e.id}`}#bt(e){this.#gt=e,this.#wt.clear();for(const t of e)for(const e of t.activation.ranges){const i=this.#wt.get(e.row)??[];i.push({startColumn:e.startColumn,endColumn:e.endColumn,link:t}),this.#wt.set(e.row,i)}}#kt(){if(!this.#G||!this.#Ee||!this.#dt)return;const e=this.#K&&this.#K.detection;if(!e)return;const t="none"===e.decoration?[]:"hover"===e.decoration?this.#mt.filter(e=>this.#Gt(e)===this.#yt):this.#mt;this.#vt={revision:this.#dt,generation:this.#ht,serial:++this.#ut,links:this.#mt},this.#Dt({type:"linkDecorations",revision:this.#dt,generation:this.#ht,serial:this.#ut,ranges:t.flatMap(e=>e.activation.ranges),underlineStyle:e.underlineStyle??"solid"})}#Mt(e){if(!this.#Ee||this.viewport.pending||!this.#K)return null;const t=this.#ft.get(e.y)?.find(t=>e.x>=t.startColumn&&e.xe.x>=t.startColumn&&e.xthis.#Gt(t)===e.id);if(i)return void this.#Ft(i.action,i.activation,t).catch(e=>this.#Rt(e));if(!this.#K.osc8){try{window.open(e.target,"_blank","noopener,noreferrer")}catch(e){this.#Rt(e)}return}const r=this.#ft.get(t.point.y)?.find(e=>t.point.x>=e.startColumn&&t.point.xthis.#Rt(e))}#Wt(e){if("key"===e.type){if(e.meta)throw new Error("Meta has no terminal key encoding; bind this shortcut to a local action instead.");this.#Ct({type:"key",key:e.key,ctrl:e.ctrl,alt:e.alt,shift:e.shift})}else"paste"===e.type?this.#qt()&&this.paste(e.text):"text"===e.type&&this.#Ct({type:"input",text:e.text})}#_t(e,t,i=!0){const r=this.#St(e);if((i||void 0!==r.action||r.route===Qe.Consume)&&r.route!==Qe.Browser&&(r.route!==Qe.Continue||"key"!==e.type))if(t?.preventDefault(),t?.stopPropagation(),void 0!==r.action)this.#It(r,e);else if(r.route!==Qe.Consume)try{this.#Wt(e)}catch(e){this.#Rt(e)}}#At(){const e=this.#me,t={signal:this.#ye.signal};let i=!1,r=null;this.#Ae=()=>{i=!1,r=null,clearTimeout(this.#Le)},this.element.addEventListener("keydown",t=>{if(t.defaultPrevented)return;if(t.composedPath().includes(this.#st))return;const r=t.composedPath()[0];!this.#Ee||t.isComposing||i||"Process"===t.key||"Dead"===t.key||t.getModifierState("AltGraph")||this.#_t({type:"key",key:t.key,code:t.code,repeat:t.repeat,...ct(t)},t,r===e||r===this.element)},{...t,capture:!0}),e.addEventListener("paste",t=>{!t.defaultPrevented&&this.#Ee&&t.clipboardData&&(this.#_t({type:"paste",text:t.clipboardData.getData("text/plain")},t),e.value="")},t),e.addEventListener("compositionstart",()=>{this.#qt()&&(i=!0,r=null,clearTimeout(this.#Le))},t),e.addEventListener("compositionend",t=>{i&&(i=!1,r="string"==typeof t.data?t.data:e.value,this.#Le=setTimeout(()=>{r&&this.#_t({type:"text",text:r}),r=null,e.value=""},0))},t),e.addEventListener("input",t=>{const n=t instanceof InputEvent?t:void 0;if(n?.isComposing||i)return;clearTimeout(this.#Le);const s=r??n?.data??e.value;r=null,s&&"insertFromPaste"!==n?.inputType&&this.#_t({type:"text",text:s},t),e.value=""},t)}focus(){if(this.#ee)return;(this.#qt()?this.#me:this.element).focus({preventScroll:!0})}requestPrimary(){if(!this.#qt())throw new Error("Terminal view does not accept input");if(this.#ve.width<=0||this.#ve.height<=0)throw new Error("Show the terminal container before taking primary");const e=this.#$t();if(!e)throw new Error("Show the terminal container before taking primary");if(null!==this.#xe.id)this.#e({type:"requestPrimary",...e});else{if(!this.#xe.isPrimary)throw new Error("Waiting for the HMP1 connection");this.resize(e.columns,e.rows)}}resize(e,t){const i=gt(e,t);if(!this.#qt())throw new Error("Terminal view does not accept input");if(!this.#xe.isPrimary)throw new Error("Only the primary view can request a terminal resize");this.#e({type:"resize",...i}),this.#Me=`${e}x${t}`}setSizing(e){const t=wt(e,this.#be.fontSize);if(!this.#Ee||this.#ee)throw new Error("Terminal view is not connected");if(this.#Ce)throw new Error("Terminal view does not accept input");if(!this.#xe.isPrimary)throw new Error("Only the primary view can change terminal sizing");this.#be=t,clearTimeout(this.#Ie),this.#Ie=void 0,this.#Me=void 0,this.#Pt(),this.#Ot(!0),this.#$.onSizingChange?.(this.sizing)}resync(){this.#e({type:"resync"})}#Nt(){this.#rt++,this.#Ee=!1,this.#lt.update([]),this.#ft.clear(),this.#mt=[],this.#bt([]),this.#pt=void 0,this.#vt=void 0,this.#ct.clear(),this.#me&&(this.#me.disabled=!0),this.#ge?.update(1,1,0),this.#g.disconnect(),this.#Fe.disconnect(),this.#He?.cancel(),this.#xt(),clearTimeout(this.#Ie),this.#Ie=void 0}#Ut(e){this.#Nt(),this.#Re.reject(e),this.#$.onStatus?.(e.message,"error"),this.#G?.terminate()}dispose(){this.#ee||(this.#ee=!0,this.#Nt(),this.#ct.dispose(),this.#Re.reject(new DOMException("Terminal view was disposed","AbortError")),clearTimeout(this.#Pe),clearTimeout(this.#Le),this.#we?.disconnect(),this.#ge?.dispose(),this.#He?.dispose(),this.#Ke?.dispose(),this.#ye.abort(),this.#Dt({type:"stop"}),this.#G?.terminate(),this.element.remove())}};if(z(),"undefined"!=typeof DedicatedWorkerGlobalScope&&globalThis instanceof DedicatedWorkerGlobalScope)switch(new URL(import.meta.url).hash){case"#hex1b-terminal-worker":await Promise.resolve().then(()=>(He(),Ne));break;case"#hex1b-link-detection-worker":await Promise.resolve().then(()=>(Ye(),Ke))}export{Qe as InputRoute,mt as MAX_FONT_SIZE,ft as MIN_FONT_SIZE,et as TerminalAction,ni as WebTerminal,It as createDefaultScrollbarRenderer,st as defaultInputBindings,qt as getCmdlineUrl,U as linkAction,Bt as parseCommandMarkParameters,Lt as renderDefaultScrollbar,Nt as renderDefaultScrollbarTooltip}; diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts deleted file mode 100644 index 8830c332806..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -export declare const InputRoute: Readonly<{ - Continue: "continue"; - Consume: "consume"; - Application: "application"; - Browser: "browser"; -}>; -export declare const TerminalAction: Readonly<{ - CopySelection: "copySelection"; - PasteClipboard: "pasteClipboard"; - CopyOrPaste: "copyOrPaste"; - ClearSelection: "clearSelection"; - ScrollToLive: "scrollToLive"; - ScrollLines: "scrollLines"; -}>; -/** Fresh, inspectable defaults. Overrides are per mounted view, never global. */ -export declare function defaultInputBindings(): InputBinding[]; -/** Resolves intent only. DOM cancellation and action execution belong to the mounted view. */ -export declare class InputPolicy { - #private; - constructor({ inputBindings, onInput, actions }?: InputPolicyOptions); - get bindings(): ({ - id: string; - match: (input: Readonly, context: TerminalInputContext) => boolean; - when?: (context: TerminalInputContext, input: Readonly) => boolean; - remove?: false; - route: InputRouteValue; - action?: never; - args?: never; - } | { - id: string; - match: (input: Readonly, context: TerminalInputContext) => boolean; - when?: (context: TerminalInputContext, input: Readonly) => boolean; - remove?: false; - action: string | InputActionHandler; - args?: unknown; - route?: never; - })[]; - resolve(input: Readonly, context: TerminalInputContext): InputDecision; -} -export declare function inputModifiers(event: Pick): InputModifiers; -import type { InputActionHandler, InputBinding, InputDecision, InputModifiers, InputPolicyOptions, InputRouteValue, TerminalInput, TerminalInputContext } from "./types.js"; -//# sourceMappingURL=input-policy.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts.map deleted file mode 100644 index 4d10b207adb..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"input-policy.d.ts","sourceRoot":"","sources":["../src/input-policy.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU;;;;;EAErB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;EAGzB,CAAC;AASH,iFAAiF;AACjF,wBAAgB,oBAAoB,IAAI,YAAY,EAAE,CA2BrD;AAOD,8FAA8F;AAC9F,qBAAa,WAAW;;gBAKV,EAAE,aAAkB,EAAE,OAAO,EAAE,OAAY,EAAE,GAAE,kBAAuB;IAgClF,IAAI,QAAQ;;;;;;;;;;;;;;;;SAA8D;IAkB1E,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,aAAa;CAoBtF;AAUD,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,GAAG,cAAc,CAExH;AACD,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAwB,aAAa,EAAE,cAAc,EACjG,kBAAkB,EAAE,eAAe,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js deleted file mode 100644 index 0212fa5c401..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js +++ /dev/null @@ -1,143 +0,0 @@ -export const InputRoute = Object.freeze({ - Continue: "continue", Consume: "consume", Application: "application", Browser: "browser" -}); -export const TerminalAction = Object.freeze({ - CopySelection: "copySelection", PasteClipboard: "pasteClipboard", CopyOrPaste: "copyOrPaste", - ClearSelection: "clearSelection", ScrollToLive: "scrollToLive", ScrollLines: "scrollLines" -}); -const browserCtrlKeys = new Set(["l", "t", "v", "w", "+", "-", "=", "0"]); -const browserFunctionKeys = new Set(["F1", "F3", "F5", "F6", "F7", "F10", "F11", "F12"]); -const terminalKeys = new Set(["Tab", "Enter", "Backspace", "Delete", "Escape", "ArrowUp", "ArrowDown", - "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown", "Insert", - ...Array.from({ length: 12 }, (_, index) => `F${index + 1}`)]); -const routes = new Set(Object.values(InputRoute)); -/** Fresh, inspectable defaults. Overrides are per mounted view, never global. */ -export function defaultInputBindings() { - return [ - { - id: "clipboard.copy-key", - match: input => input.type === "key" && input.key.toLowerCase() === "c" && !input.alt && - ((input.meta && !input.ctrl) || (input.ctrl && input.shift && !input.meta)), - action: TerminalAction.CopySelection - }, - { - id: "browser.shortcuts", - match: input => input.type === "key" && (input.meta || browserFunctionKeys.has(input.key) || - (input.ctrl && (input.key === "Tab" || input.key === "F4" || browserCtrlKeys.has(input.key.toLowerCase()) || - (input.shift && input.key.length === 1))) || (input.alt && !input.ctrl)), - route: InputRoute.Browser - }, - { - id: "terminal.keys", - match: input => input.type === "key" && (terminalKeys.has(input.key) || (input.ctrl && input.key.length === 1)), - route: InputRoute.Application - }, - { - id: "clipboard.context-click", - match: (input, context) => input.type === "pointer" && input.button === "right" && !input.meta && - (!context.mouseCaptured || input.shift || context.historical || context.readOnly), - action: TerminalAction.CopyOrPaste - } - ]; -} -function synchronous(callback, name) { - if (typeof callback !== "function" || callback.constructor.name === "AsyncFunction") - throw new TypeError(`${name} must be a synchronous function`); -} -/** Resolves intent only. DOM cancellation and action execution belong to the mounted view. */ -export class InputPolicy { - #bindings; - #intercept; - #actions; - constructor({ inputBindings = [], onInput, actions = {} } = {}) { - if (!Array.isArray(inputBindings)) - throw new TypeError("inputBindings must be an array"); - if (!actions || typeof actions !== "object" || Array.isArray(actions)) - throw new TypeError("actions must be an object"); - this.#actions = new Set(Object.values(TerminalAction)); - for (const [name, handler] of Object.entries(actions)) { - if (!name || this.#actions.has(name) || typeof handler !== "function") - throw new TypeError(`Invalid or reserved action: ${name}`); - this.#actions.add(name); - } - if (onInput !== undefined) - synchronous(onInput, "onInput"); - this.#intercept = onInput; - const defaults = defaultInputBindings(); - const ids = new Set(); - const overrides = inputBindings.map((binding) => { - if (!binding || typeof binding.id !== "string" || !binding.id || ids.has(binding.id)) - throw new TypeError("Each input binding needs a unique, nonempty id"); - ids.add(binding.id); - if (binding.remove === true) { - if (!defaults.some(item => item.id === binding.id)) - throw new TypeError(`Cannot remove unknown default binding: ${binding.id}`); - if (Object.keys(binding).some(key => !["id", "remove"].includes(key))) - throw new TypeError(`Removed binding ${binding.id} must contain only id and remove`); - return null; - } - synchronous(binding.match, `Binding ${binding.id}.match`); - if (binding.when !== undefined) - synchronous(binding.when, `Binding ${binding.id}.when`); - this.#decision(binding); - return Object.freeze({ ...binding }); - }).filter((binding) => binding !== null); - this.#bindings = [...overrides, ...defaults.filter(binding => !ids.has(binding.id))]; - } - get bindings() { return this.#bindings.map(binding => ({ ...binding })); } - #decision(value) { - if (isRoute(value)) - return { route: value }; - if (!isRecord(value) || typeof value.then === "function") - throw new TypeError("Input routing must synchronously return a route or action"); - const hasAction = Object.hasOwn(value, "action"); - const hasRoute = Object.hasOwn(value, "route"); - if (hasAction === hasRoute) - throw new TypeError("Specify exactly one action or route"); - if (hasRoute) { - if (!isRoute(value.route)) - throw new TypeError(`Unknown input route: ${String(value.route)}`); - return { route: value.route }; - } - if (!isActionHandler(value.action) && (typeof value.action !== "string" || !this.#actions.has(value.action))) - throw new TypeError(`Unknown terminal action: ${String(value.action)}`); - return { action: value.action, args: value.args }; - } - resolve(input, context) { - const intercepted = this.#intercept?.(input, context); - if (intercepted !== undefined) { - const decision = this.#decision(intercepted); - if (decision.route !== InputRoute.Continue) - return decision; - } - for (const binding of this.#bindings) { - const matched = binding.match(input, context); - if (typeof matched !== "boolean") - throw new TypeError(`Binding ${binding.id}.match must return a boolean`); - if (!matched) - continue; - if (binding.when) { - const enabled = binding.when(context, input); - if (typeof enabled !== "boolean") - throw new TypeError(`Binding ${binding.id}.when must return a boolean`); - if (!enabled) - continue; - } - const decision = this.#decision(binding); - if (decision.route !== InputRoute.Continue) - return decision; - } - return { route: InputRoute.Continue }; - } -} -function isRoute(value) { - return typeof value === "string" && [...routes].some(route => route === value); -} -function isActionHandler(value) { - return typeof value === "function"; -} -export function inputModifiers(event) { - return { ctrl: !!event.ctrlKey, alt: !!event.altKey, shift: !!event.shiftKey, meta: !!event.metaKey }; -} -import { isRecord } from "./validation.js"; -//# sourceMappingURL=input-policy.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js.map deleted file mode 100644 index 8ba35939931..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/input-policy.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"input-policy.js","sourceRoot":"","sources":["../src/input-policy.ts"],"names":[],"mappings":"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","sourcesContent":["export const InputRoute = Object.freeze({\n Continue: \"continue\", Consume: \"consume\", Application: \"application\", Browser: \"browser\"\n});\n\nexport const TerminalAction = Object.freeze({\n CopySelection: \"copySelection\", PasteClipboard: \"pasteClipboard\", CopyOrPaste: \"copyOrPaste\",\n ClearSelection: \"clearSelection\", ScrollToLive: \"scrollToLive\", ScrollLines: \"scrollLines\"\n});\n\nconst browserCtrlKeys = new Set([\"l\", \"t\", \"v\", \"w\", \"+\", \"-\", \"=\", \"0\"]);\nconst browserFunctionKeys = new Set([\"F1\", \"F3\", \"F5\", \"F6\", \"F7\", \"F10\", \"F11\", \"F12\"]);\nconst terminalKeys = new Set([\"Tab\", \"Enter\", \"Backspace\", \"Delete\", \"Escape\", \"ArrowUp\", \"ArrowDown\",\n \"ArrowLeft\", \"ArrowRight\", \"Home\", \"End\", \"PageUp\", \"PageDown\", \"Insert\",\n ...Array.from({ length: 12 }, (_, index) => `F${index + 1}`)]);\nconst routes = new Set(Object.values(InputRoute));\n\n/** Fresh, inspectable defaults. Overrides are per mounted view, never global. */\nexport function defaultInputBindings(): InputBinding[] {\n return [\n {\n id: \"clipboard.copy-key\",\n match: input => input.type === \"key\" && input.key.toLowerCase() === \"c\" && !input.alt &&\n ((input.meta && !input.ctrl) || (input.ctrl && input.shift && !input.meta)),\n action: TerminalAction.CopySelection\n },\n {\n id: \"browser.shortcuts\",\n match: input => input.type === \"key\" && (input.meta || browserFunctionKeys.has(input.key) ||\n (input.ctrl && (input.key === \"Tab\" || input.key === \"F4\" || browserCtrlKeys.has(input.key.toLowerCase()) ||\n (input.shift && input.key.length === 1))) || (input.alt && !input.ctrl)),\n route: InputRoute.Browser\n },\n {\n id: \"terminal.keys\",\n match: input => input.type === \"key\" && (terminalKeys.has(input.key) || (input.ctrl && input.key.length === 1)),\n route: InputRoute.Application\n },\n {\n id: \"clipboard.context-click\",\n match: (input, context) => input.type === \"pointer\" && input.button === \"right\" && !input.meta &&\n (!context.mouseCaptured || input.shift || context.historical || context.readOnly),\n action: TerminalAction.CopyOrPaste\n }\n ];\n}\n\nfunction synchronous(callback: unknown, name: string): void {\n if (typeof callback !== \"function\" || callback.constructor.name === \"AsyncFunction\")\n throw new TypeError(`${name} must be a synchronous function`);\n}\n\n/** Resolves intent only. DOM cancellation and action execution belong to the mounted view. */\nexport class InputPolicy {\n #bindings: InputBinding[];\n #intercept: InputPolicyOptions[\"onInput\"];\n #actions: Set;\n\n constructor({ inputBindings = [], onInput, actions = {} }: InputPolicyOptions = {}) {\n if (!Array.isArray(inputBindings)) throw new TypeError(\"inputBindings must be an array\");\n if (!actions || typeof actions !== \"object\" || Array.isArray(actions)) throw new TypeError(\"actions must be an object\");\n this.#actions = new Set(Object.values(TerminalAction));\n for (const [name, handler] of Object.entries(actions)) {\n if (!name || this.#actions.has(name) || typeof handler !== \"function\")\n throw new TypeError(`Invalid or reserved action: ${name}`);\n this.#actions.add(name);\n }\n if (onInput !== undefined) synchronous(onInput, \"onInput\");\n this.#intercept = onInput;\n const defaults = defaultInputBindings();\n const ids = new Set();\n const overrides = inputBindings.map((binding: InputBindingOverride): InputBinding | null => {\n if (!binding || typeof binding.id !== \"string\" || !binding.id || ids.has(binding.id))\n throw new TypeError(\"Each input binding needs a unique, nonempty id\");\n ids.add(binding.id);\n if (binding.remove === true) {\n if (!defaults.some(item => item.id === binding.id))\n throw new TypeError(`Cannot remove unknown default binding: ${binding.id}`);\n if (Object.keys(binding).some(key => ![\"id\", \"remove\"].includes(key)))\n throw new TypeError(`Removed binding ${binding.id} must contain only id and remove`);\n return null;\n }\n synchronous(binding.match, `Binding ${binding.id}.match`);\n if (binding.when !== undefined) synchronous(binding.when, `Binding ${binding.id}.when`);\n this.#decision(binding);\n return Object.freeze({ ...binding });\n }).filter((binding): binding is InputBinding => binding !== null);\n this.#bindings = [...overrides, ...defaults.filter(binding => !ids.has(binding.id))];\n }\n\n get bindings() { return this.#bindings.map(binding => ({ ...binding })); }\n\n #decision(value: unknown): InputDecision {\n if (isRoute(value)) return { route: value };\n if (!isRecord(value) || typeof value.then === \"function\")\n throw new TypeError(\"Input routing must synchronously return a route or action\");\n const hasAction = Object.hasOwn(value, \"action\");\n const hasRoute = Object.hasOwn(value, \"route\");\n if (hasAction === hasRoute) throw new TypeError(\"Specify exactly one action or route\");\n if (hasRoute) {\n if (!isRoute(value.route)) throw new TypeError(`Unknown input route: ${String(value.route)}`);\n return { route: value.route };\n }\n if (!isActionHandler(value.action) && (typeof value.action !== \"string\" || !this.#actions.has(value.action)))\n throw new TypeError(`Unknown terminal action: ${String(value.action)}`);\n return { action: value.action, args: value.args };\n }\n\n resolve(input: Readonly, context: TerminalInputContext): InputDecision {\n const intercepted = this.#intercept?.(input, context);\n if (intercepted !== undefined) {\n const decision = this.#decision(intercepted);\n if (decision.route !== InputRoute.Continue) return decision;\n }\n for (const binding of this.#bindings) {\n const matched = binding.match(input, context);\n if (typeof matched !== \"boolean\") throw new TypeError(`Binding ${binding.id}.match must return a boolean`);\n if (!matched) continue;\n if (binding.when) {\n const enabled = binding.when(context, input);\n if (typeof enabled !== \"boolean\") throw new TypeError(`Binding ${binding.id}.when must return a boolean`);\n if (!enabled) continue;\n }\n const decision = this.#decision(binding);\n if (decision.route !== InputRoute.Continue) return decision;\n }\n return { route: InputRoute.Continue };\n }\n}\n\nfunction isRoute(value: unknown): value is InputRouteValue {\n return typeof value === \"string\" && [...routes].some(route => route === value);\n}\n\nfunction isActionHandler(value: unknown): value is InputActionHandler {\n return typeof value === \"function\";\n}\n\nexport function inputModifiers(event: Pick): InputModifiers {\n return { ctrl: !!event.ctrlKey, alt: !!event.altKey, shift: !!event.shiftKey, meta: !!event.metaKey };\n}\nimport type { InputActionHandler, InputBinding, InputBindingOverride, InputDecision, InputModifiers,\n InputPolicyOptions, InputRouteValue, TerminalInput, TerminalInputContext } from \"./types.js\";\nimport { isRecord } from \"./validation.js\";\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts deleted file mode 100644 index 287c6dd6a6d..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { LinkScanRequest, LinkScanResponse } from "./link-worker-protocol.js"; -export declare function scanLinks(request: LinkScanRequest): LinkScanResponse; -//# sourceMappingURL=link-detection-worker.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts.map deleted file mode 100644 index 999f56b2576..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-detection-worker.d.ts","sourceRoot":"","sources":["../src/link-detection-worker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAiB,MAAM,2BAA2B,CAAC;AAkBlG,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,CA4DpE"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js deleted file mode 100644 index b2198ad28bd..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js +++ /dev/null @@ -1,100 +0,0 @@ -import { LINK_LIMITS } from "./link-options.js"; -import { linkMatchTextSize } from "./link-worker-protocol.js"; -function trimProse(text) { - let end = text.length; - const pairs = { ")": "(", "]": "[", "}": "{" }; - while (end > 0) { - const char = text[end - 1]; - if (".,;:!?".includes(char)) { - end--; - continue; - } - if (pairs[char]) { - const current = text.slice(0, end); - if (current.split(char).length > current.split(pairs[char]).length) { - end--; - continue; - } - } - break; - } - return text.slice(0, end); -} -export function scanLinks(request) { - try { - const builtin = "builtin" in request.rule ? request.rule.builtin : undefined; - let regex; - if ("source" in request.rule) { - const flags = request.rule.flags.replaceAll("g", ""); - regex = new RegExp(request.rule.source, `${flags}g`); - } - else { - const sources = { - url: String.raw `https?:\/\/[^\s\0<>"'\x60]+`, - uri: String.raw `[A-Za-z][A-Za-z0-9+.-]*:[^\s\0<>"'\x60]+`, - absolutePath: String.raw `(?:[A-Za-z]:[\\/]|/)[^\s\0<>"'\x60]*`, - homePath: String.raw `~/[^\s\0<>"'\x60]+`, - }; - regex = new RegExp(sources[request.rule.builtin], "giu"); - } - let count = 0, attempts = 0, total = 0, resultText = 0; - const results = []; - for (const chunk of request.chunks) { - total += chunk.text.length; - if (chunk.text.length > LINK_LIMITS.chunk || total > LINK_LIMITS.totalText) { - return { id: request.id, error: "limit", message: "Link scan text limit exceeded" }; - } - regex.lastIndex = 0; - const matches = []; - let match; - while ((match = regex.exec(chunk.text)) !== null) { - if (++attempts > LINK_LIMITS.totalText || count >= LINK_LIMITS.matches) { - return { id: request.id, error: "limit", message: "Link match limit exceeded" }; - } - if (!match[0].length) { - const point = chunk.text.codePointAt(regex.lastIndex); - regex.lastIndex += (regex.unicode || regex.unicodeSets) && point !== undefined && point > 0xffff ? 2 : 1; - continue; - } - let text = match[0]; - if (builtin) { - const before = chunk.text[match.index - 1]; - if (before !== undefined && !/[\s([{"'<>=]/u.test(before)) - continue; - if (builtin === "uri" && /^[a-z]:[\\/]/iu.test(text)) - continue; - text = trimProse(text); - if (!text || (builtin === "absolutePath" && (text === "/" || /^[a-z]:[\\/]$/iu.test(text)))) - continue; - if (builtin === "url") { - try { - if (!new URL(text).hostname) - continue; - } - catch { - continue; - } - } - } - const candidate = { index: match.index, text, captures: match.slice(1), groups: { ...match.groups } }; - resultText += linkMatchTextSize(candidate); - if (resultText > LINK_LIMITS.resultText) { - return { id: request.id, error: "limit", message: "Link result text limit exceeded" }; - } - matches.push(candidate); - count++; - } - results.push({ key: chunk.key, matches }); - } - return { id: request.id, results }; - } - catch (error) { - return { id: request.id, error: "worker", message: error instanceof Error ? error.message : String(error) }; - } -} -if (typeof self !== "undefined" && typeof self.postMessage === "function") { - self.addEventListener("message", (event) => { - self.postMessage(scanLinks(event.data)); - }); -} -//# sourceMappingURL=link-detection-worker.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js.map deleted file mode 100644 index 7b0447355b6..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection-worker.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-detection-worker.js","sourceRoot":"","sources":["../src/link-detection-worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,MAAM,KAAK,GAA2B,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACvE,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAC3B,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAAC,GAAG,EAAE,CAAC;YAAC,SAAS;QAAC,CAAC;QACjD,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACnC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;gBAAC,GAAG,EAAE,CAAC;gBAAC,SAAS;YAAC,CAAC;QAC1F,CAAC;QACD,MAAM;IACR,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAwB;IAChD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7E,IAAI,KAAa,CAAC;QAClB,IAAI,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACrD,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC,CAAC;QACvD,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG;gBACd,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,6BAA6B;gBAC5C,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,0CAA0C;gBACzD,YAAY,EAAE,MAAM,CAAC,GAAG,CAAA,sCAAsC;gBAC9D,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAA,oBAAoB;aACzC,CAAC;YACF,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC;QACvD,MAAM,OAAO,GAA6C,EAAE,CAAC;QAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAC3B,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,KAAK,IAAI,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC;gBAC3E,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC;YACtF,CAAC;YACD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YACpB,MAAM,OAAO,GAAoB,EAAE,CAAC;YACpC,IAAI,KAA6B,CAAC;YAClC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACjD,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC,SAAS,IAAI,KAAK,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;oBACvE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;gBAClF,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;oBACrB,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;oBACtD,KAAK,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBACzG,SAAS;gBACX,CAAC;gBACD,IAAI,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpB,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;oBAC3C,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC;wBAAE,SAAS;oBACpE,IAAI,OAAO,KAAK,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAC/D,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;oBACvB,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,cAAc,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;wBAAE,SAAS;oBACtG,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;wBACtB,IAAI,CAAC;4BAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ;gCAAE,SAAS;wBAAC,CAAC;wBAAC,MAAM,CAAC;4BAAC,SAAS;wBAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;gBACD,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACtG,UAAU,IAAI,iBAAiB,CAAC,SAAS,CAAC,CAAC;gBAC3C,IAAI,UAAU,GAAG,WAAW,CAAC,UAAU,EAAE,CAAC;oBACxC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC;gBACxF,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,KAAK,EAAE,CAAC;YACV,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9G,CAAC;AACH,CAAC;AAED,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;IAC1E,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAoC,EAAE,EAAE;QACxE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { LINK_LIMITS } from \"./link-options.js\";\nimport type { LinkScanRequest, LinkScanResponse, LinkScanMatch } from \"./link-worker-protocol.js\";\nimport { linkMatchTextSize } from \"./link-worker-protocol.js\";\n\nfunction trimProse(text: string): string {\n let end = text.length;\n const pairs: Record = { \")\": \"(\", \"]\": \"[\", \"}\": \"{\" };\n while (end > 0) {\n const char = text[end - 1];\n if (\".,;:!?\".includes(char)) { end--; continue; }\n if (pairs[char]) {\n const current = text.slice(0, end);\n if (current.split(char).length > current.split(pairs[char]).length) { end--; continue; }\n }\n break;\n }\n return text.slice(0, end);\n}\n\nexport function scanLinks(request: LinkScanRequest): LinkScanResponse {\n try {\n const builtin = \"builtin\" in request.rule ? request.rule.builtin : undefined;\n let regex: RegExp;\n if (\"source\" in request.rule) {\n const flags = request.rule.flags.replaceAll(\"g\", \"\");\n regex = new RegExp(request.rule.source, `${flags}g`);\n } else {\n const sources = {\n url: String.raw`https?:\\/\\/[^\\s\\0<>\"'\\x60]+`,\n uri: String.raw`[A-Za-z][A-Za-z0-9+.-]*:[^\\s\\0<>\"'\\x60]+`,\n absolutePath: String.raw`(?:[A-Za-z]:[\\\\/]|/)[^\\s\\0<>\"'\\x60]*`,\n homePath: String.raw`~/[^\\s\\0<>\"'\\x60]+`,\n };\n regex = new RegExp(sources[request.rule.builtin], \"giu\");\n }\n let count = 0, attempts = 0, total = 0, resultText = 0;\n const results: NonNullable = [];\n for (const chunk of request.chunks) {\n total += chunk.text.length;\n if (chunk.text.length > LINK_LIMITS.chunk || total > LINK_LIMITS.totalText) {\n return { id: request.id, error: \"limit\", message: \"Link scan text limit exceeded\" };\n }\n regex.lastIndex = 0;\n const matches: LinkScanMatch[] = [];\n let match: RegExpExecArray | null;\n while ((match = regex.exec(chunk.text)) !== null) {\n if (++attempts > LINK_LIMITS.totalText || count >= LINK_LIMITS.matches) {\n return { id: request.id, error: \"limit\", message: \"Link match limit exceeded\" };\n }\n if (!match[0].length) {\n const point = chunk.text.codePointAt(regex.lastIndex);\n regex.lastIndex += (regex.unicode || regex.unicodeSets) && point !== undefined && point > 0xffff ? 2 : 1;\n continue;\n }\n let text = match[0];\n if (builtin) {\n const before = chunk.text[match.index - 1];\n if (before !== undefined && !/[\\s([{\"'<>=]/u.test(before)) continue;\n if (builtin === \"uri\" && /^[a-z]:[\\\\/]/iu.test(text)) continue;\n text = trimProse(text);\n if (!text || (builtin === \"absolutePath\" && (text === \"/\" || /^[a-z]:[\\\\/]$/iu.test(text)))) continue;\n if (builtin === \"url\") {\n try { if (!new URL(text).hostname) continue; } catch { continue; }\n }\n }\n const candidate = { index: match.index, text, captures: match.slice(1), groups: { ...match.groups } };\n resultText += linkMatchTextSize(candidate);\n if (resultText > LINK_LIMITS.resultText) {\n return { id: request.id, error: \"limit\", message: \"Link result text limit exceeded\" };\n }\n matches.push(candidate);\n count++;\n }\n results.push({ key: chunk.key, matches });\n }\n return { id: request.id, results };\n } catch (error) {\n return { id: request.id, error: \"worker\", message: error instanceof Error ? error.message : String(error) };\n }\n}\n\nif (typeof self !== \"undefined\" && typeof self.postMessage === \"function\") {\n self.addEventListener(\"message\", (event: MessageEvent) => {\n self.postMessage(scanLinks(event.data));\n });\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts deleted file mode 100644 index 297d5b374a9..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { HyperlinkRange, TerminalCell } from "./wire-types.js"; -import type { TerminalLinkAction, TerminalLinkActivation, TerminalLinkDetectionError, TerminalLinkOptions } from "./link-types.js"; -export interface LinkDetectionSnapshot { - revision: number; - columns: number; - rows: number; - cells: readonly (TerminalCell | undefined)[]; - hyperlinks: readonly HyperlinkRange[]; -} -export interface DetectedLink { - id: string; - action: TerminalLinkAction; - activation: TerminalLinkActivation; -} -export declare class LinkDetection { - #private; - constructor(options: { - workerUrl?: string | URL; - actions: ReadonlySet; - onChange: (revision: number, links: readonly DetectedLink[]) => void; - onError: (error: TerminalLinkDetectionError) => void; - }); - configure(detection: TerminalLinkOptions["detection"]): void; - update(snapshot: LinkDetectionSnapshot): void; - advance(revision: number): void; - clear(): void; - dispose(): void; -} -//# sourceMappingURL=link-detection.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts.map deleted file mode 100644 index 82a88c0d5db..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-detection.d.ts","sourceRoot":"","sources":["../src/link-detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,0BAA0B,EAClF,mBAAmB,EAA0C,MAAM,iBAAiB,CAAC;AAOvF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAChD,KAAK,EAAE,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC;IAC7C,UAAU,EAAE,SAAS,cAAc,EAAE,CAAC;CACvC;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,sBAAsB,CAAC;CACpC;AA6BD,qBAAa,aAAa;;gBAqBZ,OAAO,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;QAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;QAC5E,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,CAAC;QACrE,OAAO,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAA;KAAE;IAIxD,SAAS,CAAC,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,GAAG,IAAI;IAQ5D,MAAM,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI;IAsB7C,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQ/B,KAAK,IAAI,IAAI;IAQb,OAAO,IAAI,IAAI;CA8JhB"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js deleted file mode 100644 index e872983761d..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js +++ /dev/null @@ -1,290 +0,0 @@ -import { LINK_LIMITS, normalizeLinks, validateLinkAction } from "./link-options.js"; -import { extractLinkText, mapLinkRange } from "./link-text.js"; -import { linkMatchTextSize } from "./link-worker-protocol.js"; -function validResponse(response, job) { - if (!Array.isArray(response.results) || response.results.length !== job.keys.size) - return false; - const keys = new Set(); - let count = 0, resultText = 0; - for (const result of response.results) { - if (!result || !job.keys.has(result.key) || keys.has(result.key) || !Array.isArray(result.matches)) - return false; - keys.add(result.key); - count += result.matches.length; - if (count > LINK_LIMITS.matches) - return false; - for (const match of result.matches) { - if (!match || !Number.isSafeInteger(match.index) || match.index < 0 || - typeof match.text !== "string" || !match.text.length || !Array.isArray(match.captures) || - !match.captures.every(capture => capture === undefined || typeof capture === "string") || - !match.groups || typeof match.groups !== "object" || Array.isArray(match.groups) || - !Object.values(match.groups).every(group => group === undefined || typeof group === "string")) - return false; - resultText += linkMatchTextSize(match); - if (resultText > LINK_LIMITS.resultText) - return false; - } - } - return true; -} -export class LinkDetection { - #options; - #detection; - #worker = null; - #timer; - #job = null; - #work = null; - #links = []; - #disabled = new Set(); - #cache = new Map(); - #cacheSizes = new Map(); - #cacheSize = 0; - #identity = 0; - #jobId = 0; - #revision = 0; - #disposed = false; - #workerFailed = false; - #pumping = false; - constructor(options) { - this.#options = { ...options, actions: new Set(options.actions) }; - } - configure(detection) { - const normalized = normalizeLinks({ detection }, this.#options.actions); - if (this.#disposed) - return; - this.#detection = normalized === false ? false : normalized.detection; - this.#disabled.clear(); - this.#workerFailed = false; - this.clear(); - } - update(snapshot) { - if (this.#disposed || snapshot.revision < this.#revision) - return; - this.#revision = snapshot.revision; - this.#work = null; - this.#publish([]); - if (!this.#detection || this.#workerFailed) - return; - const work = { identity: ++this.#identity, revision: snapshot.revision, - snapshot, chunks: new Map() }; - try { - for (const rule of this.#detection.rules) { - if (!rule.enabled || this.#disabled.has(rule.id)) - continue; - const mode = rule.text ?? "logicalLine"; - if (!work.chunks.has(mode)) - work.chunks.set(mode, extractLinkText(snapshot, mode)); - } - } - catch (error) { - this.#error("limit", null, error); - return; - } - this.#work = work; - this.#pump(); - } - advance(revision) { - if (this.#disposed || revision < this.#revision) - return; - this.#revision = revision; - if (this.#work) - this.#work.revision = revision; - this.#publish(this.#links.map(link => Object.freeze({ ...link, - activation: Object.freeze({ ...link.activation, revision }) }))); - } - clear() { - this.#terminate(); - this.#identity++; - this.#work = null; - this.#cache.clear(); - this.#cacheSizes.clear(); - this.#cacheSize = 0; - if (!this.#disposed) - this.#publish([]); - } - dispose() { - if (this.#disposed) - return; - this.clear(); - this.#disposed = true; - } - #publish(links) { - this.#links = Object.freeze([...links]); - this.#options.onChange(this.#revision, this.#links); - } - #error(code, ruleId, error) { - this.#options.onError(Object.freeze({ code, ruleId, revision: this.#revision, - message: error instanceof Error ? error.message : String(error) })); - } - #terminate() { - clearTimeout(this.#timer); - this.#timer = undefined; - if (this.#worker) { - this.#worker.terminate(); - this.#worker = null; - } - this.#job = null; - } - #failWorker(error) { - const ruleId = this.#job?.rule.id ?? null; - this.#terminate(); - this.#workerFailed = true; - this.#publish([]); - this.#error("worker", ruleId, error); - } - #pump() { - if (this.#pumping || this.#disposed || this.#job || !this.#work || !this.#detection || this.#workerFailed) - return; - this.#pumping = true; - try { - const work = this.#work; - for (const rule of this.#detection.rules) { - if (!rule.enabled || this.#disabled.has(rule.id)) - continue; - const chunks = work.chunks.get(rule.text ?? "logicalLine") ?? []; - const missing = chunks.filter(chunk => !this.#cache.has(this.#cacheKey(rule, chunk))); - if (!missing.length) - continue; - try { - if (!this.#worker) { - const worker = new Worker(this.#options.workerUrl ?? new URL("./link-detection-worker.js", import.meta.url), { type: "module", name: "hex1b-link-detection" }); - this.#worker = worker; - worker.addEventListener("message", event => { - if (this.#worker === worker) - this.#receive(event.data); - }); - worker.addEventListener("error", event => { - if (this.#worker !== worker) - return; - event.preventDefault(); - this.#failWorker(event.message); - }); - worker.addEventListener("messageerror", () => { - if (this.#worker === worker) - this.#failWorker("Invalid detection worker message"); - }); - } - const id = ++this.#jobId; - this.#job = { id, work, rule, keys: new Map(missing.map(chunk => [chunk.key, this.#cacheKey(rule, chunk)])) }; - this.#timer = setTimeout(() => { - if (this.#job?.id !== id) - return; - this.#terminate(); - this.#disabled.add(rule.id); - this.#error("timeout", rule.id, `Link rule exceeded ${LINK_LIMITS.timeoutMs} ms`); - this.#pump(); - }, LINK_LIMITS.timeoutMs); - const request = { id, - rule: rule.builtin ? { builtin: rule.builtin } : { source: rule.pattern.source, flags: rule.pattern.flags }, - chunks: missing.map(({ chunk, key }) => ({ key, text: chunk.text })) }; - this.#worker.postMessage(request); - } - catch (error) { - this.#failWorker(error); - } - return; - } - this.#resolve(work); - } - finally { - this.#pumping = false; - } - } - #cacheKey(rule, chunk) { - return JSON.stringify([rule.id, chunk.key]); - } - #receive(response) { - const job = this.#job; - if (!job || response?.id !== job.id || this.#disposed) - return; - clearTimeout(this.#timer); - this.#timer = undefined; - this.#job = null; - if (response.error) { - this.#disabled.add(job.rule.id); - this.#error(response.error, job.rule.id, response.message ?? "Link scan failed"); - } - else if (!validResponse(response, job)) { - this.#failWorker("Malformed detection response"); - return; - } - else { - for (const result of response.results) { - const key = job.keys.get(result.key); - const size = key.length + result.matches.reduce((total, match) => total + linkMatchTextSize(match), 0); - this.#cacheSize += size - (this.#cacheSizes.get(key) ?? 0); - this.#cacheSizes.set(key, size); - this.#cache.set(key, result.matches); - } - } - if (this.#work !== job.work) - this.#trimCache(); - this.#pump(); - } - #resolve(work) { - if (!this.#detection || this.#work !== work) - return; - const occupied = [...work.snapshot.hyperlinks]; - const links = []; - const overlaps = (ranges) => ranges.some(range => occupied.some(other => range.row === other.row && range.startColumn < other.endColumn && range.endColumn > other.startColumn)); - for (const rule of this.#detection.rules) { - if (!rule.enabled || this.#disabled.has(rule.id)) - continue; - const ruleLinks = [], ruleRanges = []; - try { - for (const mapped of work.chunks.get(rule.text ?? "logicalLine") ?? []) { - for (const candidate of this.#cache.get(this.#cacheKey(rule, mapped)) ?? []) { - const ranges = mapLinkRange(mapped, candidate.index, candidate.text.length); - if (!ranges || overlaps(ranges)) - continue; - const match = Object.freeze({ ...candidate, captures: Object.freeze([...candidate.captures]), - groups: Object.freeze({ ...candidate.groups }), chunk: mapped.chunk }); - const resolution = rule.resolve ? rule.resolve(match) : { target: candidate.text }; - if (this.#work !== work || this.#disposed) - return; - if (resolution === null) - continue; - if (typeof resolution !== "object" || resolution === undefined || - "then" in resolution || typeof resolution.target !== "string") - throw new TypeError("Resolver must return a synchronous link resolution or null"); - const action = resolution.action === undefined ? rule.action : resolution.action; - validateLinkAction(action, this.#options.actions); - if (links.length + ruleLinks.length >= LINK_LIMITS.matches) - throw new RangeError("Resolved link limit exceeded"); - const activation = Object.freeze({ - source: "detected", ruleId: rule.id, - kind: rule.builtin ? (rule.builtin === "url" || rule.builtin === "uri" ? "uri" : "path") : rule.kind, - text: candidate.text, target: resolution.target, - ranges: Object.freeze(ranges.map(range => Object.freeze(range))), - revision: work.revision, data: resolution.data, - }); - ruleLinks.push(Object.freeze({ id: JSON.stringify([work.identity, rule.id, - ranges[0].row, ranges[0].startColumn, candidate.index]), - action, activation })); - ruleRanges.push(...ranges); - } - } - links.push(...ruleLinks); - occupied.push(...ruleRanges); - } - catch (error) { - this.#disabled.add(rule.id); - this.#error(error instanceof RangeError ? "limit" : "resolver", rule.id, error); - if (this.#work !== work || this.#disposed) - return; - } - } - // Evict only after resolution so a viewport larger than the cache cannot cause endless rescans. - this.#trimCache(); - this.#publish(links); - } - #trimCache() { - while (this.#cache.size > LINK_LIMITS.cacheEntries || this.#cacheSize > LINK_LIMITS.cacheText) { - const key = this.#cache.keys().next().value; - this.#cache.delete(key); - this.#cacheSize -= this.#cacheSizes.get(key); - this.#cacheSizes.delete(key); - } - } -} -//# sourceMappingURL=link-detection.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js.map deleted file mode 100644 index ad22059e834..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-detection.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-detection.js","sourceRoot":"","sources":["../src/link-detection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { HyperlinkRange, TerminalCell } from \"./wire-types.js\";\nimport type { SelectionRange } from \"./types.js\";\nimport type { TerminalLinkAction, TerminalLinkActivation, TerminalLinkDetectionError,\n TerminalLinkOptions, TerminalLinkRule, TerminalLinkTextMode } from \"./link-types.js\";\nimport { LINK_LIMITS, normalizeLinks, validateLinkAction } from \"./link-options.js\";\nimport { extractLinkText, mapLinkRange } from \"./link-text.js\";\nimport type { MappedLinkChunk } from \"./link-text.js\";\nimport type { LinkScanMatch, LinkScanRequest, LinkScanResponse } from \"./link-worker-protocol.js\";\nimport { linkMatchTextSize } from \"./link-worker-protocol.js\";\n\nexport interface LinkDetectionSnapshot {\n revision: number; columns: number; rows: number;\n cells: readonly (TerminalCell | undefined)[];\n hyperlinks: readonly HyperlinkRange[];\n}\nexport interface DetectedLink {\n id: string;\n action: TerminalLinkAction;\n activation: TerminalLinkActivation;\n}\ninterface Work {\n identity: number; revision: number; snapshot: LinkDetectionSnapshot;\n chunks: Map;\n}\ninterface Job { id: number; work: Work; rule: TerminalLinkRule; keys: Map }\n\nfunction validResponse(response: LinkScanResponse, job: Job): boolean {\n if (!Array.isArray(response.results) || response.results.length !== job.keys.size) return false;\n const keys = new Set();\n let count = 0, resultText = 0;\n for (const result of response.results) {\n if (!result || !job.keys.has(result.key) || keys.has(result.key) || !Array.isArray(result.matches)) return false;\n keys.add(result.key);\n count += result.matches.length;\n if (count > LINK_LIMITS.matches) return false;\n for (const match of result.matches) {\n if (!match || !Number.isSafeInteger(match.index) || match.index < 0 ||\n typeof match.text !== \"string\" || !match.text.length || !Array.isArray(match.captures) ||\n !match.captures.every(capture => capture === undefined || typeof capture === \"string\") ||\n !match.groups || typeof match.groups !== \"object\" || Array.isArray(match.groups) ||\n !Object.values(match.groups).every(group => group === undefined || typeof group === \"string\")) return false;\n resultText += linkMatchTextSize(match);\n if (resultText > LINK_LIMITS.resultText) return false;\n }\n }\n return true;\n}\n\nexport class LinkDetection {\n #options: { workerUrl?: string | URL; actions: ReadonlySet;\n onChange: (revision: number, links: readonly DetectedLink[]) => void;\n onError: (error: TerminalLinkDetectionError) => void };\n #detection: TerminalLinkOptions[\"detection\"];\n #worker: Worker | null = null;\n #timer: ReturnType | undefined;\n #job: Job | null = null;\n #work: Work | null = null;\n #links: readonly DetectedLink[] = [];\n #disabled = new Set();\n #cache = new Map();\n #cacheSizes = new Map();\n #cacheSize = 0;\n #identity = 0;\n #jobId = 0;\n #revision = 0;\n #disposed = false;\n #workerFailed = false;\n #pumping = false;\n\n constructor(options: { workerUrl?: string | URL; actions: ReadonlySet;\n onChange: (revision: number, links: readonly DetectedLink[]) => void;\n onError: (error: TerminalLinkDetectionError) => void }) {\n this.#options = { ...options, actions: new Set(options.actions) };\n }\n\n configure(detection: TerminalLinkOptions[\"detection\"]): void {\n const normalized = normalizeLinks({ detection }, this.#options.actions);\n if (this.#disposed) return;\n this.#detection = normalized === false ? false : normalized.detection;\n this.#disabled.clear(); this.#workerFailed = false;\n this.clear();\n }\n\n update(snapshot: LinkDetectionSnapshot): void {\n if (this.#disposed || snapshot.revision < this.#revision) return;\n this.#revision = snapshot.revision;\n this.#work = null;\n this.#publish([]);\n if (!this.#detection || this.#workerFailed) return;\n const work: Work = { identity: ++this.#identity, revision: snapshot.revision,\n snapshot, chunks: new Map() };\n try {\n for (const rule of this.#detection.rules) {\n if (!rule.enabled || this.#disabled.has(rule.id)) continue;\n const mode = rule.text ?? \"logicalLine\";\n if (!work.chunks.has(mode)) work.chunks.set(mode, extractLinkText(snapshot, mode));\n }\n } catch (error) {\n this.#error(\"limit\", null, error);\n return;\n }\n this.#work = work;\n this.#pump();\n }\n\n advance(revision: number): void {\n if (this.#disposed || revision < this.#revision) return;\n this.#revision = revision;\n if (this.#work) this.#work.revision = revision;\n this.#publish(this.#links.map(link => Object.freeze({ ...link,\n activation: Object.freeze({ ...link.activation, revision }) })));\n }\n\n clear(): void {\n this.#terminate();\n this.#identity++;\n this.#work = null;\n this.#cache.clear(); this.#cacheSizes.clear(); this.#cacheSize = 0;\n if (!this.#disposed) this.#publish([]);\n }\n\n dispose(): void {\n if (this.#disposed) return;\n this.clear();\n this.#disposed = true;\n }\n\n #publish(links: readonly DetectedLink[]): void {\n this.#links = Object.freeze([...links]);\n this.#options.onChange(this.#revision, this.#links);\n }\n\n #error(code: TerminalLinkDetectionError[\"code\"], ruleId: string | null, error: unknown): void {\n this.#options.onError(Object.freeze({ code, ruleId, revision: this.#revision,\n message: error instanceof Error ? error.message : String(error) }));\n }\n\n #terminate(): void {\n clearTimeout(this.#timer); this.#timer = undefined;\n if (this.#worker) {\n this.#worker.terminate(); this.#worker = null;\n }\n this.#job = null;\n }\n\n #failWorker(error: unknown): void {\n const ruleId = this.#job?.rule.id ?? null;\n this.#terminate(); this.#workerFailed = true;\n this.#publish([]);\n this.#error(\"worker\", ruleId, error);\n }\n\n #pump(): void {\n if (this.#pumping || this.#disposed || this.#job || !this.#work || !this.#detection || this.#workerFailed) return;\n this.#pumping = true;\n try {\n const work = this.#work;\n for (const rule of this.#detection.rules) {\n if (!rule.enabled || this.#disabled.has(rule.id)) continue;\n const chunks = work.chunks.get(rule.text ?? \"logicalLine\") ?? [];\n const missing = chunks.filter(chunk => !this.#cache.has(this.#cacheKey(rule, chunk)));\n if (!missing.length) continue;\n try {\n if (!this.#worker) {\n const worker = new Worker(this.#options.workerUrl ?? new URL(\"./link-detection-worker.js\", import.meta.url),\n { type: \"module\", name: \"hex1b-link-detection\" });\n this.#worker = worker;\n worker.addEventListener(\"message\", event => {\n if (this.#worker === worker) this.#receive(event.data as LinkScanResponse);\n });\n worker.addEventListener(\"error\", event => {\n if (this.#worker !== worker) return;\n event.preventDefault(); this.#failWorker(event.message);\n });\n worker.addEventListener(\"messageerror\", () => {\n if (this.#worker === worker) this.#failWorker(\"Invalid detection worker message\");\n });\n }\n const id = ++this.#jobId;\n this.#job = { id, work, rule, keys: new Map(missing.map(chunk => [chunk.key, this.#cacheKey(rule, chunk)])) };\n this.#timer = setTimeout(() => {\n if (this.#job?.id !== id) return;\n this.#terminate(); this.#disabled.add(rule.id);\n this.#error(\"timeout\", rule.id, `Link rule exceeded ${LINK_LIMITS.timeoutMs} ms`);\n this.#pump();\n }, LINK_LIMITS.timeoutMs);\n const request: LinkScanRequest = { id,\n rule: rule.builtin ? { builtin: rule.builtin } : { source: rule.pattern!.source, flags: rule.pattern!.flags },\n chunks: missing.map(({ chunk, key }) => ({ key, text: chunk.text })) };\n this.#worker.postMessage(request);\n } catch (error) { this.#failWorker(error); }\n return;\n }\n this.#resolve(work);\n } finally { this.#pumping = false; }\n }\n\n #cacheKey(rule: TerminalLinkRule, chunk: MappedLinkChunk): string {\n return JSON.stringify([rule.id, chunk.key]);\n }\n\n #receive(response: LinkScanResponse): void {\n const job = this.#job;\n if (!job || response?.id !== job.id || this.#disposed) return;\n clearTimeout(this.#timer); this.#timer = undefined; this.#job = null;\n if (response.error) {\n this.#disabled.add(job.rule.id);\n this.#error(response.error, job.rule.id, response.message ?? \"Link scan failed\");\n } else if (!validResponse(response, job)) {\n this.#failWorker(\"Malformed detection response\"); return;\n } else {\n for (const result of response.results!) {\n const key = job.keys.get(result.key)!;\n const size = key.length + result.matches.reduce((total, match) => total + linkMatchTextSize(match), 0);\n this.#cacheSize += size - (this.#cacheSizes.get(key) ?? 0);\n this.#cacheSizes.set(key, size);\n this.#cache.set(key, result.matches);\n }\n }\n if (this.#work !== job.work) this.#trimCache();\n this.#pump();\n }\n\n #resolve(work: Work): void {\n if (!this.#detection || this.#work !== work) return;\n const occupied: SelectionRange[] = [...work.snapshot.hyperlinks];\n const links: DetectedLink[] = [];\n const overlaps = (ranges: SelectionRange[]) => ranges.some(range => occupied.some(other =>\n range.row === other.row && range.startColumn < other.endColumn && range.endColumn > other.startColumn));\n for (const rule of this.#detection.rules) {\n if (!rule.enabled || this.#disabled.has(rule.id)) continue;\n const ruleLinks: DetectedLink[] = [], ruleRanges: SelectionRange[] = [];\n try {\n for (const mapped of work.chunks.get(rule.text ?? \"logicalLine\") ?? []) {\n for (const candidate of this.#cache.get(this.#cacheKey(rule, mapped)) ?? []) {\n const ranges = mapLinkRange(mapped, candidate.index, candidate.text.length);\n if (!ranges || overlaps(ranges)) continue;\n const match = Object.freeze({ ...candidate, captures: Object.freeze([...candidate.captures]),\n groups: Object.freeze({ ...candidate.groups }), chunk: mapped.chunk });\n const resolution = rule.resolve ? rule.resolve(match) : { target: candidate.text };\n if (this.#work !== work || this.#disposed) return;\n if (resolution === null) continue;\n if (typeof resolution !== \"object\" || resolution === undefined ||\n \"then\" in resolution || typeof resolution.target !== \"string\") throw new TypeError(\"Resolver must return a synchronous link resolution or null\");\n const action = resolution.action === undefined ? rule.action : resolution.action;\n validateLinkAction(action, this.#options.actions);\n if (links.length + ruleLinks.length >= LINK_LIMITS.matches) throw new RangeError(\"Resolved link limit exceeded\");\n const activation: TerminalLinkActivation = Object.freeze({\n source: \"detected\", ruleId: rule.id,\n kind: rule.builtin ? (rule.builtin === \"url\" || rule.builtin === \"uri\" ? \"uri\" : \"path\") : rule.kind!,\n text: candidate.text, target: resolution.target,\n ranges: Object.freeze(ranges.map(range => Object.freeze(range))),\n revision: work.revision, data: resolution.data,\n });\n ruleLinks.push(Object.freeze({ id: JSON.stringify([work.identity, rule.id,\n ranges[0].row, ranges[0].startColumn, candidate.index]),\n action, activation }));\n ruleRanges.push(...ranges);\n }\n }\n links.push(...ruleLinks); occupied.push(...ruleRanges);\n } catch (error) {\n this.#disabled.add(rule.id);\n this.#error(error instanceof RangeError ? \"limit\" : \"resolver\", rule.id, error);\n if (this.#work !== work || this.#disposed) return;\n }\n }\n // Evict only after resolution so a viewport larger than the cache cannot cause endless rescans.\n this.#trimCache();\n this.#publish(links);\n }\n\n #trimCache(): void {\n while (this.#cache.size > LINK_LIMITS.cacheEntries || this.#cacheSize > LINK_LIMITS.cacheText) {\n const key = this.#cache.keys().next().value!;\n this.#cache.delete(key); this.#cacheSize -= this.#cacheSizes.get(key)!;\n this.#cacheSizes.delete(key);\n }\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts deleted file mode 100644 index 92ccdc44ef9..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { InputActionHandler, TerminalInput, TerminalInputContext } from "./types.js"; -import type { TerminalLinkAction, TerminalLinkActivation, TerminalLinkOptions } from "./link-types.js"; -export declare const LINK_LIMITS: Readonly<{ - rules: 32; - pattern: 8192; - chunk: 65536; - totalText: 262144; - cells: 262144; - matches: 2048; - resultText: 262144; - cacheEntries: 2048; - cacheText: 1048576; - timeoutMs: 250; -}>; -export declare function validateLinkAction(action: unknown, actions: ReadonlySet): asserts action is TerminalLinkAction; -export declare function normalizeLinks(options: false | TerminalLinkOptions | undefined, actions: ReadonlySet): false | TerminalLinkOptions; -export declare function isLinkActivation(input: unknown): input is TerminalLinkActivation; -export declare function linkAction(handler: (context: TerminalInputContext, link: TerminalLinkActivation, input: Readonly | undefined) => unknown): InputActionHandler; -//# sourceMappingURL=link-options.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts.map deleted file mode 100644 index 55b1306e72b..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-options.d.ts","sourceRoot":"","sources":["../src/link-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,mBAAmB,EAAoB,MAAM,iBAAiB,CAAC;AAEzH,eAAO,MAAM,WAAW;;;;;;;;;;;EAGtB,CAAC;AAEH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAItH;AAMD,wBAAgB,cAAc,CAAC,OAAO,EAAE,KAAK,GAAG,mBAAmB,GAAG,SAAS,EAC7E,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,mBAAmB,CAwD3D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAchF;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,sBAAsB,EAC9F,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,SAAS,KAAK,OAAO,GAAG,kBAAkB,CAM5E"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js deleted file mode 100644 index dd5e33b0b63..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js +++ /dev/null @@ -1,108 +0,0 @@ -export const LINK_LIMITS = Object.freeze({ - rules: 32, pattern: 8192, chunk: 65536, totalText: 262144, cells: 262144, - matches: 2048, resultText: 262144, cacheEntries: 2048, cacheText: 1048576, timeoutMs: 250, -}); -export function validateLinkAction(action, actions) { - if (typeof action !== "function" && !(typeof action === "string" && actions.has(action))) { - throw new TypeError("Link action must be a registered custom action or callback"); - } -} -function object(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); -} -export function normalizeLinks(options, actions) { - if (options === false) - return false; - if (options === undefined) - return {}; - if (!object(options)) - throw new TypeError("Invalid links configuration"); - const normalized = {}; - if (options.osc8 === false) - normalized.osc8 = false; - else if (options.osc8 !== undefined) { - if (!object(options.osc8)) - throw new TypeError("Invalid OSC 8 configuration"); - validateLinkAction(options.osc8.action, actions); - normalized.osc8 = { action: options.osc8.action }; - } - const detection = options.detection; - if (detection === false) - normalized.detection = false; - else if (detection !== undefined) { - if (!object(detection) || !Array.isArray(detection.rules)) - throw new TypeError("Invalid detection configuration"); - if (detection.rules.length > LINK_LIMITS.rules) - throw new TypeError("Too many link rules"); - if (detection.activation !== undefined && !["modifierClick", "click"].includes(detection.activation)) { - throw new TypeError("Invalid link activation"); - } - if (detection.decoration !== undefined && !["always", "hover", "none"].includes(detection.decoration)) { - throw new TypeError("Invalid link decoration"); - } - if (detection.underlineStyle !== undefined && !["solid", "dashed"].includes(detection.underlineStyle)) { - throw new TypeError("Invalid link underline style"); - } - const ids = new Set(); - const rules = detection.rules.map((rule) => { - if (!object(rule) || typeof rule.id !== "string" || !rule.id.length || rule.id.length > 256 || ids.has(rule.id)) { - throw new TypeError("Link rule IDs must be nonempty and unique"); - } - ids.add(rule.id); - if (rule.enabled !== undefined && typeof rule.enabled !== "boolean") - throw new TypeError("Invalid enabled flag"); - if (rule.text !== undefined && !["physicalRow", "logicalLine", "viewport"].includes(rule.text)) { - throw new TypeError("Invalid link text mode"); - } - if (rule.resolve !== undefined && typeof rule.resolve !== "function") - throw new TypeError("Invalid resolver"); - validateLinkAction(rule.action, actions); - const common = { id: rule.id, action: rule.action, enabled: rule.enabled ?? true, - text: rule.text ?? "logicalLine", resolve: rule.resolve }; - if (rule.builtin !== undefined) { - if (!["url", "uri", "absolutePath", "homePath"].includes(rule.builtin) || - rule.pattern !== undefined || rule.kind !== undefined) - throw new TypeError("Invalid builtin rule"); - return { ...common, builtin: rule.builtin }; - } - if (!(rule.pattern instanceof RegExp) || !["uri", "path", "custom"].includes(rule.kind)) { - throw new TypeError("Custom rules require a RegExp and kind"); - } - if (rule.pattern.source.length > LINK_LIMITS.pattern || /[^dgimsuv]/u.test(rule.pattern.flags)) { - throw new TypeError("Unsupported regex flags or oversized pattern (sticky is not supported)"); - } - return { ...common, kind: rule.kind, pattern: new RegExp(rule.pattern.source, rule.pattern.flags) }; - }); - normalized.detection = { rules, activation: detection.activation ?? "modifierClick", - decoration: detection.decoration ?? "always", underlineStyle: detection.underlineStyle ?? "solid" }; - } - return normalized; -} -export function isLinkActivation(input) { - if (!object(input)) - return false; - const value = input; - if (!["detected", "osc8"].includes(value.source) || - !["uri", "path", "custom"].includes(value.kind) || - typeof value.text !== "string" || typeof value.target !== "string" || - !Number.isSafeInteger(value.revision) || value.revision < 0 || - !Array.isArray(value.ranges) || value.ranges.length === 0) - return false; - if (value.source === "osc8" ? value.ruleId !== null || value.kind !== "uri" - : typeof value.ruleId !== "string" || value.ruleId.length === 0) - return false; - return value.ranges.every(range => object(range) && - Number.isSafeInteger(range.row) && range.row >= 0 && - Number.isSafeInteger(range.startColumn) && range.startColumn >= 0 && - Number.isSafeInteger(range.endColumn) && range.endColumn > range.startColumn); -} -export function linkAction(handler) { - if (typeof handler !== "function") - throw new TypeError("Expected a link action callback"); - return (context, args, input) => { - if (!isLinkActivation(args)) - throw new TypeError("Expected a terminal link activation"); - return handler(context, args, input); - }; -} -//# sourceMappingURL=link-options.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js.map deleted file mode 100644 index 959968456c6..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-options.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-options.js","sourceRoot":"","sources":["../src/link-options.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputActionHandler, TerminalInput, TerminalInputContext } from \"./types.js\";\nimport type { TerminalLinkAction, TerminalLinkActivation, TerminalLinkOptions, TerminalLinkRule } from \"./link-types.js\";\n\nexport const LINK_LIMITS = Object.freeze({\n rules: 32, pattern: 8192, chunk: 65536, totalText: 262144, cells: 262144,\n matches: 2048, resultText: 262144, cacheEntries: 2048, cacheText: 1048576, timeoutMs: 250,\n});\n\nexport function validateLinkAction(action: unknown, actions: ReadonlySet): asserts action is TerminalLinkAction {\n if (typeof action !== \"function\" && !(typeof action === \"string\" && actions.has(action))) {\n throw new TypeError(\"Link action must be a registered custom action or callback\");\n }\n}\n\nfunction object(value: unknown): boolean {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function normalizeLinks(options: false | TerminalLinkOptions | undefined,\n actions: ReadonlySet): false | TerminalLinkOptions {\n if (options === false) return false;\n if (options === undefined) return {};\n if (!object(options)) throw new TypeError(\"Invalid links configuration\");\n const normalized: TerminalLinkOptions = {};\n if (options.osc8 === false) normalized.osc8 = false;\n else if (options.osc8 !== undefined) {\n if (!object(options.osc8)) throw new TypeError(\"Invalid OSC 8 configuration\");\n validateLinkAction(options.osc8.action, actions);\n normalized.osc8 = { action: options.osc8.action };\n }\n const detection = options.detection;\n if (detection === false) normalized.detection = false;\n else if (detection !== undefined) {\n if (!object(detection) || !Array.isArray(detection.rules)) throw new TypeError(\"Invalid detection configuration\");\n if (detection.rules.length > LINK_LIMITS.rules) throw new TypeError(\"Too many link rules\");\n if (detection.activation !== undefined && ![\"modifierClick\", \"click\"].includes(detection.activation)) {\n throw new TypeError(\"Invalid link activation\");\n }\n if (detection.decoration !== undefined && ![\"always\", \"hover\", \"none\"].includes(detection.decoration)) {\n throw new TypeError(\"Invalid link decoration\");\n }\n if (detection.underlineStyle !== undefined && ![\"solid\", \"dashed\"].includes(detection.underlineStyle)) {\n throw new TypeError(\"Invalid link underline style\");\n }\n const ids = new Set();\n const rules = detection.rules.map((rule: TerminalLinkRule): TerminalLinkRule => {\n if (!object(rule) || typeof rule.id !== \"string\" || !rule.id.length || rule.id.length > 256 || ids.has(rule.id)) {\n throw new TypeError(\"Link rule IDs must be nonempty and unique\");\n }\n ids.add(rule.id);\n if (rule.enabled !== undefined && typeof rule.enabled !== \"boolean\") throw new TypeError(\"Invalid enabled flag\");\n if (rule.text !== undefined && ![\"physicalRow\", \"logicalLine\", \"viewport\"].includes(rule.text)) {\n throw new TypeError(\"Invalid link text mode\");\n }\n if (rule.resolve !== undefined && typeof rule.resolve !== \"function\") throw new TypeError(\"Invalid resolver\");\n validateLinkAction(rule.action, actions);\n const common = { id: rule.id, action: rule.action, enabled: rule.enabled ?? true,\n text: rule.text ?? \"logicalLine\", resolve: rule.resolve };\n if (rule.builtin !== undefined) {\n if (![\"url\", \"uri\", \"absolutePath\", \"homePath\"].includes(rule.builtin) ||\n rule.pattern !== undefined || rule.kind !== undefined) throw new TypeError(\"Invalid builtin rule\");\n return { ...common, builtin: rule.builtin };\n }\n if (!(rule.pattern instanceof RegExp) || ![\"uri\", \"path\", \"custom\"].includes(rule.kind)) {\n throw new TypeError(\"Custom rules require a RegExp and kind\");\n }\n if (rule.pattern.source.length > LINK_LIMITS.pattern || /[^dgimsuv]/u.test(rule.pattern.flags)) {\n throw new TypeError(\"Unsupported regex flags or oversized pattern (sticky is not supported)\");\n }\n return { ...common, kind: rule.kind, pattern: new RegExp(rule.pattern.source, rule.pattern.flags) };\n });\n normalized.detection = { rules, activation: detection.activation ?? \"modifierClick\",\n decoration: detection.decoration ?? \"always\", underlineStyle: detection.underlineStyle ?? \"solid\" };\n }\n return normalized;\n}\n\nexport function isLinkActivation(input: unknown): input is TerminalLinkActivation {\n if (!object(input)) return false;\n const value = input as Record;\n if (![\"detected\", \"osc8\"].includes(value.source as string) ||\n ![\"uri\", \"path\", \"custom\"].includes(value.kind as string) ||\n typeof value.text !== \"string\" || typeof value.target !== \"string\" ||\n !Number.isSafeInteger(value.revision) || (value.revision as number) < 0 ||\n !Array.isArray(value.ranges) || value.ranges.length === 0) return false;\n if (value.source === \"osc8\" ? value.ruleId !== null || value.kind !== \"uri\"\n : typeof value.ruleId !== \"string\" || value.ruleId.length === 0) return false;\n return value.ranges.every(range => object(range) &&\n Number.isSafeInteger(range.row) && (range.row as number) >= 0 &&\n Number.isSafeInteger(range.startColumn) && (range.startColumn as number) >= 0 &&\n Number.isSafeInteger(range.endColumn) && (range.endColumn as number) > (range.startColumn as number));\n}\n\nexport function linkAction(handler: (context: TerminalInputContext, link: TerminalLinkActivation,\n input: Readonly | undefined) => unknown): InputActionHandler {\n if (typeof handler !== \"function\") throw new TypeError(\"Expected a link action callback\");\n return (context, args, input) => {\n if (!isLinkActivation(args)) throw new TypeError(\"Expected a terminal link activation\");\n return handler(context, args, input);\n };\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts deleted file mode 100644 index 25817638789..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { LinkDetectionSnapshot } from "./link-detection.js"; -import type { SelectionRange, TerminalLinkUnderlineStyle } from "./types.js"; -import type { FrameMetadata, TerminalCell } from "./wire-types.js"; -type Cells = readonly (TerminalCell | undefined)[]; -type Decorations = { - revision: number; - generation: number; - serial: number; - mask: Uint8Array; -}; -/** Tracks only local presentation state; never modifies authoritative cells or runs matching. */ -export declare class LinkPresentation { - enabled: boolean; - generation: number; - private presented?; - private sent?; - private forceSnapshot; - private decorations?; - private pending?; - private lastSerial; - configure(enabled: boolean, generation: number): boolean; - private clear; - prepare(cells: Cells, metadata: FrameMetadata): void; - present(cells: Cells, metadata: FrameMetadata): { - linkGeneration?: number; - linkSnapshot?: LinkDetectionSnapshot; - }; - snapshot(): LinkDetectionSnapshot | undefined; - accept(revision: number, generation: number, serial: number, ranges: readonly SelectionRange[], busy: boolean, underlineStyle?: TerminalLinkUnderlineStyle): boolean; - submission(): { - mask?: Uint8Array; - acknowledgement?: Decorations; - }; - acknowledge(submitted: Decorations | undefined, busy: boolean): { - revision: number; - generation: number; - serial: number; - } | undefined; -} -export {}; -//# sourceMappingURL=link-presentation.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts.map deleted file mode 100644 index 42ffa596a16..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-presentation.d.ts","sourceRoot":"","sources":["../src/link-presentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEnE,KAAK,KAAK,GAAG,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC;AACnD,KAAK,WAAW,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AA0B9F,iGAAiG;AACjG,qBAAa,gBAAgB;IAC3B,OAAO,UAAS;IAChB,UAAU,SAAK;IACf,OAAO,CAAC,SAAS,CAAC,CAA4C;IAC9D,OAAO,CAAC,IAAI,CAAC,CAA4C;IACzD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,UAAU,CAAM;IAExB,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAcxD,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAOpD,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,qBAAqB,CAAA;KAAE;IAOjH,QAAQ,IAAI,qBAAqB,GAAG,SAAS;IAe7C,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,IAAI,EAAE,OAAO,EAC3G,cAAc,GAAE,0BAAoC,GAAG,OAAO;IAwBhE,UAAU,IAAI;QAAE,IAAI,CAAC,EAAE,UAAU,CAAC;QAAC,eAAe,CAAC,EAAE,WAAW,CAAA;KAAE;IAIlE,WAAW,CAAC,SAAS,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;CAMrI"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js deleted file mode 100644 index 62115e3382f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js +++ /dev/null @@ -1,125 +0,0 @@ -const TEXT_ATTRIBUTES = 64 | 1024; // Hidden and soft-wrap; other SGR attributes are presentation-only. -function sameContent(cells, metadata, previousCells, previous) { - if (cells === previousCells && metadata === previous) - return true; - if (metadata.columns !== previous.columns || metadata.rows !== previous.rows) - return false; - const history = metadata.history, oldHistory = previous.history; - if (!!history !== !!oldHistory || history?.generation !== oldHistory?.generation || - history?.buffer !== oldHistory?.buffer || history?.rowIds.length !== oldHistory?.rowIds.length || - history?.rowIds.some((id, index) => id !== oldHistory?.rowIds[index])) - return false; - if (metadata.hyperlinks.length !== previous.hyperlinks.length || - metadata.hyperlinks.some((range, index) => { - const old = previous.hyperlinks[index]; - return range.row !== old.row || range.startColumn !== old.startColumn || - range.endColumn !== old.endColumn || range.uri !== old.uri; - })) - return false; - if (cells.length !== previousCells.length) - return false; - for (let index = 0; index < cells.length; index++) { - const cell = cells[index], old = previousCells[index]; - if (cell === old) - continue; - if (!cell || !old || cell.text !== old.text || cell.width !== old.width || - (cell.attributes & TEXT_ATTRIBUTES) !== (old.attributes & TEXT_ATTRIBUTES)) - return false; - } - return true; -} -/** Tracks only local presentation state; never modifies authoritative cells or runs matching. */ -export class LinkPresentation { - enabled = false; - generation = 0; - presented; - sent; - forceSnapshot = false; - decorations; - pending; - lastSerial = -1; - configure(enabled, generation) { - if (typeof enabled !== "boolean" || !Number.isSafeInteger(generation) || generation < 0) { - throw new Error("Invalid link detection configuration"); - } - if (generation < this.generation || (enabled === this.enabled && generation === this.generation)) - return false; - this.enabled = enabled; - this.generation = generation; - this.sent = undefined; - this.forceSnapshot = enabled; - this.clear(); - this.lastSerial = -1; - return true; - } - clear() { - this.decorations = undefined; - this.pending = undefined; - } - prepare(cells, metadata) { - if (!this.enabled) - return; - if (!this.presented || !sameContent(cells, metadata, this.presented.cells, this.presented.metadata)) - this.clear(); - // A pending acknowledgement belongs to its submitted revision, even if text is unchanged. - if (this.pending?.revision !== metadata.revision) - this.pending = undefined; - } - present(cells, metadata) { - this.presented = { cells, metadata }; - if (!this.enabled) - return {}; - const snapshot = this.snapshot(); - return { linkGeneration: this.generation, ...(snapshot ? { linkSnapshot: snapshot } : {}) }; - } - snapshot() { - const current = this.presented; - if (!this.enabled || !current) - return undefined; - if (!this.forceSnapshot && this.sent && - sameContent(current.cells, current.metadata, this.sent.cells, this.sent.metadata)) { - this.sent = current; - return undefined; - } - this.forceSnapshot = false; - this.sent = current; - const { cells, metadata } = current; - return { revision: metadata.revision, columns: metadata.columns, rows: metadata.rows, - cells, hyperlinks: metadata.hyperlinks }; - } - accept(revision, generation, serial, ranges, busy, underlineStyle = "solid") { - if (![revision, generation, serial].every(value => Number.isSafeInteger(value) && value >= 0) || - !Array.isArray(ranges) || !["solid", "dashed"].includes(underlineStyle)) { - throw new Error("Invalid link decorations"); - } - const metadata = this.presented?.metadata; - if (!this.enabled || busy || !metadata || revision !== metadata.revision || - generation !== this.generation || serial <= this.lastSerial) - return false; - if (ranges.length > metadata.columns * metadata.rows || ranges.some(range => !range || - !Number.isInteger(range.row) || range.row < 0 || range.row >= metadata.rows || - !Number.isInteger(range.startColumn) || !Number.isInteger(range.endColumn) || - range.startColumn < 0 || range.endColumn <= range.startColumn || range.endColumn > metadata.columns)) { - throw new Error("Invalid link decoration ranges"); - } - const mask = new Uint8Array(metadata.columns * metadata.rows); - // Values share the renderer's SGR underline styles, without modifying any cell. - const style = underlineStyle === "dashed" ? 5 : 1; - for (const range of ranges) - mask.fill(style, range.row * metadata.columns + range.startColumn, range.row * metadata.columns + range.endColumn); - this.lastSerial = serial; - this.decorations = this.pending = { revision, generation, serial, mask }; - return true; - } - submission() { - return { mask: this.decorations?.mask, acknowledgement: this.pending }; - } - acknowledge(submitted, busy) { - if (!submitted || submitted !== this.pending || busy || !this.enabled || - submitted.generation !== this.generation || submitted.revision !== this.presented?.metadata.revision) - return undefined; - this.pending = undefined; - return { revision: submitted.revision, generation: submitted.generation, serial: submitted.serial }; - } -} -//# sourceMappingURL=link-presentation.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js.map deleted file mode 100644 index d686dd72232..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-presentation.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-presentation.js","sourceRoot":"","sources":["../src/link-presentation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LinkDetectionSnapshot } from \"./link-detection.js\";\nimport type { SelectionRange, TerminalLinkUnderlineStyle } from \"./types.js\";\nimport type { FrameMetadata, TerminalCell } from \"./wire-types.js\";\n\ntype Cells = readonly (TerminalCell | undefined)[];\ntype Decorations = { revision: number; generation: number; serial: number; mask: Uint8Array };\nconst TEXT_ATTRIBUTES = 64 | 1024; // Hidden and soft-wrap; other SGR attributes are presentation-only.\n\nfunction sameContent(cells: Cells, metadata: FrameMetadata, previousCells: Cells, previous: FrameMetadata): boolean {\n if (cells === previousCells && metadata === previous) return true;\n if (metadata.columns !== previous.columns || metadata.rows !== previous.rows) return false;\n const history = metadata.history, oldHistory = previous.history;\n if (!!history !== !!oldHistory || history?.generation !== oldHistory?.generation ||\n history?.buffer !== oldHistory?.buffer || history?.rowIds.length !== oldHistory?.rowIds.length ||\n history?.rowIds.some((id, index) => id !== oldHistory?.rowIds[index])) return false;\n if (metadata.hyperlinks.length !== previous.hyperlinks.length ||\n metadata.hyperlinks.some((range, index) => {\n const old = previous.hyperlinks[index];\n return range.row !== old.row || range.startColumn !== old.startColumn ||\n range.endColumn !== old.endColumn || range.uri !== old.uri;\n })) return false;\n if (cells.length !== previousCells.length) return false;\n for (let index = 0; index < cells.length; index++) {\n const cell = cells[index], old = previousCells[index];\n if (cell === old) continue;\n if (!cell || !old || cell.text !== old.text || cell.width !== old.width ||\n (cell.attributes & TEXT_ATTRIBUTES) !== (old.attributes & TEXT_ATTRIBUTES)) return false;\n }\n return true;\n}\n\n/** Tracks only local presentation state; never modifies authoritative cells or runs matching. */\nexport class LinkPresentation {\n enabled = false;\n generation = 0;\n private presented?: { cells: Cells; metadata: FrameMetadata };\n private sent?: { cells: Cells; metadata: FrameMetadata };\n private forceSnapshot = false;\n private decorations?: Decorations;\n private pending?: Decorations;\n private lastSerial = -1;\n\n configure(enabled: boolean, generation: number): boolean {\n if (typeof enabled !== \"boolean\" || !Number.isSafeInteger(generation) || generation < 0) {\n throw new Error(\"Invalid link detection configuration\");\n }\n if (generation < this.generation || (enabled === this.enabled && generation === this.generation)) return false;\n this.enabled = enabled;\n this.generation = generation;\n this.sent = undefined;\n this.forceSnapshot = enabled;\n this.clear();\n this.lastSerial = -1;\n return true;\n }\n\n private clear(): void {\n this.decorations = undefined;\n this.pending = undefined;\n }\n\n prepare(cells: Cells, metadata: FrameMetadata): void {\n if (!this.enabled) return;\n if (!this.presented || !sameContent(cells, metadata, this.presented.cells, this.presented.metadata)) this.clear();\n // A pending acknowledgement belongs to its submitted revision, even if text is unchanged.\n if (this.pending?.revision !== metadata.revision) this.pending = undefined;\n }\n\n present(cells: Cells, metadata: FrameMetadata): { linkGeneration?: number; linkSnapshot?: LinkDetectionSnapshot } {\n this.presented = { cells, metadata };\n if (!this.enabled) return {};\n const snapshot = this.snapshot();\n return { linkGeneration: this.generation, ...(snapshot ? { linkSnapshot: snapshot } : {}) };\n }\n\n snapshot(): LinkDetectionSnapshot | undefined {\n const current = this.presented;\n if (!this.enabled || !current) return undefined;\n if (!this.forceSnapshot && this.sent &&\n sameContent(current.cells, current.metadata, this.sent.cells, this.sent.metadata)) {\n this.sent = current;\n return undefined;\n }\n this.forceSnapshot = false;\n this.sent = current;\n const { cells, metadata } = current;\n return { revision: metadata.revision, columns: metadata.columns, rows: metadata.rows,\n cells, hyperlinks: metadata.hyperlinks };\n }\n\n accept(revision: number, generation: number, serial: number, ranges: readonly SelectionRange[], busy: boolean,\n underlineStyle: TerminalLinkUnderlineStyle = \"solid\"): boolean {\n if (![revision, generation, serial].every(value => Number.isSafeInteger(value) && value >= 0) ||\n !Array.isArray(ranges) || ![\"solid\", \"dashed\"].includes(underlineStyle)) {\n throw new Error(\"Invalid link decorations\");\n }\n const metadata = this.presented?.metadata;\n if (!this.enabled || busy || !metadata || revision !== metadata.revision ||\n generation !== this.generation || serial <= this.lastSerial) return false;\n if (ranges.length > metadata.columns * metadata.rows || ranges.some(range => !range ||\n !Number.isInteger(range.row) || range.row < 0 || range.row >= metadata.rows ||\n !Number.isInteger(range.startColumn) || !Number.isInteger(range.endColumn) ||\n range.startColumn < 0 || range.endColumn <= range.startColumn || range.endColumn > metadata.columns)) {\n throw new Error(\"Invalid link decoration ranges\");\n }\n const mask = new Uint8Array(metadata.columns * metadata.rows);\n // Values share the renderer's SGR underline styles, without modifying any cell.\n const style = underlineStyle === \"dashed\" ? 5 : 1;\n for (const range of ranges) mask.fill(style, range.row * metadata.columns + range.startColumn,\n range.row * metadata.columns + range.endColumn);\n this.lastSerial = serial;\n this.decorations = this.pending = { revision, generation, serial, mask };\n return true;\n }\n\n submission(): { mask?: Uint8Array; acknowledgement?: Decorations } {\n return { mask: this.decorations?.mask, acknowledgement: this.pending };\n }\n\n acknowledge(submitted: Decorations | undefined, busy: boolean): { revision: number; generation: number; serial: number } | undefined {\n if (!submitted || submitted !== this.pending || busy || !this.enabled ||\n submitted.generation !== this.generation || submitted.revision !== this.presented?.metadata.revision) return undefined;\n this.pending = undefined;\n return { revision: submitted.revision, generation: submitted.generation, serial: submitted.serial };\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts deleted file mode 100644 index 44aeb062629..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { SelectionRange } from "./types.js"; -import type { TerminalLinkTextChunk, TerminalLinkTextMode } from "./link-types.js"; -import type { LinkDetectionSnapshot } from "./link-detection.js"; -interface Span extends SelectionRange { - start: number; - end: number; -} -export interface MappedLinkChunk { - chunk: TerminalLinkTextChunk; - spans: Span[]; - boundaries: Set; - unsafe: Set; - key: string; -} -export declare function extractLinkText(snapshot: LinkDetectionSnapshot, mode: TerminalLinkTextMode): MappedLinkChunk[]; -export declare function mapLinkRange(mapped: MappedLinkChunk, index: number, length: number): SelectionRange[] | null; -export {}; -//# sourceMappingURL=link-text.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts.map deleted file mode 100644 index 3e52515dea4..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-text.d.ts","sourceRoot":"","sources":["../src/link-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGjE,UAAU,IAAK,SAAQ,cAAc;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE;AACpE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,qBAAqB,CAAC;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,qBAAqB,EAAE,IAAI,EAAE,oBAAoB,GAAG,eAAe,EAAE,CA4C9G;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,IAAI,CAmB5G"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js deleted file mode 100644 index 0010886dc3f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js +++ /dev/null @@ -1,92 +0,0 @@ -import { LINK_LIMITS } from "./link-options.js"; -const SOFT_WRAP = 1024; -const HIDDEN = 64; -const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" }); -export function extractLinkText(snapshot, mode) { - const { columns, rows, cells } = snapshot; - if (!Number.isSafeInteger(columns) || !Number.isSafeInteger(rows) || columns < 1 || rows < 1 || - columns * rows > LINK_LIMITS.cells) - throw new RangeError("Link grid exceeds cell limit"); - const result = []; - let text = "", spans = [], unsafe = new Set(), firstRow = 0, total = 0; - let start = "unknown"; - const wrapped = (row) => row >= 0 && !!((cells[(row + 1) * columns - 1]?.attributes ?? 0) & SOFT_WRAP); - const finish = (end) => { - const chunk = Object.freeze({ text, mode, start, end }); - const boundaries = new Set([text.length]); - for (const segment of segmenter.segment(text)) - boundaries.add(segment.index); - result.push({ chunk, spans, unsafe, boundaries, - key: JSON.stringify([mode, firstRow, start, end, text]) }); - text = ""; - spans = []; - unsafe = new Set(); - }; - for (let row = 0; row < rows; row++) { - if (text.length === 0) { - firstRow = row; - start = row === 0 ? "unknown" : wrapped(row - 1) ? "clipped" : "complete"; - } - for (let col = 0; col < columns; col++) { - const cell = cells[row * columns + col]; - const width = cell?.width ?? 1; - const valid = width >= 1 && width <= 2 && col + width <= columns && - !(cell && ((cell.attributes & HIDDEN) || cell.text.codePointAt(0) === 0x10eeee)) && - (width !== 2 || cells[row * columns + col + 1]?.width === 0); - const value = valid ? cell?.text || " " : "\0"; - if (total + value.length > LINK_LIMITS.totalText || text.length + value.length > LINK_LIMITS.chunk) { - throw new RangeError("Link text exceeds chunk or viewport limit"); - } - const offset = text.length; - text += value; - total += value.length; - if (valid) - spans.push({ start: offset, end: text.length, row, startColumn: col, endColumn: col + width }); - else { - unsafe.add(offset); - unsafe.add(text.length); - } - if (valid && width === 2) - col++; - } - const soft = wrapped(row); - if (!soft) - unsafe.add(text.length); // HWT1 cannot distinguish a full hard row from a right crop. - if (mode === "physicalRow" || (mode === "logicalLine" && !soft) || row === rows - 1) { - finish(soft ? "clipped" : "unknown"); - } - else if (!soft) { - if (++total > LINK_LIMITS.totalText || text.length + 1 > LINK_LIMITS.chunk) - throw new RangeError("Link text exceeds limit"); - text += "\n"; - } - } - return result; -} -export function mapLinkRange(mapped, index, length) { - const end = index + length, { chunk } = mapped; - if (length <= 0 || index < 0 || end > chunk.text.length || !mapped.boundaries.has(index) || - !mapped.boundaries.has(end) || (index === 0 && chunk.start !== "complete") || - (end === chunk.text.length && chunk.end !== "complete") || - chunk.text.slice(index, end).includes("\0")) - return null; - for (const boundary of mapped.unsafe) { - if (boundary === index || boundary === end || - (boundary > index && boundary < end && chunk.text[boundary - 1] !== " ")) - return null; - } - const ranges = []; - for (const span of mapped.spans) { - if (span.end <= index || span.start >= end) - continue; - if (span.start < index || span.end > end) - return null; - const last = ranges.at(-1); - if (last && last.row === span.row && last.endColumn === span.startColumn) - last.endColumn = span.endColumn; - else - ranges.push({ row: span.row, startColumn: span.startColumn, endColumn: span.endColumn }); - } - return ranges.length ? ranges : null; -} -//# sourceMappingURL=link-text.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js.map deleted file mode 100644 index 58df93776c0..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-text.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-text.js","sourceRoot":"","sources":["../src/link-text.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SelectionRange } from \"./types.js\";\nimport type { TerminalLinkTextChunk, TerminalLinkTextMode } from \"./link-types.js\";\nimport type { LinkDetectionSnapshot } from \"./link-detection.js\";\nimport { LINK_LIMITS } from \"./link-options.js\";\n\ninterface Span extends SelectionRange { start: number; end: number }\nexport interface MappedLinkChunk {\n chunk: TerminalLinkTextChunk;\n spans: Span[];\n boundaries: Set;\n unsafe: Set;\n key: string;\n}\nconst SOFT_WRAP = 1024;\nconst HIDDEN = 64;\nconst segmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\nexport function extractLinkText(snapshot: LinkDetectionSnapshot, mode: TerminalLinkTextMode): MappedLinkChunk[] {\n const { columns, rows, cells } = snapshot;\n if (!Number.isSafeInteger(columns) || !Number.isSafeInteger(rows) || columns < 1 || rows < 1 ||\n columns * rows > LINK_LIMITS.cells) throw new RangeError(\"Link grid exceeds cell limit\");\n const result: MappedLinkChunk[] = [];\n let text = \"\", spans: Span[] = [], unsafe = new Set(), firstRow = 0, total = 0;\n let start: TerminalLinkTextChunk[\"start\"] = \"unknown\";\n const wrapped = (row: number) => row >= 0 && !!((cells[(row + 1) * columns - 1]?.attributes ?? 0) & SOFT_WRAP);\n const finish = (end: TerminalLinkTextChunk[\"end\"]) => {\n const chunk = Object.freeze({ text, mode, start, end });\n const boundaries = new Set([text.length]);\n for (const segment of segmenter.segment(text)) boundaries.add(segment.index);\n result.push({ chunk, spans, unsafe, boundaries,\n key: JSON.stringify([mode, firstRow, start, end, text]) });\n text = \"\"; spans = []; unsafe = new Set();\n };\n for (let row = 0; row < rows; row++) {\n if (text.length === 0) { firstRow = row; start = row === 0 ? \"unknown\" : wrapped(row - 1) ? \"clipped\" : \"complete\"; }\n for (let col = 0; col < columns; col++) {\n const cell = cells[row * columns + col];\n const width = cell?.width ?? 1;\n const valid = width >= 1 && width <= 2 && col + width <= columns &&\n !(cell && ((cell.attributes & HIDDEN) || cell.text.codePointAt(0) === 0x10eeee)) &&\n (width !== 2 || cells[row * columns + col + 1]?.width === 0);\n const value = valid ? cell?.text || \" \" : \"\\0\";\n if (total + value.length > LINK_LIMITS.totalText || text.length + value.length > LINK_LIMITS.chunk) {\n throw new RangeError(\"Link text exceeds chunk or viewport limit\");\n }\n const offset = text.length;\n text += value; total += value.length;\n if (valid) spans.push({ start: offset, end: text.length, row, startColumn: col, endColumn: col + width });\n else { unsafe.add(offset); unsafe.add(text.length); }\n if (valid && width === 2) col++;\n }\n const soft = wrapped(row);\n if (!soft) unsafe.add(text.length); // HWT1 cannot distinguish a full hard row from a right crop.\n if (mode === \"physicalRow\" || (mode === \"logicalLine\" && !soft) || row === rows - 1) {\n finish(soft ? \"clipped\" : \"unknown\");\n } else if (!soft) {\n if (++total > LINK_LIMITS.totalText || text.length + 1 > LINK_LIMITS.chunk) throw new RangeError(\"Link text exceeds limit\");\n text += \"\\n\";\n }\n }\n return result;\n}\n\nexport function mapLinkRange(mapped: MappedLinkChunk, index: number, length: number): SelectionRange[] | null {\n const end = index + length, { chunk } = mapped;\n if (length <= 0 || index < 0 || end > chunk.text.length || !mapped.boundaries.has(index) ||\n !mapped.boundaries.has(end) || (index === 0 && chunk.start !== \"complete\") ||\n (end === chunk.text.length && chunk.end !== \"complete\") ||\n chunk.text.slice(index, end).includes(\"\\0\")) return null;\n for (const boundary of mapped.unsafe) {\n if (boundary === index || boundary === end ||\n (boundary > index && boundary < end && chunk.text[boundary - 1] !== \" \")) return null;\n }\n const ranges: SelectionRange[] = [];\n for (const span of mapped.spans) {\n if (span.end <= index || span.start >= end) continue;\n if (span.start < index || span.end > end) return null;\n const last = ranges.at(-1);\n if (last && last.row === span.row && last.endColumn === span.startColumn) last.endColumn = span.endColumn;\n else ranges.push({ row: span.row, startColumn: span.startColumn, endColumn: span.endColumn });\n }\n return ranges.length ? ranges : null;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts deleted file mode 100644 index aa43ca12237..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { InputActionHandler, SelectionRange } from "./types.js"; -export type TerminalLinkTextMode = "physicalRow" | "logicalLine" | "viewport"; -export type TerminalLinkKind = "uri" | "path" | "custom"; -export type TerminalLinkAction = string | InputActionHandler; -export type TerminalLinkUnderlineStyle = "solid" | "dashed"; -export interface TerminalLinkOptions { - osc8?: false | { - action: TerminalLinkAction; - }; - detection?: false | { - rules: readonly TerminalLinkRule[]; - activation?: "modifierClick" | "click"; - /** When inferred underlines are visible. Defaults to always. */ - decoration?: "always" | "hover" | "none"; - /** Inferred underline appearance. Defaults to solid; authored SGR styling takes precedence. */ - underlineStyle?: TerminalLinkUnderlineStyle; - }; -} -export interface TerminalLinkRuleOptions { - id: string; - enabled?: boolean; - text?: TerminalLinkTextMode; - action: TerminalLinkAction; - /** Synchronous, fast host callback. Unlike regex execution, this cannot be preempted. */ - resolve?: (match: TerminalLinkMatch) => TerminalLinkResolution | null; -} -export type TerminalLinkRule = TerminalLinkRuleOptions & ({ - builtin: "url" | "uri" | "absolutePath" | "homePath"; - pattern?: never; - kind?: never; -} | { - pattern: RegExp; - kind: TerminalLinkKind; - builtin?: never; -}); -export interface TerminalLinkTextChunk { - readonly text: string; - readonly mode: TerminalLinkTextMode; - readonly start: "complete" | "clipped" | "unknown"; - readonly end: "complete" | "clipped" | "unknown"; -} -export interface TerminalLinkMatch { - readonly text: string; - /** UTF-16 offset within chunk.text. */ - readonly index: number; - readonly captures: readonly (string | undefined)[]; - readonly groups: Readonly>; - readonly chunk: TerminalLinkTextChunk; -} -export interface TerminalLinkResolution { - readonly target: string; - readonly action?: TerminalLinkAction; - readonly data?: unknown; -} -export interface TerminalLinkActivation { - readonly source: "detected" | "osc8"; - readonly ruleId: string | null; - readonly kind: TerminalLinkKind; - readonly text: string; - readonly target: string; - readonly ranges: readonly Readonly[]; - readonly revision: number; - /** Consumer-owned payload; core snapshots are frozen, but this object is not. */ - readonly data?: unknown; -} -export interface TerminalLinkDetectionError { - readonly code: "timeout" | "limit" | "resolver" | "worker"; - readonly ruleId: string | null; - readonly revision: number; - readonly message: string; -} -//# sourceMappingURL=link-types.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts.map deleted file mode 100644 index ba00fadc63f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-types.d.ts","sourceRoot":"","sources":["../src/link-types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js deleted file mode 100644 index 22eabb5650f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=link-types.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js.map deleted file mode 100644 index 5a35288979d..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-types.js","sourceRoot":"","sources":["../src/link-types.ts"],"names":[],"mappings":"","sourcesContent":["import type { InputActionHandler, SelectionRange } from \"./types.js\";\n\nexport type TerminalLinkTextMode = \"physicalRow\" | \"logicalLine\" | \"viewport\";\nexport type TerminalLinkKind = \"uri\" | \"path\" | \"custom\";\nexport type TerminalLinkAction = string | InputActionHandler;\nexport type TerminalLinkUnderlineStyle = \"solid\" | \"dashed\";\nexport interface TerminalLinkOptions {\n osc8?: false | { action: TerminalLinkAction };\n detection?: false | {\n rules: readonly TerminalLinkRule[];\n activation?: \"modifierClick\" | \"click\";\n /** When inferred underlines are visible. Defaults to always. */\n decoration?: \"always\" | \"hover\" | \"none\";\n /** Inferred underline appearance. Defaults to solid; authored SGR styling takes precedence. */\n underlineStyle?: TerminalLinkUnderlineStyle;\n };\n}\nexport interface TerminalLinkRuleOptions {\n id: string;\n enabled?: boolean;\n text?: TerminalLinkTextMode;\n action: TerminalLinkAction;\n /** Synchronous, fast host callback. Unlike regex execution, this cannot be preempted. */\n resolve?: (match: TerminalLinkMatch) => TerminalLinkResolution | null;\n}\nexport type TerminalLinkRule = TerminalLinkRuleOptions & (\n | { builtin: \"url\" | \"uri\" | \"absolutePath\" | \"homePath\"; pattern?: never; kind?: never }\n | { pattern: RegExp; kind: TerminalLinkKind; builtin?: never }\n);\nexport interface TerminalLinkTextChunk {\n readonly text: string;\n readonly mode: TerminalLinkTextMode;\n readonly start: \"complete\" | \"clipped\" | \"unknown\";\n readonly end: \"complete\" | \"clipped\" | \"unknown\";\n}\nexport interface TerminalLinkMatch {\n readonly text: string;\n /** UTF-16 offset within chunk.text. */\n readonly index: number;\n readonly captures: readonly (string | undefined)[];\n readonly groups: Readonly>;\n readonly chunk: TerminalLinkTextChunk;\n}\nexport interface TerminalLinkResolution {\n readonly target: string;\n readonly action?: TerminalLinkAction;\n readonly data?: unknown;\n}\nexport interface TerminalLinkActivation {\n readonly source: \"detected\" | \"osc8\";\n readonly ruleId: string | null;\n readonly kind: TerminalLinkKind;\n readonly text: string;\n readonly target: string;\n readonly ranges: readonly Readonly[];\n readonly revision: number;\n /** Consumer-owned payload; core snapshots are frozen, but this object is not. */\n readonly data?: unknown;\n}\nexport interface TerminalLinkDetectionError {\n readonly code: \"timeout\" | \"limit\" | \"resolver\" | \"worker\";\n readonly ruleId: string | null;\n readonly revision: number;\n readonly message: string;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts deleted file mode 100644 index 78df2f9b9f3..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { TerminalLinkRule } from "./link-types.js"; -export interface LinkScanRequest { - id: number; - rule: { - source: string; - flags: string; - } | { - builtin: NonNullable; - }; - chunks: { - key: string; - text: string; - }[]; -} -export interface LinkScanMatch { - index: number; - text: string; - captures: (string | undefined)[]; - groups: Record; -} -export declare function linkMatchTextSize(match: LinkScanMatch): number; -export interface LinkScanResponse { - id: number; - results?: { - key: string; - matches: LinkScanMatch[]; - }[]; - error?: "limit" | "worker"; - message?: string; -} -//# sourceMappingURL=link-worker-protocol.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts.map deleted file mode 100644 index 094015fd028..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-worker-protocol.d.ts","sourceRoot":"","sources":["../src/link-worker-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAA;KAAE,CAAC;IAChG,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzC;AACD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC9D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5C;AACD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAM9D;AACD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,EAAE,CAAA;KAAE,EAAE,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js deleted file mode 100644 index 8148dfae9ba..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js +++ /dev/null @@ -1,10 +0,0 @@ -export function linkMatchTextSize(match) { - // Include entry overhead so empty capture groups cannot evade the payload budget. - let size = match.text.length + 16; - for (const capture of match.captures) - size += 8 + (capture?.length ?? 0); - for (const [name, group] of Object.entries(match.groups)) - size += 8 + name.length + (group?.length ?? 0); - return size; -} -//# sourceMappingURL=link-worker-protocol.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js.map deleted file mode 100644 index 995990aa32c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/link-worker-protocol.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"link-worker-protocol.js","sourceRoot":"","sources":["../src/link-worker-protocol.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,iBAAiB,CAAC,KAAoB;IACpD,kFAAkF;IAClF,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IAClC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ;QAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;IACzE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;IACzG,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import type { TerminalLinkRule } from \"./link-types.js\";\n\nexport interface LinkScanRequest {\n id: number;\n rule: { source: string; flags: string } | { builtin: NonNullable };\n chunks: { key: string; text: string }[];\n}\nexport interface LinkScanMatch {\n index: number; text: string; captures: (string | undefined)[];\n groups: Record;\n}\nexport function linkMatchTextSize(match: LinkScanMatch): number {\n // Include entry overhead so empty capture groups cannot evade the payload budget.\n let size = match.text.length + 16;\n for (const capture of match.captures) size += 8 + (capture?.length ?? 0);\n for (const [name, group] of Object.entries(match.groups)) size += 8 + name.length + (group?.length ?? 0);\n return size;\n}\nexport interface LinkScanResponse {\n id: number;\n results?: { key: string; matches: LinkScanMatch[] }[];\n error?: \"limit\" | \"worker\";\n message?: string;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts deleted file mode 100644 index 3a79f33da2d..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { GestureState } from "./selection-input.js"; -import type { InputDecision, MouseTrackingMode, SelectionMode, TerminalInput, TerminalPoint } from "./types.js"; -import type { MouseCommand } from "./wire-types.js"; -export interface PointerHyperlink { - id: string; - target: string; - activation?: "modifierClick" | "click"; -} -interface MouseInspection { - state?: () => Omit; - begin?: (point: TerminalPoint, selection: { - mode: SelectionMode; - extend: boolean; - }) => void; - extend?: (point: TerminalPoint) => void; - scroll?: (delta: number, endpoint?: TerminalPoint) => void; - end?: (cancelled: boolean) => void; - resolve?: (input: TerminalInput) => InputDecision; - execute?: (decision: Extract, input: TerminalInput) => void; - hyperlink?: (point: TerminalPoint) => PointerHyperlink | null; - hoverHyperlink?: (link: PointerHyperlink | null) => void; - openHyperlink?: (link: PointerHyperlink, input: TerminalInput) => void; -} -export interface MouseCapture { - update(columns: number, rows: number, tracking: MouseTrackingMode): void; - refresh(): void; - cancel(): void; - dispose(): void; -} -/** Capture input intent only; the server chooses and encodes the mouse protocol. */ -export declare function captureMouse(canvas: HTMLCanvasElement, send: (command: MouseCommand) => void, focus: () => void, inspection?: MouseInspection): MouseCapture; -export {}; -//# sourceMappingURL=mouse-input.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts.map deleted file mode 100644 index a69fce86c8f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"mouse-input.d.ts","sourceRoot":"","sources":["../src/mouse-input.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAmB,iBAAiB,EAAiB,aAAa,EAC3F,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,KAAK,EAA6B,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK/E,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC;CACxC;AAED,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE;QAAE,IAAI,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;IAC5F,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3D,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,aAAa,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChG,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,gBAAgB,GAAG,IAAI,CAAC;IAC9D,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;IACzD,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CACxE;AACD,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACzE,OAAO,IAAI,IAAI,CAAC;IAChB,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,EAC3F,KAAK,EAAE,MAAM,IAAI,EAAE,UAAU,GAAE,eAAoB,GAAG,YAAY,CA+WnE"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js deleted file mode 100644 index f56e5ded7b2..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js +++ /dev/null @@ -1,427 +0,0 @@ -import { cellPoint, WheelAccumulator, SelectionGesture } from "./selection-input.js"; -import { InputRoute, inputModifiers } from "./input-policy.js"; -const buttons = [["left", 1], ["middle", 4], ["right", 2]]; -const pointerButtons = ["left", "middle", "right"]; -/** Capture input intent only; the server chooses and encodes the mouse protocol. */ -export function captureMouse(canvas, send, focus, inspection = {}) { - const listeners = new AbortController(); - const options = { signal: listeners.signal }; - let columns = 1, rows = 1; - let tracking = 0; - let pointerId = null; - const pressed = new Set(); - let lastPoint; - let pendingMove; - let lastMove; - let scheduled; - const wheel = new WheelAccumulator(); - const gesture = new SelectionGesture(); - let wheelOwner; - let pointerEvent; - let autoScroll; - let click = { count: 0, time: 0, x: -1, y: -1 }; - let selectionClick; - let completedClick; - let routedGesture; - let contextMenuRoute; - let hyperlinkClick; - let hoverEvent; - let hoverModifiers; - let hoveredLinkId; - const originalTitle = canvas.title; - const originalCursor = canvas.style.cursor; - const state = () => ({ tracking, ...inspection.state?.() }); - function point(event, clamp = false) { - return cellPoint(event, canvas.getBoundingClientRect(), columns, rows, clamp); - } - function refreshHover(modifiers = hoverModifiers) { - if (!inspection.hyperlink) - return; - hoverModifiers = modifiers; - const position = hoverEvent && point(hoverEvent); - const link = position ? inspection.hyperlink(position) : null; - canvas.title = link ? `${link.target}\n${link.activation === "click" ? "Click" : "Ctrl/Cmd+click"} to activate link` : originalTitle; - canvas.style.cursor = link && modifiers && (link.activation === "click" || modifiers.ctrlKey || modifiers.metaKey) && - !modifiers.altKey && !modifiers.shiftKey ? "pointer" : originalCursor; - if (hyperlinkClick && hyperlinkClick.link.id !== link?.id) - hyperlinkClick.dragged = true; - if (hoveredLinkId !== link?.id) { - hoveredLinkId = link?.id; - inspection.hoverHyperlink?.(link); - } - } - function updateAutoScroll() { - clearTimeout(autoScroll); - autoScroll = undefined; - if (gesture.owner !== "local" || !pointerEvent) - return; - const bounds = canvas.getBoundingClientRect(); - const distance = pointerEvent.clientY < bounds.top ? pointerEvent.clientY - bounds.top - : pointerEvent.clientY >= bounds.bottom ? pointerEvent.clientY - bounds.bottom + 1 : 0; - if (!distance) - return; - autoScroll = setTimeout(() => { - autoScroll = undefined; - if (!pointerEvent) - return; - const position = point(pointerEvent, true); - if (gesture.owner === "local" && position) { - if (selectionClick) - selectionClick.dragged = true; - click.count = 0; - const lines = Math.sign(distance) * Math.min(8, Math.max(1, Math.ceil(Math.abs(distance) / (bounds.height / rows)))); - inspection.scroll?.(lines, gesture.scrollPoint(position)); - updateAutoScroll(); - } - }, 60); - } - function flushMove() { - if (scheduled !== undefined) - cancelAnimationFrame(scheduled); - scheduled = undefined; - if (pendingMove) - send(pendingMove); - pendingMove = undefined; - } - function changeButtons(event, position) { - for (const [button, mask] of buttons) { - const down = (event.buttons & mask) !== 0; - if (down === pressed.has(button)) - continue; - flushMove(); - if (down) - pressed.add(button); - else - pressed.delete(button); - if (down || tracking !== 9) - send({ type: "mouse", action: down ? "down" : "up", button, ...position }); - lastMove = undefined; - } - } - function cancel(report = true, cancelled = true) { - inspection.end?.(cancelled); - clearTimeout(autoScroll); - autoScroll = undefined; - pointerEvent = undefined; - selectionClick = undefined; - completedClick = undefined; - hyperlinkClick = undefined; - gesture.end(); - routedGesture = undefined; - if (report) - flushMove(); - else { - if (scheduled !== undefined) - cancelAnimationFrame(scheduled); - scheduled = undefined; - pendingMove = undefined; - } - if (report && tracking !== 9 && lastPoint) { - for (const button of pressed) - send({ type: "mouse", action: "up", button, ...lastPoint }); - } - pressed.clear(); - const captured = pointerId; - pointerId = null; - if (captured !== null && canvas.hasPointerCapture(captured)) - canvas.releasePointerCapture(captured); - lastMove = undefined; - wheel.reset(); - wheelOwner = undefined; - } - canvas.addEventListener("pointerdown", event => { - if (event.defaultPrevented && pointerId === null) - return; - if (event.pointerType !== "mouse" || ![0, 1, 2].includes(event.button)) - return; - const position = point(event); - if (!position) - return; - if (pointerId !== null) { - if (hyperlinkClick) - hyperlinkClick.dragged = true; - if (routedGesture !== InputRoute.Browser) - event.preventDefault(); - if ((gesture.owner === "app" || routedGesture === InputRoute.Application) && pointerId === event.pointerId) - changeButtons(event, position); - return; - } - focus(); - const input = { type: "pointer", button: pointerButtons[event.button], - point: Object.freeze({ ...position }), ...inputModifiers(event) }; - const decision = inspection.resolve?.(input) ?? { route: InputRoute.Continue }; - let route = decision.action !== undefined ? InputRoute.Consume : decision.route; - if (route === InputRoute.Continue && event.button === 0 && !event.altKey && !event.shiftKey) { - const link = inspection.hyperlink?.(position); - if (link && (link.activation === "click" || event.ctrlKey || event.metaKey)) { - hyperlinkClick = { link, input, x: event.clientX, y: event.clientY, dragged: false }; - route = InputRoute.Consume; - } - } - contextMenuRoute = event.button === 2 ? route : undefined; - if (hyperlinkClick) - contextMenuRoute = InputRoute.Consume; - hoverEvent = event; - refreshHover(event); - if (route !== InputRoute.Continue) { - completedClick = selectionClick = undefined; - click.count = 0; - routedGesture = route; - pointerId = event.pointerId; - lastPoint = position; - canvas.setPointerCapture(pointerId); - if (route !== InputRoute.Browser) - event.preventDefault(); - if (route === InputRoute.Application) - flushMove(); - else { - if (scheduled !== undefined) - cancelAnimationFrame(scheduled); - scheduled = undefined; - pendingMove = undefined; - } - if (route === InputRoute.Application) - changeButtons(event, position); - if (decision.action !== undefined) - inspection.execute?.(decision, input); - return; - } - // Keep focus on the hidden keyboard input instead of the canvas. - event.preventDefault(); - if (event.metaKey) - return; - const now = performance.now(); - click.count = now - click.time < 500 && position.x === click.x && position.y === click.y - ? click.count % 3 + 1 : 1; - click = { count: click.count, time: now, x: position.x, y: position.y }; - const start = gesture.begin({ button: event.button, shiftKey: event.shiftKey, altKey: event.altKey, - detail: event.detail || click.count }, position, state()); - if (!start) - return; - pointerId = event.pointerId; - pointerEvent = event; - lastPoint = position; - completedClick = undefined; - canvas.setPointerCapture(pointerId); - if (start.owner === "local") { - if (scheduled !== undefined) - cancelAnimationFrame(scheduled); - scheduled = undefined; - pendingMove = undefined; - lastMove = undefined; - selectionClick = { point: position, mode: start.mode, extend: start.extend, dragged: false }; - inspection.begin?.(position, start); - } - else - changeButtons(event, position); - }, options); - canvas.addEventListener("pointermove", event => { - if (event.pointerType !== "mouse") - return; - if (pointerId === null && event.buttons) - return; - if (pointerId !== null && pointerId !== event.pointerId) - return; - hoverEvent = event; - refreshHover(event); - if (hyperlinkClick && Math.hypot(event.clientX - hyperlinkClick.x, event.clientY - hyperlinkClick.y) >= 4) - hyperlinkClick.dragged = true; - const position = point(event, pointerId !== null); - if (!position) - return; - lastPoint = position; - if (routedGesture && routedGesture !== InputRoute.Application) - return; - if (gesture.owner === "local") { - pointerEvent = event; - const previous = gesture.endpoint; - const endpoint = gesture.move(position); - if (endpoint && previous && (endpoint.x !== previous.x || endpoint.y !== previous.y)) { - click.count = 0; - if (selectionClick) - selectionClick.dragged = true; - inspection.extend?.(endpoint); - } - updateAutoScroll(); - return; - } - if (!tracking || (!routedGesture && gesture.owner === null && (state().historical || state().readOnly || event.shiftKey))) - return; - if (pointerId === event.pointerId) - changeButtons(event, position); - if (event.metaKey || (tracking !== 1003 && !(tracking === 1002 && pressed.size))) - return; - const button = buttons.find(([name]) => pressed.has(name))?.[0] ?? "none"; - const move = { type: "mouse", action: "move", button, ...position }; - const key = JSON.stringify(move); - if (key === lastMove) - return; - lastMove = key; - pendingMove = move; - if (scheduled === undefined) - scheduled = requestAnimationFrame(flushMove); - }, options); - canvas.addEventListener("pointerup", event => { - if (pointerId !== event.pointerId) - return; - if (hyperlinkClick) { - event.preventDefault(); - const link = hyperlinkClick; - const position = point(event); - const activate = event.button === 0 && event.buttons === 0 && !link.dragged && - Math.hypot(event.clientX - link.x, event.clientY - link.y) < 4 && - position && inspection.hyperlink?.(position)?.id === link.link.id; - if (event.buttons === 0) - cancel(false, false); - else - link.dragged = true; - if (activate) - inspection.openHyperlink?.(link.link, link.input); - return; - } - const position = point(event, true) ?? lastPoint; - if (routedGesture) { - if (routedGesture === InputRoute.Application && position) - changeButtons(event, position); - if (event.buttons === 0) - cancel(routedGesture === InputRoute.Application); - return; - } - if (gesture.owner === "local") { - if (position && gesture.endpoint && (position.x !== gesture.endpoint.x || position.y !== gesture.endpoint.y)) { - if (selectionClick) - selectionClick.dragged = true; - const endpoint = gesture.move(position); - if (endpoint) - inspection.extend?.(endpoint); - } - const completed = selectionClick; - cancel(false, false); - completedClick = completed; - return; - } - if (position) - changeButtons(event, position); - if (!pressed.size) - cancel(); - }, options); - canvas.addEventListener("click", event => { - const completed = completedClick; - completedClick = undefined; - if (!completed || completed.dragged || !Number.isInteger(event.detail) || event.detail < 1) - return; - click.count = event.detail; - // Some browsers expose native multiclick counts only on click, not pointerdown. - const mode = event.detail >= 3 ? "line" : event.detail === 2 ? "word" : "character"; - if (!completed.extend && completed.mode !== "rectangle" && mode !== completed.mode) - inspection.begin?.(completed.point, { mode, extend: false }); - }, options); - canvas.addEventListener("pointercancel", () => cancel(), options); - canvas.addEventListener("pointerleave", () => { - hoverEvent = undefined; - refreshHover(); - }, options); - canvas.addEventListener("lostpointercapture", () => { - if (pointerId !== null) - cancel(); - }, options); - window.addEventListener("blur", () => { - cancel(); - hoverEvent = undefined; - refreshHover(); - }, options); - window.addEventListener("keydown", event => refreshHover(event), { ...options, capture: true }); - window.addEventListener("keyup", event => refreshHover(event), { ...options, capture: true }); - canvas.addEventListener("contextmenu", event => { - if (contextMenuRoute !== undefined && contextMenuRoute !== InputRoute.Continue) { - const route = contextMenuRoute; - contextMenuRoute = undefined; - if (route !== InputRoute.Browser) - event.preventDefault(); - return; - } - if (tracking || gesture.owner === "local") - event.preventDefault(); - }, options); - canvas.addEventListener("wheel", event => { - if (hyperlinkClick) - hyperlinkClick.dragged = true; - const input = { type: "wheel", deltaX: event.deltaX, deltaY: event.deltaY, - deltaMode: event.deltaMode, point: point(event), ...inputModifiers(event) }; - const decision = pointerId === null - ? inspection.resolve?.(input) ?? { route: InputRoute.Continue } - : { route: routedGesture ?? InputRoute.Continue }; - if (decision.route === InputRoute.Browser) - return; - if (decision.action !== undefined || decision.route === InputRoute.Consume) { - event.preventDefault(); - if (decision.action !== undefined) - inspection.execute?.(decision, input); - return; - } - // Ctrl+wheel (including trackpad pinch) remains browser zoom. - if (decision.route !== InputRoute.Application && (event.ctrlKey || event.metaKey)) - return; - const owner = decision.route === InputRoute.Application ? "app" : gesture.wheelOwner(event, state()); - if (owner === "app" && tracking === 9) - return; - const position = point(gesture.owner === "local" && pointerEvent ? pointerEvent : event, gesture.owner === "local"); - if (!position) - return; - event.preventDefault(); - flushMove(); - const bounds = canvas.getBoundingClientRect(); - if (wheelOwner !== owner) - wheel.reset(); - wheelOwner = owner; - const { x: horizontal, y: vertical } = wheel.take(event, bounds, rows); - if (owner === "local") { - if (vertical) { - if (selectionClick) - selectionClick.dragged = true; - click.count = 0; - inspection.scroll?.(vertical, gesture.scrollPoint(position)); - } - return; - } - const wheelDirections = [ - [vertical, "wheelUp", "wheelDown"], - [horizontal, "wheelLeft", "wheelRight"] - ]; - for (const [steps, negative, positive] of wheelDirections) { - if (steps) - send({ - type: "mouse", action: "wheel", button: steps < 0 ? negative : positive, - count: Math.min(32, Math.abs(steps)), ...position - }); - } - }, { ...options, passive: false }); - return { - update(nextColumns, nextRows, nextTracking) { - if (columns !== nextColumns || rows !== nextRows || tracking !== nextTracking) { - if (lastPoint) { - lastPoint = { ...lastPoint, - x: Math.min(lastPoint.x, nextColumns - 1), - y: Math.min(lastPoint.y, nextRows - 1) - }; - } - // Application mode changes do not transfer ownership mid-gesture. - if (columns !== nextColumns || rows !== nextRows) - cancel(nextTracking !== 0); - columns = nextColumns; - rows = nextRows; - tracking = nextTracking; - } - refreshHover(); - }, - refresh() { refreshHover(); }, - cancel() { cancel(); }, - dispose() { - cancel(false); - listeners.abort(); - canvas.title = originalTitle; - canvas.style.cursor = originalCursor; - } - }; -} -//# sourceMappingURL=mouse-input.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js.map deleted file mode 100644 index 43f556d5227..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/mouse-input.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"mouse-input.js","sourceRoot":"","sources":["../src/mouse-input.ts"],"names":[],"mappings":"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","sourcesContent":["import { cellPoint, WheelAccumulator, SelectionGesture } from \"./selection-input.js\";\nimport { InputRoute, inputModifiers } from \"./input-policy.js\";\nimport type { GestureState } from \"./selection-input.js\";\nimport type { InputDecision, InputRouteValue, MouseTrackingMode, PointerButton, SelectionMode,\n TerminalInput, TerminalPoint } from \"./types.js\";\nimport type { CellPosition, MouseButton, MouseCommand } from \"./wire-types.js\";\n\nconst buttons: readonly (readonly [PointerButton, number])[] = [[\"left\", 1], [\"middle\", 4], [\"right\", 2]];\nconst pointerButtons: readonly PointerButton[] = [\"left\", \"middle\", \"right\"];\ninterface SelectionClick { point: TerminalPoint; mode: SelectionMode; extend: boolean; dragged: boolean }\nexport interface PointerHyperlink {\n id: string;\n target: string;\n activation?: \"modifierClick\" | \"click\";\n}\ninterface HyperlinkClick { link: PointerHyperlink; input: TerminalInput; x: number; y: number; dragged: boolean }\ninterface MouseInspection {\n state?: () => Omit;\n begin?: (point: TerminalPoint, selection: { mode: SelectionMode; extend: boolean }) => void;\n extend?: (point: TerminalPoint) => void;\n scroll?: (delta: number, endpoint?: TerminalPoint) => void;\n end?: (cancelled: boolean) => void;\n resolve?: (input: TerminalInput) => InputDecision;\n execute?: (decision: Extract, input: TerminalInput) => void;\n hyperlink?: (point: TerminalPoint) => PointerHyperlink | null;\n hoverHyperlink?: (link: PointerHyperlink | null) => void;\n openHyperlink?: (link: PointerHyperlink, input: TerminalInput) => void;\n}\nexport interface MouseCapture {\n update(columns: number, rows: number, tracking: MouseTrackingMode): void;\n refresh(): void;\n cancel(): void;\n dispose(): void;\n}\n\n/** Capture input intent only; the server chooses and encodes the mouse protocol. */\nexport function captureMouse(canvas: HTMLCanvasElement, send: (command: MouseCommand) => void,\n focus: () => void, inspection: MouseInspection = {}): MouseCapture {\n const listeners = new AbortController();\n const options = { signal: listeners.signal };\n let columns = 1, rows = 1;\n let tracking: MouseTrackingMode = 0;\n let pointerId: number | null = null;\n const pressed = new Set();\n let lastPoint: CellPosition | undefined;\n let pendingMove: MouseCommand | undefined;\n let lastMove: string | undefined;\n let scheduled: number | undefined;\n const wheel = new WheelAccumulator();\n const gesture = new SelectionGesture();\n let wheelOwner: \"local\" | \"app\" | undefined;\n let pointerEvent: PointerEvent | undefined;\n let autoScroll: ReturnType | undefined;\n let click = { count: 0, time: 0, x: -1, y: -1 };\n let selectionClick: SelectionClick | undefined;\n let completedClick: SelectionClick | undefined;\n let routedGesture: InputRouteValue | undefined;\n let contextMenuRoute: InputRouteValue | undefined;\n let hyperlinkClick: HyperlinkClick | undefined;\n let hoverEvent: PointerEvent | undefined;\n let hoverModifiers: Pick | undefined;\n let hoveredLinkId: string | undefined;\n const originalTitle = canvas.title;\n const originalCursor = canvas.style.cursor;\n const state = () => ({ tracking, ...inspection.state?.() });\n\n function point(event: MouseEvent, clamp = false): CellPosition | null {\n return cellPoint(event, canvas.getBoundingClientRect(), columns, rows, clamp);\n }\n\n function refreshHover(modifiers = hoverModifiers) {\n if (!inspection.hyperlink) return;\n hoverModifiers = modifiers;\n const position = hoverEvent && point(hoverEvent);\n const link = position ? inspection.hyperlink(position) : null;\n canvas.title = link ? `${link.target}\\n${link.activation === \"click\" ? \"Click\" : \"Ctrl/Cmd+click\"} to activate link` : originalTitle;\n canvas.style.cursor = link && modifiers && (link.activation === \"click\" || modifiers.ctrlKey || modifiers.metaKey) &&\n !modifiers.altKey && !modifiers.shiftKey ? \"pointer\" : originalCursor;\n if (hyperlinkClick && hyperlinkClick.link.id !== link?.id) hyperlinkClick.dragged = true;\n if (hoveredLinkId !== link?.id) {\n hoveredLinkId = link?.id;\n inspection.hoverHyperlink?.(link);\n }\n }\n\n function updateAutoScroll() {\n clearTimeout(autoScroll);\n autoScroll = undefined;\n if (gesture.owner !== \"local\" || !pointerEvent) return;\n const bounds = canvas.getBoundingClientRect();\n const distance = pointerEvent.clientY < bounds.top ? pointerEvent.clientY - bounds.top\n : pointerEvent.clientY >= bounds.bottom ? pointerEvent.clientY - bounds.bottom + 1 : 0;\n if (!distance) return;\n autoScroll = setTimeout(() => {\n autoScroll = undefined;\n if (!pointerEvent) return;\n const position = point(pointerEvent, true);\n if (gesture.owner === \"local\" && position) {\n if (selectionClick) selectionClick.dragged = true;\n click.count = 0;\n const lines = Math.sign(distance) * Math.min(8, Math.max(1, Math.ceil(Math.abs(distance) / (bounds.height / rows))));\n inspection.scroll?.(lines, gesture.scrollPoint(position));\n updateAutoScroll();\n }\n }, 60);\n }\n\n function flushMove() {\n if (scheduled !== undefined) cancelAnimationFrame(scheduled);\n scheduled = undefined;\n if (pendingMove) send(pendingMove);\n pendingMove = undefined;\n }\n\n function changeButtons(event: PointerEvent, position: CellPosition): void {\n for (const [button, mask] of buttons) {\n const down = (event.buttons & mask) !== 0;\n if (down === pressed.has(button)) continue;\n flushMove();\n if (down) pressed.add(button);\n else pressed.delete(button);\n if (down || tracking !== 9)\n send({ type: \"mouse\", action: down ? \"down\" : \"up\", button, ...position });\n lastMove = undefined;\n }\n }\n\n function cancel(report = true, cancelled = true) {\n inspection.end?.(cancelled);\n clearTimeout(autoScroll);\n autoScroll = undefined;\n pointerEvent = undefined;\n selectionClick = undefined;\n completedClick = undefined;\n hyperlinkClick = undefined;\n gesture.end();\n routedGesture = undefined;\n if (report) flushMove();\n else {\n if (scheduled !== undefined) cancelAnimationFrame(scheduled);\n scheduled = undefined;\n pendingMove = undefined;\n }\n if (report && tracking !== 9 && lastPoint) {\n for (const button of pressed)\n send({ type: \"mouse\", action: \"up\", button, ...lastPoint });\n }\n pressed.clear();\n const captured = pointerId;\n pointerId = null;\n if (captured !== null && canvas.hasPointerCapture(captured))\n canvas.releasePointerCapture(captured);\n lastMove = undefined;\n wheel.reset();\n wheelOwner = undefined;\n }\n\n canvas.addEventListener(\"pointerdown\", event => {\n if (event.defaultPrevented && pointerId === null) return;\n if (event.pointerType !== \"mouse\" || ![0, 1, 2].includes(event.button)) return;\n const position = point(event);\n if (!position) return;\n if (pointerId !== null) {\n if (hyperlinkClick) hyperlinkClick.dragged = true;\n if (routedGesture !== InputRoute.Browser) event.preventDefault();\n if ((gesture.owner === \"app\" || routedGesture === InputRoute.Application) && pointerId === event.pointerId)\n changeButtons(event, position);\n return;\n }\n focus();\n const input: TerminalInput = { type: \"pointer\", button: pointerButtons[event.button],\n point: Object.freeze({ ...position }), ...inputModifiers(event) };\n const decision = inspection.resolve?.(input) ?? { route: InputRoute.Continue };\n let route = decision.action !== undefined ? InputRoute.Consume : decision.route;\n if (route === InputRoute.Continue && event.button === 0 && !event.altKey && !event.shiftKey) {\n const link = inspection.hyperlink?.(position);\n if (link && (link.activation === \"click\" || event.ctrlKey || event.metaKey)) {\n hyperlinkClick = { link, input, x: event.clientX, y: event.clientY, dragged: false };\n route = InputRoute.Consume;\n }\n }\n contextMenuRoute = event.button === 2 ? route : undefined;\n if (hyperlinkClick) contextMenuRoute = InputRoute.Consume;\n hoverEvent = event;\n refreshHover(event);\n if (route !== InputRoute.Continue) {\n completedClick = selectionClick = undefined;\n click.count = 0;\n routedGesture = route;\n pointerId = event.pointerId;\n lastPoint = position;\n canvas.setPointerCapture(pointerId);\n if (route !== InputRoute.Browser) event.preventDefault();\n if (route === InputRoute.Application) flushMove();\n else {\n if (scheduled !== undefined) cancelAnimationFrame(scheduled);\n scheduled = undefined;\n pendingMove = undefined;\n }\n if (route === InputRoute.Application) changeButtons(event, position);\n if (decision.action !== undefined) inspection.execute?.(decision, input);\n return;\n }\n // Keep focus on the hidden keyboard input instead of the canvas.\n event.preventDefault();\n if (event.metaKey) return;\n const now = performance.now();\n click.count = now - click.time < 500 && position.x === click.x && position.y === click.y\n ? click.count % 3 + 1 : 1;\n click = { count: click.count, time: now, x: position.x, y: position.y };\n const start = gesture.begin({ button: event.button, shiftKey: event.shiftKey, altKey: event.altKey,\n detail: event.detail || click.count }, position, state());\n if (!start) return;\n pointerId = event.pointerId;\n pointerEvent = event;\n lastPoint = position;\n completedClick = undefined;\n canvas.setPointerCapture(pointerId);\n if (start.owner === \"local\") {\n if (scheduled !== undefined) cancelAnimationFrame(scheduled);\n scheduled = undefined;\n pendingMove = undefined;\n lastMove = undefined;\n selectionClick = { point: position, mode: start.mode, extend: start.extend, dragged: false };\n inspection.begin?.(position, start);\n }\n else changeButtons(event, position);\n }, options);\n\n canvas.addEventListener(\"pointermove\", event => {\n if (event.pointerType !== \"mouse\") return;\n if (pointerId === null && event.buttons) return;\n if (pointerId !== null && pointerId !== event.pointerId) return;\n hoverEvent = event;\n refreshHover(event);\n if (hyperlinkClick && Math.hypot(event.clientX - hyperlinkClick.x, event.clientY - hyperlinkClick.y) >= 4)\n hyperlinkClick.dragged = true;\n const position = point(event, pointerId !== null);\n if (!position) return;\n lastPoint = position;\n if (routedGesture && routedGesture !== InputRoute.Application) return;\n if (gesture.owner === \"local\") {\n pointerEvent = event;\n const previous = gesture.endpoint;\n const endpoint = gesture.move(position);\n if (endpoint && previous && (endpoint.x !== previous.x || endpoint.y !== previous.y)) {\n click.count = 0;\n if (selectionClick) selectionClick.dragged = true;\n inspection.extend?.(endpoint);\n }\n updateAutoScroll();\n return;\n }\n if (!tracking || (!routedGesture && gesture.owner === null && (state().historical || state().readOnly || event.shiftKey))) return;\n if (pointerId === event.pointerId) changeButtons(event, position);\n if (event.metaKey || (tracking !== 1003 && !(tracking === 1002 && pressed.size))) return;\n const button = buttons.find(([name]) => pressed.has(name))?.[0] ?? \"none\";\n const move: MouseCommand = { type: \"mouse\", action: \"move\", button, ...position };\n const key = JSON.stringify(move);\n if (key === lastMove) return;\n lastMove = key;\n pendingMove = move;\n if (scheduled === undefined) scheduled = requestAnimationFrame(flushMove);\n }, options);\n\n canvas.addEventListener(\"pointerup\", event => {\n if (pointerId !== event.pointerId) return;\n if (hyperlinkClick) {\n event.preventDefault();\n const link = hyperlinkClick;\n const position = point(event);\n const activate = event.button === 0 && event.buttons === 0 && !link.dragged &&\n Math.hypot(event.clientX - link.x, event.clientY - link.y) < 4 &&\n position && inspection.hyperlink?.(position)?.id === link.link.id;\n if (event.buttons === 0) cancel(false, false);\n else link.dragged = true;\n if (activate) inspection.openHyperlink?.(link.link, link.input);\n return;\n }\n const position = point(event, true) ?? lastPoint;\n if (routedGesture) {\n if (routedGesture === InputRoute.Application && position) changeButtons(event, position);\n if (event.buttons === 0) cancel(routedGesture === InputRoute.Application);\n return;\n }\n if (gesture.owner === \"local\") {\n if (position && gesture.endpoint && (position.x !== gesture.endpoint.x || position.y !== gesture.endpoint.y)) {\n if (selectionClick) selectionClick.dragged = true;\n const endpoint = gesture.move(position);\n if (endpoint) inspection.extend?.(endpoint);\n }\n const completed = selectionClick;\n cancel(false, false);\n completedClick = completed;\n return;\n }\n if (position) changeButtons(event, position);\n if (!pressed.size) cancel();\n }, options);\n canvas.addEventListener(\"click\", event => {\n const completed = completedClick;\n completedClick = undefined;\n if (!completed || completed.dragged || !Number.isInteger(event.detail) || event.detail < 1) return;\n click.count = event.detail;\n // Some browsers expose native multiclick counts only on click, not pointerdown.\n const mode = event.detail >= 3 ? \"line\" : event.detail === 2 ? \"word\" : \"character\";\n if (!completed.extend && completed.mode !== \"rectangle\" && mode !== completed.mode)\n inspection.begin?.(completed.point, { mode, extend: false });\n }, options);\n canvas.addEventListener(\"pointercancel\", () => cancel(), options);\n canvas.addEventListener(\"pointerleave\", () => {\n hoverEvent = undefined;\n refreshHover();\n }, options);\n canvas.addEventListener(\"lostpointercapture\", () => {\n if (pointerId !== null) cancel();\n }, options);\n window.addEventListener(\"blur\", () => {\n cancel();\n hoverEvent = undefined;\n refreshHover();\n }, options);\n window.addEventListener(\"keydown\", event => refreshHover(event), { ...options, capture: true });\n window.addEventListener(\"keyup\", event => refreshHover(event), { ...options, capture: true });\n canvas.addEventListener(\"contextmenu\", event => {\n if (contextMenuRoute !== undefined && contextMenuRoute !== InputRoute.Continue) {\n const route = contextMenuRoute;\n contextMenuRoute = undefined;\n if (route !== InputRoute.Browser) event.preventDefault();\n return;\n }\n if (tracking || gesture.owner === \"local\") event.preventDefault();\n }, options);\n canvas.addEventListener(\"wheel\", event => {\n if (hyperlinkClick) hyperlinkClick.dragged = true;\n const input: TerminalInput = { type: \"wheel\", deltaX: event.deltaX, deltaY: event.deltaY,\n deltaMode: event.deltaMode, point: point(event), ...inputModifiers(event) };\n const decision: InputDecision = pointerId === null\n ? inspection.resolve?.(input) ?? { route: InputRoute.Continue }\n : { route: routedGesture ?? InputRoute.Continue };\n if (decision.route === InputRoute.Browser) return;\n if (decision.action !== undefined || decision.route === InputRoute.Consume) {\n event.preventDefault();\n if (decision.action !== undefined) inspection.execute?.(decision, input);\n return;\n }\n // Ctrl+wheel (including trackpad pinch) remains browser zoom.\n if (decision.route !== InputRoute.Application && (event.ctrlKey || event.metaKey)) return;\n const owner = decision.route === InputRoute.Application ? \"app\" : gesture.wheelOwner(event, state());\n if (owner === \"app\" && tracking === 9) return;\n const position = point(gesture.owner === \"local\" && pointerEvent ? pointerEvent : event, gesture.owner === \"local\");\n if (!position) return;\n event.preventDefault();\n flushMove();\n const bounds = canvas.getBoundingClientRect();\n if (wheelOwner !== owner) wheel.reset();\n wheelOwner = owner;\n const { x: horizontal, y: vertical } = wheel.take(event, bounds, rows);\n if (owner === \"local\") {\n if (vertical) {\n if (selectionClick) selectionClick.dragged = true;\n click.count = 0;\n inspection.scroll?.(vertical, gesture.scrollPoint(position));\n }\n return;\n }\n const wheelDirections: [number, MouseButton, MouseButton][] = [\n [vertical, \"wheelUp\", \"wheelDown\"],\n [horizontal, \"wheelLeft\", \"wheelRight\"]\n ];\n for (const [steps, negative, positive] of wheelDirections) {\n if (steps) send({\n type: \"mouse\", action: \"wheel\", button: steps < 0 ? negative : positive,\n count: Math.min(32, Math.abs(steps)), ...position\n });\n }\n }, { ...options, passive: false });\n\n return {\n update(nextColumns, nextRows, nextTracking) {\n if (columns !== nextColumns || rows !== nextRows || tracking !== nextTracking) {\n if (lastPoint) {\n lastPoint = { ...lastPoint,\n x: Math.min(lastPoint.x, nextColumns - 1),\n y: Math.min(lastPoint.y, nextRows - 1)\n };\n }\n // Application mode changes do not transfer ownership mid-gesture.\n if (columns !== nextColumns || rows !== nextRows) cancel(nextTracking !== 0);\n columns = nextColumns;\n rows = nextRows;\n tracking = nextTracking;\n }\n refreshHover();\n },\n refresh() { refreshHover(); },\n cancel() { cancel(); },\n dispose() {\n cancel(false);\n listeners.abort();\n canvas.title = originalTitle;\n canvas.style.cursor = originalCursor;\n }\n };\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts deleted file mode 100644 index 9759264663f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { HistoryMetadata, TerminalCell, TerminalFrame } from "./wire-types.js"; -export declare const LIMITS: Readonly<{ - commandBytes: number; - frameBytes: number; - metadataBytes: number; - titleUnits: 4096; - commandMarkParameterUnits: 8192; - cells: 262144; - images: 4096; - placements: 16384; - textureBytes: number; -}>; -export declare function assertCommandSize(command: unknown): void; -export declare function validateHistory(history: unknown, columns: number, rows: number): asserts history is HistoryMetadata | null; -/** Decode one complete, experimental HWT1 message, checking every boundary before reading. */ -export declare function decodeFrame(buffer: unknown): TerminalFrame; -/** Mirror text, not ANSI; continuation cells are already represented by their lead glyph. */ -export declare function screenText(cells: readonly (TerminalCell | undefined)[], columns: number, rows: number): string; -//# sourceMappingURL=protocol.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts.map deleted file mode 100644 index 282b587dd7e..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,eAAe,EAAiB,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIlH,eAAO,MAAM,MAAM;;;;;;;;;;EAUjB,CAAC;AAKH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGxD;AAyBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,IAAI,eAAe,GAAG,IAAI,CAqC1H;AA6JD,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,aAAa,CAiD1D;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,YAAY,GAAG,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAY9G"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js deleted file mode 100644 index 6436a85a16a..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js +++ /dev/null @@ -1,337 +0,0 @@ -import { isRecord } from "./validation.js"; -// Binary validation is deliberately independent of the GPU and the transport. -export const LIMITS = Object.freeze({ - commandBytes: 64 * 1024, - frameBytes: 96 * 1024 * 1024, - metadataBytes: 8 * 1024 * 1024, - titleUnits: 4096, - commandMarkParameterUnits: 8192, - cells: 262144, - images: 4096, - placements: 16384, - textureBytes: 256 * 1024 * 1024, -}); -const utf8 = new TextDecoder("utf-8", { fatal: true }); -const utf8Encoder = new TextEncoder(); -export function assertCommandSize(command) { - if (utf8Encoder.encode(JSON.stringify(command)).byteLength > LIMITS.commandBytes) - throw new RangeError("Terminal input exceeds the 64 KiB message limit. Send a smaller amount of text."); -} -function integer(value, name, min = 0, max = Number.MAX_SAFE_INTEGER) { - if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min || value > max) { - throw new Error(`Invalid ${name}: ${String(value)}`); - } - return value; -} -function array(value, name, limit) { - if (!Array.isArray(value) || value.length > limit) - throw new Error(`Invalid ${name}`); -} -function key(value) { - if (typeof value !== "string" || !value.length || value.length > 1024) { - throw new Error("Invalid image key"); - } -} -function rowId(value, name) { - if (typeof value !== "string" || !/^[1-9][0-9]{0,18}$/u.test(value) || BigInt(value) > 9223372036854775807n) { - throw new Error(`Invalid ${name}`); - } -} -export function validateHistory(history, columns, rows) { - if (history === null) - return; - if (!isRecord(history)) - throw new Error("Missing history metadata"); - rowId(history.generation, "history generation"); - if (history.buffer !== "main" && history.buffer !== "alternate") - throw new Error("Invalid history buffer"); - const totalRows = integer(history.totalRows, "history total rows", rows, 2147483647); - const liveTop = integer(history.liveTop, "history live top", 0, totalRows - rows); - if (liveTop !== totalRows - rows) - throw new Error("Inconsistent history extent"); - integer(history.top, "history top", 0, liveTop); - if (typeof history.following !== "boolean" || (history.following && history.top !== history.liveTop)) { - throw new Error("Invalid history following state"); - } - integer(history.requestId, "viewport request id"); - array(history.rowIds, "viewport row ids", rows); - if (history.rowIds.length !== rows || new Set(history.rowIds).size !== rows) - throw new Error("Invalid viewport row ids"); - for (const id of history.rowIds) - rowId(id, "viewport row id"); - const selection = history.selection; - if (!isRecord(selection) || typeof selection.status !== "string" || - !["none", "valid", "invalidated"].includes(selection.status)) - throw new Error("Invalid selection status"); - integer(selection.requestId, "selection request id"); - if (typeof selection.mode !== "string" || !["character", "word", "line", "rectangle"].includes(selection.mode)) - throw new Error("Invalid selection mode"); - array(selection.ranges, "selection ranges", rows); - let previousRow = -1; - for (const range of selection.ranges) { - if (!isRecord(range)) - throw new Error("Invalid selection range"); - const row = integer(range.row, "selection range row", previousRow + 1, rows - 1); - const startColumn = integer(range.startColumn, "selection start column", 0, columns - 1); - integer(range.endColumn, "selection end column", startColumn + 1, columns); - previousRow = row; - } - validateSelectionText(selection); - if (selection.status !== "valid" && selection.ranges.length) - throw new Error("Inactive selection has highlight ranges"); - if (history.copy !== null) { - if (!isRecord(history.copy)) - throw new Error("Missing copy metadata"); - integer(history.copy.requestId, "copy request id", 1); - validateSelectionText(history.copy); - } -} -function validateSelectionText(selection) { - if (typeof selection.status !== "string" || !["none", "valid", "invalidated"].includes(selection.status) || - (selection.status === "valid" - ? typeof selection.text !== "string" || selection.text.length > LIMITS.metadataBytes - : selection.text !== null)) { - throw new Error("Invalid selection text"); - } -} -function validateMetadata(metadata) { - if (!isRecord(metadata) || metadata.version !== 1 || typeof metadata.full !== "boolean") { - throw new Error("Unsupported frame metadata version"); - } - integer(metadata.revision, "revision", 1); - integer(metadata.baseRevision, "base revision"); - if (typeof metadata.title !== "string" || metadata.title.length > LIMITS.titleUnits || - /[\u0000-\u001f\u007f-\u009f\ud800-\udfff]/u.test(metadata.title)) { - throw new Error("Invalid terminal title"); - } - const progress = metadata.progress; - if (!isRecord(progress) || typeof progress.state !== "string" || - !["none", "normal", "error", "indeterminate", "warning"].includes(progress.state)) { - throw new Error("Invalid terminal progress"); - } - if (progress.state === "none" || progress.state === "indeterminate") { - if (progress.percentage !== null) - throw new Error("Unexpected terminal progress percentage"); - } - else { - integer(progress.percentage, "terminal progress percentage", 0, 100); - } - const shell = metadata.shellIntegration; - if (!isRecord(shell) || typeof shell.phase !== "string" || - !["unknown", "prompt", "commandLine", "executing", "finished"].includes(shell.phase)) { - throw new Error("Invalid terminal shell integration"); - } - if (shell.lastExitCode !== null) - integer(shell.lastExitCode, "terminal shell exit code", -2147483648, 2147483647); - if (shell.phase === "unknown" && shell.lastExitCode !== null) - throw new Error("Unknown terminal shell phase has an exit code"); - const workingDirectory = metadata.workingDirectory; - if (!isRecord(workingDirectory)) - throw new Error("Invalid terminal working directory"); - const wdFields = [workingDirectory.uri, workingDirectory.host, workingDirectory.path]; - if (wdFields.every(field => field === null)) { - // No directory reported yet. - } - else if (wdFields.some(field => typeof field !== "string" || field.length > LIMITS.metadataBytes)) { - throw new Error("Invalid terminal working directory"); - } - const commandMark = metadata.commandMark; - if (commandMark !== null) { - if (!isRecord(commandMark) || typeof commandMark.phase !== "string" || - !["unknown", "prompt", "commandLine", "executing", "finished"].includes(commandMark.phase)) { - throw new Error("Invalid terminal command mark"); - } - if (commandMark.exitCode !== null) { - integer(commandMark.exitCode, "terminal command mark exit code", -2147483648, 2147483647); - if (commandMark.phase !== "finished") - throw new Error("Non-finished terminal command mark has an exit code"); - } - if (commandMark.rawParameters !== null && - (typeof commandMark.rawParameters !== "string" || commandMark.rawParameters.length > LIMITS.commandMarkParameterUnits)) { - throw new Error("Invalid terminal command mark parameters"); - } - } - const columns = integer(metadata.columns, "columns", 1, 1024); - const rows = integer(metadata.rows, "rows", 1, 512); - if (typeof metadata.mouseTracking !== "number" || ![0, 9, 1000, 1002, 1003].includes(metadata.mouseTracking)) { - throw new Error("Unsupported mouse tracking mode"); - } - if (!isRecord(metadata.peer) || typeof metadata.peer.isPrimary !== "boolean") - throw new Error("Invalid peer state"); - for (const field of ["id", "primaryId"]) { - const id = metadata.peer[field]; - if (id !== null && (typeof id !== "string" || !id.length || id.length > 256)) { - throw new Error(`Invalid peer ${field}`); - } - } - if (metadata.peer.id !== null && metadata.peer.isPrimary !== (metadata.peer.id === metadata.peer.primaryId)) { - throw new Error("Inconsistent primary peer state"); - } - integer(columns * rows, "cell count", 1, LIMITS.cells); - validateHistory(metadata.history, columns, rows); - array(metadata.hyperlinks, "hyperlinks", columns * rows); - let previousLinkEnd = 0; - for (const link of metadata.hyperlinks) { - if (!isRecord(link)) - throw new Error("Invalid hyperlink"); - const row = integer(link.row, "hyperlink row", 0, rows - 1); - const start = integer(link.startColumn, "hyperlink start column", 0, columns - 1); - const end = integer(link.endColumn, "hyperlink end column", start + 1, columns); - if (row * columns + start < previousLinkEnd) - throw new Error("Unordered or overlapping hyperlinks"); - previousLinkEnd = row * columns + end; - if (typeof link.uri !== "string" || !link.uri.length || link.uri.length > LIMITS.metadataBytes) - throw new Error("Invalid hyperlink URI"); - } - if (metadata.cellWidth !== 10 || metadata.cellHeight !== 20) { - throw new Error("This spike requires server geometry of 10 × 20 logical pixels"); - } - for (const field of ["defaultBackground", "defaultForeground"]) { - if (metadata[field] !== undefined) - integer(metadata[field], field, 0, 0xffffffff); - } - if (!isRecord(metadata.cursor) || typeof metadata.cursor.visible !== "boolean") - throw new Error("Invalid cursor"); - integer(metadata.cursor.x, "cursor x", -1, 1024); - integer(metadata.cursor.y, "cursor y", -1, 512); - const shapes = ["Default", "BlinkingBlock", "SteadyBlock", "BlinkingUnderline", "SteadyUnderline", "BlinkingBar", "SteadyBar"]; - if (typeof metadata.cursor.shape === "string") - metadata.cursor.shape = shapes.indexOf(metadata.cursor.shape); - integer(metadata.cursor.shape, "cursor shape", 0, 6); - array(metadata.images, "images", LIMITS.images); - array(metadata.retainedImages, "retained image keys", LIMITS.images); - array(metadata.placements, "placements", LIMITS.placements); - array(metadata.warnings, "warnings", 256); - if (metadata.warnings.some(w => typeof w !== "string")) - throw new Error("Invalid warning"); - if (!isRecord(metadata.stats)) - throw new Error("Invalid server metrics"); - for (const field of ["workloadBytes", "outputBatches", "captureMs", "elapsedMs"]) { - const metric = metadata.stats[field]; - if (typeof metric !== "number" || !Number.isFinite(metric) || metric < 0) { - throw new Error(`Invalid server metric ${field}`); - } - } - const retained = new Set(); - for (const imageKey of metadata.retainedImages) { - key(imageKey); - if (retained.has(imageKey)) - throw new Error("Duplicate retained image key"); - retained.add(imageKey); - } - const imageKeys = new Set(); - let decodedImageBytes = 0; - for (const image of metadata.images) { - if (!isRecord(image)) - throw new Error("Invalid image"); - key(image.key); - if (imageKeys.has(image.key) || !retained.has(image.key)) - throw new Error("Inconsistent new image keys"); - imageKeys.add(image.key); - const width = integer(image.width, "image width", 1, 16384); - const height = integer(image.height, "image height", 1, 16384); - const byteLength = integer(image.byteLength, "image byte length", 1, LIMITS.frameBytes); - if (image.format !== "rgba" && image.format !== "png") - throw new Error("Unsupported image format"); - if (image.format === "rgba" && byteLength !== width * height * 4) { - throw new Error("RGBA image size mismatch"); - } - decodedImageBytes += width * height * 4; - if (decodedImageBytes > LIMITS.textureBytes) - throw new Error("New images exceed decoded texture budget"); - } - for (const placement of metadata.placements) { - if (!isRecord(placement)) - throw new Error("Invalid placement"); - key(placement.key); - if (!retained.has(placement.key)) - throw new Error("Placement references an unretained image"); - if (placement.kind !== "kgp" && placement.kind !== "sixel") - throw new Error("Invalid placement kind"); - for (const field of ["x", "y", "width", "height", "sourceX", "sourceY", "sourceWidth", "sourceHeight", "clipX", "clipY", "clipWidth", "clipHeight", "z"]) { - const coordinate = placement[field]; - if (typeof coordinate !== "number" || !Number.isFinite(coordinate) || Math.abs(coordinate) > 2147483648) { - throw new Error(`Invalid placement ${field}`); - } - } - for (const field of ["width", "height", "sourceWidth", "sourceHeight", "clipWidth", "clipHeight"]) { - const coordinate = placement[field]; - if (typeof coordinate !== "number" || coordinate < 0) - throw new Error(`Negative placement ${field}`); - } - } -} -/** Decode one complete, experimental HWT1 message, checking every boundary before reading. */ -export function decodeFrame(buffer) { - if (!(buffer instanceof ArrayBuffer) || buffer.byteLength > LIMITS.frameBytes) { - throw new Error("Invalid or oversized binary frame"); - } - const view = new DataView(buffer); - let offset = 0; - const requireBytes = (count) => { - if (count < 0 || count > view.byteLength - offset) - throw new Error("Truncated HWT1 frame"); - }; - const u32 = () => { requireBytes(4); const n = view.getUint32(offset, true); offset += 4; return n; }; - if (u32() !== 0x31545748) - throw new Error("Unsupported frame magic (expected HWT1)"); - const metadataLength = integer(u32(), "metadata length", 2, LIMITS.metadataBytes); - requireBytes(metadataLength); - const metadata = JSON.parse(utf8.decode(new Uint8Array(buffer, offset, metadataLength))); - offset += metadataLength; - validateMetadata(metadata); - const cellCount = metadata.columns * metadata.rows; - const changedCount = integer(u32(), "changed cell count", 0, cellCount); - if (changedCount > Math.floor((view.byteLength - offset) / 22)) - throw new Error("Truncated cell records"); - if (metadata.full && changedCount !== cellCount) - throw new Error("Incomplete full frame"); - const cells = []; - const seen = new Set(); - for (let i = 0; i < changedCount; i++) { - requireBytes(22); - const index = u32(); - if (index >= cellCount || seen.has(index)) - throw new Error("Invalid or duplicate cell index"); - seen.add(index); - const foreground = u32(); - const background = u32(); - const underlineColor = u32(); - const attributes = view.getUint16(offset, true); - const width = view.getUint8(offset + 2); - const underlineStyle = view.getUint8(offset + 3); - const textLength = view.getUint16(offset + 4, true); - offset += 6; - if (underlineStyle > 5) - throw new Error("Unsupported underline style"); - requireBytes(textLength); - const text = utf8.decode(new Uint8Array(buffer, offset, textLength)); - offset += textLength; - cells.push({ index, foreground, background, underlineColor, attributes, width, underlineStyle, text }); - } - const imageBytes = metadata.images.reduce((total, image) => total + image.byteLength, 0); - if (imageBytes !== view.byteLength - offset) - throw new Error("Image payload length mismatch"); - const images = metadata.images.map(image => { - const bytes = new Uint8Array(buffer, offset, image.byteLength); - offset += image.byteLength; - return { ...image, bytes }; - }); - return { metadata, cells, images }; -} -/** Mirror text, not ANSI; continuation cells are already represented by their lead glyph. */ -export function screenText(cells, columns, rows) { - const lines = []; - for (let y = 0; y < rows; y++) { - let line = ""; - for (let x = 0; x < columns; x++) { - const cell = cells[y * columns + x]; - if (!cell || cell.width === 0) - continue; - line += cell.attributes & 64 ? " ".repeat(cell.width) : (cell.text || " "); - } - lines.push(line.replace(/ +$/u, "")); - } - return lines.join("\n"); -} -//# sourceMappingURL=protocol.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js.map deleted file mode 100644 index b6b2d7a41f6..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/protocol.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FrameMetadata, HistoryMetadata, SelectionText, TerminalCell, TerminalFrame } from \"./wire-types.js\";\nimport { isRecord } from \"./validation.js\";\n\n// Binary validation is deliberately independent of the GPU and the transport.\nexport const LIMITS = Object.freeze({\n commandBytes: 64 * 1024,\n frameBytes: 96 * 1024 * 1024,\n metadataBytes: 8 * 1024 * 1024,\n titleUnits: 4096,\n commandMarkParameterUnits: 8192,\n cells: 262144,\n images: 4096,\n placements: 16384,\n textureBytes: 256 * 1024 * 1024,\n});\n\nconst utf8 = new TextDecoder(\"utf-8\", { fatal: true });\nconst utf8Encoder = new TextEncoder();\n\nexport function assertCommandSize(command: unknown): void {\n if (utf8Encoder.encode(JSON.stringify(command)).byteLength > LIMITS.commandBytes)\n throw new RangeError(\"Terminal input exceeds the 64 KiB message limit. Send a smaller amount of text.\");\n}\n\nfunction integer(value: unknown, name: string, min = 0, max = Number.MAX_SAFE_INTEGER): number {\n if (typeof value !== \"number\" || !Number.isSafeInteger(value) || value < min || value > max) {\n throw new Error(`Invalid ${name}: ${String(value)}`);\n }\n return value;\n}\n\nfunction array(value: unknown, name: string, limit: number): asserts value is unknown[] {\n if (!Array.isArray(value) || value.length > limit) throw new Error(`Invalid ${name}`);\n}\n\nfunction key(value: unknown): asserts value is string {\n if (typeof value !== \"string\" || !value.length || value.length > 1024) {\n throw new Error(\"Invalid image key\");\n }\n}\n\nfunction rowId(value: unknown, name: string): asserts value is string {\n if (typeof value !== \"string\" || !/^[1-9][0-9]{0,18}$/u.test(value) || BigInt(value) > 9223372036854775807n) {\n throw new Error(`Invalid ${name}`);\n }\n}\n\nexport function validateHistory(history: unknown, columns: number, rows: number): asserts history is HistoryMetadata | null {\n if (history === null) return;\n if (!isRecord(history)) throw new Error(\"Missing history metadata\");\n rowId(history.generation, \"history generation\");\n if (history.buffer !== \"main\" && history.buffer !== \"alternate\") throw new Error(\"Invalid history buffer\");\n const totalRows = integer(history.totalRows, \"history total rows\", rows, 2147483647);\n const liveTop = integer(history.liveTop, \"history live top\", 0, totalRows - rows);\n if (liveTop !== totalRows - rows) throw new Error(\"Inconsistent history extent\");\n integer(history.top, \"history top\", 0, liveTop);\n if (typeof history.following !== \"boolean\" || (history.following && history.top !== history.liveTop)) {\n throw new Error(\"Invalid history following state\");\n }\n integer(history.requestId, \"viewport request id\");\n array(history.rowIds, \"viewport row ids\", rows);\n if (history.rowIds.length !== rows || new Set(history.rowIds).size !== rows) throw new Error(\"Invalid viewport row ids\");\n for (const id of history.rowIds) rowId(id, \"viewport row id\");\n const selection = history.selection;\n if (!isRecord(selection) || typeof selection.status !== \"string\" ||\n ![\"none\", \"valid\", \"invalidated\"].includes(selection.status)) throw new Error(\"Invalid selection status\");\n integer(selection.requestId, \"selection request id\");\n if (typeof selection.mode !== \"string\" || ![\"character\", \"word\", \"line\", \"rectangle\"].includes(selection.mode)) throw new Error(\"Invalid selection mode\");\n array(selection.ranges, \"selection ranges\", rows);\n let previousRow = -1;\n for (const range of selection.ranges) {\n if (!isRecord(range)) throw new Error(\"Invalid selection range\");\n const row = integer(range.row, \"selection range row\", previousRow + 1, rows - 1);\n const startColumn = integer(range.startColumn, \"selection start column\", 0, columns - 1);\n integer(range.endColumn, \"selection end column\", startColumn + 1, columns);\n previousRow = row;\n }\n validateSelectionText(selection);\n if (selection.status !== \"valid\" && selection.ranges.length) throw new Error(\"Inactive selection has highlight ranges\");\n if (history.copy !== null) {\n if (!isRecord(history.copy)) throw new Error(\"Missing copy metadata\");\n integer(history.copy.requestId, \"copy request id\", 1);\n validateSelectionText(history.copy);\n }\n}\n\nfunction validateSelectionText(selection: Record): asserts selection is Record & SelectionText {\n if (typeof selection.status !== \"string\" || ![\"none\", \"valid\", \"invalidated\"].includes(selection.status) ||\n (selection.status === \"valid\"\n ? typeof selection.text !== \"string\" || selection.text.length > LIMITS.metadataBytes\n : selection.text !== null)) {\n throw new Error(\"Invalid selection text\");\n }\n}\n\nfunction validateMetadata(metadata: unknown): asserts metadata is FrameMetadata {\n if (!isRecord(metadata) || metadata.version !== 1 || typeof metadata.full !== \"boolean\") {\n throw new Error(\"Unsupported frame metadata version\");\n }\n integer(metadata.revision, \"revision\", 1);\n integer(metadata.baseRevision, \"base revision\");\n if (typeof metadata.title !== \"string\" || metadata.title.length > LIMITS.titleUnits ||\n /[\\u0000-\\u001f\\u007f-\\u009f\\ud800-\\udfff]/u.test(metadata.title)) {\n throw new Error(\"Invalid terminal title\");\n }\n const progress = metadata.progress;\n if (!isRecord(progress) || typeof progress.state !== \"string\" ||\n ![\"none\", \"normal\", \"error\", \"indeterminate\", \"warning\"].includes(progress.state)) {\n throw new Error(\"Invalid terminal progress\");\n }\n if (progress.state === \"none\" || progress.state === \"indeterminate\") {\n if (progress.percentage !== null) throw new Error(\"Unexpected terminal progress percentage\");\n } else {\n integer(progress.percentage, \"terminal progress percentage\", 0, 100);\n }\n const shell = metadata.shellIntegration;\n if (!isRecord(shell) || typeof shell.phase !== \"string\" ||\n ![\"unknown\", \"prompt\", \"commandLine\", \"executing\", \"finished\"].includes(shell.phase)) {\n throw new Error(\"Invalid terminal shell integration\");\n }\n if (shell.lastExitCode !== null)\n integer(shell.lastExitCode, \"terminal shell exit code\", -2147483648, 2147483647);\n if (shell.phase === \"unknown\" && shell.lastExitCode !== null)\n throw new Error(\"Unknown terminal shell phase has an exit code\");\n const workingDirectory = metadata.workingDirectory;\n if (!isRecord(workingDirectory)) throw new Error(\"Invalid terminal working directory\");\n const wdFields = [workingDirectory.uri, workingDirectory.host, workingDirectory.path];\n if (wdFields.every(field => field === null)) {\n // No directory reported yet.\n } else if (wdFields.some(field => typeof field !== \"string\" || field.length > LIMITS.metadataBytes)) {\n throw new Error(\"Invalid terminal working directory\");\n }\n const commandMark = metadata.commandMark;\n if (commandMark !== null) {\n if (!isRecord(commandMark) || typeof commandMark.phase !== \"string\" ||\n ![\"unknown\", \"prompt\", \"commandLine\", \"executing\", \"finished\"].includes(commandMark.phase)) {\n throw new Error(\"Invalid terminal command mark\");\n }\n if (commandMark.exitCode !== null) {\n integer(commandMark.exitCode, \"terminal command mark exit code\", -2147483648, 2147483647);\n if (commandMark.phase !== \"finished\") throw new Error(\"Non-finished terminal command mark has an exit code\");\n }\n if (commandMark.rawParameters !== null &&\n (typeof commandMark.rawParameters !== \"string\" || commandMark.rawParameters.length > LIMITS.commandMarkParameterUnits)) {\n throw new Error(\"Invalid terminal command mark parameters\");\n }\n }\n const columns = integer(metadata.columns, \"columns\", 1, 1024);\n const rows = integer(metadata.rows, \"rows\", 1, 512);\n if (typeof metadata.mouseTracking !== \"number\" || ![0, 9, 1000, 1002, 1003].includes(metadata.mouseTracking)) {\n throw new Error(\"Unsupported mouse tracking mode\");\n }\n if (!isRecord(metadata.peer) || typeof metadata.peer.isPrimary !== \"boolean\") throw new Error(\"Invalid peer state\");\n for (const field of [\"id\", \"primaryId\"]) {\n const id = metadata.peer[field];\n if (id !== null && (typeof id !== \"string\" || !id.length || id.length > 256)) {\n throw new Error(`Invalid peer ${field}`);\n }\n }\n if (metadata.peer.id !== null && metadata.peer.isPrimary !== (metadata.peer.id === metadata.peer.primaryId)) {\n throw new Error(\"Inconsistent primary peer state\");\n }\n integer(columns * rows, \"cell count\", 1, LIMITS.cells);\n validateHistory(metadata.history, columns, rows);\n array(metadata.hyperlinks, \"hyperlinks\", columns * rows);\n let previousLinkEnd = 0;\n for (const link of metadata.hyperlinks) {\n if (!isRecord(link)) throw new Error(\"Invalid hyperlink\");\n const row = integer(link.row, \"hyperlink row\", 0, rows - 1);\n const start = integer(link.startColumn, \"hyperlink start column\", 0, columns - 1);\n const end = integer(link.endColumn, \"hyperlink end column\", start + 1, columns);\n if (row * columns + start < previousLinkEnd) throw new Error(\"Unordered or overlapping hyperlinks\");\n previousLinkEnd = row * columns + end;\n if (typeof link.uri !== \"string\" || !link.uri.length || link.uri.length > LIMITS.metadataBytes)\n throw new Error(\"Invalid hyperlink URI\");\n }\n if (metadata.cellWidth !== 10 || metadata.cellHeight !== 20) {\n throw new Error(\"This spike requires server geometry of 10 × 20 logical pixels\");\n }\n for (const field of [\"defaultBackground\", \"defaultForeground\"]) {\n if (metadata[field] !== undefined) integer(metadata[field], field, 0, 0xffffffff);\n }\n if (!isRecord(metadata.cursor) || typeof metadata.cursor.visible !== \"boolean\") throw new Error(\"Invalid cursor\");\n integer(metadata.cursor.x, \"cursor x\", -1, 1024);\n integer(metadata.cursor.y, \"cursor y\", -1, 512);\n const shapes = [\"Default\", \"BlinkingBlock\", \"SteadyBlock\", \"BlinkingUnderline\", \"SteadyUnderline\", \"BlinkingBar\", \"SteadyBar\"];\n if (typeof metadata.cursor.shape === \"string\") metadata.cursor.shape = shapes.indexOf(metadata.cursor.shape);\n integer(metadata.cursor.shape, \"cursor shape\", 0, 6);\n array(metadata.images, \"images\", LIMITS.images);\n array(metadata.retainedImages, \"retained image keys\", LIMITS.images);\n array(metadata.placements, \"placements\", LIMITS.placements);\n array(metadata.warnings, \"warnings\", 256);\n if (metadata.warnings.some(w => typeof w !== \"string\")) throw new Error(\"Invalid warning\");\n if (!isRecord(metadata.stats)) throw new Error(\"Invalid server metrics\");\n for (const field of [\"workloadBytes\", \"outputBatches\", \"captureMs\", \"elapsedMs\"]) {\n const metric = metadata.stats[field];\n if (typeof metric !== \"number\" || !Number.isFinite(metric) || metric < 0) {\n throw new Error(`Invalid server metric ${field}`);\n }\n }\n const retained = new Set();\n for (const imageKey of metadata.retainedImages) {\n key(imageKey);\n if (retained.has(imageKey)) throw new Error(\"Duplicate retained image key\");\n retained.add(imageKey);\n }\n const imageKeys = new Set();\n let decodedImageBytes = 0;\n for (const image of metadata.images) {\n if (!isRecord(image)) throw new Error(\"Invalid image\");\n key(image.key);\n if (imageKeys.has(image.key) || !retained.has(image.key)) throw new Error(\"Inconsistent new image keys\");\n imageKeys.add(image.key);\n const width = integer(image.width, \"image width\", 1, 16384);\n const height = integer(image.height, \"image height\", 1, 16384);\n const byteLength = integer(image.byteLength, \"image byte length\", 1, LIMITS.frameBytes);\n if (image.format !== \"rgba\" && image.format !== \"png\") throw new Error(\"Unsupported image format\");\n if (image.format === \"rgba\" && byteLength !== width * height * 4) {\n throw new Error(\"RGBA image size mismatch\");\n }\n decodedImageBytes += width * height * 4;\n if (decodedImageBytes > LIMITS.textureBytes) throw new Error(\"New images exceed decoded texture budget\");\n }\n for (const placement of metadata.placements) {\n if (!isRecord(placement)) throw new Error(\"Invalid placement\");\n key(placement.key);\n if (!retained.has(placement.key)) throw new Error(\"Placement references an unretained image\");\n if (placement.kind !== \"kgp\" && placement.kind !== \"sixel\") throw new Error(\"Invalid placement kind\");\n for (const field of [\"x\", \"y\", \"width\", \"height\", \"sourceX\", \"sourceY\", \"sourceWidth\", \"sourceHeight\", \"clipX\", \"clipY\", \"clipWidth\", \"clipHeight\", \"z\"]) {\n const coordinate = placement[field];\n if (typeof coordinate !== \"number\" || !Number.isFinite(coordinate) || Math.abs(coordinate) > 2147483648) {\n throw new Error(`Invalid placement ${field}`);\n }\n }\n for (const field of [\"width\", \"height\", \"sourceWidth\", \"sourceHeight\", \"clipWidth\", \"clipHeight\"]) {\n const coordinate = placement[field];\n if (typeof coordinate !== \"number\" || coordinate < 0) throw new Error(`Negative placement ${field}`);\n }\n }\n}\n\n/** Decode one complete, experimental HWT1 message, checking every boundary before reading. */\nexport function decodeFrame(buffer: unknown): TerminalFrame {\n if (!(buffer instanceof ArrayBuffer) || buffer.byteLength > LIMITS.frameBytes) {\n throw new Error(\"Invalid or oversized binary frame\");\n }\n const view = new DataView(buffer);\n let offset = 0;\n const requireBytes = (count: number) => {\n if (count < 0 || count > view.byteLength - offset) throw new Error(\"Truncated HWT1 frame\");\n };\n const u32 = () => { requireBytes(4); const n = view.getUint32(offset, true); offset += 4; return n; };\n if (u32() !== 0x31545748) throw new Error(\"Unsupported frame magic (expected HWT1)\");\n const metadataLength = integer(u32(), \"metadata length\", 2, LIMITS.metadataBytes);\n requireBytes(metadataLength);\n const metadata: unknown = JSON.parse(utf8.decode(new Uint8Array(buffer, offset, metadataLength)));\n offset += metadataLength;\n validateMetadata(metadata);\n const cellCount = metadata.columns * metadata.rows;\n const changedCount = integer(u32(), \"changed cell count\", 0, cellCount);\n if (changedCount > Math.floor((view.byteLength - offset) / 22)) throw new Error(\"Truncated cell records\");\n if (metadata.full && changedCount !== cellCount) throw new Error(\"Incomplete full frame\");\n const cells = [];\n const seen = new Set();\n for (let i = 0; i < changedCount; i++) {\n requireBytes(22);\n const index = u32();\n if (index >= cellCount || seen.has(index)) throw new Error(\"Invalid or duplicate cell index\");\n seen.add(index);\n const foreground = u32();\n const background = u32();\n const underlineColor = u32();\n const attributes = view.getUint16(offset, true);\n const width = view.getUint8(offset + 2);\n const underlineStyle = view.getUint8(offset + 3);\n const textLength = view.getUint16(offset + 4, true);\n offset += 6;\n if (underlineStyle > 5) throw new Error(\"Unsupported underline style\");\n requireBytes(textLength);\n const text = utf8.decode(new Uint8Array(buffer, offset, textLength));\n offset += textLength;\n cells.push({ index, foreground, background, underlineColor, attributes, width, underlineStyle, text });\n }\n const imageBytes = metadata.images.reduce((total, image) => total + image.byteLength, 0);\n if (imageBytes !== view.byteLength - offset) throw new Error(\"Image payload length mismatch\");\n const images = metadata.images.map(image => {\n const bytes = new Uint8Array(buffer, offset, image.byteLength);\n offset += image.byteLength;\n return { ...image, bytes };\n });\n return { metadata, cells, images };\n}\n\n/** Mirror text, not ANSI; continuation cells are already represented by their lead glyph. */\nexport function screenText(cells: readonly (TerminalCell | undefined)[], columns: number, rows: number): string {\n const lines = [];\n for (let y = 0; y < rows; y++) {\n let line = \"\";\n for (let x = 0; x < columns; x++) {\n const cell = cells[y * columns + x];\n if (!cell || cell.width === 0) continue;\n line += cell.attributes & 64 ? \" \".repeat(cell.width) : (cell.text || \" \");\n }\n lines.push(line.replace(/ +$/u, \"\"));\n }\n return lines.join(\"\\n\");\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts deleted file mode 100644 index ae7a639c2ef..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -export declare const QUAD_STRIDE = 16; -export type RenderColor = [number, number, number, number]; -export type RenderPixels = Uint8Array | Uint8ClampedArray; -export interface RenderTexture { - readonly width: number; - readonly height: number; - writePixels(pixels: RenderPixels, width: number, height: number, x?: number, y?: number): void; - writeBitmap(bitmap: ImageBitmap): void; - destroy(): void; -} -export interface RenderBatch { - resource: RenderTexture; - start: number; - count: number; -} -/** Internal graphics boundary; layout, rasterization and ordering belong to TerminalRenderer. */ -export interface RenderBackend { - readonly kind: "webgpu" | "webgl2"; - readonly maxTextureDimension2D: number; - readonly maxCanvasDimension2D: number; - readonly instanceBufferBytes: number; - createTexture(width: number, height: number, label: string): RenderTexture; - resize(width: number, height: number): void; - submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[], background: RenderColor): void; - idle(): Promise; - dispose(): void; -} -/** Only capability/device acquisition failures may trigger automatic backend fallback. */ -export declare class RendererUnavailableError extends Error { -} -//# sourceMappingURL=render-backend.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts.map deleted file mode 100644 index d85d9d3c42b..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"render-backend.d.ts","sourceRoot":"","sources":["../src/render-backend.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAEpF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/F,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAAG,QAAQ,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAEtF,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC;IAC3E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,EAC7F,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,0FAA0F;AAC1F,qBAAa,wBAAyB,SAAQ,KAAK;CAAG"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js deleted file mode 100644 index e6e78a47c75..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js +++ /dev/null @@ -1,5 +0,0 @@ -export const QUAD_STRIDE = 16; -/** Only capability/device acquisition failures may trigger automatic backend fallback. */ -export class RendererUnavailableError extends Error { -} -//# sourceMappingURL=render-backend.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js.map deleted file mode 100644 index b6882c78307..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/render-backend.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"render-backend.js","sourceRoot":"","sources":["../src/render-backend.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AA6B9B,0FAA0F;AAC1F,MAAM,OAAO,wBAAyB,SAAQ,KAAK;CAAG","sourcesContent":["export const QUAD_STRIDE = 16;\n\nexport type RenderColor = [number, number, number, number];\nexport type RenderPixels = Uint8Array | Uint8ClampedArray;\n\nexport interface RenderTexture {\n readonly width: number;\n readonly height: number;\n writePixels(pixels: RenderPixels, width: number, height: number, x?: number, y?: number): void;\n writeBitmap(bitmap: ImageBitmap): void;\n destroy(): void;\n}\n\nexport interface RenderBatch { resource: RenderTexture; start: number; count: number }\n\n/** Internal graphics boundary; layout, rasterization and ordering belong to TerminalRenderer. */\nexport interface RenderBackend {\n readonly kind: \"webgpu\" | \"webgl2\";\n readonly maxTextureDimension2D: number;\n readonly maxCanvasDimension2D: number;\n readonly instanceBufferBytes: number;\n createTexture(width: number, height: number, label: string): RenderTexture;\n resize(width: number, height: number): void;\n submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[],\n background: RenderColor): void;\n idle(): Promise;\n dispose(): void;\n}\n\n/** Only capability/device acquisition failures may trigger automatic backend fallback. */\nexport class RendererUnavailableError extends Error {}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts deleted file mode 100644 index e2624ba44d4..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { TerminalRendererPreference } from "./types.js"; -export declare function normalizeRenderer(value?: unknown): TerminalRendererPreference; -//# sourceMappingURL=renderer-options.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts.map deleted file mode 100644 index 05a5c8d716a..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"renderer-options.d.ts","sourceRoot":"","sources":["../src/renderer-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAE7D,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,OAAgB,GAAG,0BAA0B,CAGrF"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js deleted file mode 100644 index 309b8d17616..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js +++ /dev/null @@ -1,6 +0,0 @@ -export function normalizeRenderer(value = "auto") { - if (value === "auto" || value === "webgpu" || value === "webgl2") - return value; - throw new TypeError('renderer must be "auto", "webgpu", or "webgl2"'); -} -//# sourceMappingURL=renderer-options.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js.map deleted file mode 100644 index 31a12b3d503..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer-options.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"renderer-options.js","sourceRoot":"","sources":["../src/renderer-options.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAAC,QAAiB,MAAM;IACvD,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC/E,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;AACxE,CAAC","sourcesContent":["import type { TerminalRendererPreference } from \"./types.js\";\n\nexport function normalizeRenderer(value: unknown = \"auto\"): TerminalRendererPreference {\n if (value === \"auto\" || value === \"webgpu\" || value === \"webgl2\") return value;\n throw new TypeError('renderer must be \"auto\", \"webgpu\", or \"webgl2\"');\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts deleted file mode 100644 index fb91da7fae3..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { RenderBackend, RenderBatch, RenderColor, RenderTexture } from "./render-backend.js"; -import type { FontMetrics, LoadedFont, NormalizedFont } from "./terminal-font.js"; -import type { TerminalFont, TerminalRendererPreference, TerminalSize } from "./types.js"; -import type { FrameImage, FrameMetadata, ImagePlacement, TerminalCell } from "./wire-types.js"; -type Vector4 = RenderColor; -type TextureResource = RenderTexture; -interface Shelf { - x: number; - y: number; - rowHeight: number; -} -interface GlyphPlacement { - key: string; - cell: TerminalCell; - x: number; - y: number; - width: number; - height: number; -} -interface Glyph { - colored: boolean; - u0: number; - v0: number; - u1: number; - v1: number; -} -type Batch = RenderBatch; -/** Shared instanced-quad preparation; Canvas2D rasterizes reusable glyphs for either backend. */ -export declare class TerminalRenderer { - canvas: OffscreenCanvas; - scale: number; - backingScale: number; - backend: RenderBackend; - fallbackReason?: string; - fontConfiguration: NormalizedFont; - fontMetrics: Map; - images: Map; - glyphs: Map; - imageUploadBytes: number; - imagePayloadBytes: number; - glyphUploadBytes: number; - atlasRebuilds: number; - textureBytes: number; - instances: Float32Array; - disposed: boolean; - columns: number; - rows: number; - font: LoadedFont; - rasterCanvas: OffscreenCanvas; - raster: OffscreenCanvasRenderingContext2D; - atlas: TextureResource; - glyphKeyUnits: number; - shelf: Shelf; - canvasLimited: boolean; - width: number; - height: number; - quadCount: number; - batches: Batch[]; - static create(canvas: OffscreenCanvas, scale: number, onFatal: (error: Error) => void, font?: TerminalFont, preference?: TerminalRendererPreference): Promise; - constructor(canvas: OffscreenCanvas, scale: number, backend: RenderBackend, font: NormalizedFont); - initialize(): Promise; - createTexture(width: number, height: number, label: string): TextureResource; - resetAtlas(size: number): void; - resize(columns: number, rows: number, viewport?: TerminalSize): void; - /** Call only between submissions. Missing/over-budget resources terminate the session. */ - updateImages(incoming: readonly FrameImage[], retainedKeys: readonly string[]): Promise; - prepareGlyphs(cells: readonly (TerminalCell | undefined)[]): void; - uploadGlyph({ key, cell, x, y, width, height }: GlyphPlacement): void; - /** Clip geometry and UVs together. Only adjacent compatible textures may coalesce. */ - quad(resource: TextureResource, x: number, y: number, width: number, height: number, color: Vector4, mode?: number, uv?: Vector4, clip?: Vector4): void; - solid(x: number, y: number, width: number, height: number, color: Vector4): void; - placement(placement: ImagePlacement): void; - decorations(cell: TerminalCell, x: number, y: number, width: number, foreground: Vector4): void; - private underline; - render(cells: readonly (TerminalCell | undefined)[], metadata: FrameMetadata, blinkOn: boolean, linkDecorations?: Uint8Array): { - cpuMs: number; - quads: number; - drawCalls: number; - }; - metrics(): { - renderer: "webgl2" | "webgpu"; - rendererFallbackReason: string | undefined; - fontFamily: string; - rasterScale: number; - backingScale: number; - backingWidth: number; - backingHeight: number; - imageCount: number; - textureBytes: number; - atlasGlyphs: number; - atlasBytes: number; - atlasRebuilds: number; - imageUploadBytes: number; - imagePayloadBytes: number; - glyphUploadBytes: number; - instanceBufferBytes: number; - }; - idle(): Promise; - dispose(): void; -} -export {}; -//# sourceMappingURL=renderer.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts.map deleted file mode 100644 index 65fbe6f3491..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js deleted file mode 100644 index 7c1131e96bc..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js +++ /dev/null @@ -1,473 +0,0 @@ -import { LIMITS } from "./protocol.js"; -import { loadFont, measureFont, normalizeFont } from "./terminal-font.js"; -import { createRenderBackend } from "./backend-selection.js"; -import { QUAD_STRIDE } from "./render-backend.js"; -const CELL_WIDTH = 10; -const CELL_HEIGHT = 20; -const MAX_QUADS = 1024 * 1024; -const MAX_GLYPHS = 16384; -const MAX_GLYPH_KEY_UNITS = 1024 * 1024; -const STRIDE = QUAD_STRIDE; -const WHITE = [1, 1, 1, 1]; -function rgba(packed) { - return [ - (packed & 255) / 255, - ((packed >>> 8) & 255) / 255, - ((packed >>> 16) & 255) / 255, - ((packed >>> 24) & 255) / 255, - ]; -} -function glyphKey(cell) { - return `${cell.attributes & 5}/${cell.width}/${cell.text}`; -} -function isKgpPlaceholder(cell) { - // The base scalar and following diacritics encode an image reference, not a glyph. - // Keep the authoritative text and colors intact even when no image is placed. - return cell.text.codePointAt(0) === 0x10eeee; -} -/** Shelf packer. Plans are computed before mutating the atlas used by a submitted frame. */ -function packGlyphs(glyphs, size, scale, initial = { x: 0, y: 0, rowHeight: 0 }) { - let { x, y, rowHeight } = initial; - const placements = []; - for (const [key, cell] of glyphs) { - const width = Math.ceil(cell.width * CELL_WIDTH * scale) + 4; - const height = Math.ceil(CELL_HEIGHT * scale) + 4; - if (width > size || height > size) - return null; - if (x + width > size) { - x = 0; - y += rowHeight; - rowHeight = 0; - } - if (y + height > size) - return null; - placements.push({ key, cell, x, y, width, height }); - x += width; - rowHeight = Math.max(rowHeight, height); - } - return { placements, shelf: { x, y, rowHeight } }; -} -/** Shared instanced-quad preparation; Canvas2D rasterizes reusable glyphs for either backend. */ -export class TerminalRenderer { - canvas; - scale; - backingScale; - backend; - fallbackReason; - fontConfiguration; - fontMetrics; - images; - glyphs; - imageUploadBytes; - imagePayloadBytes; - glyphUploadBytes; - atlasRebuilds; - textureBytes; - instances; - disposed; - columns; - rows; - // Initialized by create() before the renderer can prepare or submit frames. - font; - rasterCanvas; - raster; - atlas; - glyphKeyUnits = 0; - shelf = { x: 0, y: 0, rowHeight: 0 }; - canvasLimited = false; - width = 0; - height = 0; - quadCount = 0; - batches = []; - static async create(canvas, scale, onFatal, font, preference = "auto") { - const normalizedFont = normalizeFont(font); - if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) - throw new Error("Invalid backing scale"); - const { backend, fallbackReason } = await createRenderBackend(canvas, onFatal, preference); - const renderer = new TerminalRenderer(canvas, scale, backend, normalizedFont); - renderer.fallbackReason = fallbackReason; - try { - await renderer.initialize(); - return renderer; - } - catch (error) { - renderer.dispose(); - throw error; - } - } - constructor(canvas, scale, backend, font) { - if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) - throw new Error("Invalid backing scale"); - this.canvas = canvas; - this.scale = scale; - this.backingScale = scale; - this.backend = backend; - this.fontConfiguration = font; - this.fontMetrics = new Map(); - this.images = new Map(); - this.glyphs = new Map(); - this.imageUploadBytes = 0; - this.imagePayloadBytes = 0; - this.glyphUploadBytes = 0; - this.atlasRebuilds = 0; - this.textureBytes = 0; - this.instances = new Float32Array(4096 * STRIDE); - this.disposed = false; - this.columns = 0; - this.rows = 0; - } - async initialize() { - this.font = await loadFont(this.fontConfiguration); - this.rasterCanvas = new OffscreenCanvas(1, 1); - const raster = this.rasterCanvas.getContext("2d", { willReadFrequently: true }); - if (!raster) - throw new Error("Worker glyph rasterization is unavailable"); - this.raster = raster; - this.resetAtlas(Math.min(2048, this.backend.maxTextureDimension2D)); - } - createTexture(width, height, label) { - return this.backend.createTexture(width, height, label); - } - resetAtlas(size) { - this.atlas?.destroy(); - this.atlas = this.createTexture(size, size, "Glyph atlas"); - this.glyphs.clear(); - this.glyphKeyUnits = 0; - this.shelf = { x: 0, y: 0, rowHeight: 0 }; - } - resize(columns, rows, viewport) { - const width = columns * CELL_WIDTH; - const height = rows * CELL_HEIGHT; - const limit = this.backend.maxCanvasDimension2D; - const requested = Math.min(this.scale, viewport ? viewport.width / width : Infinity, viewport ? viewport.height / height : Infinity); - this.canvasLimited = width * requested > limit || height * requested > limit; - this.backingScale = Math.min(requested, limit / width, limit / height); - const backingWidth = Math.max(1, Math.min(limit, Math.ceil(width * this.backingScale))); - const backingHeight = Math.max(1, Math.min(limit, Math.ceil(height * this.backingScale))); - if (this.columns === columns && this.rows === rows && this.canvas.width === backingWidth && this.canvas.height === backingHeight) - return; - this.columns = columns; - this.rows = rows; - this.width = width; - this.height = height; - this.canvas.width = backingWidth; - this.canvas.height = backingHeight; - this.backend.resize(width, height); - } - /** Call only between submissions. Missing/over-budget resources terminate the session. */ - async updateImages(incoming, retainedKeys) { - const retained = new Set(retainedKeys); - const replacements = new Map(incoming.map(image => [image.key, image])); - let projectedBytes = 0; - for (const key of retained) { - const image = replacements.get(key) || this.images.get(key); - if (!image) - throw new Error(`Missing retained image resource: ${key}`); - projectedBytes += image.width * image.height * 4; - } - if (projectedBytes > LIMITS.textureBytes) - throw new Error("Retained images exceed the 256 MiB texture budget"); - for (const [key, image] of this.images) { - if (!retained.has(key) || replacements.has(key)) { - image.destroy(); - this.textureBytes -= image.width * image.height * 4; - this.images.delete(key); - } - } - for (const image of incoming) { - const resource = this.createTexture(image.width, image.height, `Image ${image.key}`); - try { - if (image.format === "rgba") { - resource.writePixels(image.bytes, image.width, image.height); - } - else { - // Check IHDR before decoding so a tiny PNG cannot claim unbounded decoded dimensions. - const png = new DataView(image.bytes.buffer, image.bytes.byteOffset, image.bytes.byteLength); - if (png.byteLength < 33 || png.getUint32(0) !== 0x89504e47 || png.getUint32(4) !== 0x0d0a1a0a || - png.getUint32(8) !== 13 || png.getUint32(12) !== 0x49484452 || - png.getUint32(16) !== image.width || png.getUint32(20) !== image.height) { - throw new Error(`PNG header dimensions do not match resource ${image.key}`); - } - const bitmap = await createImageBitmap(new Blob([image.bytes], { type: "image/png" }), { - premultiplyAlpha: "none", - colorSpaceConversion: "none", - }); - try { - if (bitmap.width !== image.width || bitmap.height !== image.height) - throw new Error("Decoded PNG dimension mismatch"); - resource.writeBitmap(bitmap); - } - finally { - bitmap.close(); - } - } - this.images.set(image.key, resource); - this.textureBytes += image.width * image.height * 4; - this.imageUploadBytes += image.width * image.height * 4; - this.imagePayloadBytes += image.byteLength; - } - catch (error) { - resource.destroy(); - throw error; - } - } - } - prepareGlyphs(cells) { - const visible = new Map(); - for (const cell of cells) { - if (!cell || !cell.width || !cell.text.trim() || (cell.attributes & 64) || isKgpPlaceholder(cell)) - continue; - visible.set(glyphKey(cell), cell); - } - const keyUnits = (glyphs) => [...glyphs.keys()].reduce((total, key) => total + key.length, 0); - if (visible.size > MAX_GLYPHS || keyUnits(visible) > MAX_GLYPH_KEY_UNITS) { - throw new Error("Visible glyph metadata exceeds the bounded glyph cache"); - } - const missing = new Map([...visible].filter(([key]) => !this.glyphs.has(key))); - if (!missing.size) - return; - const metadataFits = this.glyphs.size + missing.size <= MAX_GLYPHS && - this.glyphKeyUnits + keyUnits(missing) <= MAX_GLYPH_KEY_UNITS; - let plan = metadataFits ? packGlyphs(missing, this.atlas.width, this.scale, this.shelf) : null; - if (!plan) { - let size = this.atlas.width; - const maxSize = Math.min(4096, this.backend.maxTextureDimension2D); - while (!(plan = packGlyphs(visible, size, this.scale)) && size < maxSize) { - size = Math.min(size * 2, maxSize); - } - if (!plan) - throw new Error("Visible glyphs exceed the bounded 4096² mask atlas; reduce the grid or backing scale"); - this.resetAtlas(size); - this.atlasRebuilds++; - } - for (const placement of plan.placements) - this.uploadGlyph(placement); - this.shelf = plan.shelf; - } - uploadGlyph({ key, cell, x, y, width, height }) { - const scale = this.scale; - const raster = this.raster; - const style = cell.attributes & 5; - let metrics = this.fontMetrics.get(style); - if (!metrics) { - metrics = measureFont(raster, this.font.cssFamily, style, scale, CELL_WIDTH, CELL_HEIGHT); - this.fontMetrics.set(style, metrics); - } - this.rasterCanvas.width = width; - this.rasterCanvas.height = height; - raster.font = metrics.font; - raster.textBaseline = "alphabetic"; - raster.textAlign = "left"; - raster.fillStyle = "white"; - // One transform per font style, not per glyph: borders remain font outlines, - // and graphemes are clipped to their server-owned span without individual stretching. - raster.setTransform(metrics.xScale, 0, 0, metrics.yScale, 2, 2 + metrics.baseline); - raster.fillText(cell.text, 0, 0); - const pixels = raster.getImageData(0, 0, width, height); - let colored = false; - for (let i = 0; i < pixels.data.length; i += 4) { - if (pixels.data[i + 3] && (pixels.data[i] !== pixels.data[i + 1] || pixels.data[i + 1] !== pixels.data[i + 2])) { - colored = true; - break; - } - } - this.atlas.writePixels(pixels.data, width, height, x, y); - this.glyphUploadBytes += width * height * 4; - this.glyphs.set(key, { - colored, - u0: (x + 2) / this.atlas.width, - v0: (y + 2) / this.atlas.height, - u1: (x + 2 + cell.width * CELL_WIDTH * scale) / this.atlas.width, - v1: (y + 2 + CELL_HEIGHT * scale) / this.atlas.height, - }); - this.glyphKeyUnits += key.length; - } - /** Clip geometry and UVs together. Only adjacent compatible textures may coalesce. */ - quad(resource, x, y, width, height, color, mode = 0, uv = [0, 0, 1, 1], clip = [0, 0, this.width, this.height]) { - if (width <= 0 || height <= 0 || color[3] <= 0) - return; - const left = Math.max(0, x, clip[0]); - const top = Math.max(0, y, clip[1]); - const right = Math.min(this.width, x + width, clip[0] + clip[2]); - const bottom = Math.min(this.height, y + height, clip[1] + clip[3]); - if (right <= left || bottom <= top) - return; - if (this.quadCount >= MAX_QUADS) - throw new Error("Frame exceeds bounded quad budget"); - const offset = this.quadCount * STRIDE; - if (offset + STRIDE > this.instances.length) { - const grown = new Float32Array(Math.min(this.instances.length * 2, MAX_QUADS * STRIDE)); - grown.set(this.instances); - this.instances = grown; - } - const du = uv[2] - uv[0]; - const dv = uv[3] - uv[1]; - this.instances.set([ - left, top, right - left, bottom - top, - uv[0] + (left - x) / width * du, - uv[1] + (top - y) / height * dv, - uv[0] + (right - x) / width * du, - uv[1] + (bottom - y) / height * dv, - ...color, mode, 0, 0, 0, - ], offset); - const last = this.batches[this.batches.length - 1]; - if (last?.resource === resource) - last.count++; - else - this.batches.push({ resource, start: this.quadCount, count: 1 }); - this.quadCount++; - } - solid(x, y, width, height, color) { - this.quad(this.atlas, x, y, width, height, color); - } - placement(placement) { - const image = this.images.get(placement.key); - if (!image) - throw new Error(`Placement texture is missing: ${placement.key}`); - const { sourceX: sx, sourceY: sy, sourceWidth: sw, sourceHeight: sh } = placement; - if (!sw || !sh || !placement.width || !placement.height) - return; - // Clip out-of-texture source regions in destination space instead of stretching edge texels. - const sourceLeft = placement.x + Math.max(0, -sx) / sw * placement.width; - const sourceTop = placement.y + Math.max(0, -sy) / sh * placement.height; - const sourceRight = placement.x + Math.min(sw, image.width - sx) / sw * placement.width; - const sourceBottom = placement.y + Math.min(sh, image.height - sy) / sh * placement.height; - const left = Math.max(sourceLeft, placement.clipX); - const top = Math.max(sourceTop, placement.clipY); - const right = Math.min(sourceRight, placement.clipX + placement.clipWidth); - const bottom = Math.min(sourceBottom, placement.clipY + placement.clipHeight); - this.quad(image, placement.x, placement.y, placement.width, placement.height, WHITE, 2, [sx / image.width, sy / image.height, (sx + sw) / image.width, (sy + sh) / image.height], [left, top, Math.max(0, right - left), Math.max(0, bottom - top)]); - } - decorations(cell, x, y, width, foreground) { - if (cell.attributes & 128) - this.solid(x, y + 10, width, 1, foreground); - if (cell.attributes & 256) - this.solid(x, y + 1, width, 1, foreground); - const style = cell.underlineStyle || (cell.attributes & 8 ? 1 : 0); - this.underline(style, x, y, width, rgba(cell.underlineColor)); - } - underline(style, x, y, width, color) { - if (style === 1) - this.solid(x, y + 18, width, 1, color); - else if (style === 2) { - this.solid(x, y + 16, width, 1, color); - this.solid(x, y + 18, width, 1, color); - } - else if (style === 3) { - for (let dx = 0; dx < width; dx++) { - this.solid(x + dx, y + 17 + Math.round(Math.sin((x + dx) * Math.PI / 4)), 1, 1, color); - } - } - else if (style === 4 || style === 5) { - const step = style === 4 ? 2 : 5; - const segment = style === 4 ? 1 : 3; - for (let dx = 0; dx < width; dx += step) - this.solid(x + dx, y + 18, Math.min(segment, width - dx), 1, color); - } - } - render(cells, metadata, blinkOn, linkDecorations) { - const start = performance.now(); - this.quadCount = 0; - this.batches = []; - const placements = metadata.placements.map((placement, order) => ({ - placement, - order, - z: placement.kind === "sixel" ? -1 : placement.z, - })).sort((a, b) => a.z - b.z || a.order - b.order); - for (const item of placements) - if (item.z < -1073741824) - this.placement(item.placement); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - if (!cell) - continue; - this.solid((i % this.columns) * CELL_WIDTH, Math.floor(i / this.columns) * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT, rgba(cell.background)); - } - for (const item of placements) - if (item.z >= -1073741824 && item.z < 0) - this.placement(item.placement); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - if (!cell || !cell.width || (cell.attributes & 64) || ((cell.attributes & 16) && !blinkOn)) - continue; - const x = (i % this.columns) * CELL_WIDTH; - const y = Math.floor(i / this.columns) * CELL_HEIGHT; - const width = Math.min(cell.width * CELL_WIDTH, this.width - x); - const foreground = rgba(cell.foreground); - const glyph = isKgpPlaceholder(cell) ? undefined : this.glyphs.get(glyphKey(cell)); - if (glyph) { - const tint = glyph.colored ? (cell.attributes & 2 ? [0.5, 0.5, 0.5, 1] : WHITE) : foreground; - this.quad(this.atlas, x, y, cell.width * CELL_WIDTH, CELL_HEIGHT, tint, glyph.colored ? 2 : 1, [glyph.u0, glyph.v0, glyph.u1, glyph.v1]); - } - // Reverse and dim are already reflected in server-projected colors. - this.decorations(cell, x, y, width, foreground); - if (linkDecorations?.[i] && !cell.underlineStyle && !(cell.attributes & 8) && !isKgpPlaceholder(cell)) { - this.underline(linkDecorations[i], x, y, width, foreground); - } - } - for (const item of placements) - if (item.z >= 0) - this.placement(item.placement); - const cursor = metadata.cursor; - const cursorBlink = cursor.shape === 0 || cursor.shape % 2 === 1; - if (cursor.visible && (!cursorBlink || blinkOn) && cursor.x >= 0 && cursor.x < this.columns && cursor.y >= 0 && cursor.y < this.rows) { - const cell = cells[cursor.y * this.columns + cursor.x]; - const color = rgba(cell?.foreground ?? 0xffffffff); - const x = cursor.x * CELL_WIDTH; - const y = cursor.y * CELL_HEIGHT; - if (cursor.shape === 3 || cursor.shape === 4) - this.solid(x, y + 18, CELL_WIDTH, 2, color); - else if (cursor.shape === 5 || cursor.shape === 6) - this.solid(x, y, 2, CELL_HEIGHT, color); - else { - color[3] *= 0.55; - this.solid(x, y, CELL_WIDTH, CELL_HEIGHT, color); - } - } - const base = rgba(metadata.defaultBackground ?? cells[0]?.background ?? 0xff000000); - this.backend.submit(this.instances, this.quadCount, this.batches, base); - return { cpuMs: performance.now() - start, quads: this.quadCount, drawCalls: this.batches.length }; - } - metrics() { - return { - renderer: this.backend.kind, - rendererFallbackReason: this.fallbackReason, - fontFamily: this.font.family, - rasterScale: this.scale, - backingScale: this.backingScale, - backingWidth: this.canvas.width, - backingHeight: this.canvas.height, - imageCount: this.images.size, - textureBytes: this.textureBytes, - atlasGlyphs: this.glyphs.size, - atlasBytes: this.atlas.width * this.atlas.height * 4, - atlasRebuilds: this.atlasRebuilds, - imageUploadBytes: this.imageUploadBytes, - imagePayloadBytes: this.imagePayloadBytes, - glyphUploadBytes: this.glyphUploadBytes, - instanceBufferBytes: this.backend.instanceBufferBytes, - }; - } - async idle() { - await this.backend.idle(); - } - dispose() { - if (this.disposed) - return; - this.disposed = true; - for (const image of this.images.values()) - image.destroy(); - this.images.clear(); - this.textureBytes = 0; - this.atlas?.destroy(); - this.glyphs.clear(); - this.glyphKeyUnits = 0; - this.batches = []; - this.quadCount = 0; - this.instances = new Float32Array(0); - this.font?.dispose(); - this.fontMetrics.clear(); - this.backend.dispose(); - } -} -//# sourceMappingURL=renderer.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js.map deleted file mode 100644 index 124d22db1df..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/renderer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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","sourcesContent":["import { LIMITS } from \"./protocol.js\";\nimport { loadFont, measureFont, normalizeFont } from \"./terminal-font.js\";\nimport { createRenderBackend } from \"./backend-selection.js\";\nimport { QUAD_STRIDE } from \"./render-backend.js\";\nimport type { RenderBackend, RenderBatch, RenderColor, RenderTexture } from \"./render-backend.js\";\nimport type { FontMetrics, LoadedFont, NormalizedFont } from \"./terminal-font.js\";\nimport type { TerminalFont, TerminalRendererPreference, TerminalSize } from \"./types.js\";\nimport type { FrameImage, FrameMetadata, ImagePlacement, TerminalCell } from \"./wire-types.js\";\n\ntype Vector4 = RenderColor;\ntype TextureResource = RenderTexture;\ninterface Shelf { x: number; y: number; rowHeight: number }\ninterface GlyphPlacement { key: string; cell: TerminalCell; x: number; y: number; width: number; height: number }\ninterface Glyph { colored: boolean; u0: number; v0: number; u1: number; v1: number }\ntype Batch = RenderBatch;\n\nconst CELL_WIDTH = 10;\nconst CELL_HEIGHT = 20;\nconst MAX_QUADS = 1024 * 1024;\nconst MAX_GLYPHS = 16384;\nconst MAX_GLYPH_KEY_UNITS = 1024 * 1024;\nconst STRIDE = QUAD_STRIDE;\nconst WHITE: Vector4 = [1, 1, 1, 1];\n\nfunction rgba(packed: number): Vector4 {\n return [\n (packed & 255) / 255,\n ((packed >>> 8) & 255) / 255,\n ((packed >>> 16) & 255) / 255,\n ((packed >>> 24) & 255) / 255,\n ];\n}\n\nfunction glyphKey(cell: TerminalCell): string {\n return `${cell.attributes & 5}/${cell.width}/${cell.text}`;\n}\n\nfunction isKgpPlaceholder(cell: TerminalCell): boolean {\n // The base scalar and following diacritics encode an image reference, not a glyph.\n // Keep the authoritative text and colors intact even when no image is placed.\n return cell.text.codePointAt(0) === 0x10eeee;\n}\n\n/** Shelf packer. Plans are computed before mutating the atlas used by a submitted frame. */\nfunction packGlyphs(glyphs: ReadonlyMap, size: number, scale: number,\n initial: Shelf = { x: 0, y: 0, rowHeight: 0 }): { placements: GlyphPlacement[]; shelf: Shelf } | null {\n let { x, y, rowHeight } = initial;\n const placements = [];\n for (const [key, cell] of glyphs) {\n const width = Math.ceil(cell.width * CELL_WIDTH * scale) + 4;\n const height = Math.ceil(CELL_HEIGHT * scale) + 4;\n if (width > size || height > size) return null;\n if (x + width > size) { x = 0; y += rowHeight; rowHeight = 0; }\n if (y + height > size) return null;\n placements.push({ key, cell, x, y, width, height });\n x += width;\n rowHeight = Math.max(rowHeight, height);\n }\n return { placements, shelf: { x, y, rowHeight } };\n}\n\n/** Shared instanced-quad preparation; Canvas2D rasterizes reusable glyphs for either backend. */\nexport class TerminalRenderer {\n canvas: OffscreenCanvas;\n scale: number;\n backingScale: number;\n backend: RenderBackend;\n fallbackReason?: string;\n fontConfiguration: NormalizedFont;\n fontMetrics: Map;\n images: Map;\n glyphs: Map;\n imageUploadBytes: number;\n imagePayloadBytes: number;\n glyphUploadBytes: number;\n atlasRebuilds: number;\n textureBytes: number;\n instances: Float32Array;\n disposed: boolean;\n columns: number;\n rows: number;\n // Initialized by create() before the renderer can prepare or submit frames.\n font!: LoadedFont;\n rasterCanvas!: OffscreenCanvas;\n raster!: OffscreenCanvasRenderingContext2D;\n atlas!: TextureResource;\n glyphKeyUnits = 0;\n shelf: Shelf = { x: 0, y: 0, rowHeight: 0 };\n canvasLimited = false;\n width = 0;\n height = 0;\n quadCount = 0;\n batches: Batch[] = [];\n\n static async create(canvas: OffscreenCanvas, scale: number, onFatal: (error: Error) => void,\n font?: TerminalFont, preference: TerminalRendererPreference = \"auto\"): Promise {\n const normalizedFont = normalizeFont(font);\n if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) throw new Error(\"Invalid backing scale\");\n const { backend, fallbackReason } = await createRenderBackend(canvas, onFatal, preference);\n const renderer = new TerminalRenderer(canvas, scale, backend, normalizedFont);\n renderer.fallbackReason = fallbackReason;\n try {\n await renderer.initialize();\n return renderer;\n } catch (error) {\n renderer.dispose();\n throw error;\n }\n }\n\n constructor(canvas: OffscreenCanvas, scale: number, backend: RenderBackend, font: NormalizedFont) {\n if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) throw new Error(\"Invalid backing scale\");\n this.canvas = canvas;\n this.scale = scale;\n this.backingScale = scale;\n this.backend = backend;\n this.fontConfiguration = font;\n this.fontMetrics = new Map();\n this.images = new Map();\n this.glyphs = new Map();\n this.imageUploadBytes = 0;\n this.imagePayloadBytes = 0;\n this.glyphUploadBytes = 0;\n this.atlasRebuilds = 0;\n this.textureBytes = 0;\n this.instances = new Float32Array(4096 * STRIDE);\n this.disposed = false;\n this.columns = 0;\n this.rows = 0;\n }\n\n async initialize() {\n this.font = await loadFont(this.fontConfiguration);\n this.rasterCanvas = new OffscreenCanvas(1, 1);\n const raster = this.rasterCanvas.getContext(\"2d\", { willReadFrequently: true });\n if (!raster) throw new Error(\"Worker glyph rasterization is unavailable\");\n this.raster = raster;\n this.resetAtlas(Math.min(2048, this.backend.maxTextureDimension2D));\n }\n\n createTexture(width: number, height: number, label: string): TextureResource {\n return this.backend.createTexture(width, height, label);\n }\n\n resetAtlas(size: number): void {\n this.atlas?.destroy();\n this.atlas = this.createTexture(size, size, \"Glyph atlas\");\n this.glyphs.clear();\n this.glyphKeyUnits = 0;\n this.shelf = { x: 0, y: 0, rowHeight: 0 };\n }\n\n resize(columns: number, rows: number, viewport?: TerminalSize): void {\n const width = columns * CELL_WIDTH;\n const height = rows * CELL_HEIGHT;\n const limit = this.backend.maxCanvasDimension2D;\n const requested = Math.min(this.scale, viewport ? viewport.width / width : Infinity, viewport ? viewport.height / height : Infinity);\n this.canvasLimited = width * requested > limit || height * requested > limit;\n this.backingScale = Math.min(requested, limit / width, limit / height);\n const backingWidth = Math.max(1, Math.min(limit, Math.ceil(width * this.backingScale)));\n const backingHeight = Math.max(1, Math.min(limit, Math.ceil(height * this.backingScale)));\n if (this.columns === columns && this.rows === rows && this.canvas.width === backingWidth && this.canvas.height === backingHeight) return;\n this.columns = columns;\n this.rows = rows;\n this.width = width;\n this.height = height;\n this.canvas.width = backingWidth;\n this.canvas.height = backingHeight;\n this.backend.resize(width, height);\n }\n\n /** Call only between submissions. Missing/over-budget resources terminate the session. */\n async updateImages(incoming: readonly FrameImage[], retainedKeys: readonly string[]): Promise {\n const retained = new Set(retainedKeys);\n const replacements = new Map(incoming.map(image => [image.key, image]));\n let projectedBytes = 0;\n for (const key of retained) {\n const image = replacements.get(key) || this.images.get(key);\n if (!image) throw new Error(`Missing retained image resource: ${key}`);\n projectedBytes += image.width * image.height * 4;\n }\n if (projectedBytes > LIMITS.textureBytes) throw new Error(\"Retained images exceed the 256 MiB texture budget\");\n for (const [key, image] of this.images) {\n if (!retained.has(key) || replacements.has(key)) {\n image.destroy();\n this.textureBytes -= image.width * image.height * 4;\n this.images.delete(key);\n }\n }\n for (const image of incoming) {\n const resource = this.createTexture(image.width, image.height, `Image ${image.key}`);\n try {\n if (image.format === \"rgba\") {\n resource.writePixels(image.bytes, image.width, image.height);\n } else {\n // Check IHDR before decoding so a tiny PNG cannot claim unbounded decoded dimensions.\n const png = new DataView(image.bytes.buffer, image.bytes.byteOffset, image.bytes.byteLength);\n if (png.byteLength < 33 || png.getUint32(0) !== 0x89504e47 || png.getUint32(4) !== 0x0d0a1a0a ||\n png.getUint32(8) !== 13 || png.getUint32(12) !== 0x49484452 ||\n png.getUint32(16) !== image.width || png.getUint32(20) !== image.height) {\n throw new Error(`PNG header dimensions do not match resource ${image.key}`);\n }\n const bitmap = await createImageBitmap(new Blob([image.bytes], { type: \"image/png\" }), {\n premultiplyAlpha: \"none\",\n colorSpaceConversion: \"none\",\n });\n try {\n if (bitmap.width !== image.width || bitmap.height !== image.height) throw new Error(\"Decoded PNG dimension mismatch\");\n resource.writeBitmap(bitmap);\n } finally {\n bitmap.close();\n }\n }\n this.images.set(image.key, resource);\n this.textureBytes += image.width * image.height * 4;\n this.imageUploadBytes += image.width * image.height * 4;\n this.imagePayloadBytes += image.byteLength;\n } catch (error) {\n resource.destroy();\n throw error;\n }\n }\n }\n\n prepareGlyphs(cells: readonly (TerminalCell | undefined)[]): void {\n const visible = new Map();\n for (const cell of cells) {\n if (!cell || !cell.width || !cell.text.trim() || (cell.attributes & 64) || isKgpPlaceholder(cell)) continue;\n visible.set(glyphKey(cell), cell);\n }\n const keyUnits = (glyphs: ReadonlyMap) => [...glyphs.keys()].reduce((total, key) => total + key.length, 0);\n if (visible.size > MAX_GLYPHS || keyUnits(visible) > MAX_GLYPH_KEY_UNITS) {\n throw new Error(\"Visible glyph metadata exceeds the bounded glyph cache\");\n }\n const missing = new Map([...visible].filter(([key]) => !this.glyphs.has(key)));\n if (!missing.size) return;\n const metadataFits = this.glyphs.size + missing.size <= MAX_GLYPHS &&\n this.glyphKeyUnits + keyUnits(missing) <= MAX_GLYPH_KEY_UNITS;\n let plan = metadataFits ? packGlyphs(missing, this.atlas.width, this.scale, this.shelf) : null;\n if (!plan) {\n let size = this.atlas.width;\n const maxSize = Math.min(4096, this.backend.maxTextureDimension2D);\n while (!(plan = packGlyphs(visible, size, this.scale)) && size < maxSize) {\n size = Math.min(size * 2, maxSize);\n }\n if (!plan) throw new Error(\"Visible glyphs exceed the bounded 4096² mask atlas; reduce the grid or backing scale\");\n this.resetAtlas(size);\n this.atlasRebuilds++;\n }\n for (const placement of plan.placements) this.uploadGlyph(placement);\n this.shelf = plan.shelf;\n }\n\n uploadGlyph({ key, cell, x, y, width, height }: GlyphPlacement): void {\n const scale = this.scale;\n const raster = this.raster;\n const style = cell.attributes & 5;\n let metrics = this.fontMetrics.get(style);\n if (!metrics) {\n metrics = measureFont(raster, this.font.cssFamily, style, scale, CELL_WIDTH, CELL_HEIGHT);\n this.fontMetrics.set(style, metrics);\n }\n this.rasterCanvas.width = width;\n this.rasterCanvas.height = height;\n raster.font = metrics.font;\n raster.textBaseline = \"alphabetic\";\n raster.textAlign = \"left\";\n raster.fillStyle = \"white\";\n // One transform per font style, not per glyph: borders remain font outlines,\n // and graphemes are clipped to their server-owned span without individual stretching.\n raster.setTransform(metrics.xScale, 0, 0, metrics.yScale, 2, 2 + metrics.baseline);\n raster.fillText(cell.text, 0, 0);\n const pixels = raster.getImageData(0, 0, width, height);\n let colored = false;\n for (let i = 0; i < pixels.data.length; i += 4) {\n if (pixels.data[i + 3] && (pixels.data[i] !== pixels.data[i + 1] || pixels.data[i + 1] !== pixels.data[i + 2])) {\n colored = true;\n break;\n }\n }\n this.atlas.writePixels(pixels.data, width, height, x, y);\n this.glyphUploadBytes += width * height * 4;\n this.glyphs.set(key, {\n colored,\n u0: (x + 2) / this.atlas.width,\n v0: (y + 2) / this.atlas.height,\n u1: (x + 2 + cell.width * CELL_WIDTH * scale) / this.atlas.width,\n v1: (y + 2 + CELL_HEIGHT * scale) / this.atlas.height,\n });\n this.glyphKeyUnits += key.length;\n }\n\n /** Clip geometry and UVs together. Only adjacent compatible textures may coalesce. */\n quad(resource: TextureResource, x: number, y: number, width: number, height: number,\n color: Vector4, mode = 0, uv: Vector4 = [0, 0, 1, 1],\n clip: Vector4 = [0, 0, this.width, this.height]): void {\n if (width <= 0 || height <= 0 || color[3] <= 0) return;\n const left = Math.max(0, x, clip[0]);\n const top = Math.max(0, y, clip[1]);\n const right = Math.min(this.width, x + width, clip[0] + clip[2]);\n const bottom = Math.min(this.height, y + height, clip[1] + clip[3]);\n if (right <= left || bottom <= top) return;\n if (this.quadCount >= MAX_QUADS) throw new Error(\"Frame exceeds bounded quad budget\");\n const offset = this.quadCount * STRIDE;\n if (offset + STRIDE > this.instances.length) {\n const grown = new Float32Array(Math.min(this.instances.length * 2, MAX_QUADS * STRIDE));\n grown.set(this.instances);\n this.instances = grown;\n }\n const du = uv[2] - uv[0];\n const dv = uv[3] - uv[1];\n this.instances.set([\n left, top, right - left, bottom - top,\n uv[0] + (left - x) / width * du,\n uv[1] + (top - y) / height * dv,\n uv[0] + (right - x) / width * du,\n uv[1] + (bottom - y) / height * dv,\n ...color, mode, 0, 0, 0,\n ], offset);\n const last = this.batches[this.batches.length - 1];\n if (last?.resource === resource) last.count++;\n else this.batches.push({ resource, start: this.quadCount, count: 1 });\n this.quadCount++;\n }\n\n solid(x: number, y: number, width: number, height: number, color: Vector4): void {\n this.quad(this.atlas, x, y, width, height, color);\n }\n\n placement(placement: ImagePlacement): void {\n const image = this.images.get(placement.key);\n if (!image) throw new Error(`Placement texture is missing: ${placement.key}`);\n const { sourceX: sx, sourceY: sy, sourceWidth: sw, sourceHeight: sh } = placement;\n if (!sw || !sh || !placement.width || !placement.height) return;\n // Clip out-of-texture source regions in destination space instead of stretching edge texels.\n const sourceLeft = placement.x + Math.max(0, -sx) / sw * placement.width;\n const sourceTop = placement.y + Math.max(0, -sy) / sh * placement.height;\n const sourceRight = placement.x + Math.min(sw, image.width - sx) / sw * placement.width;\n const sourceBottom = placement.y + Math.min(sh, image.height - sy) / sh * placement.height;\n const left = Math.max(sourceLeft, placement.clipX);\n const top = Math.max(sourceTop, placement.clipY);\n const right = Math.min(sourceRight, placement.clipX + placement.clipWidth);\n const bottom = Math.min(sourceBottom, placement.clipY + placement.clipHeight);\n this.quad(\n image, placement.x, placement.y, placement.width, placement.height, WHITE, 2,\n [sx / image.width, sy / image.height, (sx + sw) / image.width, (sy + sh) / image.height],\n [left, top, Math.max(0, right - left), Math.max(0, bottom - top)],\n );\n }\n\n decorations(cell: TerminalCell, x: number, y: number, width: number, foreground: Vector4): void {\n if (cell.attributes & 128) this.solid(x, y + 10, width, 1, foreground);\n if (cell.attributes & 256) this.solid(x, y + 1, width, 1, foreground);\n const style = cell.underlineStyle || (cell.attributes & 8 ? 1 : 0);\n this.underline(style, x, y, width, rgba(cell.underlineColor));\n }\n\n private underline(style: number, x: number, y: number, width: number, color: Vector4): void {\n if (style === 1) this.solid(x, y + 18, width, 1, color);\n else if (style === 2) {\n this.solid(x, y + 16, width, 1, color);\n this.solid(x, y + 18, width, 1, color);\n } else if (style === 3) {\n for (let dx = 0; dx < width; dx++) {\n this.solid(x + dx, y + 17 + Math.round(Math.sin((x + dx) * Math.PI / 4)), 1, 1, color);\n }\n } else if (style === 4 || style === 5) {\n const step = style === 4 ? 2 : 5;\n const segment = style === 4 ? 1 : 3;\n for (let dx = 0; dx < width; dx += step) this.solid(x + dx, y + 18, Math.min(segment, width - dx), 1, color);\n }\n }\n\n render(cells: readonly (TerminalCell | undefined)[], metadata: FrameMetadata, blinkOn: boolean,\n linkDecorations?: Uint8Array) {\n const start = performance.now();\n this.quadCount = 0;\n this.batches = [];\n const placements = metadata.placements.map((placement, order) => ({\n placement,\n order,\n z: placement.kind === \"sixel\" ? -1 : placement.z,\n })).sort((a, b) => a.z - b.z || a.order - b.order);\n for (const item of placements) if (item.z < -1073741824) this.placement(item.placement);\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i];\n if (!cell) continue;\n this.solid((i % this.columns) * CELL_WIDTH, Math.floor(i / this.columns) * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT, rgba(cell.background));\n }\n for (const item of placements) if (item.z >= -1073741824 && item.z < 0) this.placement(item.placement);\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i];\n if (!cell || !cell.width || (cell.attributes & 64) || ((cell.attributes & 16) && !blinkOn)) continue;\n const x = (i % this.columns) * CELL_WIDTH;\n const y = Math.floor(i / this.columns) * CELL_HEIGHT;\n const width = Math.min(cell.width * CELL_WIDTH, this.width - x);\n const foreground = rgba(cell.foreground);\n const glyph = isKgpPlaceholder(cell) ? undefined : this.glyphs.get(glyphKey(cell));\n if (glyph) {\n const tint: Vector4 = glyph.colored ? (cell.attributes & 2 ? [0.5, 0.5, 0.5, 1] : WHITE) : foreground;\n this.quad(this.atlas, x, y, cell.width * CELL_WIDTH, CELL_HEIGHT,\n tint, glyph.colored ? 2 : 1, [glyph.u0, glyph.v0, glyph.u1, glyph.v1]);\n }\n // Reverse and dim are already reflected in server-projected colors.\n this.decorations(cell, x, y, width, foreground);\n if (linkDecorations?.[i] && !cell.underlineStyle && !(cell.attributes & 8) && !isKgpPlaceholder(cell)) {\n this.underline(linkDecorations[i], x, y, width, foreground);\n }\n }\n for (const item of placements) if (item.z >= 0) this.placement(item.placement);\n const cursor = metadata.cursor;\n const cursorBlink = cursor.shape === 0 || cursor.shape % 2 === 1;\n if (cursor.visible && (!cursorBlink || blinkOn) && cursor.x >= 0 && cursor.x < this.columns && cursor.y >= 0 && cursor.y < this.rows) {\n const cell = cells[cursor.y * this.columns + cursor.x];\n const color = rgba(cell?.foreground ?? 0xffffffff);\n const x = cursor.x * CELL_WIDTH;\n const y = cursor.y * CELL_HEIGHT;\n if (cursor.shape === 3 || cursor.shape === 4) this.solid(x, y + 18, CELL_WIDTH, 2, color);\n else if (cursor.shape === 5 || cursor.shape === 6) this.solid(x, y, 2, CELL_HEIGHT, color);\n else {\n color[3] *= 0.55;\n this.solid(x, y, CELL_WIDTH, CELL_HEIGHT, color);\n }\n }\n const base = rgba(metadata.defaultBackground ?? cells[0]?.background ?? 0xff000000);\n this.backend.submit(this.instances, this.quadCount, this.batches, base);\n return { cpuMs: performance.now() - start, quads: this.quadCount, drawCalls: this.batches.length };\n }\n\n metrics() {\n return {\n renderer: this.backend.kind,\n rendererFallbackReason: this.fallbackReason,\n fontFamily: this.font.family,\n rasterScale: this.scale,\n backingScale: this.backingScale,\n backingWidth: this.canvas.width,\n backingHeight: this.canvas.height,\n imageCount: this.images.size,\n textureBytes: this.textureBytes,\n atlasGlyphs: this.glyphs.size,\n atlasBytes: this.atlas.width * this.atlas.height * 4,\n atlasRebuilds: this.atlasRebuilds,\n imageUploadBytes: this.imageUploadBytes,\n imagePayloadBytes: this.imagePayloadBytes,\n glyphUploadBytes: this.glyphUploadBytes,\n instanceBufferBytes: this.backend.instanceBufferBytes,\n };\n }\n\n async idle() {\n await this.backend.idle();\n }\n\n dispose() {\n if (this.disposed) return;\n this.disposed = true;\n for (const image of this.images.values()) image.destroy();\n this.images.clear();\n this.textureBytes = 0;\n this.atlas?.destroy();\n this.glyphs.clear();\n this.glyphKeyUnits = 0;\n this.batches = [];\n this.quadCount = 0;\n this.instances = new Float32Array(0);\n this.font?.dispose();\n this.fontMetrics.clear();\n this.backend.dispose();\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts deleted file mode 100644 index 68ff0db3197..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { MouseTrackingMode, SelectionMode, TerminalPoint, TerminalSelection } from "./types.js"; -import type { CellPosition } from "./wire-types.js"; -export interface GestureState { - tracking: MouseTrackingMode; - historical?: boolean; - readOnly?: boolean; - selection?: Pick; -} -export type GestureStart = { - owner: "app"; -} | { - owner: "local"; - mode: SelectionMode; - extend: boolean; -}; -type PointEvent = Pick; -export declare function cellPoint(event: PointEvent, bounds: Pick, columns: number, rows: number, clamp?: boolean): CellPosition | null; -export declare class WheelAccumulator { - x: number; - y: number; - reset(): void; - take(event: Pick, bounds: Pick, rows: number): TerminalPoint; -} -/** Ownership and granularity are latched, independent of subsequent modifiers. */ -export declare class SelectionGesture { - owner: "local" | "app" | null; - mode: SelectionMode; - endpoint: TerminalPoint | null; - begin(event: Pick, point: TerminalPoint, { tracking, historical, readOnly, selection }: GestureState): GestureStart | null; - move(point: TerminalPoint): TerminalPoint | null; - scrollPoint(point: TerminalPoint): TerminalPoint | undefined; - wheelOwner(event: Pick, { tracking, historical, readOnly }: GestureState): "local" | "app"; - end(): void; -} -export {}; -//# sourceMappingURL=selection-input.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts.map deleted file mode 100644 index 93db0635181..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"selection-input.d.ts","sourceRoot":"","sources":["../src/selection-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACrG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;CACtE;AACD,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AACvG,KAAK,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC;AAE9F,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC,EACrG,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,YAAY,GAAG,IAAI,CAUnE;AAED,qBAAa,gBAAgB;IAC3B,CAAC,SAAK;IACN,CAAC,SAAK;IACN,KAAK;IACL,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa;CAY/H;AAED,kFAAkF;AAClF,qBAAa,gBAAgB;IAC3B,KAAK,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,CAAQ;IACrC,IAAI,EAAE,aAAa,CAAe;IAClC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAQ;IACtC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,aAAa,EAC9F,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,YAAY,GAAG,YAAY,GAAG,IAAI;IAYnF,IAAI,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,IAAI;IAKhD,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,GAAG,SAAS;IAK5D,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,YAAY,GAAG,OAAO,GAAG,KAAK;IAGlH,GAAG;CACJ"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js deleted file mode 100644 index 76fdefd15d7..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js +++ /dev/null @@ -1,69 +0,0 @@ -export function cellPoint(event, bounds, columns, rows, clamp = false) { - if (!(bounds.width > 0) || !(bounds.height > 0)) - return null; - const x = Math.floor((event.clientX - bounds.left) * columns / bounds.width); - const y = Math.floor((event.clientY - bounds.top) * rows / bounds.height); - if (!clamp && (x < 0 || y < 0 || x >= columns || y >= rows)) - return null; - return { - x: Math.max(0, Math.min(columns - 1, x)), - y: Math.max(0, Math.min(rows - 1, y)), - shift: !!event.shiftKey, alt: !!event.altKey, ctrl: !!event.ctrlKey - }; -} -export class WheelAccumulator { - x = 0; - y = 0; - reset() { this.x = this.y = 0; } - take(event, bounds, rows) { - const cellHeight = bounds.height / rows; - if (!(cellHeight > 0)) - return { x: 0, y: 0 }; - const unit = event.deltaMode === 1 ? 1 : event.deltaMode === 2 ? rows : 1 / cellHeight; - this.x += event.deltaX * unit; - this.y += event.deltaY * unit; - const x = Math.trunc(this.x); - const y = Math.trunc(this.y); - this.x -= x; - this.y -= y; - return { x: Math.max(-32, Math.min(32, x)), y: Math.max(-32, Math.min(32, y)) }; - } -} -/** Ownership and granularity are latched, independent of subsequent modifiers. */ -export class SelectionGesture { - owner = null; - mode = "character"; - endpoint = null; - begin(event, point, { tracking, historical, readOnly, selection }) { - if (this.owner) - return null; - const local = historical || readOnly || !tracking || event.shiftKey; - if (local && event.button !== 0) - return null; - this.owner = local ? "local" : "app"; - if (!local) - return { owner: "app" }; - const extend = !!event.shiftKey && !!selection && ["valid", "pending"].includes(selection.status) && selection.canExtend !== false; - this.mode = extend && selection ? selection.mode : event.altKey ? "rectangle" - : event.detail >= 3 ? "line" : event.detail === 2 ? "word" : "character"; - this.endpoint = { x: point.x, y: point.y }; - return { owner: this.owner, mode: this.mode, extend }; - } - move(point) { - if (this.owner !== "local") - return null; - this.endpoint = { x: point.x, y: point.y }; - return { ...this.endpoint }; - } - scrollPoint(point) { - if (this.owner !== "local") - return undefined; - // A wheel cannot widen a rectangle; only actual pointer movement can. - return { x: this.mode === "rectangle" && this.endpoint ? this.endpoint.x : point.x, y: point.y }; - } - wheelOwner(event, { tracking, historical, readOnly }) { - return this.owner ?? (historical || readOnly || !tracking || event.shiftKey ? "local" : "app"); - } - end() { this.owner = null; } -} -//# sourceMappingURL=selection-input.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js.map deleted file mode 100644 index 9f6a51bb6e8..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-input.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"selection-input.js","sourceRoot":"","sources":["../src/selection-input.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MouseTrackingMode, SelectionMode, TerminalPoint, TerminalSelection } from \"./types.js\";\nimport type { CellPosition } from \"./wire-types.js\";\n\nexport interface GestureState {\n tracking: MouseTrackingMode;\n historical?: boolean;\n readOnly?: boolean;\n selection?: Pick;\n}\nexport type GestureStart = { owner: \"app\" } | { owner: \"local\"; mode: SelectionMode; extend: boolean };\ntype PointEvent = Pick;\n\nexport function cellPoint(event: PointEvent, bounds: Pick,\n columns: number, rows: number, clamp = false): CellPosition | null {\n if (!(bounds.width > 0) || !(bounds.height > 0)) return null;\n const x = Math.floor((event.clientX - bounds.left) * columns / bounds.width);\n const y = Math.floor((event.clientY - bounds.top) * rows / bounds.height);\n if (!clamp && (x < 0 || y < 0 || x >= columns || y >= rows)) return null;\n return {\n x: Math.max(0, Math.min(columns - 1, x)),\n y: Math.max(0, Math.min(rows - 1, y)),\n shift: !!event.shiftKey, alt: !!event.altKey, ctrl: !!event.ctrlKey\n };\n}\n\nexport class WheelAccumulator {\n x = 0;\n y = 0;\n reset() { this.x = this.y = 0; }\n take(event: Pick, bounds: Pick, rows: number): TerminalPoint {\n const cellHeight = bounds.height / rows;\n if (!(cellHeight > 0)) return { x: 0, y: 0 };\n const unit = event.deltaMode === 1 ? 1 : event.deltaMode === 2 ? rows : 1 / cellHeight;\n this.x += event.deltaX * unit;\n this.y += event.deltaY * unit;\n const x = Math.trunc(this.x);\n const y = Math.trunc(this.y);\n this.x -= x;\n this.y -= y;\n return { x: Math.max(-32, Math.min(32, x)), y: Math.max(-32, Math.min(32, y)) };\n }\n}\n\n/** Ownership and granularity are latched, independent of subsequent modifiers. */\nexport class SelectionGesture {\n owner: \"local\" | \"app\" | null = null;\n mode: SelectionMode = \"character\";\n endpoint: TerminalPoint | null = null;\n begin(event: Pick, point: TerminalPoint,\n { tracking, historical, readOnly, selection }: GestureState): GestureStart | null {\n if (this.owner) return null;\n const local = historical || readOnly || !tracking || event.shiftKey;\n if (local && event.button !== 0) return null;\n this.owner = local ? \"local\" : \"app\";\n if (!local) return { owner: \"app\" };\n const extend = !!event.shiftKey && !!selection && [\"valid\", \"pending\"].includes(selection.status) && selection.canExtend !== false;\n this.mode = extend && selection ? selection.mode : event.altKey ? \"rectangle\"\n : event.detail >= 3 ? \"line\" : event.detail === 2 ? \"word\" : \"character\";\n this.endpoint = { x: point.x, y: point.y };\n return { owner: this.owner, mode: this.mode, extend };\n }\n move(point: TerminalPoint): TerminalPoint | null {\n if (this.owner !== \"local\") return null;\n this.endpoint = { x: point.x, y: point.y };\n return { ...this.endpoint };\n }\n scrollPoint(point: TerminalPoint): TerminalPoint | undefined {\n if (this.owner !== \"local\") return undefined;\n // A wheel cannot widen a rectangle; only actual pointer movement can.\n return { x: this.mode === \"rectangle\" && this.endpoint ? this.endpoint.x : point.x, y: point.y };\n }\n wheelOwner(event: Pick, { tracking, historical, readOnly }: GestureState): \"local\" | \"app\" {\n return this.owner ?? (historical || readOnly || !tracking || event.shiftKey ? \"local\" : \"app\");\n }\n end() { this.owner = null; }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts deleted file mode 100644 index b7634165894..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { RunTerminalAction, SelectionRange, SelectionUIState, TerminalGeometry, TerminalSize, WebTerminalOptions } from "./types.js"; -export declare function sameSelectionUIState(a: SelectionUIState | undefined, b: SelectionUIState): boolean; -export declare function selectionRectangles(ranges: readonly SelectionRange[], geometry: TerminalGeometry, canvasSize: TerminalSize): Readonly<{ - left: number; - top: number; - width: number; - height: number; -}>[]; -/** Owns UI notification/default rendering, not terminal selection or clipboard state. */ -export declare class SelectionUI { - #private; - constructor({ element, overlay, button, signal, getState, runAction, onSelectionUI, reportError }: { - element: HTMLDivElement; - overlay: HTMLDivElement; - button: HTMLButtonElement; - signal: AbortSignal; - getState: () => SelectionUIState; - runAction: RunTerminalAction; - onSelectionUI?: WebTerminalOptions["onSelectionUI"]; - reportError: (error: unknown) => void; - }); - refresh(force?: boolean): void; -} -//# sourceMappingURL=selection-ui.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts.map deleted file mode 100644 index abfb316ad38..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"selection-ui.d.ts","sourceRoot":"","sources":["../src/selection-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAC7C,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAY3F,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,gBAAgB,GAAG,SAAS,EAAE,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAQlG;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY;;;;;KAO1H;AAED,yFAAyF;AACzF,qBAAa,WAAW;;gBAaV,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE;QACjG,OAAO,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,cAAc,CAAC;QAAC,MAAM,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,WAAW,CAAC;QACjG,QAAQ,EAAE,MAAM,gBAAgB,CAAC;QAAC,SAAS,EAAE,iBAAiB,CAAC;QAC/D,aAAa,CAAC,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;QAAC,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;KAC5F;IA4BD,OAAO,CAAC,KAAK,UAAQ;CAqCtB"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js deleted file mode 100644 index 01f2f299f75..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js +++ /dev/null @@ -1,113 +0,0 @@ -import { isRecord } from "./validation.js"; -const selectionFields = ["status", "mode", "requestId", "active", "pending", "canExtend", - "text", "message", "copying", "copyError"]; -const viewportFields = ["available", "following", "pending", "generation", "buffer", - "top", "liveTop", "totalRows", "requestId"]; -const geometryFields = ["columns", "rows", "cellWidth", "cellHeight", "mouseTracking"]; -const rangeFields = ["row", "startColumn", "endColumn"]; -const equalFields = (a, b, fields) => fields.every(field => a?.[field] === b?.[field]); -export function sameSelectionUIState(a, b) { - return !!a && a.connected === b.connected && a.readOnly === b.readOnly && - equalFields(a.selection, b.selection, selectionFields) && - equalFields(a.viewport, b.viewport, viewportFields) && - equalFields(a.geometry, b.geometry, geometryFields) && - equalFields(a.canvasSize, b.canvasSize, ["width", "height"]) && - a.selection.ranges.length === b.selection.ranges.length && - a.selection.ranges.every((range, index) => equalFields(range, b.selection.ranges[index], rangeFields)); -} -export function selectionRectangles(ranges, geometry, canvasSize) { - const width = canvasSize.width / geometry.columns; - const height = canvasSize.height / geometry.rows; - return ranges.map(range => Object.freeze({ - left: range.startColumn * width, top: range.row * height, - width: (range.endColumn - range.startColumn) * width, height - })); -} -/** Owns UI notification/default rendering, not terminal selection or clipboard state. */ -export class SelectionUI { - #element; - #overlay; - #button; - #signal; - #getState; - #runAction; - #reportError; - #previous; - #notification; - #queued = false; - #force = false; - constructor({ element, overlay, button, signal, getState, runAction, onSelectionUI, reportError }) { - this.#element = element; - this.#overlay = overlay; - this.#button = button; - this.#signal = signal; - this.#getState = getState; - this.#runAction = runAction; - this.#reportError = reportError; - if (onSelectionUI) { - element.addEventListener("selectionui", event => { - if (!this.#notification || event !== this.#notification) - return; - const snapshot = this.#previous; - try { - const result = onSelectionUI(this.#notification); - if (result !== undefined) { - if (isRecord(result) && typeof result.then === "function") - Promise.resolve(result).catch(error => { - if (!signal.aborted && this.#previous === snapshot) - reportError(error); - }); - throw new TypeError("onSelectionUI must finish synchronously; use preventDefault() and signal for UI ownership"); - } - } - catch (error) { - event.preventDefault(); - reportError(error); - } - }, { signal }); - } - } - refresh(force = false) { - this.#force ||= force; - if (this.#queued || this.#signal.aborted) - return; - this.#queued = true; - // Coalesce geometry and history from the same presented frame before notifying the host. - queueMicrotask(() => { - this.#queued = false; - if (this.#signal.aborted) - return; - const state = this.#getState(); - const force = this.#force; - this.#force = false; - if (!force && sameSelectionUIState(this.#previous, state)) - return; - const snapshot = Object.freeze({ - ...state, - selection: Object.freeze({ ...state.selection, - ranges: Object.freeze(state.selection.ranges.map(range => Object.freeze({ ...range }))) }), - viewport: Object.freeze({ ...state.viewport, rowIds: Object.freeze([...(state.viewport.rowIds ?? [])]) }), - geometry: Object.freeze({ ...state.geometry }), - canvasSize: Object.freeze({ ...state.canvasSize }) - }); - this.#previous = snapshot; - const detail = Object.freeze({ - ...snapshot, overlay: this.#overlay, signal: this.#signal, runAction: this.#runAction, - rects: Object.freeze(selectionRectangles(snapshot.selection.ranges, snapshot.geometry, snapshot.canvasSize)) - }); - const event = new CustomEvent("selectionui", { cancelable: true, detail }); - this.#notification = event; - this.#reportError(null); - this.#element.dispatchEvent(event); - if (this.#signal.aborted) - return; - const selection = snapshot.selection; - this.#button.hidden = event.defaultPrevented || ["none", "unavailable"].includes(selection.status); - this.#button.disabled = !snapshot.connected || selection.status !== "valid" || selection.copying; - const label = selection.copying ? "Copying\u2026" : "Copy"; - if (this.#button.textContent !== label) - this.#button.textContent = label; - }); - } -} -//# sourceMappingURL=selection-ui.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js.map deleted file mode 100644 index 4dc472c0726..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/selection-ui.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"selection-ui.js","sourceRoot":"","sources":["../src/selection-ui.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RunTerminalAction, SelectionRange, SelectionUIDetail, SelectionUIEvent,\n SelectionUIState, TerminalGeometry, TerminalSize, WebTerminalOptions } from \"./types.js\";\nimport { isRecord } from \"./validation.js\";\n\nconst selectionFields = [\"status\", \"mode\", \"requestId\", \"active\", \"pending\", \"canExtend\",\n \"text\", \"message\", \"copying\", \"copyError\"] as const;\nconst viewportFields = [\"available\", \"following\", \"pending\", \"generation\", \"buffer\",\n \"top\", \"liveTop\", \"totalRows\", \"requestId\"] as const;\nconst geometryFields = [\"columns\", \"rows\", \"cellWidth\", \"cellHeight\", \"mouseTracking\"] as const;\nconst rangeFields = [\"row\", \"startColumn\", \"endColumn\"] as const;\nconst equalFields = (a: T | undefined, b: T | undefined, fields: readonly (keyof T)[]) =>\n fields.every(field => a?.[field] === b?.[field]);\n\nexport function sameSelectionUIState(a: SelectionUIState | undefined, b: SelectionUIState): boolean {\n return !!a && a.connected === b.connected && a.readOnly === b.readOnly &&\n equalFields(a.selection, b.selection, selectionFields) &&\n equalFields(a.viewport, b.viewport, viewportFields) &&\n equalFields(a.geometry, b.geometry, geometryFields) &&\n equalFields(a.canvasSize, b.canvasSize, [\"width\", \"height\"]) &&\n a.selection.ranges.length === b.selection.ranges.length &&\n a.selection.ranges.every((range, index) => equalFields(range, b.selection.ranges[index], rangeFields));\n}\n\nexport function selectionRectangles(ranges: readonly SelectionRange[], geometry: TerminalGeometry, canvasSize: TerminalSize) {\n const width = canvasSize.width / geometry.columns;\n const height = canvasSize.height / geometry.rows;\n return ranges.map(range => Object.freeze({\n left: range.startColumn * width, top: range.row * height,\n width: (range.endColumn - range.startColumn) * width, height\n }));\n}\n\n/** Owns UI notification/default rendering, not terminal selection or clipboard state. */\nexport class SelectionUI {\n #element: HTMLDivElement;\n #overlay: HTMLDivElement;\n #button: HTMLButtonElement;\n #signal: AbortSignal;\n #getState: () => SelectionUIState;\n #runAction: RunTerminalAction;\n #reportError: (error: unknown) => void;\n #previous: SelectionUIState | undefined;\n #notification: SelectionUIEvent | undefined;\n #queued = false;\n #force = false;\n\n constructor({ element, overlay, button, signal, getState, runAction, onSelectionUI, reportError }: {\n element: HTMLDivElement; overlay: HTMLDivElement; button: HTMLButtonElement; signal: AbortSignal;\n getState: () => SelectionUIState; runAction: RunTerminalAction;\n onSelectionUI?: WebTerminalOptions[\"onSelectionUI\"]; reportError: (error: unknown) => void;\n }) {\n this.#element = element;\n this.#overlay = overlay;\n this.#button = button;\n this.#signal = signal;\n this.#getState = getState;\n this.#runAction = runAction;\n this.#reportError = reportError;\n if (onSelectionUI) {\n element.addEventListener(\"selectionui\", event => {\n if (!this.#notification || event !== this.#notification) return;\n const snapshot = this.#previous;\n try {\n const result: unknown = onSelectionUI(this.#notification);\n if (result !== undefined) {\n if (isRecord(result) && typeof result.then === \"function\") Promise.resolve(result).catch(error => {\n if (!signal.aborted && this.#previous === snapshot) reportError(error);\n });\n throw new TypeError(\"onSelectionUI must finish synchronously; use preventDefault() and signal for UI ownership\");\n }\n } catch (error) {\n event.preventDefault();\n reportError(error);\n }\n }, { signal });\n }\n }\n\n refresh(force = false) {\n this.#force ||= force;\n if (this.#queued || this.#signal.aborted) return;\n this.#queued = true;\n // Coalesce geometry and history from the same presented frame before notifying the host.\n queueMicrotask(() => {\n this.#queued = false;\n if (this.#signal.aborted) return;\n const state = this.#getState();\n const force = this.#force;\n this.#force = false;\n if (!force && sameSelectionUIState(this.#previous, state)) return;\n const snapshot: SelectionUIState = Object.freeze({\n ...state,\n selection: Object.freeze({ ...state.selection,\n ranges: Object.freeze(state.selection.ranges.map(range => Object.freeze({ ...range }))) }),\n viewport: Object.freeze({ ...state.viewport, rowIds: Object.freeze([...(state.viewport.rowIds ?? [])]) }),\n geometry: Object.freeze({ ...state.geometry }),\n canvasSize: Object.freeze({ ...state.canvasSize })\n });\n this.#previous = snapshot;\n const detail = Object.freeze({\n ...snapshot, overlay: this.#overlay, signal: this.#signal, runAction: this.#runAction,\n rects: Object.freeze(selectionRectangles(snapshot.selection.ranges, snapshot.geometry, snapshot.canvasSize))\n });\n const event = new CustomEvent(\"selectionui\", { cancelable: true, detail });\n this.#notification = event;\n this.#reportError(null);\n this.#element.dispatchEvent(event);\n if (this.#signal.aborted) return;\n const selection = snapshot.selection;\n this.#button.hidden = event.defaultPrevented || [\"none\", \"unavailable\"].includes(selection.status);\n this.#button.disabled = !snapshot.connected || selection.status !== \"valid\" || selection.copying;\n const label = selection.copying ? \"Copying\\u2026\" : \"Copy\";\n if (this.#button.textContent !== label) this.#button.textContent = label;\n });\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts deleted file mode 100644 index 437d7a01c96..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** Resolve caller-relative URLs before sending the configuration to the worker. */ -export declare function normalizeFont(font?: TerminalFont, baseUrl?: string): NormalizedFont; -/** Use the rendering context's FontFaceSet; a worker cannot inherit page fonts. */ -export declare function loadFont(configuration: TerminalFont): Promise; -/** Fit one font-wide advance and line box to the authoritative cell dimensions. */ -export declare function measureFont(raster: OffscreenCanvasRenderingContext2D, cssFamily: string, attributes: number, scale: number, cellWidth: number, cellHeight: number): FontMetrics; -import type { TerminalFont, TerminalFontFace } from "./types.js"; -export interface NormalizedFont { - family: string; - faces: Required[]; -} -export interface LoadedFont { - family: string; - cssFamily: string; - dispose(): void; -} -export interface FontMetrics { - font: string; - xScale: number; - yScale: number; - baseline: number; -} -//# sourceMappingURL=terminal-font.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts.map deleted file mode 100644 index c3c99a91d21..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-font.d.ts","sourceRoot":"","sources":["../src/terminal-font.ts"],"names":[],"mappings":"AAGA,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,OAAO,SAAkB,GAAG,cAAc,CA2B5F;AAED,mFAAmF;AACnF,wBAAsB,QAAQ,CAAC,aAAa,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA6B/E;AAED,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,MAAM,EAAE,iCAAiC,EAAE,SAAS,EAAE,MAAM,EACtF,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAcvF;AACD,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGjE,MAAM,WAAW,cAAc;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAA;CAAE;AACvF,MAAM,WAAW,UAAU;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,IAAI,CAAA;CAAE;AAClF,MAAM,WAAW,WAAW;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js deleted file mode 100644 index 042a51904a5..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js +++ /dev/null @@ -1,85 +0,0 @@ -const defaultFontUrl = new URL("./fonts/cascadia-mono-nf/CascadiaMonoNF.woff2", import.meta.url).href; -const genericFamilies = new Set(["monospace", "serif", "sans-serif", "system-ui"]); -/** Resolve caller-relative URLs before sending the configuration to the worker. */ -export function normalizeFont(font, baseUrl = import.meta.url) { - font ??= { family: "Cascadia Mono NF", faces: [{ url: defaultFontUrl, weight: "200 700" }] }; - if (typeof font !== "object" || Array.isArray(font) || - typeof font.family !== "string" || !font.family.trim() || font.family.length > 256 || - /[\u0000-\u001f\u007f]/u.test(font.family)) { - throw new TypeError("font.family must be a single, nonempty font family name"); - } - const faces = font.faces ?? []; - if (!Array.isArray(faces) || faces.length > 16) - throw new TypeError("font.faces must contain at most 16 font sources"); - return { - family: font.family.trim(), - faces: faces.map(face => { - if (!face || typeof face.url !== "string" || !face.url.trim()) { - throw new TypeError("Each font face requires a URL"); - } - for (const field of ["weight", "style"]) { - if (face[field] !== undefined && (typeof face[field] !== "string" || face[field].length > 64)) { - throw new TypeError(`Font face ${field} must be a CSS descriptor string`); - } - } - return { - url: new URL(face.url, baseUrl).href, - weight: face.weight ?? "400", - style: face.style ?? "normal" - }; - }) - }; -} -/** Use the rendering context's FontFaceSet; a worker cannot inherit page fonts. */ -export async function loadFont(configuration) { - const { family, faces } = normalizeFont(configuration); - const scope = globalThis; - const fontSet = "fonts" in scope && isFontSet(scope.fonts) - ? scope.fonts : globalThis.document?.fonts; - if (!fontSet || typeof FontFace !== "function") { - throw new Error("Terminal font loading requires the CSS Font Loading API in the rendering context"); - } - const generic = genericFamilies.has(family.toLowerCase()); - const cssFamily = generic ? family.toLowerCase() : JSON.stringify(family); - if (generic && faces.length) - throw new TypeError("A generic font family cannot have downloadable faces"); - const sources = faces.length ? faces : generic ? [] : [{ local: family }]; - const loaded = []; - for (const source of sources) { - try { - const face = new FontFace(family, "local" in source ? `local(${JSON.stringify(source.local)})` : `url(${JSON.stringify(source.url)})`, { weight: source.weight ?? "400", style: source.style ?? "normal" }); - loaded.push(await face.load()); - } - catch (error) { - throw new Error(`Could not load terminal font "${family}" from ${"url" in source ? source.url : "local fonts"}`, { cause: error }); - } - } - for (const face of loaded) - fontSet.add(face); - return { - family, cssFamily, - dispose() { for (const face of loaded) - fontSet.delete(face); } - }; -} -/** Fit one font-wide advance and line box to the authoritative cell dimensions. */ -export function measureFont(raster, cssFamily, attributes, scale, cellWidth, cellHeight) { - const font = `${attributes & 4 ? "italic " : ""}${attributes & 1 ? "bold " : ""}${16 * scale}px ${cssFamily}`; - raster.font = font; - raster.textBaseline = "alphabetic"; - raster.textAlign = "left"; - const metrics = raster.measureText("M"); - const lineHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; - if (!Number.isFinite(metrics.width) || metrics.width <= 0 || - !Number.isFinite(lineHeight) || lineHeight <= 0) { - throw new Error(`Invalid terminal font metrics for ${cssFamily}`); - } - const xScale = cellWidth * scale / metrics.width; - const yScale = cellHeight * scale / lineHeight; - return { font, xScale, yScale, baseline: metrics.fontBoundingBoxAscent * yScale }; -} -import { isRecord } from "./validation.js"; -function isFontSet(value) { - return isRecord(value) && typeof value.add === "function" && typeof value.delete === "function"; -} -//# sourceMappingURL=terminal-font.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js.map deleted file mode 100644 index c9b5c4dbe32..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-font.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-font.js","sourceRoot":"","sources":["../src/terminal-font.ts"],"names":[],"mappings":"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","sourcesContent":["const defaultFontUrl = new URL(\"./fonts/cascadia-mono-nf/CascadiaMonoNF.woff2\", import.meta.url).href;\nconst genericFamilies = new Set([\"monospace\", \"serif\", \"sans-serif\", \"system-ui\"]);\n\n/** Resolve caller-relative URLs before sending the configuration to the worker. */\nexport function normalizeFont(font?: TerminalFont, baseUrl = import.meta.url): NormalizedFont {\n font ??= { family: \"Cascadia Mono NF\", faces: [{ url: defaultFontUrl, weight: \"200 700\" }] };\n if (typeof font !== \"object\" || Array.isArray(font) ||\n typeof font.family !== \"string\" || !font.family.trim() || font.family.length > 256 ||\n /[\\u0000-\\u001f\\u007f]/u.test(font.family)) {\n throw new TypeError(\"font.family must be a single, nonempty font family name\");\n }\n const faces = font.faces ?? [];\n if (!Array.isArray(faces) || faces.length > 16) throw new TypeError(\"font.faces must contain at most 16 font sources\");\n return {\n family: font.family.trim(),\n faces: faces.map(face => {\n if (!face || typeof face.url !== \"string\" || !face.url.trim()) {\n throw new TypeError(\"Each font face requires a URL\");\n }\n for (const field of [\"weight\", \"style\"] as const) {\n if (face[field] !== undefined && (typeof face[field] !== \"string\" || face[field].length > 64)) {\n throw new TypeError(`Font face ${field} must be a CSS descriptor string`);\n }\n }\n return {\n url: new URL(face.url, baseUrl).href,\n weight: face.weight ?? \"400\",\n style: face.style ?? \"normal\"\n };\n })\n };\n}\n\n/** Use the rendering context's FontFaceSet; a worker cannot inherit page fonts. */\nexport async function loadFont(configuration: TerminalFont): Promise {\n const { family, faces } = normalizeFont(configuration);\n const scope: object = globalThis;\n const fontSet = \"fonts\" in scope && isFontSet(scope.fonts)\n ? scope.fonts : globalThis.document?.fonts;\n if (!fontSet || typeof FontFace !== \"function\") {\n throw new Error(\"Terminal font loading requires the CSS Font Loading API in the rendering context\");\n }\n const generic = genericFamilies.has(family.toLowerCase());\n const cssFamily = generic ? family.toLowerCase() : JSON.stringify(family);\n if (generic && faces.length) throw new TypeError(\"A generic font family cannot have downloadable faces\");\n const sources: (TerminalFontFace | { local: string; weight?: string; style?: string })[] =\n faces.length ? faces : generic ? [] : [{ local: family }];\n const loaded: FontFace[] = [];\n for (const source of sources) {\n try {\n const face = new FontFace(family,\n \"local\" in source ? `local(${JSON.stringify(source.local)})` : `url(${JSON.stringify(source.url)})`,\n { weight: source.weight ?? \"400\", style: source.style ?? \"normal\" });\n loaded.push(await face.load());\n } catch (error) {\n throw new Error(`Could not load terminal font \"${family}\" from ${\"url\" in source ? source.url : \"local fonts\"}`, { cause: error });\n }\n }\n for (const face of loaded) fontSet.add(face);\n return {\n family, cssFamily,\n dispose() { for (const face of loaded) fontSet.delete(face); }\n };\n}\n\n/** Fit one font-wide advance and line box to the authoritative cell dimensions. */\nexport function measureFont(raster: OffscreenCanvasRenderingContext2D, cssFamily: string,\n attributes: number, scale: number, cellWidth: number, cellHeight: number): FontMetrics {\n const font = `${attributes & 4 ? \"italic \" : \"\"}${attributes & 1 ? \"bold \" : \"\"}${16 * scale}px ${cssFamily}`;\n raster.font = font;\n raster.textBaseline = \"alphabetic\";\n raster.textAlign = \"left\";\n const metrics = raster.measureText(\"M\");\n const lineHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;\n if (!Number.isFinite(metrics.width) || metrics.width <= 0 ||\n !Number.isFinite(lineHeight) || lineHeight <= 0) {\n throw new Error(`Invalid terminal font metrics for ${cssFamily}`);\n }\n const xScale = cellWidth * scale / metrics.width;\n const yScale = cellHeight * scale / lineHeight;\n return { font, xScale, yScale, baseline: metrics.fontBoundingBoxAscent * yScale };\n}\nimport type { TerminalFont, TerminalFontFace } from \"./types.js\";\nimport { isRecord } from \"./validation.js\";\n\nexport interface NormalizedFont { family: string; faces: Required[] }\nexport interface LoadedFont { family: string; cssFamily: string; dispose(): void }\nexport interface FontMetrics { font: string; xScale: number; yScale: number; baseline: number }\n\nfunction isFontSet(value: unknown): value is Pick {\n return isRecord(value) && typeof value.add === \"function\" && typeof value.delete === \"function\";\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts deleted file mode 100644 index c980a37f25c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export declare const MIN_FONT_SIZE = 8; -export declare const MAX_FONT_SIZE = 32; -export declare function dimensions(columns: number, rows: number): TerminalGrid; -export declare function normalizeSizing(sizing?: TerminalSizing, previousFontSize?: number): TerminalSizingState; -export declare function requestedGrid(size: TerminalSize, geometry: TerminalGeometry, sizing: TerminalSizingState): TerminalGrid | null; -export declare function fittedScale(size: TerminalSize, geometry: TerminalGeometry, isPrimary: boolean, sizing: TerminalSizingState): number; -import type { TerminalGeometry, TerminalGrid, TerminalSize, TerminalSizing, TerminalSizingState } from "./types.js"; -//# sourceMappingURL=terminal-sizing.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts.map deleted file mode 100644 index fd4c60d2532..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-sizing.d.ts","sourceRoot":"","sources":["../src/terminal-sizing.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,eAAO,MAAM,aAAa,KAAK,CAAC;AAGhC,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,CAMtE;AAED,wBAAgB,eAAe,CAAC,MAAM,GAAE,cAAiC,EAAE,gBAAgB,SAAmB,GAAG,mBAAmB,CAWnI;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,GAAG,YAAY,GAAG,IAAI,CAQ9H;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAKnI;AACD,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js deleted file mode 100644 index e733886269b..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js +++ /dev/null @@ -1,38 +0,0 @@ -export const MIN_FONT_SIZE = 8; -export const MAX_FONT_SIZE = 32; -const NATIVE_FONT_SIZE = 16; -export function dimensions(columns, rows) { - if (!Number.isInteger(columns) || columns < 20 || columns > 300 || - !Number.isInteger(rows) || rows < 10 || rows > 100) { - throw new RangeError("Requested grid must be 20-300 columns by 10-100 rows"); - } - return { columns, rows }; -} -export function normalizeSizing(sizing = { mode: "auto" }, previousFontSize = NATIVE_FONT_SIZE) { - if (!sizing || !["auto", "fixed"].includes(sizing.mode)) { - throw new TypeError("Sizing mode must be 'auto' or 'fixed'"); - } - const fontSize = sizing.fontSize ?? previousFontSize; - if (!Number.isInteger(fontSize) || fontSize < MIN_FONT_SIZE || fontSize > MAX_FONT_SIZE) { - throw new RangeError(`Font size must be an integer from ${MIN_FONT_SIZE} to ${MAX_FONT_SIZE}`); - } - return sizing.mode === "fixed" - ? { mode: "fixed", fontSize, ...dimensions(sizing.columns, sizing.rows) } - : { mode: "auto", fontSize }; -} -export function requestedGrid(size, geometry, sizing) { - if (sizing.mode === "fixed") - return { columns: sizing.columns, rows: sizing.rows }; - if (size.width <= 0 || size.height <= 0) - return null; - const scale = sizing.fontSize / NATIVE_FONT_SIZE; - return { - columns: Math.max(20, Math.min(300, Math.floor(size.width / (geometry.cellWidth * scale)))), - rows: Math.max(10, Math.min(100, Math.floor(size.height / (geometry.cellHeight * scale)))) - }; -} -export function fittedScale(size, geometry, isPrimary, sizing) { - const maximum = isPrimary && sizing.mode === "auto" ? sizing.fontSize / NATIVE_FONT_SIZE : Infinity; - return Math.max(0, Math.min(maximum, size.width / (geometry.columns * geometry.cellWidth), size.height / (geometry.rows * geometry.cellHeight))); -} -//# sourceMappingURL=terminal-sizing.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js.map deleted file mode 100644 index 69afc47cb45..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-sizing.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-sizing.js","sourceRoot":"","sources":["../src/terminal-sizing.ts"],"names":[],"mappings":"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","sourcesContent":["export const MIN_FONT_SIZE = 8;\nexport const MAX_FONT_SIZE = 32;\nconst NATIVE_FONT_SIZE = 16;\n\nexport function dimensions(columns: number, rows: number): TerminalGrid {\n if (!Number.isInteger(columns) || columns < 20 || columns > 300 ||\n !Number.isInteger(rows) || rows < 10 || rows > 100) {\n throw new RangeError(\"Requested grid must be 20-300 columns by 10-100 rows\");\n }\n return { columns, rows };\n}\n\nexport function normalizeSizing(sizing: TerminalSizing = { mode: \"auto\" }, previousFontSize = NATIVE_FONT_SIZE): TerminalSizingState {\n if (!sizing || ![\"auto\", \"fixed\"].includes(sizing.mode)) {\n throw new TypeError(\"Sizing mode must be 'auto' or 'fixed'\");\n }\n const fontSize = sizing.fontSize ?? previousFontSize;\n if (!Number.isInteger(fontSize) || fontSize < MIN_FONT_SIZE || fontSize > MAX_FONT_SIZE) {\n throw new RangeError(`Font size must be an integer from ${MIN_FONT_SIZE} to ${MAX_FONT_SIZE}`);\n }\n return sizing.mode === \"fixed\"\n ? { mode: \"fixed\", fontSize, ...dimensions(sizing.columns, sizing.rows) }\n : { mode: \"auto\", fontSize };\n}\n\nexport function requestedGrid(size: TerminalSize, geometry: TerminalGeometry, sizing: TerminalSizingState): TerminalGrid | null {\n if (sizing.mode === \"fixed\") return { columns: sizing.columns, rows: sizing.rows };\n if (size.width <= 0 || size.height <= 0) return null;\n const scale = sizing.fontSize / NATIVE_FONT_SIZE;\n return {\n columns: Math.max(20, Math.min(300, Math.floor(size.width / (geometry.cellWidth * scale)))),\n rows: Math.max(10, Math.min(100, Math.floor(size.height / (geometry.cellHeight * scale))))\n };\n}\n\nexport function fittedScale(size: TerminalSize, geometry: TerminalGeometry, isPrimary: boolean, sizing: TerminalSizingState): number {\n const maximum = isPrimary && sizing.mode === \"auto\" ? sizing.fontSize / NATIVE_FONT_SIZE : Infinity;\n return Math.max(0, Math.min(maximum,\n size.width / (geometry.columns * geometry.cellWidth),\n size.height / (geometry.rows * geometry.cellHeight)));\n}\nimport type { TerminalGeometry, TerminalGrid, TerminalSize, TerminalSizing, TerminalSizingState } from \"./types.js\";\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts deleted file mode 100644 index f999c015236..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export declare const terminalThemeCss = "\n :host {\n \n --cp-terminal-surface: #ffffff;\n --cp-terminal-text: #242424;\n --cp-terminal-text-muted: #5c5c5c;\n --cp-terminal-border-strong: #919191;\n --cp-terminal-accent: #b11f4b;\n --cp-terminal-accent-soft: rgba(177, 31, 75, 0.08);\n --cp-terminal-danger: #dc2626;\n\n --cp-terminal-font-family: \"Segoe UI\", Aptos, Calibri, -apple-system, BlinkMacSystemFont, sans-serif;\n }\n @media (prefers-color-scheme: dark) { :host { \n --cp-terminal-surface: #292929;\n --cp-terminal-text: #dedede;\n --cp-terminal-text-muted: #919191;\n --cp-terminal-border-strong: #5f5f5f;\n --cp-terminal-accent: #fd8ea1;\n --cp-terminal-accent-soft: rgba(253, 142, 161, 0.14);\n --cp-terminal-danger: #f87171;\n } }\n :host-context([data-theme=\"light\"]) { \n --cp-terminal-surface: #ffffff;\n --cp-terminal-text: #242424;\n --cp-terminal-text-muted: #5c5c5c;\n --cp-terminal-border-strong: #919191;\n --cp-terminal-accent: #b11f4b;\n --cp-terminal-accent-soft: rgba(177, 31, 75, 0.08);\n --cp-terminal-danger: #dc2626;\n }\n :host-context([data-theme=\"dark\"]) { \n --cp-terminal-surface: #292929;\n --cp-terminal-text: #dedede;\n --cp-terminal-text-muted: #919191;\n --cp-terminal-border-strong: #5f5f5f;\n --cp-terminal-accent: #fd8ea1;\n --cp-terminal-accent-soft: rgba(253, 142, 161, 0.14);\n --cp-terminal-danger: #f87171;\n }\n .viewport {\n --cp-view-surface: var(--cp-surface, var(--cp-terminal-surface));\n --cp-view-text: var(--cp-text, var(--cp-terminal-text));\n --cp-view-text-muted: var(--cp-text-muted, var(--cp-terminal-text-muted));\n --cp-view-border-strong: var(--cp-border-strong, var(--cp-terminal-border-strong));\n --cp-view-accent: var(--cp-accent, var(--cp-terminal-accent));\n --cp-view-accent-soft: var(--cp-accent-soft, var(--cp-terminal-accent-soft));\n --cp-view-danger: var(--cp-danger, var(--cp-terminal-danger));\n --cp-view-font-family: var(--cp-font-family, var(--cp-terminal-font-family));\n }\n"; -//# sourceMappingURL=terminal-theme.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts.map deleted file mode 100644 index 6bfed02c2c8..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-theme.d.ts","sourceRoot":"","sources":["../src/terminal-theme.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,gBAAgB,28DAkB5B,CAAC"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js deleted file mode 100644 index 142d3a039af..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js +++ /dev/null @@ -1,39 +0,0 @@ -const light = ` - --cp-terminal-surface: #ffffff; - --cp-terminal-text: #242424; - --cp-terminal-text-muted: #5c5c5c; - --cp-terminal-border-strong: #919191; - --cp-terminal-accent: #b11f4b; - --cp-terminal-accent-soft: rgba(177, 31, 75, 0.08); - --cp-terminal-danger: #dc2626; -`; -const dark = ` - --cp-terminal-surface: #292929; - --cp-terminal-text: #dedede; - --cp-terminal-text-muted: #919191; - --cp-terminal-border-strong: #5f5f5f; - --cp-terminal-accent: #fd8ea1; - --cp-terminal-accent-soft: rgba(253, 142, 161, 0.14); - --cp-terminal-danger: #f87171; -`; -// Shared embedding tokens take precedence; defaults never overwrite inherited --cp-* colors. -export const terminalThemeCss = ` - :host { - ${light} - --cp-terminal-font-family: "Segoe UI", Aptos, Calibri, -apple-system, BlinkMacSystemFont, sans-serif; - } - @media (prefers-color-scheme: dark) { :host { ${dark} } } - :host-context([data-theme="light"]) { ${light} } - :host-context([data-theme="dark"]) { ${dark} } - .viewport { - --cp-view-surface: var(--cp-surface, var(--cp-terminal-surface)); - --cp-view-text: var(--cp-text, var(--cp-terminal-text)); - --cp-view-text-muted: var(--cp-text-muted, var(--cp-terminal-text-muted)); - --cp-view-border-strong: var(--cp-border-strong, var(--cp-terminal-border-strong)); - --cp-view-accent: var(--cp-accent, var(--cp-terminal-accent)); - --cp-view-accent-soft: var(--cp-accent-soft, var(--cp-terminal-accent-soft)); - --cp-view-danger: var(--cp-danger, var(--cp-terminal-danger)); - --cp-view-font-family: var(--cp-font-family, var(--cp-terminal-font-family)); - } -`; -//# sourceMappingURL=terminal-theme.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js.map deleted file mode 100644 index ea0b0189538..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-theme.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-theme.js","sourceRoot":"","sources":["../src/terminal-theme.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG;;;;;;;;CAQb,CAAC;AAEF,MAAM,IAAI,GAAG;;;;;;;;CAQZ,CAAC;AAEF,6FAA6F;AAC7F,MAAM,CAAC,MAAM,gBAAgB,GAAG;;MAE1B,KAAK;;;kDAGuC,IAAI;0CACZ,KAAK;yCACN,IAAI;;;;;;;;;;;CAW5C,CAAC","sourcesContent":["const light = `\n --cp-terminal-surface: #ffffff;\n --cp-terminal-text: #242424;\n --cp-terminal-text-muted: #5c5c5c;\n --cp-terminal-border-strong: #919191;\n --cp-terminal-accent: #b11f4b;\n --cp-terminal-accent-soft: rgba(177, 31, 75, 0.08);\n --cp-terminal-danger: #dc2626;\n`;\n\nconst dark = `\n --cp-terminal-surface: #292929;\n --cp-terminal-text: #dedede;\n --cp-terminal-text-muted: #919191;\n --cp-terminal-border-strong: #5f5f5f;\n --cp-terminal-accent: #fd8ea1;\n --cp-terminal-accent-soft: rgba(253, 142, 161, 0.14);\n --cp-terminal-danger: #f87171;\n`;\n\n// Shared embedding tokens take precedence; defaults never overwrite inherited --cp-* colors.\nexport const terminalThemeCss = `\n :host {\n ${light}\n --cp-terminal-font-family: \"Segoe UI\", Aptos, Calibri, -apple-system, BlinkMacSystemFont, sans-serif;\n }\n @media (prefers-color-scheme: dark) { :host { ${dark} } }\n :host-context([data-theme=\"light\"]) { ${light} }\n :host-context([data-theme=\"dark\"]) { ${dark} }\n .viewport {\n --cp-view-surface: var(--cp-surface, var(--cp-terminal-surface));\n --cp-view-text: var(--cp-text, var(--cp-terminal-text));\n --cp-view-text-muted: var(--cp-text-muted, var(--cp-terminal-text-muted));\n --cp-view-border-strong: var(--cp-border-strong, var(--cp-terminal-border-strong));\n --cp-view-accent: var(--cp-accent, var(--cp-terminal-accent));\n --cp-view-accent-soft: var(--cp-accent-soft, var(--cp-terminal-accent-soft));\n --cp-view-danger: var(--cp-danger, var(--cp-terminal-danger));\n --cp-view-font-family: var(--cp-font-family, var(--cp-terminal-font-family));\n }\n`;\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts deleted file mode 100644 index b89c1fa6091..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=terminal-worker.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts.map deleted file mode 100644 index f51558176de..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-worker.d.ts","sourceRoot":"","sources":["../src/terminal-worker.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js deleted file mode 100644 index f9abc9fd8ff..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js +++ /dev/null @@ -1,331 +0,0 @@ -import { decodeFrame, screenText } from "./protocol.js"; -import { TerminalRenderer } from "./renderer.js"; -import { LinkPresentation } from "./link-presentation.js"; -import { errorMessage } from "./validation.js"; -let renderer; -let socket; -let failed = false; -let stopped = false; -let processing = false; -let drawing = false; -let frameInFlight = false; -let scheduled = false; -let needsRender = false; -let hasBlink = false; -let lastBlink = true; -let metadata; -let cells = []; -let localRevision = 0; -let pendingFrame; -let renderPromise = Promise.resolve(); -let metricsTimer; -let blinkTimer; -let viewport; -const links = new LinkPresentation(); -const stats = { - revision: 0, fullFrames: 0, frames: 0, presentations: 0, - changedCells: 0, lastChangedCells: 0, discardedFrames: 0, - imageCount: 0, textureBytes: 0, atlasGlyphs: 0, atlasBytes: 0, - bytesReceived: 0, imageUploadBytes: 0, imagePayloadBytes: 0, - gpu: "initializing", connected: false, warnings: [], - fps: 0, receivedKBps: 0, workloadMBps: 0, - captureMs: 0, rendererCpuMs: 0, preparationCpuMs: 0, - workloadBytes: 0, outputBatches: 0, serverElapsedMs: 0, -}; -let sample = { time: performance.now(), presentations: 0, bytes: 0, workload: 0 }; -function postStatus(message, level = "info") { - self.postMessage({ type: "status", message, level }); -} -function send(message) { - if (socket?.readyState === WebSocket.OPEN) - socket.send(JSON.stringify(message)); -} -function emitStats(text) { - if (renderer && !renderer.disposed) - Object.assign(stats, renderer.metrics()); - self.postMessage({ type: "stats", stats: { ...stats }, ...(text === undefined ? {} : { text }) }); -} -function fail(error) { - if (failed || stopped) - return; - failed = true; - const message = errorMessage(error); - stats.gpu = "error"; - stats.connected = false; - clearInterval(metricsTimer); - clearInterval(blinkTimer); - socket?.close(); - emitStats(); - postStatus(message, "error"); - renderer?.dispose(); -} -self.addEventListener("error", event => { - event.preventDefault(); - fail(event.error || new Error(event.message)); -}); -self.addEventListener("unhandledrejection", event => { - event.preventDefault(); - fail(event.reason); -}); -/** At most one state frame, one decode, and one GPU submission are outstanding. */ -function scheduleRender() { - needsRender = true; - if (scheduled || drawing || processing || failed || stopped || !metadata) - return; - scheduled = true; - self.requestAnimationFrame(() => { - scheduled = false; - if (processing || drawing || failed || stopped) - return; - renderPromise = drawFrame(); - }); -} -async function drawFrame() { - if (!needsRender || !metadata || !renderer) - return; - needsRender = false; - drawing = true; - const frame = pendingFrame; - try { - if (frame) - links.prepare(cells, metadata); - const linkSubmission = links.submission(); - renderer.resize(metadata.columns, metadata.rows, viewport); - const blink = Math.floor(performance.now() / 600) % 2 === 0; - const result = renderer.render(cells, metadata, blink, linkSubmission.mask); - // This is bounded completion/backpressure, not GPU readback or a GPU timing measurement. - await renderer.idle(); - if (failed || stopped) - return; - lastBlink = blink; - stats.presentations++; - stats.rendererCpuMs = result.cpuMs; - stats.quads = result.quads; - stats.drawCalls = result.drawCalls; - stats.warnings = renderer.canvasLimited - ? [...metadata.warnings, `Canvas resolution capped by the GPU; terminal grid remains ${metadata.columns}x${metadata.rows}`] - : metadata.warnings; - if (frame) { - pendingFrame = undefined; - frameInFlight = false; - stats.frames++; - if (frame.full) - stats.fullFrames++; - stats.revision = frame.revision; - stats.changedCells += frame.changedCells; - stats.lastChangedCells = frame.changedCells; - stats.captureMs = metadata.stats.captureMs; - stats.workloadBytes = metadata.stats.workloadBytes; - stats.outputBatches = metadata.stats.outputBatches; - stats.serverElapsedMs = metadata.stats.elapsedMs; - stats.columns = metadata.columns; - stats.rows = metadata.rows; - stats.mouseTracking = metadata.mouseTracking; - stats.peer = metadata.peer; - stats.history = metadata.history; - const text = screenText(cells, metadata.columns, metadata.rows); - self.postMessage({ - type: "geometry", columns: metadata.columns, rows: metadata.rows, - cellWidth: metadata.cellWidth, cellHeight: metadata.cellHeight, - mouseTracking: metadata.mouseTracking, peer: metadata.peer, - history: metadata.history, revision: frame.revision, title: metadata.title, - progress: metadata.progress, shellIntegration: metadata.shellIntegration, - workingDirectory: metadata.workingDirectory, commandMark: metadata.commandMark, - text, hyperlinks: metadata.hyperlinks, ...links.present(cells, metadata) - }); - send({ type: "ack", revision: frame.revision }); - emitStats(text); - } - else { - const snapshot = links.snapshot(); - if (snapshot) - self.postMessage({ type: "linkSnapshot", generation: links.generation, snapshot }); - } - const acknowledgement = links.acknowledge(linkSubmission.acknowledgement, processing || frameInFlight); - if (acknowledgement) - self.postMessage({ type: "linkDecorations", ...acknowledgement }); - } - catch (error) { - fail(error); - } - finally { - drawing = false; - if (needsRender && !processing) - scheduleRender(); - } -} -async function receiveFrame(buffer) { - if (failed || stopped || !renderer) - return; - if (frameInFlight) - throw new Error("Server sent a second state frame before acknowledgement"); - frameInFlight = true; - processing = true; - stats.bytesReceived += buffer.byteLength || 0; - try { - const frame = decodeFrame(buffer); - const next = frame.metadata; - if (!next.full && (next.baseRevision !== localRevision || next.revision <= localRevision || - !metadata || next.columns !== metadata.columns || next.rows !== metadata.rows)) { - stats.discardedFrames++; - frameInFlight = false; - // A discarded frame must release the server's one-in-flight gate before resync. - send({ type: "ack", revision: next.revision }); - send({ type: "resync" }); - postStatus(`Revision mismatch (local ${localRevision}, base ${next.baseRevision}); requesting a full frame`); - return; - } - await renderPromise; - if (failed || stopped) - return; - // No blink presentation may reference textures while this resource transaction is in progress. - await renderer.idle(); - await renderer.updateImages(frame.images, next.retainedImages); - if (failed || stopped) - return; - const preparationStart = performance.now(); - const nextCells = next.full - ? new Array(next.columns * next.rows) : cells.slice(); - for (const cell of frame.cells) - nextCells[cell.index] = cell; - renderer.prepareGlyphs(nextCells); - cells = nextCells; - metadata = next; - localRevision = next.revision; - pendingFrame = { revision: next.revision, full: next.full, changedCells: frame.cells.length }; - hasBlink = cells.some(cell => cell && (cell.attributes & 16) && !(cell.attributes & 64)) || - (next.cursor.visible && (next.cursor.shape === 0 || next.cursor.shape % 2 === 1)); - stats.preparationCpuMs = performance.now() - preparationStart; - needsRender = true; - } - finally { - processing = false; - if (needsRender) - scheduleRender(); - } -} -async function initialize(message) { - if (renderer || socket) - throw new Error("Worker is already initialized"); - if (typeof self.requestAnimationFrame !== "function") { - throw new Error("This browser does not support requestAnimationFrame in a dedicated OffscreenCanvas worker"); - } - postStatus("Loading terminal font and initializing renderer..."); - renderer = await TerminalRenderer.create(message.canvas, message.scale, fail, message.font, message.renderer); - if (failed || stopped) { - renderer?.dispose(); - return; - } - stats.gpu = "ready"; - stats.backingScale = message.scale; - emitStats(); - const rendererName = renderer.backend.kind === "webgpu" ? "WebGPU" : "WebGL2"; - if (renderer.fallbackReason) - postStatus(`Using WebGL2: ${renderer.fallbackReason}`); - postStatus(`${rendererName} ready. Attaching terminal view...`); - const url = new URL(message.url); - if (!["ws:", "wss:"].includes(url.protocol)) { - throw new Error("The terminal WebSocket URL must use ws: or wss:"); - } - socket = new WebSocket(url); - socket.binaryType = "arraybuffer"; - socket.addEventListener("open", () => { - if (failed || stopped) - return; - stats.connected = true; - self.postMessage({ type: "connected" }); - postStatus(`Connected · ${rendererName} worker · server-authoritative cells and graphics`, "ready"); - emitStats(); - }); - socket.addEventListener("message", event => { - if (!(event.data instanceof ArrayBuffer)) { - fail(new Error(`Expected binary HWT1 frame, received ${String(event.data).slice(0, 200)}`)); - return; - } - receiveFrame(event.data).catch(fail); - }); - // WebSocket errors are followed by close, which carries the browser's actual status. - // Rejecting mount on error would terminate this worker before that status can be delivered. - socket.addEventListener("close", event => { - stats.connected = false; - if (!failed && !stopped) { - stats.gpu = "stopped"; - stats.fps = 0; - stopped = true; - clearInterval(metricsTimer); - clearInterval(blinkTimer); - renderer?.dispose(); - self.postMessage({ type: "closed", details: { - code: event.code, reason: event.reason, wasClean: event.wasClean - } }); - postStatus(`View disconnected (${event.code}${event.reason ? `: ${event.reason}` : ""}). Attach another view to reconnect.`, "error"); - emitStats(); - } - }); - metricsTimer = setInterval(() => { - const now = performance.now(); - const seconds = (now - sample.time) / 1000; - stats.fps = (stats.presentations - sample.presentations) / seconds; - stats.receivedKBps = (stats.bytesReceived - sample.bytes) / seconds / 1000; - stats.workloadMBps = Math.max(0, stats.workloadBytes - sample.workload) / seconds / 1000000; - sample = { time: now, presentations: stats.presentations, bytes: stats.bytesReceived, workload: stats.workloadBytes }; - emitStats(); - }, 1000); - blinkTimer = setInterval(() => { - const blinkOn = Math.floor(performance.now() / 600) % 2 === 0; - if (hasBlink && blinkOn !== lastBlink) - scheduleRender(); - }, 100); -} -self.addEventListener("message", event => { - const message = event.data; - if (message.type === "init") { - initialize(message).catch(fail); - } - else if (message.type === "stop") { - stopped = true; - clearInterval(metricsTimer); - clearInterval(blinkTimer); - socket?.close(1000, "View detached"); - renderer?.dispose(); - self.close(); - } - else if (message.type === "viewport" && !failed && !stopped) { - if (!Number.isFinite(message.width) || !Number.isFinite(message.height) || message.width < 0 || message.height < 0) { - fail(new Error("Invalid mounted viewport dimensions")); - return; - } - if (viewport?.width === message.width && viewport?.height === message.height) - return; - viewport = { width: message.width, height: message.height }; - scheduleRender(); - } - else if (message.type === "command" && !failed && !stopped) { - send(message.command); - } - else if (message.type === "linkDetection" && !failed && !stopped) { - try { - if (!links.configure(message.enabled, message.generation)) - return; - if (!processing && !drawing && !pendingFrame) { - const snapshot = links.snapshot(); - if (snapshot) - self.postMessage({ type: "linkSnapshot", generation: links.generation, snapshot }); - } - scheduleRender(); - } - catch (error) { - fail(error); - } - } - else if (message.type === "linkDecorations" && !failed && !stopped) { - try { - if (links.accept(message.revision, message.generation, message.serial, message.ranges, processing || frameInFlight || !!pendingFrame, message.underlineStyle)) - scheduleRender(); - } - catch (error) { - fail(error); - } - } -}); -//# sourceMappingURL=terminal-worker.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js.map deleted file mode 100644 index ac5e6784008..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/terminal-worker.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"terminal-worker.js","sourceRoot":"","sources":["../src/terminal-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodeFrame, screenText } from \"./protocol.js\";\nimport { TerminalRenderer } from \"./renderer.js\";\nimport { LinkPresentation } from \"./link-presentation.js\";\nimport type { TerminalSize, TerminalStatusLevel } from \"./types.js\";\nimport type { FrameMetadata, TerminalCell, TerminalCommand, WorkerInputMessage, WorkerOutputMessage, WorkerStats } from \"./wire-types.js\";\nimport { errorMessage } from \"./validation.js\";\n\n// This module is only executed as a dedicated worker. Keeping its global local to\n// this module avoids leaking worker/WebGPU ambient dependencies to public declarations.\ndeclare const self: {\n postMessage(message: WorkerOutputMessage): void;\n requestAnimationFrame(callback: FrameRequestCallback): number;\n close(): void;\n addEventListener(type: \"error\", callback: (event: ErrorEvent) => void): void;\n addEventListener(type: \"unhandledrejection\", callback: (event: PromiseRejectionEvent) => void): void;\n addEventListener(type: \"message\", callback: (event: MessageEvent) => void): void;\n};\n\nlet renderer: TerminalRenderer | undefined;\nlet socket: WebSocket | undefined;\nlet failed = false;\nlet stopped = false;\nlet processing = false;\nlet drawing = false;\nlet frameInFlight = false;\nlet scheduled = false;\nlet needsRender = false;\nlet hasBlink = false;\nlet lastBlink = true;\nlet metadata: FrameMetadata | undefined;\nlet cells: (TerminalCell | undefined)[] = [];\nlet localRevision = 0;\nlet pendingFrame: { revision: number; full: boolean; changedCells: number } | undefined;\nlet renderPromise = Promise.resolve();\nlet metricsTimer: ReturnType | undefined;\nlet blinkTimer: ReturnType | undefined;\nlet viewport: TerminalSize | undefined;\nconst links = new LinkPresentation();\nconst stats: WorkerStats = {\n revision: 0, fullFrames: 0, frames: 0, presentations: 0,\n changedCells: 0, lastChangedCells: 0, discardedFrames: 0,\n imageCount: 0, textureBytes: 0, atlasGlyphs: 0, atlasBytes: 0,\n bytesReceived: 0, imageUploadBytes: 0, imagePayloadBytes: 0,\n gpu: \"initializing\", connected: false, warnings: [],\n fps: 0, receivedKBps: 0, workloadMBps: 0,\n captureMs: 0, rendererCpuMs: 0, preparationCpuMs: 0,\n workloadBytes: 0, outputBatches: 0, serverElapsedMs: 0,\n};\nlet sample = { time: performance.now(), presentations: 0, bytes: 0, workload: 0 };\n\nfunction postStatus(message: string, level: TerminalStatusLevel = \"info\"): void {\n self.postMessage({ type: \"status\", message, level });\n}\n\nfunction send(message: TerminalCommand): void {\n if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify(message));\n}\n\nfunction emitStats(text?: string): void {\n if (renderer && !renderer.disposed) Object.assign(stats, renderer.metrics());\n self.postMessage({ type: \"stats\", stats: { ...stats }, ...(text === undefined ? {} : { text }) });\n}\n\nfunction fail(error: unknown): void {\n if (failed || stopped) return;\n failed = true;\n const message = errorMessage(error);\n stats.gpu = \"error\";\n stats.connected = false;\n clearInterval(metricsTimer);\n clearInterval(blinkTimer);\n socket?.close();\n emitStats();\n postStatus(message, \"error\");\n renderer?.dispose();\n}\n\nself.addEventListener(\"error\", event => {\n event.preventDefault();\n fail(event.error || new Error(event.message));\n});\nself.addEventListener(\"unhandledrejection\", event => {\n event.preventDefault();\n fail(event.reason);\n});\n\n/** At most one state frame, one decode, and one GPU submission are outstanding. */\nfunction scheduleRender() {\n needsRender = true;\n if (scheduled || drawing || processing || failed || stopped || !metadata) return;\n scheduled = true;\n self.requestAnimationFrame(() => {\n scheduled = false;\n if (processing || drawing || failed || stopped) return;\n renderPromise = drawFrame();\n });\n}\n\nasync function drawFrame() {\n if (!needsRender || !metadata || !renderer) return;\n needsRender = false;\n drawing = true;\n const frame = pendingFrame;\n try {\n if (frame) links.prepare(cells, metadata);\n const linkSubmission = links.submission();\n renderer.resize(metadata.columns, metadata.rows, viewport);\n const blink = Math.floor(performance.now() / 600) % 2 === 0;\n const result = renderer.render(cells, metadata, blink, linkSubmission.mask);\n // This is bounded completion/backpressure, not GPU readback or a GPU timing measurement.\n await renderer.idle();\n if (failed || stopped) return;\n lastBlink = blink;\n stats.presentations++;\n stats.rendererCpuMs = result.cpuMs;\n stats.quads = result.quads;\n stats.drawCalls = result.drawCalls;\n stats.warnings = renderer.canvasLimited\n ? [...metadata.warnings, `Canvas resolution capped by the GPU; terminal grid remains ${metadata.columns}x${metadata.rows}`]\n : metadata.warnings;\n if (frame) {\n pendingFrame = undefined;\n frameInFlight = false;\n stats.frames++;\n if (frame.full) stats.fullFrames++;\n stats.revision = frame.revision;\n stats.changedCells += frame.changedCells;\n stats.lastChangedCells = frame.changedCells;\n stats.captureMs = metadata.stats.captureMs;\n stats.workloadBytes = metadata.stats.workloadBytes;\n stats.outputBatches = metadata.stats.outputBatches;\n stats.serverElapsedMs = metadata.stats.elapsedMs;\n stats.columns = metadata.columns;\n stats.rows = metadata.rows;\n stats.mouseTracking = metadata.mouseTracking;\n stats.peer = metadata.peer;\n stats.history = metadata.history;\n const text = screenText(cells, metadata.columns, metadata.rows);\n self.postMessage({\n type: \"geometry\", columns: metadata.columns, rows: metadata.rows,\n cellWidth: metadata.cellWidth, cellHeight: metadata.cellHeight,\n mouseTracking: metadata.mouseTracking, peer: metadata.peer,\n history: metadata.history, revision: frame.revision, title: metadata.title,\n progress: metadata.progress, shellIntegration: metadata.shellIntegration,\n workingDirectory: metadata.workingDirectory, commandMark: metadata.commandMark,\n text, hyperlinks: metadata.hyperlinks, ...links.present(cells, metadata)\n });\n send({ type: \"ack\", revision: frame.revision });\n emitStats(text);\n } else {\n const snapshot = links.snapshot();\n if (snapshot) self.postMessage({ type: \"linkSnapshot\", generation: links.generation, snapshot });\n }\n const acknowledgement = links.acknowledge(linkSubmission.acknowledgement, processing || frameInFlight);\n if (acknowledgement) self.postMessage({ type: \"linkDecorations\", ...acknowledgement });\n } catch (error) {\n fail(error);\n } finally {\n drawing = false;\n if (needsRender && !processing) scheduleRender();\n }\n}\n\nasync function receiveFrame(buffer: ArrayBuffer): Promise {\n if (failed || stopped || !renderer) return;\n if (frameInFlight) throw new Error(\"Server sent a second state frame before acknowledgement\");\n frameInFlight = true;\n processing = true;\n stats.bytesReceived += buffer.byteLength || 0;\n try {\n const frame = decodeFrame(buffer);\n const next = frame.metadata;\n if (!next.full && (next.baseRevision !== localRevision || next.revision <= localRevision ||\n !metadata || next.columns !== metadata.columns || next.rows !== metadata.rows)) {\n stats.discardedFrames++;\n frameInFlight = false;\n // A discarded frame must release the server's one-in-flight gate before resync.\n send({ type: \"ack\", revision: next.revision });\n send({ type: \"resync\" });\n postStatus(`Revision mismatch (local ${localRevision}, base ${next.baseRevision}); requesting a full frame`);\n return;\n }\n await renderPromise;\n if (failed || stopped) return;\n // No blink presentation may reference textures while this resource transaction is in progress.\n await renderer.idle();\n await renderer.updateImages(frame.images, next.retainedImages);\n if (failed || stopped) return;\n const preparationStart = performance.now();\n const nextCells: (TerminalCell | undefined)[] = next.full\n ? new Array(next.columns * next.rows) : cells.slice();\n for (const cell of frame.cells) nextCells[cell.index] = cell;\n renderer.prepareGlyphs(nextCells);\n cells = nextCells;\n metadata = next;\n localRevision = next.revision;\n pendingFrame = { revision: next.revision, full: next.full, changedCells: frame.cells.length };\n hasBlink = cells.some(cell => cell && (cell.attributes & 16) && !(cell.attributes & 64)) ||\n (next.cursor.visible && (next.cursor.shape === 0 || next.cursor.shape % 2 === 1));\n stats.preparationCpuMs = performance.now() - preparationStart;\n needsRender = true;\n } finally {\n processing = false;\n if (needsRender) scheduleRender();\n }\n}\n\nasync function initialize(message: Extract): Promise {\n if (renderer || socket) throw new Error(\"Worker is already initialized\");\n if (typeof self.requestAnimationFrame !== \"function\") {\n throw new Error(\"This browser does not support requestAnimationFrame in a dedicated OffscreenCanvas worker\");\n }\n postStatus(\"Loading terminal font and initializing renderer...\");\n renderer = await TerminalRenderer.create(message.canvas, message.scale, fail, message.font, message.renderer);\n if (failed || stopped) {\n renderer?.dispose();\n return;\n }\n stats.gpu = \"ready\";\n stats.backingScale = message.scale;\n emitStats();\n const rendererName = renderer.backend.kind === \"webgpu\" ? \"WebGPU\" : \"WebGL2\";\n if (renderer.fallbackReason) postStatus(`Using WebGL2: ${renderer.fallbackReason}`);\n postStatus(`${rendererName} ready. Attaching terminal view...`);\n const url = new URL(message.url);\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) {\n throw new Error(\"The terminal WebSocket URL must use ws: or wss:\");\n }\n socket = new WebSocket(url);\n socket.binaryType = \"arraybuffer\";\n socket.addEventListener(\"open\", () => {\n if (failed || stopped) return;\n stats.connected = true;\n self.postMessage({ type: \"connected\" });\n postStatus(`Connected · ${rendererName} worker · server-authoritative cells and graphics`, \"ready\");\n emitStats();\n });\n socket.addEventListener(\"message\", event => {\n if (!(event.data instanceof ArrayBuffer)) {\n fail(new Error(`Expected binary HWT1 frame, received ${String(event.data).slice(0, 200)}`));\n return;\n }\n receiveFrame(event.data).catch(fail);\n });\n // WebSocket errors are followed by close, which carries the browser's actual status.\n // Rejecting mount on error would terminate this worker before that status can be delivered.\n socket.addEventListener(\"close\", event => {\n stats.connected = false;\n if (!failed && !stopped) {\n stats.gpu = \"stopped\";\n stats.fps = 0;\n stopped = true;\n clearInterval(metricsTimer);\n clearInterval(blinkTimer);\n renderer?.dispose();\n self.postMessage({ type: \"closed\", details: {\n code: event.code, reason: event.reason, wasClean: event.wasClean\n } });\n postStatus(`View disconnected (${event.code}${event.reason ? `: ${event.reason}` : \"\"}). Attach another view to reconnect.`, \"error\");\n emitStats();\n }\n });\n metricsTimer = setInterval(() => {\n const now = performance.now();\n const seconds = (now - sample.time) / 1000;\n stats.fps = (stats.presentations - sample.presentations) / seconds;\n stats.receivedKBps = (stats.bytesReceived - sample.bytes) / seconds / 1000;\n stats.workloadMBps = Math.max(0, stats.workloadBytes - sample.workload) / seconds / 1000000;\n sample = { time: now, presentations: stats.presentations, bytes: stats.bytesReceived, workload: stats.workloadBytes };\n emitStats();\n }, 1000);\n blinkTimer = setInterval(() => {\n const blinkOn = Math.floor(performance.now() / 600) % 2 === 0;\n if (hasBlink && blinkOn !== lastBlink) scheduleRender();\n }, 100);\n}\n\nself.addEventListener(\"message\", event => {\n const message = event.data;\n if (message.type === \"init\") {\n initialize(message).catch(fail);\n } else if (message.type === \"stop\") {\n stopped = true;\n clearInterval(metricsTimer);\n clearInterval(blinkTimer);\n socket?.close(1000, \"View detached\");\n renderer?.dispose();\n self.close();\n } else if (message.type === \"viewport\" && !failed && !stopped) {\n if (!Number.isFinite(message.width) || !Number.isFinite(message.height) || message.width < 0 || message.height < 0) {\n fail(new Error(\"Invalid mounted viewport dimensions\"));\n return;\n }\n if (viewport?.width === message.width && viewport?.height === message.height) return;\n viewport = { width: message.width, height: message.height };\n scheduleRender();\n } else if (message.type === \"command\" && !failed && !stopped) {\n send(message.command);\n } else if (message.type === \"linkDetection\" && !failed && !stopped) {\n try {\n if (!links.configure(message.enabled, message.generation)) return;\n if (!processing && !drawing && !pendingFrame) {\n const snapshot = links.snapshot();\n if (snapshot) self.postMessage({ type: \"linkSnapshot\", generation: links.generation, snapshot });\n }\n scheduleRender();\n } catch (error) { fail(error); }\n } else if (message.type === \"linkDecorations\" && !failed && !stopped) {\n try {\n if (links.accept(message.revision, message.generation, message.serial, message.ranges,\n processing || frameInFlight || !!pendingFrame, message.underlineStyle)) scheduleRender();\n } catch (error) { fail(error); }\n }\n});\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts deleted file mode 100644 index f468573fda1..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts +++ /dev/null @@ -1,437 +0,0 @@ -import type { TerminalLinkOptions, TerminalLinkDetectionError } from "./link-types.js"; -export type * from "./link-types.js"; -/** Logical terminal dimensions, confirmed by the producer rather than reflowed locally. */ -export interface TerminalGrid { - columns: number; - rows: number; -} -export interface TerminalSize { - width: number; - height: number; -} -export interface TerminalPoint { - x: number; - y: number; -} -export type MouseTrackingMode = 0 | 9 | 1000 | 1002 | 1003; -export interface TerminalGeometry extends TerminalGrid { - cellWidth: number; - cellHeight: number; - mouseTracking: MouseTrackingMode; -} -export interface TerminalPeer { - id: string | null; - primaryId: string | null; - isPrimary: boolean; -} -/** Font size is an integer from 8 to 32; fixed grids allow 20–300 columns and 10–100 rows. */ -export type TerminalSizing = { - mode: "auto"; - fontSize?: number; -} | { - mode: "fixed"; - fontSize?: number; - columns: number; - rows: number; -}; -export type TerminalSizingState = { - mode: "auto"; - fontSize: number; -} | { - mode: "fixed"; - fontSize: number; - columns: number; - rows: number; -}; -export interface TerminalFontFace { - url: string; - weight?: string; - style?: string; -} -/** Without faces, a non-generic family must be installed locally in the worker's environment. */ -export interface TerminalFont { - family: string; - faces?: readonly TerminalFontFace[]; -} -export type TerminalBuffer = "main" | "alternate"; -export type SelectionMode = "character" | "word" | "line" | "rectangle"; -export interface SelectionRange { - row: number; - startColumn: number; - endColumn: number; -} -export type TerminalViewport = ({ - available: true; - generation: string; - buffer: TerminalBuffer; - totalRows: number; - liveTop: number; - top: number; - requestId: number; - rowIds: readonly string[]; - revision: number; -} | { - available: false; - generation?: undefined; - buffer?: undefined; - totalRows?: undefined; - liveTop?: undefined; - top?: undefined; - requestId?: undefined; - rowIds?: readonly string[]; - revision?: undefined; -}) & { - following: boolean; - pending: boolean; - followTail: boolean; - offset: number; -}; -export type TerminalSelection = ({ - status: "valid"; - text: string; - requestId: number; - revision: number; -} | { - status: "none" | "invalidated"; - text: null; - requestId: number; - revision: number; -} | { - status: "pending"; - text: null; - requestId: number; - revision?: undefined; -} | { - status: "unavailable"; - text: null; - requestId?: undefined; - revision?: undefined; -}) & { - mode: SelectionMode; - ranges: readonly Readonly[]; - canExtend?: boolean; - message: string; - active: boolean; - pending: boolean; - copying: boolean; - copyError: string; -}; -export type TerminalStatusLevel = "info" | "ready" | "error"; -/** Native WebSocket close details, not an assertion that the terminal workload completed. */ -export interface TerminalCloseDetails { - /** RFC 6455 status reported by the browser, including 1006 for abnormal loss without a close frame. */ - readonly code: number; - /** Peer-provided close reason, or "". Treat as untrusted text. */ - readonly reason: string; - /** Whether the browser observed a clean WebSocket closing handshake, not workload success. */ - readonly wasClean: boolean; -} -export type TerminalRendererKind = "webgpu" | "webgl2"; -/** Auto prefers WebGPU and falls back to WebGL2 for capability/device acquisition failures. */ -export type TerminalRendererPreference = "auto" | TerminalRendererKind; -/** Metrics are initially empty; individual fields appear as initialization and presentation proceed. */ -export interface TerminalStats { - /** Active backend; absent until renderer initialization completes. */ - renderer?: TerminalRendererKind; - /** Why auto selected WebGL2 instead of WebGPU; absent for explicit selection or WebGPU. */ - rendererFallbackReason?: string; - revision?: number; - fullFrames?: number; - frames?: number; - presentations?: number; - changedCells?: number; - lastChangedCells?: number; - discardedFrames?: number; - imageCount?: number; - textureBytes?: number; - atlasGlyphs?: number; - atlasBytes?: number; - bytesReceived?: number; - imageUploadBytes?: number; - imagePayloadBytes?: number; - gpu?: "initializing" | "ready" | "error" | "stopped"; - connected?: boolean; - warnings?: readonly string[]; - fps?: number; - receivedKBps?: number; - workloadMBps?: number; - captureMs?: number; - rendererCpuMs?: number; - preparationCpuMs?: number; - workloadBytes?: number; - outputBatches?: number; - serverElapsedMs?: number; - quads?: number; - drawCalls?: number; - columns?: number; - rows?: number; - mouseTracking?: MouseTrackingMode; - peer?: TerminalPeer; - fontFamily?: string; - rasterScale?: number; - backingScale?: number; - backingWidth?: number; - backingHeight?: number; - atlasRebuilds?: number; - glyphUploadBytes?: number; - instanceBufferBytes?: number; -} -export interface InputModifiers { - ctrl: boolean; - alt: boolean; - shift: boolean; - meta: boolean; -} -export type PointerButton = "left" | "middle" | "right"; -/** Input intents contain no browser event. Returning a route controls browser cancellation. */ -export type TerminalInput = ({ - type: "key"; - key: string; - code: string; - repeat: boolean; -} & InputModifiers) | ({ - type: "pointer"; - button: PointerButton; - point: Readonly; -} & InputModifiers) | ({ - type: "wheel"; - deltaX: number; - deltaY: number; - deltaMode: number; - point: Readonly | null; -} & InputModifiers) | { - type: "paste" | "text"; - text: string; -}; -export type InputRouteValue = "continue" | "consume" | "application" | "browser"; -export type TerminalActionName = "copySelection" | "pasteClipboard" | "copyOrPaste" | "clearSelection" | "scrollToLive" | "scrollLines"; -export interface CopySelectionOptions { - clear?: boolean; -} -export interface TerminalInputContext { - readonly terminal: WebTerminalHandle; - readonly selection: TerminalSelection; - readonly viewport: TerminalViewport; - readonly buffer: TerminalBuffer | null; - readonly mouseCaptured: boolean; - readonly historical: boolean; - readonly readOnly: boolean; - readonly connected: boolean; - readonly peer: TerminalPeer; -} -/** Custom actions validate their own arguments and may complete asynchronously. */ -export type InputActionHandler = (context: TerminalInputContext, args: unknown, input: Readonly | undefined) => unknown; -export type InputDecision = { - route: InputRouteValue; - action?: never; - args?: never; -} | { - action: string | InputActionHandler; - args?: unknown; - route?: never; -}; -export type InputInterceptor = (input: Readonly, context: TerminalInputContext) => InputRouteValue | InputDecision | undefined; -export type InputBinding = { - id: string; - match: (input: Readonly, context: TerminalInputContext) => boolean; - when?: (context: TerminalInputContext, input: Readonly) => boolean; - remove?: false; -} & InputDecision; -export type InputBindingOverride = InputBinding | { - id: string; - remove: true; -}; -export interface InputPolicyOptions { - inputBindings?: readonly InputBindingOverride[]; - onInput?: InputInterceptor; - actions?: Readonly>; -} -/** Built-ins have typed arguments/results; custom names and callbacks own their argument validation. */ -export interface RunTerminalAction { - (action: "copySelection", args?: CopySelectionOptions, input?: TerminalInput): Promise; - (action: "pasteClipboard", args?: undefined, input?: TerminalInput): Promise; - (action: "copyOrPaste", args?: undefined, input?: TerminalInput): Promise; - (action: "clearSelection" | "scrollToLive", args?: undefined, input?: TerminalInput): Promise; - (action: "scrollLines", args: number, input?: TerminalInput): Promise; - (action: Name extends TerminalActionName ? never : Name, args?: unknown, input?: TerminalInput): Promise; - (action: InputActionHandler, args?: unknown, input?: TerminalInput): Promise; -} -export interface SelectionRectangle { - left: number; - top: number; - width: number; - height: number; -} -export interface SelectionUIState { - readonly selection: Readonly; - readonly viewport: Readonly; - readonly geometry: Readonly; - readonly canvasSize: Readonly; - readonly connected: boolean; - readonly readOnly: boolean; -} -/** A frozen snapshot; preventDefault() synchronously to replace the built-in Copy button. */ -export interface SelectionUIDetail extends SelectionUIState { - readonly overlay: HTMLDivElement; - readonly signal: AbortSignal; - readonly runAction: RunTerminalAction; - readonly rects: readonly Readonly[]; -} -export type SelectionUIEvent = CustomEvent; -/** Application-reported OSC 9;4 indicator, independent of shell execution. */ -export type TerminalProgressState = "none" | "normal" | "error" | "indeterminate" | "warning"; -/** Immutable current progress; a hidden or indeterminate indicator has no percentage. */ -export interface TerminalProgress { - readonly state: TerminalProgressState; - readonly percentage: number | null; -} -/** Last reported OSC 133 phase. Unknown does not mean idle; commandLine is not execution. */ -export type TerminalShellIntegrationPhase = "unknown" | "prompt" | "commandLine" | "executing" | "finished"; -/** Current shell phase and latest reported completion status, not command history. */ -export interface TerminalShellIntegration { - readonly phase: TerminalShellIntegrationPhase; - /** Null means no reported status, not success. Preserved across the next prompt/command. */ - readonly lastExitCode: number | null; -} -/** Last reported OSC 7 working directory, or all-null before any is reported. */ -export interface TerminalWorkingDirectory { - /** Raw URI as reported by the shell (typically `file://`), or null. */ - readonly uri: string | null; - /** Authority from the URI; "" for a local/unqualified authority. Null when uri is null. */ - readonly host: string | null; - /** Decoded filesystem path from the URI. Null when uri is null. */ - readonly path: string | null; -} -/** - * Latest OSC 133 marker, distinct from {@link TerminalShellIntegration}: it additionally carries - * any raw trailing `key=value` parameters (e.g. a `cmdline_url` extension on marker C). This is - * the single most-recent marker only — the server does not transport a mark history or event - * log over this wire; consumers that want their own history should accumulate distinct values - * from {@link WebTerminalOptions.onCommandMarkChange} themselves. - */ -export interface TerminalCommandMark { - readonly phase: TerminalShellIntegrationPhase; - readonly exitCode: number | null; - /** Verbatim `key=value[;key=value...]` trailing the marker, or null when none was present. */ - readonly rawParameters: string | null; -} -export interface WebTerminalOptions extends InputPolicyOptions { - url: string | URL; - /** Optional module-worker entry, resolved against the page URL. Defaults to the bundled worker. */ - workerUrl?: string | URL; - /** Optional isolated regex worker entry, resolved against the page URL. */ - linkDetectionWorkerUrl?: string | URL; - /** Per-view link interaction. Detection is opt-in; omitted preserves legacy OSC 8 navigation. */ - links?: false | TerminalLinkOptions; - /** Detection failures are local to this feature and also reported through onStatus. */ - onLinkDetectionError?: (error: TerminalLinkDetectionError) => void; - signal?: AbortSignal; - scale?: number | "auto"; - /** Mount-time backend selection. Defaults to auto; explicit modes never fall back. */ - renderer?: TerminalRendererPreference; - font?: TerminalFont; - sizing?: TerminalSizing; - label?: string; - /** Initial per-view input policy. Change it later with setReadOnly; not a server authorization boundary. */ - readOnly?: boolean; - onStatus?: (message: string, level: TerminalStatusLevel) => void; - /** - * Receives the native WebSocket close details once, including connection failures and closes - * before the first frame. The view is disconnected before this callback; a pending mount - * rejects after notification. No callback is synthesized for abort, disposal, initialization - * failure, or mount timeout, and none runs after disposal. This client never reconnects - * automatically. Interpret application close codes in the host; even 1000 is not proof of - * workload completion. Callback exceptions reach the host and are not retried. - */ - onClose?: (details: TerminalCloseDetails) => void; - onGeometry?: (geometry: TerminalGeometry) => void; - onSizingChange?: (sizing: TerminalSizingState) => void; - onRoleChange?: (peer: TerminalPeer) => void; - /** - * Receives the first authoritative presented title (including "") before mount resolves, - * then distinct presented changes. The title getter is updated first. Titles are untrusted - * text; render with textContent, not HTML. No notifications after disposal. - */ - onTitleChange?: (title: string) => void; - /** - * Receives the first authoritative presented progress before mount resolves, then distinct - * presented changes. Both activity getters update before either callback. Intermediate - * states may coalesce; this is not a callback for every OSC sequence. None hides the indicator. - * No notifications after disposal; connection loss does not manufacture a progress clear. - */ - onProgressChange?: (progress: TerminalProgress) => void; - /** - * Receives the first authoritative presented shell state before mount resolves, then distinct - * presented changes. This is not a lossless command-start/finish stream: entire commands may - * occur between frames. Replays provide current state, never synthetic command executions. - */ - onShellIntegrationChange?: (shellIntegration: TerminalShellIntegration) => void; - /** - * Receives the first authoritative presented working directory before mount resolves, then - * distinct presented changes. All-null means none reported yet; a malformed or non-`file` OSC 7 - * report does not change presented state. No notifications after disposal. - */ - onWorkingDirectoryChange?: (workingDirectory: TerminalWorkingDirectory) => void; - /** - * Receives the first authoritative presented command mark before mount resolves (null if none - * yet reported), then distinct presented changes. Only the latest marker is transmitted, not a - * history; entire commands may occur between frames. No notifications after disposal. - */ - onCommandMarkChange?: (commandMark: TerminalCommandMark | null) => void; - onStats?: (stats: TerminalStats, text: string | undefined) => void; - onViewportChange?: (viewport: TerminalViewport) => void; - onSelectionChange?: (selection: TerminalSelection) => void; - onInputError?: (error: Error) => void; - /** Must return undefined synchronously; async handlers cannot claim default UI ownership. */ - onSelectionUI?: (event: SelectionUIEvent) => undefined; -} -/** Owns only the appended element and browser connection, not the server terminal. */ -export interface WebTerminalHandle { - readonly element: HTMLDivElement; - readonly geometry: TerminalGeometry; - readonly peer: TerminalPeer; - readonly connected: boolean; - /** Whether this view blocks application input, resize, and primary takeover. */ - readonly readOnly: boolean; - /** Current presented workload title, or "" when unset/cleared. Retained on disconnect/dispose. */ - readonly title: string; - /** Current presented progress, initially none. Retained on disconnect/dispose; check connected. */ - readonly progress: TerminalProgress; - /** Current presented shell state, initially unknown. Retained on disconnect/dispose. */ - readonly shellIntegration: TerminalShellIntegration; - /** Current presented working directory, all-null initially. Retained on disconnect/dispose. */ - readonly workingDirectory: TerminalWorkingDirectory; - /** Latest presented command mark, or null if none reported yet. Retained on disconnect/dispose. */ - readonly commandMark: TerminalCommandMark | null; - readonly stats: TerminalStats; - readonly screenText: string; - readonly sizing: TerminalSizingState; - readonly inputBindings: InputBinding[]; - readonly inputContext: TerminalInputContext; - readonly viewport: TerminalViewport; - readonly selection: TerminalSelection; - runAction: RunTerminalAction; - scrollLines(delta: number): void; - scrollToLive(): void; - clearSelection(): void; - refreshSelectionUI(): void; - copySelection(options?: CopySelectionOptions): Promise; - paste(text: string): void; - pasteClipboard(): Promise; - /** - * Changes this view's input policy without remounting or changing peer roles. - * Output, history, selection and copying remain available. Cancels active gestures, - * pending composition and clipboard paste; already dispatched commands cannot be recalled. - * Hosts must separately enforce permissions on their per-view Hwt1PresentationAdapter. - */ - setReadOnly(readOnly: boolean): void; - /** Atomically replaces link options, cancelling active link gestures and stale detections. */ - setLinks(options: false | TerminalLinkOptions): void; - focus(): void; - requestPrimary(): void; - resize(columns: number, rows: number): void; - setSizing(sizing: TerminalSizing): void; - resync(): void; - dispose(): void; -} -//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts.map deleted file mode 100644 index 9da8ebfb5aa..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js deleted file mode 100644 index 718fd38ae40..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js.map deleted file mode 100644 index cea0fe85acc..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { TerminalLinkOptions, TerminalLinkDetectionError } from \"./link-types.js\";\nexport type * from \"./link-types.js\";\n\n/** Logical terminal dimensions, confirmed by the producer rather than reflowed locally. */\nexport interface TerminalGrid { columns: number; rows: number }\nexport interface TerminalSize { width: number; height: number }\nexport interface TerminalPoint { x: number; y: number }\nexport type MouseTrackingMode = 0 | 9 | 1000 | 1002 | 1003;\nexport interface TerminalGeometry extends TerminalGrid {\n cellWidth: number;\n cellHeight: number;\n mouseTracking: MouseTrackingMode;\n}\nexport interface TerminalPeer {\n id: string | null;\n primaryId: string | null;\n isPrimary: boolean;\n}\n/** Font size is an integer from 8 to 32; fixed grids allow 20–300 columns and 10–100 rows. */\nexport type TerminalSizing =\n | { mode: \"auto\"; fontSize?: number }\n | { mode: \"fixed\"; fontSize?: number; columns: number; rows: number };\nexport type TerminalSizingState =\n | { mode: \"auto\"; fontSize: number }\n | { mode: \"fixed\"; fontSize: number; columns: number; rows: number };\nexport interface TerminalFontFace { url: string; weight?: string; style?: string }\n/** Without faces, a non-generic family must be installed locally in the worker's environment. */\nexport interface TerminalFont { family: string; faces?: readonly TerminalFontFace[] }\nexport type TerminalBuffer = \"main\" | \"alternate\";\nexport type SelectionMode = \"character\" | \"word\" | \"line\" | \"rectangle\";\nexport interface SelectionRange { row: number; startColumn: number; endColumn: number }\nexport type TerminalViewport = (\n | { available: true; generation: string; buffer: TerminalBuffer; totalRows: number;\n liveTop: number; top: number; requestId: number; rowIds: readonly string[]; revision: number }\n | { available: false; generation?: undefined; buffer?: undefined; totalRows?: undefined;\n liveTop?: undefined; top?: undefined; requestId?: undefined; rowIds?: readonly string[]; revision?: undefined }\n) & { following: boolean; pending: boolean; followTail: boolean; offset: number };\nexport type TerminalSelection = (\n | { status: \"valid\"; text: string; requestId: number; revision: number }\n | { status: \"none\" | \"invalidated\"; text: null; requestId: number; revision: number }\n | { status: \"pending\"; text: null; requestId: number; revision?: undefined }\n | { status: \"unavailable\"; text: null; requestId?: undefined; revision?: undefined }\n) & {\n mode: SelectionMode;\n ranges: readonly Readonly[];\n canExtend?: boolean;\n message: string;\n active: boolean;\n pending: boolean;\n copying: boolean;\n copyError: string;\n};\nexport type TerminalStatusLevel = \"info\" | \"ready\" | \"error\";\n/** Native WebSocket close details, not an assertion that the terminal workload completed. */\nexport interface TerminalCloseDetails {\n /** RFC 6455 status reported by the browser, including 1006 for abnormal loss without a close frame. */\n readonly code: number;\n /** Peer-provided close reason, or \"\". Treat as untrusted text. */\n readonly reason: string;\n /** Whether the browser observed a clean WebSocket closing handshake, not workload success. */\n readonly wasClean: boolean;\n}\nexport type TerminalRendererKind = \"webgpu\" | \"webgl2\";\n/** Auto prefers WebGPU and falls back to WebGL2 for capability/device acquisition failures. */\nexport type TerminalRendererPreference = \"auto\" | TerminalRendererKind;\n/** Metrics are initially empty; individual fields appear as initialization and presentation proceed. */\nexport interface TerminalStats {\n /** Active backend; absent until renderer initialization completes. */\n renderer?: TerminalRendererKind;\n /** Why auto selected WebGL2 instead of WebGPU; absent for explicit selection or WebGPU. */\n rendererFallbackReason?: string;\n revision?: number; fullFrames?: number; frames?: number; presentations?: number;\n changedCells?: number; lastChangedCells?: number; discardedFrames?: number;\n imageCount?: number; textureBytes?: number; atlasGlyphs?: number; atlasBytes?: number;\n bytesReceived?: number; imageUploadBytes?: number; imagePayloadBytes?: number;\n gpu?: \"initializing\" | \"ready\" | \"error\" | \"stopped\"; connected?: boolean; warnings?: readonly string[];\n fps?: number; receivedKBps?: number; workloadMBps?: number;\n captureMs?: number; rendererCpuMs?: number; preparationCpuMs?: number;\n workloadBytes?: number; outputBatches?: number; serverElapsedMs?: number;\n quads?: number; drawCalls?: number; columns?: number; rows?: number; mouseTracking?: MouseTrackingMode;\n peer?: TerminalPeer; fontFamily?: string; rasterScale?: number; backingScale?: number;\n backingWidth?: number; backingHeight?: number; atlasRebuilds?: number;\n glyphUploadBytes?: number; instanceBufferBytes?: number;\n}\nexport interface InputModifiers { ctrl: boolean; alt: boolean; shift: boolean; meta: boolean }\nexport type PointerButton = \"left\" | \"middle\" | \"right\";\n/** Input intents contain no browser event. Returning a route controls browser cancellation. */\nexport type TerminalInput =\n | ({ type: \"key\"; key: string; code: string; repeat: boolean } & InputModifiers)\n | ({ type: \"pointer\"; button: PointerButton; point: Readonly } & InputModifiers)\n | ({ type: \"wheel\"; deltaX: number; deltaY: number; deltaMode: number;\n point: Readonly | null } & InputModifiers)\n | { type: \"paste\" | \"text\"; text: string };\nexport type InputRouteValue = \"continue\" | \"consume\" | \"application\" | \"browser\";\nexport type TerminalActionName = \"copySelection\" | \"pasteClipboard\" | \"copyOrPaste\"\n | \"clearSelection\" | \"scrollToLive\" | \"scrollLines\";\nexport interface CopySelectionOptions { clear?: boolean }\nexport interface TerminalInputContext {\n readonly terminal: WebTerminalHandle;\n readonly selection: TerminalSelection;\n readonly viewport: TerminalViewport;\n readonly buffer: TerminalBuffer | null;\n readonly mouseCaptured: boolean;\n readonly historical: boolean;\n readonly readOnly: boolean;\n readonly connected: boolean;\n readonly peer: TerminalPeer;\n}\n/** Custom actions validate their own arguments and may complete asynchronously. */\nexport type InputActionHandler =\n (context: TerminalInputContext, args: unknown, input: Readonly | undefined) => unknown;\nexport type InputDecision =\n | { route: InputRouteValue; action?: never; args?: never }\n | { action: string | InputActionHandler; args?: unknown; route?: never };\nexport type InputInterceptor =\n (input: Readonly, context: TerminalInputContext) => InputRouteValue | InputDecision | undefined;\nexport type InputBinding = {\n id: string;\n match: (input: Readonly, context: TerminalInputContext) => boolean;\n when?: (context: TerminalInputContext, input: Readonly) => boolean;\n remove?: false;\n} & InputDecision;\nexport type InputBindingOverride = InputBinding | { id: string; remove: true };\nexport interface InputPolicyOptions {\n inputBindings?: readonly InputBindingOverride[];\n onInput?: InputInterceptor;\n actions?: Readonly>;\n}\n/** Built-ins have typed arguments/results; custom names and callbacks own their argument validation. */\nexport interface RunTerminalAction {\n (action: \"copySelection\", args?: CopySelectionOptions, input?: TerminalInput): Promise;\n (action: \"pasteClipboard\", args?: undefined, input?: TerminalInput): Promise;\n (action: \"copyOrPaste\", args?: undefined, input?: TerminalInput): Promise;\n (action: \"clearSelection\" | \"scrollToLive\", args?: undefined, input?: TerminalInput): Promise;\n (action: \"scrollLines\", args: number, input?: TerminalInput): Promise;\n (action: Name extends TerminalActionName ? never : Name,\n args?: unknown, input?: TerminalInput): Promise;\n (action: InputActionHandler, args?: unknown, input?: TerminalInput): Promise;\n}\nexport interface SelectionRectangle { left: number; top: number; width: number; height: number }\nexport interface SelectionUIState {\n readonly selection: Readonly;\n readonly viewport: Readonly;\n readonly geometry: Readonly;\n readonly canvasSize: Readonly;\n readonly connected: boolean;\n readonly readOnly: boolean;\n}\n/** A frozen snapshot; preventDefault() synchronously to replace the built-in Copy button. */\nexport interface SelectionUIDetail extends SelectionUIState {\n readonly overlay: HTMLDivElement;\n readonly signal: AbortSignal;\n readonly runAction: RunTerminalAction;\n readonly rects: readonly Readonly[];\n}\nexport type SelectionUIEvent = CustomEvent;\n/** Application-reported OSC 9;4 indicator, independent of shell execution. */\nexport type TerminalProgressState = \"none\" | \"normal\" | \"error\" | \"indeterminate\" | \"warning\";\n/** Immutable current progress; a hidden or indeterminate indicator has no percentage. */\nexport interface TerminalProgress {\n readonly state: TerminalProgressState;\n readonly percentage: number | null;\n}\n/** Last reported OSC 133 phase. Unknown does not mean idle; commandLine is not execution. */\nexport type TerminalShellIntegrationPhase = \"unknown\" | \"prompt\" | \"commandLine\" | \"executing\" | \"finished\";\n/** Current shell phase and latest reported completion status, not command history. */\nexport interface TerminalShellIntegration {\n readonly phase: TerminalShellIntegrationPhase;\n /** Null means no reported status, not success. Preserved across the next prompt/command. */\n readonly lastExitCode: number | null;\n}\n/** Last reported OSC 7 working directory, or all-null before any is reported. */\nexport interface TerminalWorkingDirectory {\n /** Raw URI as reported by the shell (typically `file://`), or null. */\n readonly uri: string | null;\n /** Authority from the URI; \"\" for a local/unqualified authority. Null when uri is null. */\n readonly host: string | null;\n /** Decoded filesystem path from the URI. Null when uri is null. */\n readonly path: string | null;\n}\n/**\n * Latest OSC 133 marker, distinct from {@link TerminalShellIntegration}: it additionally carries\n * any raw trailing `key=value` parameters (e.g. a `cmdline_url` extension on marker C). This is\n * the single most-recent marker only — the server does not transport a mark history or event\n * log over this wire; consumers that want their own history should accumulate distinct values\n * from {@link WebTerminalOptions.onCommandMarkChange} themselves.\n */\nexport interface TerminalCommandMark {\n readonly phase: TerminalShellIntegrationPhase;\n readonly exitCode: number | null;\n /** Verbatim `key=value[;key=value...]` trailing the marker, or null when none was present. */\n readonly rawParameters: string | null;\n}\nexport interface WebTerminalOptions extends InputPolicyOptions {\n url: string | URL;\n /** Optional module-worker entry, resolved against the page URL. Defaults to the bundled worker. */\n workerUrl?: string | URL;\n /** Optional isolated regex worker entry, resolved against the page URL. */\n linkDetectionWorkerUrl?: string | URL;\n /** Per-view link interaction. Detection is opt-in; omitted preserves legacy OSC 8 navigation. */\n links?: false | TerminalLinkOptions;\n /** Detection failures are local to this feature and also reported through onStatus. */\n onLinkDetectionError?: (error: TerminalLinkDetectionError) => void;\n signal?: AbortSignal;\n scale?: number | \"auto\";\n /** Mount-time backend selection. Defaults to auto; explicit modes never fall back. */\n renderer?: TerminalRendererPreference;\n font?: TerminalFont;\n sizing?: TerminalSizing;\n label?: string;\n /** Initial per-view input policy. Change it later with setReadOnly; not a server authorization boundary. */\n readOnly?: boolean;\n onStatus?: (message: string, level: TerminalStatusLevel) => void;\n /**\n * Receives the native WebSocket close details once, including connection failures and closes\n * before the first frame. The view is disconnected before this callback; a pending mount\n * rejects after notification. No callback is synthesized for abort, disposal, initialization\n * failure, or mount timeout, and none runs after disposal. This client never reconnects\n * automatically. Interpret application close codes in the host; even 1000 is not proof of\n * workload completion. Callback exceptions reach the host and are not retried.\n */\n onClose?: (details: TerminalCloseDetails) => void;\n onGeometry?: (geometry: TerminalGeometry) => void;\n onSizingChange?: (sizing: TerminalSizingState) => void;\n onRoleChange?: (peer: TerminalPeer) => void;\n /**\n * Receives the first authoritative presented title (including \"\") before mount resolves,\n * then distinct presented changes. The title getter is updated first. Titles are untrusted\n * text; render with textContent, not HTML. No notifications after disposal.\n */\n onTitleChange?: (title: string) => void;\n /**\n * Receives the first authoritative presented progress before mount resolves, then distinct\n * presented changes. Both activity getters update before either callback. Intermediate\n * states may coalesce; this is not a callback for every OSC sequence. None hides the indicator.\n * No notifications after disposal; connection loss does not manufacture a progress clear.\n */\n onProgressChange?: (progress: TerminalProgress) => void;\n /**\n * Receives the first authoritative presented shell state before mount resolves, then distinct\n * presented changes. This is not a lossless command-start/finish stream: entire commands may\n * occur between frames. Replays provide current state, never synthetic command executions.\n */\n onShellIntegrationChange?: (shellIntegration: TerminalShellIntegration) => void;\n /**\n * Receives the first authoritative presented working directory before mount resolves, then\n * distinct presented changes. All-null means none reported yet; a malformed or non-`file` OSC 7\n * report does not change presented state. No notifications after disposal.\n */\n onWorkingDirectoryChange?: (workingDirectory: TerminalWorkingDirectory) => void;\n /**\n * Receives the first authoritative presented command mark before mount resolves (null if none\n * yet reported), then distinct presented changes. Only the latest marker is transmitted, not a\n * history; entire commands may occur between frames. No notifications after disposal.\n */\n onCommandMarkChange?: (commandMark: TerminalCommandMark | null) => void;\n onStats?: (stats: TerminalStats, text: string | undefined) => void;\n onViewportChange?: (viewport: TerminalViewport) => void;\n onSelectionChange?: (selection: TerminalSelection) => void;\n onInputError?: (error: Error) => void;\n /** Must return undefined synchronously; async handlers cannot claim default UI ownership. */\n onSelectionUI?: (event: SelectionUIEvent) => undefined;\n}\n/** Owns only the appended element and browser connection, not the server terminal. */\nexport interface WebTerminalHandle {\n readonly element: HTMLDivElement;\n readonly geometry: TerminalGeometry;\n readonly peer: TerminalPeer;\n readonly connected: boolean;\n /** Whether this view blocks application input, resize, and primary takeover. */\n readonly readOnly: boolean;\n /** Current presented workload title, or \"\" when unset/cleared. Retained on disconnect/dispose. */\n readonly title: string;\n /** Current presented progress, initially none. Retained on disconnect/dispose; check connected. */\n readonly progress: TerminalProgress;\n /** Current presented shell state, initially unknown. Retained on disconnect/dispose. */\n readonly shellIntegration: TerminalShellIntegration;\n /** Current presented working directory, all-null initially. Retained on disconnect/dispose. */\n readonly workingDirectory: TerminalWorkingDirectory;\n /** Latest presented command mark, or null if none reported yet. Retained on disconnect/dispose. */\n readonly commandMark: TerminalCommandMark | null;\n readonly stats: TerminalStats;\n readonly screenText: string;\n readonly sizing: TerminalSizingState;\n readonly inputBindings: InputBinding[];\n readonly inputContext: TerminalInputContext;\n readonly viewport: TerminalViewport;\n readonly selection: TerminalSelection;\n runAction: RunTerminalAction;\n scrollLines(delta: number): void;\n scrollToLive(): void;\n clearSelection(): void;\n refreshSelectionUI(): void;\n copySelection(options?: CopySelectionOptions): Promise;\n paste(text: string): void;\n pasteClipboard(): Promise;\n /**\n * Changes this view's input policy without remounting or changing peer roles.\n * Output, history, selection and copying remain available. Cancels active gestures,\n * pending composition and clipboard paste; already dispatched commands cannot be recalled.\n * Hosts must separately enforce permissions on their per-view Hwt1PresentationAdapter.\n */\n setReadOnly(readOnly: boolean): void;\n /** Atomically replaces link options, cancelling active link gestures and stale detections. */\n setLinks(options: false | TerminalLinkOptions): void;\n focus(): void;\n requestPrimary(): void;\n resize(columns: number, rows: number): void;\n setSizing(sizing: TerminalSizing): void;\n resync(): void;\n dispose(): void;\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts deleted file mode 100644 index 631c85a3212..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare function isRecord(value: unknown): value is Record; -export declare function errorMessage(error: unknown): string; -//# sourceMappingURL=validation.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts.map deleted file mode 100644 index 93ca51b791c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js deleted file mode 100644 index f27d691f498..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js +++ /dev/null @@ -1,7 +0,0 @@ -export function isRecord(value) { - return value !== null && typeof value === "object" && !Array.isArray(value); -} -export function errorMessage(error) { - return isRecord(error) && typeof error.message === "string" ? error.message : String(error); -} -//# sourceMappingURL=validation.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js.map deleted file mode 100644 index a5b15277658..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/validation.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9F,CAAC","sourcesContent":["export function isRecord(value: unknown): value is Record {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport function errorMessage(error: unknown): string {\n return isRecord(error) && typeof error.message === \"string\" ? error.message : String(error);\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts deleted file mode 100644 index bffbc1b6ffb..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -import type { CopySelectionOptions, InputActionHandler, InputBinding, TerminalActionName, TerminalGeometry, TerminalInput, TerminalInputContext, TerminalPeer, TerminalSelection, TerminalSizing, TerminalSizingState, TerminalStats, TerminalViewport, TerminalProgress, TerminalShellIntegration, TerminalWorkingDirectory, TerminalCommandMark, TerminalLinkOptions, WebTerminalHandle, WebTerminalOptions } from "./types.js"; -export { InputRoute, TerminalAction, defaultInputBindings } from "./input-policy.js"; -/** - * First-party HWT1 client. Owns only the element it appends, not the caller's - * container or the server terminal. The HWT1 wire and this spike API evolve together. - */ -export declare class WebTerminal implements WebTerminalHandle { - #private; - readonly element: HTMLDivElement; - /** Resolves after a connected terminal frame is presented. Supply signal to cancel mounting. */ - static mount(container: HTMLElement, options: WebTerminalOptions): Promise; - private constructor(); - get geometry(): TerminalGeometry; - get peer(): TerminalPeer; - get connected(): boolean; - get readOnly(): boolean; - /** Current presented workload title; retained on disconnect/dispose. Treat as untrusted text. */ - get title(): string; - get progress(): TerminalProgress; - get shellIntegration(): TerminalShellIntegration; - get workingDirectory(): TerminalWorkingDirectory; - get commandMark(): TerminalCommandMark | null; - get stats(): TerminalStats; - get screenText(): string; - get sizing(): TerminalSizingState; - get inputBindings(): InputBinding[]; - get viewport(): TerminalViewport; - get selection(): TerminalSelection; - scrollLines(delta: number): void; - scrollToLive(): void; - clearSelection(): void; - /** Re-notifies selection UI hosts after an external styling/policy change. */ - refreshSelectionUI(): void; - copySelection({ clear }?: CopySelectionOptions): Promise; - get inputContext(): TerminalInputContext; - /** Named actions are shared by controls and bindings. Custom callbacks receive context, args, and input. */ - runAction(action: "copySelection", args?: CopySelectionOptions, input?: TerminalInput): Promise; - runAction(action: "pasteClipboard", args?: undefined, input?: TerminalInput): Promise; - runAction(action: "copyOrPaste", args?: undefined, input?: TerminalInput): Promise; - runAction(action: "clearSelection" | "scrollToLive", args?: undefined, input?: TerminalInput): Promise; - runAction(action: "scrollLines", args: number, input?: TerminalInput): Promise; - runAction(action: Name extends TerminalActionName ? never : Name, args?: unknown, input?: TerminalInput): Promise; - runAction(action: InputActionHandler, args?: unknown, input?: TerminalInput): Promise; - /** Sends an explicit paste through the producer's mode-aware input encoder. */ - paste(text: string): void; - pasteClipboard(): Promise; - /** Changes per-view input policy without reconnecting; server authorization remains host-owned. */ - setReadOnly(readOnly: boolean): void; - setLinks(options: false | TerminalLinkOptions): void; - focus(): void; - /** Request HMP1 primary explicitly; peer notifications confirm the result. */ - requestPrimary(): void; - /** Request a grid; never reflow locally before the authoritative response. */ - resize(columns: number, rows: number): void; - /** Change the primary's sizing policy; applied grid dimensions still come from the server. */ - setSizing(sizing: TerminalSizing): void; - resync(): void; - /** Detach this view. The server-side shared terminal is not terminated. */ - dispose(): void; -} -//# sourceMappingURL=web-terminal.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts.map deleted file mode 100644 index efd0b1e0b5f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"web-terminal.d.ts","sourceRoot":"","sources":["../src/web-terminal.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAiB,YAAY,EACjF,kBAAkB,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,YAAY,EAC3D,iBAAiB,EAAE,cAAc,EAAE,mBAAmB,EAAE,aAAa,EAAE,gBAAgB,EACnH,gBAAgB,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,mBAAmB,EACzF,mBAAmB,EACnB,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAG5D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAQrF;;;GAGG;AACH,qBAAa,WAAY,YAAW,iBAAiB;;IACnD,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IA+DjC,gGAAgG;WACnF,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqB7F,OAAO;IA2CP,IAAI,QAAQ,IAAI,gBAAgB,CAAkC;IAClE,IAAI,IAAI,IAAI,YAAY,CAA8B;IACtD,IAAI,SAAS,YAA8B;IAC3C,IAAI,QAAQ,IAAI,OAAO,CAA2B;IAClD,iGAAiG;IACjG,IAAI,KAAK,IAAI,MAAM,CAAwB;IAC3C,IAAI,QAAQ,IAAI,gBAAgB,CAAkC;IAClE,IAAI,gBAAgB,IAAI,wBAAwB,CAA0C;IAC1F,IAAI,gBAAgB,IAAI,wBAAwB,CAA0C;IAC1F,IAAI,WAAW,IAAI,mBAAmB,GAAG,IAAI,CAAgE;IAC7G,IAAI,KAAK,IAAI,aAAa,CAA+B;IACzD,IAAI,UAAU,WAA+B;IAC7C,IAAI,MAAM,IAAI,mBAAmB,CAAgC;IACjE,IAAI,aAAa,IAAI,YAAY,EAAE,CAAkC;IACrE,IAAI,QAAQ,IAAI,gBAAgB,CAI/B;IACD,IAAI,SAAS,IAAI,iBAAiB,CAIjC;IA8UD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAChC,YAAY;IACZ,cAAc;IAEd,8EAA8E;IAC9E,kBAAkB;IAyBZ,aAAa,CAAC,EAAE,KAAa,EAAE,GAAE,oBAAyB,GAAG,OAAO,CAAC,MAAM,CAAC;IAsClF,IAAI,YAAY,IAAI,oBAAoB,CAOvC;IAmBD,4GAA4G;IAC5G,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IACvG,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7F,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IACtG,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5G,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IACpF,SAAS,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,IAAI,SAAS,kBAAkB,GAAG,KAAK,GAAG,IAAI,EACnF,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAC1D,SAAS,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAoC9F,+EAA+E;IAC/E,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMnB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAgBvC,mGAAmG;IACnG,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAwBpC,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,mBAAmB,GAAG,IAAI;IA8LpD,KAAK;IAML,8EAA8E;IAC9E,cAAc;IAad,8EAA8E;IAC9E,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ3C,8FAA8F;IAC9F,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAcvC,MAAM;IA2BN,2EAA2E;IAC3E,OAAO;CAeR"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js deleted file mode 100644 index e8c5f3f69ce..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js +++ /dev/null @@ -1,1052 +0,0 @@ -import { captureMouse } from "./mouse-input.js"; -import { normalizeFont } from "./terminal-font.js"; -import { normalizeRenderer } from "./renderer-options.js"; -import { dimensions, normalizeSizing, requestedGrid, fittedScale } from "./terminal-sizing.js"; -import { HistoryState } from "./history-state.js"; -import { terminalThemeCss } from "./terminal-theme.js"; -import { InputPolicy, InputRoute, TerminalAction, inputModifiers } from "./input-policy.js"; -import { assertCommandSize } from "./protocol.js"; -import { SelectionUI } from "./selection-ui.js"; -import { Hyperlinks } from "./hyperlinks.js"; -import { LinkDetection } from "./link-detection.js"; -import { normalizeLinks } from "./link-options.js"; -import { errorMessage, isRecord } from "./validation.js"; -export { InputRoute, TerminalAction, defaultInputBindings } from "./input-policy.js"; -function requiredElement(root, selector, type) { - const element = root.querySelector(selector); - if (!(element instanceof type)) - throw new Error(`Missing terminal element: ${selector}`); - return element; -} -/** - * First-party HWT1 client. Owns only the element it appends, not the caller's - * container or the server terminal. The HWT1 wire and this spike API evolve together. - */ -export class WebTerminal { - element; - #options; - #renderer; - // DOM and worker fields are initialized by mount before a handle is returned. - #worker; - #surface; - #canvas; - #input; - #mouse; - #observer; - #listeners = new AbortController(); - #size = { width: 0, height: 0 }; - #sizing = normalizeSizing(); - #geometry = { columns: 80, rows: 24, cellWidth: 10, cellHeight: 20, mouseTracking: 0 }; - #peer = { id: null, primaryId: null, isPrimary: false }; - #connected = false; - #closed = false; - #readOnly; - #disposed = false; - #hasGeometry = false; - #resizeTimer; - #lastRequested; - #compositionTimer; - #resetComposition; - #ready = Promise.withResolvers(); - #readyTimer; - #stats = {}; - #screenText = ""; - #title = ""; - #hasTitle = false; - #progress = { state: "none", percentage: null }; - #shellIntegration = { phase: "unknown", lastExitCode: null }; - #workingDirectory = { uri: null, host: null, path: null }; - #commandMark = null; - #hasActivity = false; - #history; - #highlights; - #inspection; - #inspectionError = ""; - #copySerial = 0; - #copying = false; - #policy; - #actions; - #clipboardAction = false; - #inputSerial = 0; - #selectionUI; - #selectionOverlay; - #selectionUIError = ""; - #canvasSize = { width: 0, height: 0 }; - #hyperlinks = new Hyperlinks(); - #links; - #linkDetector; - #linkGeneration = 1; - #linkRevision = 0; - #linkSerial = 0; - #linkSnapshot; - #osc8Rows = new Map(); - #detectedLinks = []; - #presentedLinks = []; - #detectedRows = new Map(); - #hoveredLinkId; - #pendingLinks; - /** Resolves after a connected terminal frame is presented. Supply signal to cancel mounting. */ - static async mount(container, options) { - if (!(container instanceof HTMLElement)) - throw new TypeError("A terminal container HTMLElement is required"); - if (!options?.url) - throw new TypeError("A terminal WebSocket URL is required"); - if (options.signal?.aborted) - throw options.signal.reason; - if (normalizeRenderer(options.renderer) === "webgpu" && (!window.isSecureContext || !navigator.gpu)) { - throw new Error("The requested WebGPU renderer requires WebGPU over HTTPS or localhost"); - } - if (!window.Worker || !window.ResizeObserver || !window.OffscreenCanvas || - !HTMLCanvasElement.prototype.transferControlToOffscreen) { - throw new Error("WebTerminal requires module workers, ResizeObserver, and a transferable OffscreenCanvas"); - } - const terminal = new WebTerminal(options); - try { - await Promise.all([terminal.#ready.promise, Promise.resolve().then(() => terminal.#start(container))]); - return terminal; - } - catch (error) { - terminal.dispose(); - throw error; - } - } - constructor(options) { - this.#options = options; - if (options.readOnly !== undefined && typeof options.readOnly !== "boolean") - throw new TypeError("readOnly must be a boolean"); - this.#readOnly = options.readOnly ?? false; - this.#renderer = normalizeRenderer(options.renderer); - if (options.workerUrl !== undefined && !(options.workerUrl instanceof URL) && - (typeof options.workerUrl !== "string" || !options.workerUrl.trim())) - throw new TypeError("workerUrl must be a nonempty URL string or URL"); - if (options.linkDetectionWorkerUrl !== undefined && !(options.linkDetectionWorkerUrl instanceof URL) && - (typeof options.linkDetectionWorkerUrl !== "string" || !options.linkDetectionWorkerUrl.trim())) - throw new TypeError("linkDetectionWorkerUrl must be a nonempty URL string or URL"); - if (options.onLinkDetectionError !== undefined && typeof options.onLinkDetectionError !== "function") - throw new TypeError("onLinkDetectionError must be a function"); - if (options.onSelectionUI !== undefined && (typeof options.onSelectionUI !== "function" || - options.onSelectionUI.constructor.name === "AsyncFunction")) - throw new TypeError("onSelectionUI must be a synchronous event handler"); - this.#policy = new InputPolicy(options); - this.#actions = new Map(Object.entries(options.actions ?? {})); - this.#links = normalizeLinks(options.links, new Set(this.#actions.keys())); - this.#linkDetector = new LinkDetection({ - workerUrl: options.linkDetectionWorkerUrl === undefined ? undefined : new URL(options.linkDetectionWorkerUrl, location.href), - actions: new Set(this.#actions.keys()), - onChange: (revision, links) => { - if (this.#disposed || !this.#connected || revision !== this.#linkRevision) - return; - this.#detectedLinks = links; - this.#replacePresentedLinks([]); - this.#requestLinkDecorations(); - }, - onError: error => { - if (this.#disposed) - return; - try { - this.#options.onStatus?.(`Link detection: ${error.message}`, "error"); - } - finally { - this.#options.onLinkDetectionError?.(error); - } - }, - }); - this.#linkDetector.configure(this.#links ? this.#links.detection : false); - this.#history = new HistoryState(command => this.#send(command), () => this.#inspectionChanged()); - this.element = document.createElement("div"); - this.element.className = "hex1b-terminal"; - this.element.tabIndex = -1; - this.element.style.cssText = "width:100%;height:100%;min-width:0;min-height:0;contain:strict"; - } - get geometry() { return { ...this.#geometry }; } - get peer() { return { ...this.#peer }; } - get connected() { return this.#connected; } - get readOnly() { return this.#readOnly; } - /** Current presented workload title; retained on disconnect/dispose. Treat as untrusted text. */ - get title() { return this.#title; } - get progress() { return { ...this.#progress }; } - get shellIntegration() { return { ...this.#shellIntegration }; } - get workingDirectory() { return { ...this.#workingDirectory }; } - get commandMark() { return this.#commandMark ? { ...this.#commandMark } : null; } - get stats() { return { ...this.#stats }; } - get screenText() { return this.#screenText; } - get sizing() { return { ...this.#sizing }; } - get inputBindings() { return this.#policy.bindings; } - get viewport() { - const viewport = this.#history.viewport; - return { ...viewport, followTail: viewport.following, - offset: viewport.available ? viewport.liveTop - viewport.top : 0 }; - } - get selection() { - const selection = this.#history.selection; - return { ...selection, active: selection.status === "valid", pending: selection.status === "pending", - copying: this.#copying, copyError: this.#inspectionError }; - } - #start(container) { - if (this.#options.signal?.aborted) - throw this.#options.signal.reason; - const url = new URL(this.#options.url, location.href); - if (url.protocol === "https:") - url.protocol = "wss:"; - if (url.protocol === "http:") - url.protocol = "ws:"; - if (!["ws:", "wss:"].includes(url.protocol)) - throw new TypeError("A ws: or wss: URL is required"); - const scale = this.#options.scale === undefined || this.#options.scale === "auto" - ? Math.min(3, Math.max(0.5, window.devicePixelRatio || 1)) : this.#options.scale; - if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) - throw new RangeError("Backing scale must be 0.5-3 or 'auto'"); - const font = normalizeFont(this.#options.font, location.href); - this.#sizing = normalizeSizing(this.#options.sizing); - const shadow = this.element.attachShadow({ mode: "open" }); - shadow.innerHTML = ` - -
- - - - -
- - - -
`; - this.#surface = requiredElement(shadow, ".surface", HTMLDivElement); - this.#canvas = requiredElement(shadow, "canvas", HTMLCanvasElement); - this.#input = requiredElement(shadow, "textarea", HTMLTextAreaElement); - this.#highlights = requiredElement(shadow, ".highlights", HTMLDivElement); - this.#inspection = requiredElement(shadow, ".inspection", HTMLDivElement); - this.#selectionOverlay = document.createElement("div"); - this.#selectionOverlay.slot = "selection-ui"; - this.#selectionOverlay.className = "hex1b-selection-overlay"; - this.#selectionOverlay.style.cssText = "position:relative;width:100%;height:100%;pointer-events:none"; - this.element.append(this.#selectionOverlay); - this.#selectionUI = new SelectionUI({ - element: this.element, overlay: this.#selectionOverlay, - button: requiredElement(this.#inspection, ".copy-selection", HTMLButtonElement), signal: this.#listeners.signal, - getState: () => ({ selection: this.selection, viewport: this.viewport, geometry: this.geometry, - canvasSize: this.#canvasSize, connected: this.#connected, readOnly: this.#readOnly }), - runAction: this.runAction.bind(this), - onSelectionUI: this.#options.onSelectionUI, - reportError: error => { - this.#selectionUIError = error ? `Selection UI failed: ${errorMessage(error)}` : ""; - this.#selectionOverlay.hidden = !!error; - this.#renderInspectionStatus(); - if (error) - this.#options.onStatus?.(this.#selectionUIError, "error"); - } - }); - this.#selectionUI.refresh(); - this.#input.setAttribute("aria-label", this.#options.label || "Terminal input. Click outside to use page controls."); - this.#input.disabled = true; - container.append(this.element); - const inspect = (operation) => { - try { - this.#inspectionError = ""; - operation(); - } - catch (error) { - this.#inspectionError = errorMessage(error); - this.#inspectionChanged(); - } - }; - this.#mouse = captureMouse(this.#canvas, command => this.#inputCommand(command), () => this.focus(), { - state: () => ({ historical: !this.viewport.following || this.viewport.pending, readOnly: this.#readOnly, - selection: this.selection }), - begin: (point, selection) => inspect(() => this.#history.begin(point, selection)), - extend: point => inspect(() => this.#history.extend(point)), - scroll: (delta, endpoint) => inspect(() => this.#history.scroll(delta, endpoint)), - end: cancelled => this.#history.endGesture(cancelled), - resolve: input => this.#resolveInput(input), - execute: (decision, input) => this.#executeInputAction(decision, input), - hyperlink: point => this.#linkAt(point), - hoverHyperlink: link => { - this.#hoveredLinkId = link?.id; - if (this.#links && this.#links.detection && this.#links.detection.decoration === "hover") - this.#requestLinkDecorations(); - }, - openHyperlink: (link, input) => this.#activateLink(link, input), - }); - this.#bindKeyboard(); - requiredElement(this.#inspection, ".return-live", HTMLButtonElement).addEventListener("click", () => { - this.runAction(TerminalAction.ScrollToLive).catch(error => this.#actionFailed(error)); - }, { signal: this.#listeners.signal }); - requiredElement(this.#inspection, ".copy-selection", HTMLButtonElement).addEventListener("click", () => { - this.runAction(TerminalAction.CopySelection).catch(error => this.#actionFailed(error)); - }, { signal: this.#listeners.signal }); - requiredElement(shadow, ".viewport", HTMLDivElement).addEventListener("pointerdown", event => { - if (event.composedPath().includes(this.#selectionOverlay)) - return; - if ((event.target instanceof Element && event.target.closest("button")) || - event.target === this.#canvas || event.target === this.#input) - return; - event.preventDefault(); - this.focus(); - }, { signal: this.#listeners.signal }); - this.#observer = new ResizeObserver(entries => { - const { width, height } = entries[0].contentRect; - const changed = width !== this.#size.width || height !== this.#size.height; - this.#size = { width, height }; - this.#fit(); - if (changed) - this.#queueResize(); - }); - // Observe the caller's outer box, never the fitted inner surface. - this.#observer.observe(container); - window.addEventListener("pagehide", () => this.dispose(), { signal: this.#listeners.signal }); - this.#options.signal?.addEventListener("abort", () => this.dispose(), { once: true, signal: this.#listeners.signal }); - this.#readyTimer = setTimeout(() => this.#fail(new Error("Timed out waiting for the terminal's first frame")), 30000); - this.#worker = this.#options.workerUrl === undefined - ? new Worker(new URL("./terminal-worker.js", import.meta.url), { type: "module", name: "Hex1b WebTerminal" }) - : new Worker(new URL(this.#options.workerUrl, location.href), { type: "module", name: "Hex1b WebTerminal" }); - this.#worker.addEventListener("message", (event) => this.#message(event.data)); - this.#worker.addEventListener("error", event => { - event.preventDefault(); - this.#fail(new Error(event.message || "Terminal worker failed")); - }); - this.#worker.addEventListener("messageerror", () => this.#fail(new Error("Terminal worker message could not be decoded"))); - const canvas = this.#canvas.transferControlToOffscreen(); - this.#post({ type: "init", canvas, url: url.href, scale, font, - renderer: this.#renderer }, [canvas]); - this.#postLinkConfiguration(); - } - #message(message) { - if (this.#disposed) - return; - if (message.type === "connected") { - this.#connected = true; - this.#input.disabled = !this.#canInput(); - } - else if (message.type === "closed") { - if (this.#closed) - return; - this.#closed = true; - clearTimeout(this.#readyTimer); - try { - this.#disconnect(); - if (!this.#disposed) - this.#options.onClose?.(Object.freeze({ ...message.details })); - } - finally { - this.#ready.reject(new Error(`Terminal WebSocket closed (${message.details.code}${message.details.reason ? `: ${message.details.reason}` : ""}) before mounting completed`)); - } - } - else if (message.type === "status") { - if (message.level === "error") { - this.#disconnect(); - this.#ready.reject(new Error(message.message)); - } - this.#options.onStatus?.(message.message, message.level); - } - else if (message.type === "geometry") { - this.#linkRevision = message.revision; - this.#osc8Rows.clear(); - for (const range of message.hyperlinks) { - const row = this.#osc8Rows.get(range.row) ?? []; - row.push(range); - this.#osc8Rows.set(range.row, row); - } - this.#replacePresentedLinks([]); - this.#pendingLinks = undefined; - const first = !this.#hasGeometry; - const geometryChanged = first || ["columns", "rows", "cellWidth", "cellHeight", "mouseTracking"] - .some(field => this.#geometry[field] !== message[field]); - this.#geometry = { - columns: message.columns, rows: message.rows, - cellWidth: message.cellWidth, cellHeight: message.cellHeight, mouseTracking: message.mouseTracking - }; - this.#hasGeometry = true; - const oldPeer = this.#peer; - this.#peer = message.peer; - this.#input.disabled = !this.#canInput(); - if (this.#canInput() && document.activeElement === this.element && !this.element.shadowRoot?.activeElement) - this.focus(); - this.#hyperlinks.update(message.hyperlinks); - if (geometryChanged || oldPeer.isPrimary !== this.#peer.isPrimary) - this.#fit(); - if (!this.#peer.isPrimary) { - clearTimeout(this.#resizeTimer); - this.#resizeTimer = undefined; - this.#lastRequested = undefined; - } - if (first || (!oldPeer.isPrimary && this.#peer.isPrimary)) - this.#queueResize(true); - if (this.#lastRequested === `${message.columns}x${message.rows}`) - this.#lastRequested = undefined; - if (Object.hasOwn(message, "history")) { - this.#screenText = message.text; - this.#history.accept(message.history, message.revision); - } - if (message.linkGeneration === this.#linkGeneration) { - if (message.linkSnapshot) - this.#acceptLinkSnapshot(message.linkSnapshot); - else { - if (this.#linkSnapshot) - this.#linkSnapshot = { ...this.#linkSnapshot, revision: message.revision }; - this.#linkDetector.advance(message.revision); - } - } - this.#mouse?.update(message.columns, message.rows, message.mouseTracking); - if (geometryChanged) - this.#options.onGeometry?.(this.geometry); - if (first) - this.#options.onSizingChange?.(this.sizing); - if (oldPeer.id !== this.#peer.id || oldPeer.primaryId !== this.#peer.primaryId || oldPeer.isPrimary !== this.#peer.isPrimary) { - this.#options.onRoleChange?.(this.peer); - } - if (!this.#disposed && this.#connected && (this.#peer.id !== null || this.#peer.isPrimary)) { - const titleChanged = !this.#hasTitle || this.#title !== message.title; - const progressChanged = !this.#hasActivity || this.#progress.state !== message.progress.state || - this.#progress.percentage !== message.progress.percentage; - const shellChanged = !this.#hasActivity || this.#shellIntegration.phase !== message.shellIntegration.phase || - this.#shellIntegration.lastExitCode !== message.shellIntegration.lastExitCode; - const workingDirectoryChanged = !this.#hasActivity || - this.#workingDirectory.uri !== message.workingDirectory.uri; - const commandMarkChanged = !this.#hasActivity || this.#commandMark?.phase !== message.commandMark?.phase || - this.#commandMark?.exitCode !== message.commandMark?.exitCode || - this.#commandMark?.rawParameters !== message.commandMark?.rawParameters; - this.#title = message.title; - this.#hasTitle = true; - this.#progress = { ...message.progress }; - this.#shellIntegration = { ...message.shellIntegration }; - this.#workingDirectory = { ...message.workingDirectory }; - this.#commandMark = message.commandMark ? { ...message.commandMark } : null; - this.#hasActivity = true; - if (titleChanged) - this.#options.onTitleChange?.(this.#title); - if (!this.#disposed && progressChanged) - this.#options.onProgressChange?.(this.progress); - if (!this.#disposed && shellChanged) - this.#options.onShellIntegrationChange?.(this.shellIntegration); - if (!this.#disposed && workingDirectoryChanged) - this.#options.onWorkingDirectoryChange?.(this.workingDirectory); - if (!this.#disposed && commandMarkChanged) - this.#options.onCommandMarkChange?.(this.commandMark); - } - } - else if (message.type === "linkSnapshot") { - if (message.generation === this.#linkGeneration && message.snapshot.revision === this.#linkRevision) - this.#acceptLinkSnapshot(message.snapshot); - } - else if (message.type === "linkDecorations") { - const pending = this.#pendingLinks; - if (this.#connected && pending && pending.serial === message.serial && - pending.generation === message.generation && message.generation === this.#linkGeneration && - pending.revision === message.revision && message.revision === this.#linkRevision) { - this.#pendingLinks = undefined; - this.#replacePresentedLinks(pending.links); - this.#mouse?.refresh(); - } - } - else if (message.type === "history") { - this.#screenText = message.text; - this.#history.accept(message.history, message.revision); - } - else if (message.type === "stats") { - this.#stats = message.stats; - if (message.text !== undefined) - this.#screenText = message.text; - if (message.stats.revision > 0 && this.#hasTitle && this.#hasActivity && this.#connected && - (this.#peer.id !== null || this.#peer.isPrimary)) { - clearTimeout(this.#readyTimer); - this.#ready.resolve(this); - } - this.#options.onStats?.(this.stats, message.text); - } - } - #fit() { - const width = this.#geometry.columns * this.#geometry.cellWidth; - const height = this.#geometry.rows * this.#geometry.cellHeight; - const scale = fittedScale(this.#size, this.#geometry, this.#peer.isPrimary, this.#sizing); - this.#surface.style.width = `${width * scale}px`; - this.#surface.style.height = `${height * scale}px`; - // Overlay positions use layout pixels, before any ancestor CSS transforms. - const style = getComputedStyle(this.#surface); - this.#canvasSize = { width: Number.parseFloat(style.width), height: Number.parseFloat(style.height) }; - this.#selectionUI?.refresh(); - const dpr = window.devicePixelRatio || 1; - this.#post({ type: "viewport", width: Math.ceil(width * scale * dpr), height: Math.ceil(height * scale * dpr) }); - } - #fittedGrid() { - return requestedGrid(this.#size, this.#geometry, this.#sizing); - } - #queueResize(includeFixed = false) { - if (this.#sizing.mode === "fixed" && !includeFixed) - return; - if (!this.#canInput() || !this.#peer.isPrimary || this.#resizeTimer !== undefined || this.#disposed) - return; - // Throttle (rather than debounce) so dragging a primary view updates peers live. - this.#resizeTimer = setTimeout(() => { - this.#resizeTimer = undefined; - const grid = this.#fittedGrid(); - if (!grid || !this.#peer.isPrimary || !this.#canInput()) - return; - const key = `${grid.columns}x${grid.rows}`; - if ((grid.columns === this.#geometry.columns && grid.rows === this.#geometry.rows) || key === this.#lastRequested) - return; - this.resize(grid.columns, grid.rows); - }, 50); - } - #post(message, transfer = []) { - this.#worker?.postMessage(message, transfer); - } - #send(command) { - if (!this.#connected || this.#disposed) - throw new Error("Terminal view is not connected"); - if (this.#readOnly && ["input", "paste", "key", "mouse", "resize", "requestPrimary"].includes(command.type)) - throw new Error("Terminal view does not accept input"); - this.#post({ type: "command", command }); - } - #inputCommand(command) { - if (this.#disposed || !this.#canInput()) - return; - assertCommandSize(command); - this.#inputSerial++; - if (["input", "paste", "key"].includes(command.type) && this.viewport.available) { - this.#mouse?.cancel(); - if (this.selection.status !== "none") - this.clearSelection(); - if (!this.viewport.following || this.viewport.pending) - this.scrollToLive(); - } - this.#send(command); - } - #inspectionChanged() { - const viewport = this.viewport; - const selection = this.selection; - this.#mouse?.refresh(); - if (selection.status === "invalidated") - this.#mouse?.cancel(); - if (this.#highlights) { - this.#highlights.replaceChildren(...selection.ranges.map(range => { - const element = document.createElement("span"); - element.className = "highlight"; - element.setAttribute("part", "selection-highlight"); - element.style.cssText = `left:${range.startColumn / this.#geometry.columns * 100}%;top:${range.row / this.#geometry.rows * 100}%;width:${(range.endColumn - range.startColumn) / this.#geometry.columns * 100}%;height:${100 / this.#geometry.rows}%`; - return element; - })); - const live = requiredElement(this.#inspection, ".return-live", HTMLButtonElement); - live.hidden = !viewport.available || (viewport.following && !viewport.pending); - live.disabled = !this.#connected; - this.#renderInspectionStatus(); - } - if (!this.#disposed) - this.#selectionUI?.refresh(); - this.#options.onViewportChange?.(viewport); - this.#options.onSelectionChange?.(selection); - } - scrollLines(delta) { this.#history.scroll(delta); } - scrollToLive() { this.#history.live(); } - clearSelection() { this.#inspectionError = ""; this.#history.clear(); } - /** Re-notifies selection UI hosts after an external styling/policy change. */ - refreshSelectionUI() { - if (this.#disposed) - throw new Error("Terminal view is disposed"); - this.#selectionUI?.refresh(true); - } - #renderInspectionStatus() { - if (!this.#inspection) - return; - const selection = this.selection; - const viewport = this.viewport; - const status = requiredElement(this.#inspection, ".inspection-message", HTMLSpanElement); - status.textContent = this.#selectionUIError || this.#inspectionError || - (selection.status === "unavailable" ? "" : selection.message) || - (viewport.available && !viewport.following ? `${viewport.liveTop - viewport.top} rows above live` : ""); - status.hidden = !status.textContent; - status.dataset.level = this.#selectionUIError || this.#inspectionError || - selection.status === "invalidated" ? "error" : "info"; - } - #actionFailed(error) { - const failure = error instanceof Error ? error : new Error(String(error)); - this.#inspectionError = `Input action failed: ${failure.message}`; - this.#inspectionChanged(); - this.#options.onInputError?.(failure); - } - async copySelection({ clear = false } = {}) { - if (typeof clear !== "boolean") - throw new TypeError("Copy clear must be a boolean"); - const serial = ++this.#copySerial; - const selectionId = this.selection.requestId; - const generation = this.viewport.generation; - this.#inspectionError = ""; - try { - if (!this.#connected || this.#disposed) - throw new Error("Terminal view is not connected"); - if (!navigator.clipboard?.write || typeof ClipboardItem !== "function") { - throw new Error("Clipboard writing is unavailable. Use a secure browser context with clipboard permission."); - } - const text = this.#history.copy(); - this.#copying = true; - this.#inspectionChanged(); - // Invoke the clipboard during the user gesture; producer extraction can complete asynchronously. - const item = new ClipboardItem({ "text/plain": text.then(value => new Blob([value], { type: "text/plain" })) }); - const [value] = await Promise.all([text, navigator.clipboard.write([item])]); - if (clear && serial === this.#copySerial && this.selection.status === "valid" && - this.selection.requestId === selectionId && this.viewport.generation === generation) - this.clearSelection(); - return value; - } - catch (error) { - if (serial === this.#copySerial) { - this.#history.cancelCopy(error); - this.#inspectionError = `Copy failed: ${errorMessage(error)}`; - } - throw error; - } - finally { - if (serial === this.#copySerial) - this.#copying = false; - this.#inspectionChanged(); - } - } - #canInput() { - return this.#connected && this.#hasGeometry && !this.#readOnly && - (this.#peer.id !== null || this.#peer.isPrimary); - } - get inputContext() { - return Object.freeze({ - terminal: this, selection: this.selection, viewport: this.viewport, - buffer: this.viewport.buffer ?? null, mouseCaptured: !this.#readOnly && this.#geometry.mouseTracking !== 0, - historical: !this.viewport.following || this.viewport.pending, - readOnly: this.#readOnly, connected: this.#connected, peer: this.peer - }); - } - #resolveInput(input) { - try { - const decision = this.#policy.resolve(Object.freeze(input), this.inputContext); - if (this.#readOnly && decision.route === InputRoute.Application) - return { route: input.type === "pointer" || input.type === "wheel" ? InputRoute.Continue : InputRoute.Consume }; - return decision; - } - catch (error) { - this.#actionFailed(error); - return { route: InputRoute.Consume }; - } - } - #executeInputAction(decision, input) { - this.#performAction(decision.action, decision.args, input).catch(error => this.#actionFailed(error)); - } - async runAction(action, args, input) { - return this.#performAction(action, args, input); - } - async #performAction(action, args, input) { - if (this.#disposed) - throw new Error("Terminal view is disposed"); - this.#inspectionError = ""; - if (typeof action === "function") - return action(this.inputContext, args, input); - const customAction = this.#actions.get(action); - if (customAction) - return customAction(this.inputContext, args, input); - switch (action) { - case TerminalAction.CopySelection: - if (args === undefined) - return this.copySelection(); - if (!isRecord(args)) - throw new TypeError("Copy options must be an object"); - if (args.clear !== undefined && typeof args.clear !== "boolean") - throw new TypeError("Copy clear must be a boolean"); - return this.copySelection({ clear: args.clear }); - case TerminalAction.PasteClipboard: return this.pasteClipboard(); - case TerminalAction.ClearSelection: return this.clearSelection(); - case TerminalAction.ScrollToLive: return this.scrollToLive(); - case TerminalAction.ScrollLines: - if (typeof args !== "number") - throw new RangeError("Scroll delta must be a signed 32-bit integer"); - return this.scrollLines(args); - case TerminalAction.CopyOrPaste: - if (this.#clipboardAction) - throw new Error("A clipboard action is still in progress. Try again when it finishes."); - this.#clipboardAction = true; - try { - if (this.selection.active || (this.selection.pending && this.selection.canExtend)) - return await this.copySelection({ clear: true }); - if (!this.#readOnly) - return await this.pasteClipboard(); - return; - } - finally { - this.#clipboardAction = false; - } - default: throw new TypeError(`Unknown terminal action: ${action}`); - } - } - /** Sends an explicit paste through the producer's mode-aware input encoder. */ - paste(text) { - if (typeof text !== "string") - throw new TypeError("Paste text must be a string"); - if (!this.#canInput()) - throw new Error("Terminal view does not accept input"); - if (text) - this.#inputCommand({ type: "paste", text }); - } - async pasteClipboard() { - if (!this.#canInput()) - throw new Error("Terminal view does not accept input"); - if (!navigator.clipboard?.readText) - throw new Error("Clipboard reading is unavailable. Use the browser's paste shortcut instead."); - const serial = this.#inputSerial; - const generation = this.viewport.generation; - const selectionId = this.selection.requestId; - const focused = document.activeElement; - const text = await navigator.clipboard.readText(); - if (!this.#canInput() || serial !== this.#inputSerial || generation !== this.viewport.generation || - selectionId !== this.selection.requestId || document.activeElement !== focused) - throw new Error("Terminal input, selection, focus, or buffer changed while reading the clipboard. Paste again."); - this.paste(text); - return text; - } - /** Changes per-view input policy without reconnecting; server authorization remains host-owned. */ - setReadOnly(readOnly) { - if (typeof readOnly !== "boolean") - throw new TypeError("readOnly must be a boolean"); - if (this.#disposed) - throw new Error("Terminal view is disposed"); - if (this.#readOnly === readOnly) - return; - const inputFocused = document.activeElement === this.element && - (!this.element.shadowRoot?.activeElement || this.element.shadowRoot.activeElement === this.#input); - this.#readOnly = readOnly; - this.#inputSerial++; - this.#resetComposition?.(); - if (this.#input) { - this.#input.value = ""; - this.#input.disabled = !this.#canInput(); - } - // Set the policy before cancelling so pending moves and button releases cannot leak. - this.#mouse?.cancel(); - this.#mouse?.refresh(); - clearTimeout(this.#resizeTimer); - this.#resizeTimer = undefined; - this.#lastRequested = undefined; - this.#queueResize(true); - if (inputFocused) - this.focus(); - this.#selectionUI?.refresh(); - } - setLinks(options) { - if (this.#disposed) - throw new Error("Terminal view is disposed"); - if (options === undefined) - throw new TypeError("Link options must be an object or false"); - const next = normalizeLinks(options, new Set(this.#actions.keys())); - this.#links = next; - this.#linkGeneration++; - this.#detectedLinks = []; - this.#replacePresentedLinks([]); - this.#pendingLinks = undefined; - this.#linkSnapshot = undefined; - this.#hoveredLinkId = undefined; - this.#linkDetector.configure(next ? next.detection : false); - this.#mouse?.cancel(); - this.#mouse?.refresh(); - this.#postLinkConfiguration(); - } - #postLinkConfiguration() { - const enabled = !!(this.#links && (this.#links.osc8 || - (this.#links.detection && this.#links.detection.rules.some(rule => rule.enabled !== false)))); - this.#post({ type: "linkDetection", enabled, generation: this.#linkGeneration }); - } - #acceptLinkSnapshot(snapshot) { - this.#linkSnapshot = snapshot; - this.#detectedLinks = []; - this.#replacePresentedLinks([]); - this.#pendingLinks = undefined; - this.#linkDetector.update(snapshot); - this.#mouse?.refresh(); - } - #detectedPointerId(link) { - return `detected/${this.#linkGeneration}/${this.#linkRevision}/${link.id}`; - } - #replacePresentedLinks(links) { - this.#presentedLinks = links; - this.#detectedRows.clear(); - for (const link of links) { - for (const range of link.activation.ranges) { - const row = this.#detectedRows.get(range.row) ?? []; - row.push({ startColumn: range.startColumn, endColumn: range.endColumn, link }); - this.#detectedRows.set(range.row, row); - } - } - } - #requestLinkDecorations() { - if (!this.#worker || !this.#connected || !this.#linkRevision) - return; - const detection = this.#links && this.#links.detection; - if (!detection) - return; - const visible = detection.decoration === "none" ? [] : detection.decoration === "hover" - ? this.#detectedLinks.filter(link => this.#detectedPointerId(link) === this.#hoveredLinkId) - : this.#detectedLinks; - this.#pendingLinks = { revision: this.#linkRevision, generation: this.#linkGeneration, - serial: ++this.#linkSerial, links: this.#detectedLinks }; - this.#post({ type: "linkDecorations", revision: this.#linkRevision, generation: this.#linkGeneration, - serial: this.#linkSerial, ranges: visible.flatMap(link => link.activation.ranges), - underlineStyle: detection.underlineStyle ?? "solid" }); - } - #linkAt(point) { - if (!this.#connected || this.viewport.pending || !this.#links) - return null; - const osc8 = this.#osc8Rows.get(point.y)?.find(range => point.x >= range.startColumn && point.x < range.endColumn); - if (osc8) { - if (this.#links.osc8 === false) - return null; - const target = this.#links.osc8 - ? (!/[\u0000-\u0020\u007f]/u.test(osc8.uri) && URL.canParse(osc8.uri) ? osc8.uri : null) - : this.#hyperlinks.at(point); - if (!target || (this.#links.osc8 && this.#linkSnapshot?.revision !== this.#linkRevision)) - return null; - return { id: `osc8/${this.#linkGeneration}/${this.#linkRevision}/${osc8.row}/${osc8.startColumn}/${osc8.endColumn}`, - target, activation: "modifierClick" }; - } - const detection = this.#links.detection; - if (!detection) - return null; - const link = this.#detectedRows.get(point.y)?.find(range => point.x >= range.startColumn && point.x < range.endColumn)?.link; - return link ? { id: this.#detectedPointerId(link), target: link.activation.target, - activation: detection.activation ?? "modifierClick" } : null; - } - #activateLink(pointer, input) { - if (input.type !== "pointer" || this.#linkAt(input.point)?.id !== pointer.id || !this.#links) - return; - const detected = this.#presentedLinks.find(link => this.#detectedPointerId(link) === pointer.id); - if (detected) { - this.#performAction(detected.action, detected.activation, input).catch(error => this.#actionFailed(error)); - return; - } - if (!this.#links.osc8) { - try { - window.open(pointer.target, "_blank", "noopener,noreferrer"); - } - catch (error) { - this.#actionFailed(error); - } - return; - } - const range = this.#osc8Rows.get(input.point.y)?.find(range => input.point.x >= range.startColumn && input.point.x < range.endColumn); - const snapshot = this.#linkSnapshot; - if (!range || !snapshot) - return; - let text = ""; - for (let column = range.startColumn; column < range.endColumn; column++) { - const cell = snapshot.cells[range.row * snapshot.columns + column]; - if (cell?.width && !(cell.attributes & 64)) - text += cell.text; - } - const activation = Object.freeze({ - source: "osc8", ruleId: null, kind: "uri", target: pointer.target, text, - revision: this.#linkRevision, - ranges: Object.freeze([Object.freeze({ row: range.row, startColumn: range.startColumn, endColumn: range.endColumn })]), - }); - this.#performAction(this.#links.osc8.action, activation, input).catch(error => this.#actionFailed(error)); - } - #forwardInput(input) { - if (input.type === "key") { - if (input.meta) - throw new Error("Meta has no terminal key encoding; bind this shortcut to a local action instead."); - this.#inputCommand({ type: "key", key: input.key, ctrl: input.ctrl, alt: input.alt, shift: input.shift }); - } - else if (input.type === "paste") { - if (this.#canInput()) - this.paste(input.text); - } - else if (input.type === "text") { - this.#inputCommand({ type: "input", text: input.text }); - } - } - #dispatchInput(input, event, allowApplication = true) { - const decision = this.#resolveInput(input); - // Shortcuts may run on inspection controls, but Enter/Space must still activate the control. - if (!allowApplication && decision.action === undefined && decision.route !== InputRoute.Consume) - return; - if (decision.route === InputRoute.Browser || - (decision.route === InputRoute.Continue && input.type === "key")) - return; - event?.preventDefault(); - event?.stopPropagation(); - if (decision.action !== undefined) - this.#executeInputAction(decision, input); - else if (decision.route !== InputRoute.Consume) { - try { - this.#forwardInput(input); - } - catch (error) { - this.#actionFailed(error); - } - } - } - #bindKeyboard() { - const input = this.#input; - const options = { signal: this.#listeners.signal }; - let composing = false; - let compositionCommit = null; - this.#resetComposition = () => { - composing = false; - compositionCommit = null; - clearTimeout(this.#compositionTimer); - }; - this.element.addEventListener("keydown", event => { - if (event.defaultPrevented) - return; - if (event.composedPath().includes(this.#selectionOverlay)) - return; - const target = event.composedPath()[0]; - if (!this.#connected || event.isComposing || composing || event.key === "Process" || event.key === "Dead") - return; - if (event.getModifierState("AltGraph")) - return; - this.#dispatchInput({ type: "key", key: event.key, code: event.code, repeat: event.repeat, - ...inputModifiers(event) }, event, target === input || target === this.element); - }, { ...options, capture: true }); - input.addEventListener("paste", event => { - if (event.defaultPrevented || !this.#connected || !event.clipboardData) - return; - this.#dispatchInput({ type: "paste", text: event.clipboardData.getData("text/plain") }, event); - input.value = ""; - }, options); - input.addEventListener("compositionstart", () => { - if (!this.#canInput()) - return; - composing = true; - compositionCommit = null; - clearTimeout(this.#compositionTimer); - }, options); - input.addEventListener("compositionend", event => { - if (!composing) - return; - composing = false; - // Accommodate browsers placing the final input before or after compositionend. - compositionCommit = typeof event.data === "string" ? event.data : input.value; - this.#compositionTimer = setTimeout(() => { - if (compositionCommit) - this.#dispatchInput({ type: "text", text: compositionCommit }); - compositionCommit = null; - input.value = ""; - }, 0); - }, options); - input.addEventListener("input", event => { - const inputEvent = event instanceof InputEvent ? event : undefined; - if (inputEvent?.isComposing || composing) - return; - clearTimeout(this.#compositionTimer); - const text = compositionCommit ?? inputEvent?.data ?? input.value; - compositionCommit = null; - if (text && inputEvent?.inputType !== "insertFromPaste") - this.#dispatchInput({ type: "text", text }, event); - input.value = ""; - }, options); - } - focus() { - if (this.#disposed) - return; - const target = this.#canInput() ? this.#input : this.element; - target.focus({ preventScroll: true }); - } - /** Request HMP1 primary explicitly; peer notifications confirm the result. */ - requestPrimary() { - if (!this.#canInput()) - throw new Error("Terminal view does not accept input"); - if (this.#size.width <= 0 || this.#size.height <= 0) - throw new Error("Show the terminal container before taking primary"); - const grid = this.#fittedGrid(); - if (!grid) - throw new Error("Show the terminal container before taking primary"); - if (this.#peer.id === null) { - if (!this.#peer.isPrimary) - throw new Error("Waiting for the HMP1 connection"); - this.resize(grid.columns, grid.rows); - return; - } - this.#send({ type: "requestPrimary", ...grid }); - } - /** Request a grid; never reflow locally before the authoritative response. */ - resize(columns, rows) { - const grid = dimensions(columns, rows); - if (!this.#canInput()) - throw new Error("Terminal view does not accept input"); - if (!this.#peer.isPrimary) - throw new Error("Only the primary view can request a terminal resize"); - this.#send({ type: "resize", ...grid }); - this.#lastRequested = `${columns}x${rows}`; - } - /** Change the primary's sizing policy; applied grid dimensions still come from the server. */ - setSizing(sizing) { - const next = normalizeSizing(sizing, this.#sizing.fontSize); - if (!this.#connected || this.#disposed) - throw new Error("Terminal view is not connected"); - if (this.#readOnly) - throw new Error("Terminal view does not accept input"); - if (!this.#peer.isPrimary) - throw new Error("Only the primary view can change terminal sizing"); - this.#sizing = next; - clearTimeout(this.#resizeTimer); - this.#resizeTimer = undefined; - this.#lastRequested = undefined; - this.#fit(); - this.#queueResize(true); - this.#options.onSizingChange?.(this.sizing); - } - resync() { this.#send({ type: "resync" }); } - #disconnect() { - this.#inputSerial++; - this.#connected = false; - this.#hyperlinks.update([]); - this.#osc8Rows.clear(); - this.#detectedLinks = []; - this.#replacePresentedLinks([]); - this.#linkSnapshot = undefined; - this.#pendingLinks = undefined; - this.#linkDetector.clear(); - if (this.#input) - this.#input.disabled = true; - this.#mouse?.update(1, 1, 0); - this.#history.disconnect(); - this.#inspectionChanged(); - clearTimeout(this.#resizeTimer); - this.#resizeTimer = undefined; - } - #fail(error) { - this.#disconnect(); - this.#ready.reject(error); - this.#options.onStatus?.(error.message, "error"); - this.#worker?.terminate(); - } - /** Detach this view. The server-side shared terminal is not terminated. */ - dispose() { - if (this.#disposed) - return; - this.#disposed = true; - this.#disconnect(); - this.#linkDetector.dispose(); - this.#ready.reject(new DOMException("Terminal view was disposed", "AbortError")); - clearTimeout(this.#readyTimer); - clearTimeout(this.#compositionTimer); - this.#observer?.disconnect(); - this.#mouse?.dispose(); - this.#listeners.abort(); - this.#post({ type: "stop" }); - this.#worker?.terminate(); - this.element.remove(); - } -} -//# sourceMappingURL=web-terminal.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js.map deleted file mode 100644 index 51dd24e5e91..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/web-terminal.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"web-terminal.js","sourceRoot":"","sources":["../src/web-terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import { captureMouse } from \"./mouse-input.js\";\nimport { normalizeFont } from \"./terminal-font.js\";\nimport { normalizeRenderer } from \"./renderer-options.js\";\nimport { dimensions, normalizeSizing, requestedGrid, fittedScale } from \"./terminal-sizing.js\";\nimport { HistoryState } from \"./history-state.js\";\nimport { terminalThemeCss } from \"./terminal-theme.js\";\nimport { InputPolicy, InputRoute, TerminalAction, inputModifiers } from \"./input-policy.js\";\nimport { assertCommandSize } from \"./protocol.js\";\nimport { SelectionUI } from \"./selection-ui.js\";\nimport { Hyperlinks } from \"./hyperlinks.js\";\nimport { LinkDetection } from \"./link-detection.js\";\nimport { normalizeLinks } from \"./link-options.js\";\nimport type { DetectedLink, LinkDetectionSnapshot } from \"./link-detection.js\";\nimport type { MouseCapture, PointerHyperlink } from \"./mouse-input.js\";\nimport type { CopySelectionOptions, InputActionHandler, InputDecision, InputBinding,\n TerminalActionName, TerminalGeometry, TerminalInput, TerminalInputContext, TerminalPeer,\n TerminalRendererPreference, TerminalSelection, TerminalSizing, TerminalSizingState, TerminalStats, TerminalViewport,\n TerminalProgress, TerminalShellIntegration, TerminalWorkingDirectory, TerminalCommandMark,\n TerminalLinkOptions, TerminalLinkActivation, TerminalPoint,\n WebTerminalHandle, WebTerminalOptions } from \"./types.js\";\nimport type { HyperlinkRange, InputCommand, TerminalCommand, WorkerInputMessage, WorkerOutputMessage } from \"./wire-types.js\";\nimport { errorMessage, isRecord } from \"./validation.js\";\nexport { InputRoute, TerminalAction, defaultInputBindings } from \"./input-policy.js\";\n\nfunction requiredElement(root: ParentNode, selector: string, type: new () => T): T {\n const element = root.querySelector(selector);\n if (!(element instanceof type)) throw new Error(`Missing terminal element: ${selector}`);\n return element;\n}\n\n/**\n * First-party HWT1 client. Owns only the element it appends, not the caller's\n * container or the server terminal. The HWT1 wire and this spike API evolve together.\n */\nexport class WebTerminal implements WebTerminalHandle {\n readonly element: HTMLDivElement;\n #options: WebTerminalOptions;\n #renderer: TerminalRendererPreference;\n // DOM and worker fields are initialized by mount before a handle is returned.\n #worker!: Worker;\n #surface!: HTMLDivElement;\n #canvas!: HTMLCanvasElement;\n #input!: HTMLTextAreaElement;\n #mouse: MouseCapture | undefined;\n #observer: ResizeObserver | undefined;\n #listeners = new AbortController();\n #size = { width: 0, height: 0 };\n #sizing = normalizeSizing();\n #geometry: TerminalGeometry = { columns: 80, rows: 24, cellWidth: 10, cellHeight: 20, mouseTracking: 0 };\n #peer: TerminalPeer = { id: null, primaryId: null, isPrimary: false };\n #connected = false;\n #closed = false;\n #readOnly: boolean;\n #disposed = false;\n #hasGeometry = false;\n #resizeTimer: ReturnType | undefined;\n #lastRequested: string | undefined;\n #compositionTimer: ReturnType | undefined;\n #resetComposition: (() => void) | undefined;\n #ready = Promise.withResolvers();\n #readyTimer: ReturnType | undefined;\n #stats: TerminalStats = {};\n #screenText = \"\";\n #title = \"\";\n #hasTitle = false;\n #progress: TerminalProgress = { state: \"none\", percentage: null };\n #shellIntegration: TerminalShellIntegration = { phase: \"unknown\", lastExitCode: null };\n #workingDirectory: TerminalWorkingDirectory = { uri: null, host: null, path: null };\n #commandMark: TerminalCommandMark | null = null;\n #hasActivity = false;\n #history: HistoryState;\n #highlights!: HTMLDivElement;\n #inspection!: HTMLDivElement;\n #inspectionError = \"\";\n #copySerial = 0;\n #copying = false;\n #policy: InputPolicy;\n #actions: Map;\n #clipboardAction = false;\n #inputSerial = 0;\n #selectionUI: SelectionUI | undefined;\n #selectionOverlay!: HTMLDivElement;\n #selectionUIError = \"\";\n #canvasSize = { width: 0, height: 0 };\n #hyperlinks = new Hyperlinks();\n #links: false | TerminalLinkOptions;\n #linkDetector: LinkDetection;\n #linkGeneration = 1;\n #linkRevision = 0;\n #linkSerial = 0;\n #linkSnapshot: LinkDetectionSnapshot | undefined;\n #osc8Rows = new Map();\n #detectedLinks: readonly DetectedLink[] = [];\n #presentedLinks: readonly DetectedLink[] = [];\n #detectedRows = new Map();\n #hoveredLinkId: string | undefined;\n #pendingLinks: { revision: number; generation: number; serial: number; links: readonly DetectedLink[] } | undefined;\n\n /** Resolves after a connected terminal frame is presented. Supply signal to cancel mounting. */\n static async mount(container: HTMLElement, options: WebTerminalOptions): Promise {\n if (!(container instanceof HTMLElement)) throw new TypeError(\"A terminal container HTMLElement is required\");\n if (!options?.url) throw new TypeError(\"A terminal WebSocket URL is required\");\n if (options.signal?.aborted) throw options.signal.reason;\n if (normalizeRenderer(options.renderer) === \"webgpu\" && (!window.isSecureContext || !navigator.gpu)) {\n throw new Error(\"The requested WebGPU renderer requires WebGPU over HTTPS or localhost\");\n }\n if (!window.Worker || !window.ResizeObserver || !window.OffscreenCanvas ||\n !HTMLCanvasElement.prototype.transferControlToOffscreen) {\n throw new Error(\"WebTerminal requires module workers, ResizeObserver, and a transferable OffscreenCanvas\");\n }\n const terminal = new WebTerminal(options);\n try {\n await Promise.all([terminal.#ready.promise, Promise.resolve().then(() => terminal.#start(container))]);\n return terminal;\n } catch (error) {\n terminal.dispose();\n throw error;\n }\n }\n\n private constructor(options: WebTerminalOptions) {\n this.#options = options;\n if (options.readOnly !== undefined && typeof options.readOnly !== \"boolean\")\n throw new TypeError(\"readOnly must be a boolean\");\n this.#readOnly = options.readOnly ?? false;\n this.#renderer = normalizeRenderer(options.renderer);\n if (options.workerUrl !== undefined && !(options.workerUrl instanceof URL) &&\n (typeof options.workerUrl !== \"string\" || !options.workerUrl.trim()))\n throw new TypeError(\"workerUrl must be a nonempty URL string or URL\");\n if (options.linkDetectionWorkerUrl !== undefined && !(options.linkDetectionWorkerUrl instanceof URL) &&\n (typeof options.linkDetectionWorkerUrl !== \"string\" || !options.linkDetectionWorkerUrl.trim()))\n throw new TypeError(\"linkDetectionWorkerUrl must be a nonempty URL string or URL\");\n if (options.onLinkDetectionError !== undefined && typeof options.onLinkDetectionError !== \"function\")\n throw new TypeError(\"onLinkDetectionError must be a function\");\n if (options.onSelectionUI !== undefined && (typeof options.onSelectionUI !== \"function\" ||\n options.onSelectionUI.constructor.name === \"AsyncFunction\"))\n throw new TypeError(\"onSelectionUI must be a synchronous event handler\");\n this.#policy = new InputPolicy(options);\n this.#actions = new Map(Object.entries(options.actions ?? {}));\n this.#links = normalizeLinks(options.links, new Set(this.#actions.keys()));\n this.#linkDetector = new LinkDetection({\n workerUrl: options.linkDetectionWorkerUrl === undefined ? undefined : new URL(options.linkDetectionWorkerUrl, location.href),\n actions: new Set(this.#actions.keys()),\n onChange: (revision, links) => {\n if (this.#disposed || !this.#connected || revision !== this.#linkRevision) return;\n this.#detectedLinks = links;\n this.#replacePresentedLinks([]);\n this.#requestLinkDecorations();\n },\n onError: error => {\n if (this.#disposed) return;\n try { this.#options.onStatus?.(`Link detection: ${error.message}`, \"error\"); }\n finally { this.#options.onLinkDetectionError?.(error); }\n },\n });\n this.#linkDetector.configure(this.#links ? this.#links.detection : false);\n this.#history = new HistoryState(command => this.#send(command), () => this.#inspectionChanged());\n this.element = document.createElement(\"div\");\n this.element.className = \"hex1b-terminal\";\n this.element.tabIndex = -1;\n this.element.style.cssText = \"width:100%;height:100%;min-width:0;min-height:0;contain:strict\";\n }\n\n get geometry(): TerminalGeometry { return { ...this.#geometry }; }\n get peer(): TerminalPeer { return { ...this.#peer }; }\n get connected() { return this.#connected; }\n get readOnly(): boolean { return this.#readOnly; }\n /** Current presented workload title; retained on disconnect/dispose. Treat as untrusted text. */\n get title(): string { return this.#title; }\n get progress(): TerminalProgress { return { ...this.#progress }; }\n get shellIntegration(): TerminalShellIntegration { return { ...this.#shellIntegration }; }\n get workingDirectory(): TerminalWorkingDirectory { return { ...this.#workingDirectory }; }\n get commandMark(): TerminalCommandMark | null { return this.#commandMark ? { ...this.#commandMark } : null; }\n get stats(): TerminalStats { return { ...this.#stats }; }\n get screenText() { return this.#screenText; }\n get sizing(): TerminalSizingState { return { ...this.#sizing }; }\n get inputBindings(): InputBinding[] { return this.#policy.bindings; }\n get viewport(): TerminalViewport {\n const viewport = this.#history.viewport;\n return { ...viewport, followTail: viewport.following,\n offset: viewport.available ? viewport.liveTop - viewport.top : 0 };\n }\n get selection(): TerminalSelection {\n const selection = this.#history.selection;\n return { ...selection, active: selection.status === \"valid\", pending: selection.status === \"pending\",\n copying: this.#copying, copyError: this.#inspectionError };\n }\n\n #start(container: HTMLElement): void {\n if (this.#options.signal?.aborted) throw this.#options.signal.reason;\n const url = new URL(this.#options.url, location.href);\n if (url.protocol === \"https:\") url.protocol = \"wss:\";\n if (url.protocol === \"http:\") url.protocol = \"ws:\";\n if (![\"ws:\", \"wss:\"].includes(url.protocol)) throw new TypeError(\"A ws: or wss: URL is required\");\n const scale = this.#options.scale === undefined || this.#options.scale === \"auto\"\n ? Math.min(3, Math.max(0.5, window.devicePixelRatio || 1)) : this.#options.scale;\n if (!Number.isFinite(scale) || scale < 0.5 || scale > 3) throw new RangeError(\"Backing scale must be 0.5-3 or 'auto'\");\n const font = normalizeFont(this.#options.font, location.href);\n this.#sizing = normalizeSizing(this.#options.sizing);\n const shadow = this.element.attachShadow({ mode: \"open\" });\n shadow.innerHTML = `\n \n
\n \n
\n \n \n
\n \n \n \n
`;\n this.#surface = requiredElement(shadow, \".surface\", HTMLDivElement);\n this.#canvas = requiredElement(shadow, \"canvas\", HTMLCanvasElement);\n this.#input = requiredElement(shadow, \"textarea\", HTMLTextAreaElement);\n this.#highlights = requiredElement(shadow, \".highlights\", HTMLDivElement);\n this.#inspection = requiredElement(shadow, \".inspection\", HTMLDivElement);\n this.#selectionOverlay = document.createElement(\"div\");\n this.#selectionOverlay.slot = \"selection-ui\";\n this.#selectionOverlay.className = \"hex1b-selection-overlay\";\n this.#selectionOverlay.style.cssText = \"position:relative;width:100%;height:100%;pointer-events:none\";\n this.element.append(this.#selectionOverlay);\n this.#selectionUI = new SelectionUI({\n element: this.element, overlay: this.#selectionOverlay,\n button: requiredElement(this.#inspection, \".copy-selection\", HTMLButtonElement), signal: this.#listeners.signal,\n getState: () => ({ selection: this.selection, viewport: this.viewport, geometry: this.geometry,\n canvasSize: this.#canvasSize, connected: this.#connected, readOnly: this.#readOnly }),\n runAction: this.runAction.bind(this),\n onSelectionUI: this.#options.onSelectionUI,\n reportError: error => {\n this.#selectionUIError = error ? `Selection UI failed: ${errorMessage(error)}` : \"\";\n this.#selectionOverlay.hidden = !!error;\n this.#renderInspectionStatus();\n if (error) this.#options.onStatus?.(this.#selectionUIError, \"error\");\n }\n });\n this.#selectionUI.refresh();\n this.#input.setAttribute(\"aria-label\", this.#options.label || \"Terminal input. Click outside to use page controls.\");\n this.#input.disabled = true;\n container.append(this.element);\n const inspect = (operation: () => void) => {\n try { this.#inspectionError = \"\"; operation(); }\n catch (error) { this.#inspectionError = errorMessage(error); this.#inspectionChanged(); }\n };\n this.#mouse = captureMouse(this.#canvas, command => this.#inputCommand(command), () => this.focus(), {\n state: () => ({ historical: !this.viewport.following || this.viewport.pending, readOnly: this.#readOnly,\n selection: this.selection }),\n begin: (point, selection) => inspect(() => this.#history.begin(point, selection)),\n extend: point => inspect(() => this.#history.extend(point)),\n scroll: (delta, endpoint) => inspect(() => this.#history.scroll(delta, endpoint)),\n end: cancelled => this.#history.endGesture(cancelled),\n resolve: input => this.#resolveInput(input),\n execute: (decision, input) => this.#executeInputAction(decision, input),\n hyperlink: point => this.#linkAt(point),\n hoverHyperlink: link => {\n this.#hoveredLinkId = link?.id;\n if (this.#links && this.#links.detection && this.#links.detection.decoration === \"hover\")\n this.#requestLinkDecorations();\n },\n openHyperlink: (link, input) => this.#activateLink(link, input),\n });\n this.#bindKeyboard();\n requiredElement(this.#inspection, \".return-live\", HTMLButtonElement).addEventListener(\"click\", () => {\n this.runAction(TerminalAction.ScrollToLive).catch(error => this.#actionFailed(error));\n }, { signal: this.#listeners.signal });\n requiredElement(this.#inspection, \".copy-selection\", HTMLButtonElement).addEventListener(\"click\", () => {\n this.runAction(TerminalAction.CopySelection).catch(error => this.#actionFailed(error));\n }, { signal: this.#listeners.signal });\n requiredElement(shadow, \".viewport\", HTMLDivElement).addEventListener(\"pointerdown\", event => {\n if (event.composedPath().includes(this.#selectionOverlay)) return;\n if ((event.target instanceof Element && event.target.closest(\"button\")) ||\n event.target === this.#canvas || event.target === this.#input) return;\n event.preventDefault();\n this.focus();\n }, { signal: this.#listeners.signal });\n this.#observer = new ResizeObserver(entries => {\n const { width, height } = entries[0].contentRect;\n const changed = width !== this.#size.width || height !== this.#size.height;\n this.#size = { width, height };\n this.#fit();\n if (changed) this.#queueResize();\n });\n // Observe the caller's outer box, never the fitted inner surface.\n this.#observer.observe(container);\n window.addEventListener(\"pagehide\", () => this.dispose(), { signal: this.#listeners.signal });\n this.#options.signal?.addEventListener(\"abort\", () => this.dispose(), { once: true, signal: this.#listeners.signal });\n this.#readyTimer = setTimeout(() => this.#fail(new Error(\"Timed out waiting for the terminal's first frame\")), 30000);\n this.#worker = this.#options.workerUrl === undefined\n ? new Worker(new URL(\"./terminal-worker.js\", import.meta.url), { type: \"module\", name: \"Hex1b WebTerminal\" })\n : new Worker(new URL(this.#options.workerUrl, location.href), { type: \"module\", name: \"Hex1b WebTerminal\" });\n this.#worker.addEventListener(\"message\", (event: MessageEvent) => this.#message(event.data));\n this.#worker.addEventListener(\"error\", event => {\n event.preventDefault();\n this.#fail(new Error(event.message || \"Terminal worker failed\"));\n });\n this.#worker.addEventListener(\"messageerror\", () => this.#fail(new Error(\"Terminal worker message could not be decoded\")));\n const canvas = this.#canvas.transferControlToOffscreen();\n this.#post({ type: \"init\", canvas, url: url.href, scale, font,\n renderer: this.#renderer }, [canvas]);\n this.#postLinkConfiguration();\n }\n\n #message(message: WorkerOutputMessage): void {\n if (this.#disposed) return;\n if (message.type === \"connected\") {\n this.#connected = true;\n this.#input.disabled = !this.#canInput();\n } else if (message.type === \"closed\") {\n if (this.#closed) return;\n this.#closed = true;\n clearTimeout(this.#readyTimer);\n try {\n this.#disconnect();\n if (!this.#disposed) this.#options.onClose?.(Object.freeze({ ...message.details }));\n } finally {\n this.#ready.reject(new Error(`Terminal WebSocket closed (${message.details.code}${\n message.details.reason ? `: ${message.details.reason}` : \"\"}) before mounting completed`));\n }\n } else if (message.type === \"status\") {\n if (message.level === \"error\") {\n this.#disconnect();\n this.#ready.reject(new Error(message.message));\n }\n this.#options.onStatus?.(message.message, message.level);\n } else if (message.type === \"geometry\") {\n this.#linkRevision = message.revision;\n this.#osc8Rows.clear();\n for (const range of message.hyperlinks) {\n const row = this.#osc8Rows.get(range.row) ?? [];\n row.push(range);\n this.#osc8Rows.set(range.row, row);\n }\n this.#replacePresentedLinks([]);\n this.#pendingLinks = undefined;\n const first = !this.#hasGeometry;\n const geometryChanged = first || ([\"columns\", \"rows\", \"cellWidth\", \"cellHeight\", \"mouseTracking\"] as const)\n .some(field => this.#geometry[field] !== message[field]);\n this.#geometry = {\n columns: message.columns, rows: message.rows,\n cellWidth: message.cellWidth, cellHeight: message.cellHeight, mouseTracking: message.mouseTracking\n };\n this.#hasGeometry = true;\n const oldPeer = this.#peer;\n this.#peer = message.peer;\n this.#input.disabled = !this.#canInput();\n if (this.#canInput() && document.activeElement === this.element && !this.element.shadowRoot?.activeElement) this.focus();\n this.#hyperlinks.update(message.hyperlinks);\n if (geometryChanged || oldPeer.isPrimary !== this.#peer.isPrimary) this.#fit();\n if (!this.#peer.isPrimary) {\n clearTimeout(this.#resizeTimer);\n this.#resizeTimer = undefined;\n this.#lastRequested = undefined;\n }\n if (first || (!oldPeer.isPrimary && this.#peer.isPrimary)) this.#queueResize(true);\n if (this.#lastRequested === `${message.columns}x${message.rows}`) this.#lastRequested = undefined;\n if (Object.hasOwn(message, \"history\")) {\n this.#screenText = message.text;\n this.#history.accept(message.history, message.revision);\n }\n if (message.linkGeneration === this.#linkGeneration) {\n if (message.linkSnapshot) this.#acceptLinkSnapshot(message.linkSnapshot);\n else {\n if (this.#linkSnapshot) this.#linkSnapshot = { ...this.#linkSnapshot, revision: message.revision };\n this.#linkDetector.advance(message.revision);\n }\n }\n this.#mouse?.update(message.columns, message.rows, message.mouseTracking);\n if (geometryChanged) this.#options.onGeometry?.(this.geometry);\n if (first) this.#options.onSizingChange?.(this.sizing);\n if (oldPeer.id !== this.#peer.id || oldPeer.primaryId !== this.#peer.primaryId || oldPeer.isPrimary !== this.#peer.isPrimary) {\n this.#options.onRoleChange?.(this.peer);\n }\n if (!this.#disposed && this.#connected && (this.#peer.id !== null || this.#peer.isPrimary)) {\n const titleChanged = !this.#hasTitle || this.#title !== message.title;\n const progressChanged = !this.#hasActivity || this.#progress.state !== message.progress.state ||\n this.#progress.percentage !== message.progress.percentage;\n const shellChanged = !this.#hasActivity || this.#shellIntegration.phase !== message.shellIntegration.phase ||\n this.#shellIntegration.lastExitCode !== message.shellIntegration.lastExitCode;\n const workingDirectoryChanged = !this.#hasActivity ||\n this.#workingDirectory.uri !== message.workingDirectory.uri;\n const commandMarkChanged = !this.#hasActivity || this.#commandMark?.phase !== message.commandMark?.phase ||\n this.#commandMark?.exitCode !== message.commandMark?.exitCode ||\n this.#commandMark?.rawParameters !== message.commandMark?.rawParameters;\n this.#title = message.title;\n this.#hasTitle = true;\n this.#progress = { ...message.progress };\n this.#shellIntegration = { ...message.shellIntegration };\n this.#workingDirectory = { ...message.workingDirectory };\n this.#commandMark = message.commandMark ? { ...message.commandMark } : null;\n this.#hasActivity = true;\n if (titleChanged) this.#options.onTitleChange?.(this.#title);\n if (!this.#disposed && progressChanged) this.#options.onProgressChange?.(this.progress);\n if (!this.#disposed && shellChanged) this.#options.onShellIntegrationChange?.(this.shellIntegration);\n if (!this.#disposed && workingDirectoryChanged) this.#options.onWorkingDirectoryChange?.(this.workingDirectory);\n if (!this.#disposed && commandMarkChanged) this.#options.onCommandMarkChange?.(this.commandMark);\n }\n } else if (message.type === \"linkSnapshot\") {\n if (message.generation === this.#linkGeneration && message.snapshot.revision === this.#linkRevision)\n this.#acceptLinkSnapshot(message.snapshot);\n } else if (message.type === \"linkDecorations\") {\n const pending = this.#pendingLinks;\n if (this.#connected && pending && pending.serial === message.serial &&\n pending.generation === message.generation && message.generation === this.#linkGeneration &&\n pending.revision === message.revision && message.revision === this.#linkRevision) {\n this.#pendingLinks = undefined;\n this.#replacePresentedLinks(pending.links);\n this.#mouse?.refresh();\n }\n } else if (message.type === \"history\") {\n this.#screenText = message.text;\n this.#history.accept(message.history, message.revision);\n } else if (message.type === \"stats\") {\n this.#stats = message.stats;\n if (message.text !== undefined) this.#screenText = message.text;\n if (message.stats.revision > 0 && this.#hasTitle && this.#hasActivity && this.#connected &&\n (this.#peer.id !== null || this.#peer.isPrimary)) {\n clearTimeout(this.#readyTimer);\n this.#ready.resolve(this);\n }\n this.#options.onStats?.(this.stats, message.text);\n }\n }\n\n #fit() {\n const width = this.#geometry.columns * this.#geometry.cellWidth;\n const height = this.#geometry.rows * this.#geometry.cellHeight;\n const scale = fittedScale(this.#size, this.#geometry, this.#peer.isPrimary, this.#sizing);\n this.#surface.style.width = `${width * scale}px`;\n this.#surface.style.height = `${height * scale}px`;\n // Overlay positions use layout pixels, before any ancestor CSS transforms.\n const style = getComputedStyle(this.#surface);\n this.#canvasSize = { width: Number.parseFloat(style.width), height: Number.parseFloat(style.height) };\n this.#selectionUI?.refresh();\n const dpr = window.devicePixelRatio || 1;\n this.#post({ type: \"viewport\", width: Math.ceil(width * scale * dpr), height: Math.ceil(height * scale * dpr) });\n }\n\n #fittedGrid() {\n return requestedGrid(this.#size, this.#geometry, this.#sizing);\n }\n\n #queueResize(includeFixed = false) {\n if (this.#sizing.mode === \"fixed\" && !includeFixed) return;\n if (!this.#canInput() || !this.#peer.isPrimary || this.#resizeTimer !== undefined || this.#disposed) return;\n // Throttle (rather than debounce) so dragging a primary view updates peers live.\n this.#resizeTimer = setTimeout(() => {\n this.#resizeTimer = undefined;\n const grid = this.#fittedGrid();\n if (!grid || !this.#peer.isPrimary || !this.#canInput()) return;\n const key = `${grid.columns}x${grid.rows}`;\n if ((grid.columns === this.#geometry.columns && grid.rows === this.#geometry.rows) || key === this.#lastRequested) return;\n this.resize(grid.columns, grid.rows);\n }, 50);\n }\n\n #post(message: WorkerInputMessage, transfer: Transferable[] = []): void {\n this.#worker?.postMessage(message, transfer);\n }\n\n #send(command: TerminalCommand): void {\n if (!this.#connected || this.#disposed) throw new Error(\"Terminal view is not connected\");\n if (this.#readOnly && [\"input\", \"paste\", \"key\", \"mouse\", \"resize\", \"requestPrimary\"].includes(command.type))\n throw new Error(\"Terminal view does not accept input\");\n this.#post({ type: \"command\", command });\n }\n\n #inputCommand(command: InputCommand): void {\n if (this.#disposed || !this.#canInput()) return;\n assertCommandSize(command);\n this.#inputSerial++;\n if ([\"input\", \"paste\", \"key\"].includes(command.type) && this.viewport.available) {\n this.#mouse?.cancel();\n if (this.selection.status !== \"none\") this.clearSelection();\n if (!this.viewport.following || this.viewport.pending) this.scrollToLive();\n }\n this.#send(command);\n }\n\n #inspectionChanged() {\n const viewport = this.viewport;\n const selection = this.selection;\n this.#mouse?.refresh();\n if (selection.status === \"invalidated\") this.#mouse?.cancel();\n if (this.#highlights) {\n this.#highlights.replaceChildren(...selection.ranges.map(range => {\n const element = document.createElement(\"span\");\n element.className = \"highlight\";\n element.setAttribute(\"part\", \"selection-highlight\");\n element.style.cssText = `left:${range.startColumn / this.#geometry.columns * 100}%;top:${range.row / this.#geometry.rows * 100}%;width:${(range.endColumn - range.startColumn) / this.#geometry.columns * 100}%;height:${100 / this.#geometry.rows}%`;\n return element;\n }));\n const live = requiredElement(this.#inspection, \".return-live\", HTMLButtonElement);\n live.hidden = !viewport.available || (viewport.following && !viewport.pending);\n live.disabled = !this.#connected;\n this.#renderInspectionStatus();\n }\n if (!this.#disposed) this.#selectionUI?.refresh();\n this.#options.onViewportChange?.(viewport);\n this.#options.onSelectionChange?.(selection);\n }\n\n scrollLines(delta: number): void { this.#history.scroll(delta); }\n scrollToLive() { this.#history.live(); }\n clearSelection() { this.#inspectionError = \"\"; this.#history.clear(); }\n\n /** Re-notifies selection UI hosts after an external styling/policy change. */\n refreshSelectionUI() {\n if (this.#disposed) throw new Error(\"Terminal view is disposed\");\n this.#selectionUI?.refresh(true);\n }\n\n #renderInspectionStatus() {\n if (!this.#inspection) return;\n const selection = this.selection;\n const viewport = this.viewport;\n const status = requiredElement(this.#inspection, \".inspection-message\", HTMLSpanElement);\n status.textContent = this.#selectionUIError || this.#inspectionError ||\n (selection.status === \"unavailable\" ? \"\" : selection.message) ||\n (viewport.available && !viewport.following ? `${viewport.liveTop - viewport.top} rows above live` : \"\");\n status.hidden = !status.textContent;\n status.dataset.level = this.#selectionUIError || this.#inspectionError ||\n selection.status === \"invalidated\" ? \"error\" : \"info\";\n }\n\n #actionFailed(error: unknown): void {\n const failure = error instanceof Error ? error : new Error(String(error));\n this.#inspectionError = `Input action failed: ${failure.message}`;\n this.#inspectionChanged();\n this.#options.onInputError?.(failure);\n }\n\n async copySelection({ clear = false }: CopySelectionOptions = {}): Promise {\n if (typeof clear !== \"boolean\") throw new TypeError(\"Copy clear must be a boolean\");\n const serial = ++this.#copySerial;\n const selectionId = this.selection.requestId;\n const generation = this.viewport.generation;\n this.#inspectionError = \"\";\n try {\n if (!this.#connected || this.#disposed) throw new Error(\"Terminal view is not connected\");\n if (!navigator.clipboard?.write || typeof ClipboardItem !== \"function\") {\n throw new Error(\"Clipboard writing is unavailable. Use a secure browser context with clipboard permission.\");\n }\n const text = this.#history.copy();\n this.#copying = true;\n this.#inspectionChanged();\n // Invoke the clipboard during the user gesture; producer extraction can complete asynchronously.\n const item = new ClipboardItem({ \"text/plain\": text.then(value => new Blob([value], { type: \"text/plain\" })) });\n const [value] = await Promise.all([text, navigator.clipboard.write([item])]);\n if (clear && serial === this.#copySerial && this.selection.status === \"valid\" &&\n this.selection.requestId === selectionId && this.viewport.generation === generation)\n this.clearSelection();\n return value;\n } catch (error) {\n if (serial === this.#copySerial) {\n this.#history.cancelCopy(error);\n this.#inspectionError = `Copy failed: ${errorMessage(error)}`;\n }\n throw error;\n } finally {\n if (serial === this.#copySerial) this.#copying = false;\n this.#inspectionChanged();\n }\n }\n\n #canInput() {\n return this.#connected && this.#hasGeometry && !this.#readOnly &&\n (this.#peer.id !== null || this.#peer.isPrimary);\n }\n\n get inputContext(): TerminalInputContext {\n return Object.freeze({\n terminal: this, selection: this.selection, viewport: this.viewport,\n buffer: this.viewport.buffer ?? null, mouseCaptured: !this.#readOnly && this.#geometry.mouseTracking !== 0,\n historical: !this.viewport.following || this.viewport.pending,\n readOnly: this.#readOnly, connected: this.#connected, peer: this.peer\n });\n }\n\n #resolveInput(input: TerminalInput): InputDecision {\n try {\n const decision = this.#policy.resolve(Object.freeze(input), this.inputContext);\n if (this.#readOnly && decision.route === InputRoute.Application)\n return { route: input.type === \"pointer\" || input.type === \"wheel\" ? InputRoute.Continue : InputRoute.Consume };\n return decision;\n }\n catch (error) {\n this.#actionFailed(error);\n return { route: InputRoute.Consume };\n }\n }\n\n #executeInputAction(decision: Extract, input: TerminalInput): void {\n this.#performAction(decision.action, decision.args, input).catch(error => this.#actionFailed(error));\n }\n\n /** Named actions are shared by controls and bindings. Custom callbacks receive context, args, and input. */\n runAction(action: \"copySelection\", args?: CopySelectionOptions, input?: TerminalInput): Promise;\n runAction(action: \"pasteClipboard\", args?: undefined, input?: TerminalInput): Promise;\n runAction(action: \"copyOrPaste\", args?: undefined, input?: TerminalInput): Promise;\n runAction(action: \"clearSelection\" | \"scrollToLive\", args?: undefined, input?: TerminalInput): Promise;\n runAction(action: \"scrollLines\", args: number, input?: TerminalInput): Promise;\n runAction(action: Name extends TerminalActionName ? never : Name,\n args?: unknown, input?: TerminalInput): Promise;\n runAction(action: InputActionHandler, args?: unknown, input?: TerminalInput): Promise;\n async runAction(action: string | InputActionHandler, args?: unknown, input?: TerminalInput): Promise {\n return this.#performAction(action, args, input);\n }\n\n async #performAction(action: string | InputActionHandler, args?: unknown, input?: TerminalInput): Promise {\n if (this.#disposed) throw new Error(\"Terminal view is disposed\");\n this.#inspectionError = \"\";\n if (typeof action === \"function\") return action(this.inputContext, args, input);\n const customAction = this.#actions.get(action);\n if (customAction) return customAction(this.inputContext, args, input);\n switch (action) {\n case TerminalAction.CopySelection:\n if (args === undefined) return this.copySelection();\n if (!isRecord(args)) throw new TypeError(\"Copy options must be an object\");\n if (args.clear !== undefined && typeof args.clear !== \"boolean\") throw new TypeError(\"Copy clear must be a boolean\");\n return this.copySelection({ clear: args.clear });\n case TerminalAction.PasteClipboard: return this.pasteClipboard();\n case TerminalAction.ClearSelection: return this.clearSelection();\n case TerminalAction.ScrollToLive: return this.scrollToLive();\n case TerminalAction.ScrollLines:\n if (typeof args !== \"number\") throw new RangeError(\"Scroll delta must be a signed 32-bit integer\");\n return this.scrollLines(args);\n case TerminalAction.CopyOrPaste:\n if (this.#clipboardAction) throw new Error(\"A clipboard action is still in progress. Try again when it finishes.\");\n this.#clipboardAction = true;\n try {\n if (this.selection.active || (this.selection.pending && this.selection.canExtend))\n return await this.copySelection({ clear: true });\n if (!this.#readOnly) return await this.pasteClipboard();\n return;\n } finally { this.#clipboardAction = false; }\n default: throw new TypeError(`Unknown terminal action: ${action}`);\n }\n }\n\n /** Sends an explicit paste through the producer's mode-aware input encoder. */\n paste(text: string): void {\n if (typeof text !== \"string\") throw new TypeError(\"Paste text must be a string\");\n if (!this.#canInput()) throw new Error(\"Terminal view does not accept input\");\n if (text) this.#inputCommand({ type: \"paste\", text });\n }\n\n async pasteClipboard(): Promise {\n if (!this.#canInput()) throw new Error(\"Terminal view does not accept input\");\n if (!navigator.clipboard?.readText)\n throw new Error(\"Clipboard reading is unavailable. Use the browser's paste shortcut instead.\");\n const serial = this.#inputSerial;\n const generation = this.viewport.generation;\n const selectionId = this.selection.requestId;\n const focused = document.activeElement;\n const text = await navigator.clipboard.readText();\n if (!this.#canInput() || serial !== this.#inputSerial || generation !== this.viewport.generation ||\n selectionId !== this.selection.requestId || document.activeElement !== focused)\n throw new Error(\"Terminal input, selection, focus, or buffer changed while reading the clipboard. Paste again.\");\n this.paste(text);\n return text;\n }\n\n /** Changes per-view input policy without reconnecting; server authorization remains host-owned. */\n setReadOnly(readOnly: boolean): void {\n if (typeof readOnly !== \"boolean\") throw new TypeError(\"readOnly must be a boolean\");\n if (this.#disposed) throw new Error(\"Terminal view is disposed\");\n if (this.#readOnly === readOnly) return;\n const inputFocused = document.activeElement === this.element &&\n (!this.element.shadowRoot?.activeElement || this.element.shadowRoot.activeElement === this.#input);\n this.#readOnly = readOnly;\n this.#inputSerial++;\n this.#resetComposition?.();\n if (this.#input) {\n this.#input.value = \"\";\n this.#input.disabled = !this.#canInput();\n }\n // Set the policy before cancelling so pending moves and button releases cannot leak.\n this.#mouse?.cancel();\n this.#mouse?.refresh();\n clearTimeout(this.#resizeTimer);\n this.#resizeTimer = undefined;\n this.#lastRequested = undefined;\n this.#queueResize(true);\n if (inputFocused) this.focus();\n this.#selectionUI?.refresh();\n }\n\n setLinks(options: false | TerminalLinkOptions): void {\n if (this.#disposed) throw new Error(\"Terminal view is disposed\");\n if (options === undefined) throw new TypeError(\"Link options must be an object or false\");\n const next = normalizeLinks(options, new Set(this.#actions.keys()));\n this.#links = next;\n this.#linkGeneration++;\n this.#detectedLinks = [];\n this.#replacePresentedLinks([]);\n this.#pendingLinks = undefined;\n this.#linkSnapshot = undefined;\n this.#hoveredLinkId = undefined;\n this.#linkDetector.configure(next ? next.detection : false);\n this.#mouse?.cancel();\n this.#mouse?.refresh();\n this.#postLinkConfiguration();\n }\n\n #postLinkConfiguration(): void {\n const enabled = !!(this.#links && (this.#links.osc8 ||\n (this.#links.detection && this.#links.detection.rules.some(rule => rule.enabled !== false))));\n this.#post({ type: \"linkDetection\", enabled, generation: this.#linkGeneration });\n }\n\n #acceptLinkSnapshot(snapshot: LinkDetectionSnapshot): void {\n this.#linkSnapshot = snapshot;\n this.#detectedLinks = [];\n this.#replacePresentedLinks([]);\n this.#pendingLinks = undefined;\n this.#linkDetector.update(snapshot);\n this.#mouse?.refresh();\n }\n\n #detectedPointerId(link: DetectedLink): string {\n return `detected/${this.#linkGeneration}/${this.#linkRevision}/${link.id}`;\n }\n\n #replacePresentedLinks(links: readonly DetectedLink[]): void {\n this.#presentedLinks = links;\n this.#detectedRows.clear();\n for (const link of links) {\n for (const range of link.activation.ranges) {\n const row = this.#detectedRows.get(range.row) ?? [];\n row.push({ startColumn: range.startColumn, endColumn: range.endColumn, link });\n this.#detectedRows.set(range.row, row);\n }\n }\n }\n\n #requestLinkDecorations(): void {\n if (!this.#worker || !this.#connected || !this.#linkRevision) return;\n const detection = this.#links && this.#links.detection;\n if (!detection) return;\n const visible = detection.decoration === \"none\" ? [] : detection.decoration === \"hover\"\n ? this.#detectedLinks.filter(link => this.#detectedPointerId(link) === this.#hoveredLinkId)\n : this.#detectedLinks;\n this.#pendingLinks = { revision: this.#linkRevision, generation: this.#linkGeneration,\n serial: ++this.#linkSerial, links: this.#detectedLinks };\n this.#post({ type: \"linkDecorations\", revision: this.#linkRevision, generation: this.#linkGeneration,\n serial: this.#linkSerial, ranges: visible.flatMap(link => link.activation.ranges),\n underlineStyle: detection.underlineStyle ?? \"solid\" });\n }\n\n #linkAt(point: TerminalPoint): PointerHyperlink | null {\n if (!this.#connected || this.viewport.pending || !this.#links) return null;\n const osc8 = this.#osc8Rows.get(point.y)?.find(range =>\n point.x >= range.startColumn && point.x < range.endColumn);\n if (osc8) {\n if (this.#links.osc8 === false) return null;\n const target = this.#links.osc8\n ? (!/[\\u0000-\\u0020\\u007f]/u.test(osc8.uri) && URL.canParse(osc8.uri) ? osc8.uri : null)\n : this.#hyperlinks.at(point);\n if (!target || (this.#links.osc8 && this.#linkSnapshot?.revision !== this.#linkRevision)) return null;\n return { id: `osc8/${this.#linkGeneration}/${this.#linkRevision}/${osc8.row}/${osc8.startColumn}/${osc8.endColumn}`,\n target, activation: \"modifierClick\" };\n }\n const detection = this.#links.detection;\n if (!detection) return null;\n const link = this.#detectedRows.get(point.y)?.find(range =>\n point.x >= range.startColumn && point.x < range.endColumn)?.link;\n return link ? { id: this.#detectedPointerId(link), target: link.activation.target,\n activation: detection.activation ?? \"modifierClick\" } : null;\n }\n\n #activateLink(pointer: PointerHyperlink, input: TerminalInput): void {\n if (input.type !== \"pointer\" || this.#linkAt(input.point)?.id !== pointer.id || !this.#links) return;\n const detected = this.#presentedLinks.find(link => this.#detectedPointerId(link) === pointer.id);\n if (detected) {\n this.#performAction(detected.action, detected.activation, input).catch(error => this.#actionFailed(error));\n return;\n }\n if (!this.#links.osc8) {\n try { window.open(pointer.target, \"_blank\", \"noopener,noreferrer\"); }\n catch (error) { this.#actionFailed(error); }\n return;\n }\n const range = this.#osc8Rows.get(input.point.y)?.find(range =>\n input.point.x >= range.startColumn && input.point.x < range.endColumn);\n const snapshot = this.#linkSnapshot;\n if (!range || !snapshot) return;\n let text = \"\";\n for (let column = range.startColumn; column < range.endColumn; column++) {\n const cell = snapshot.cells[range.row * snapshot.columns + column];\n if (cell?.width && !(cell.attributes & 64)) text += cell.text;\n }\n const activation: TerminalLinkActivation = Object.freeze({\n source: \"osc8\", ruleId: null, kind: \"uri\", target: pointer.target, text,\n revision: this.#linkRevision,\n ranges: Object.freeze([Object.freeze({ row: range.row, startColumn: range.startColumn, endColumn: range.endColumn })]),\n });\n this.#performAction(this.#links.osc8.action, activation, input).catch(error => this.#actionFailed(error));\n }\n\n #forwardInput(input: TerminalInput): void {\n if (input.type === \"key\") {\n if (input.meta) throw new Error(\"Meta has no terminal key encoding; bind this shortcut to a local action instead.\");\n this.#inputCommand({ type: \"key\", key: input.key, ctrl: input.ctrl, alt: input.alt, shift: input.shift });\n } else if (input.type === \"paste\") {\n if (this.#canInput()) this.paste(input.text);\n } else if (input.type === \"text\") {\n this.#inputCommand({ type: \"input\", text: input.text });\n }\n }\n\n #dispatchInput(input: TerminalInput, event?: Event, allowApplication = true): void {\n const decision = this.#resolveInput(input);\n // Shortcuts may run on inspection controls, but Enter/Space must still activate the control.\n if (!allowApplication && decision.action === undefined && decision.route !== InputRoute.Consume) return;\n if (decision.route === InputRoute.Browser ||\n (decision.route === InputRoute.Continue && input.type === \"key\")) return;\n event?.preventDefault();\n event?.stopPropagation();\n if (decision.action !== undefined) this.#executeInputAction(decision, input);\n else if (decision.route !== InputRoute.Consume) {\n try { this.#forwardInput(input); }\n catch (error) { this.#actionFailed(error); }\n }\n }\n\n #bindKeyboard() {\n const input = this.#input;\n const options = { signal: this.#listeners.signal };\n let composing = false;\n let compositionCommit: string | null = null;\n this.#resetComposition = () => {\n composing = false;\n compositionCommit = null;\n clearTimeout(this.#compositionTimer);\n };\n this.element.addEventListener(\"keydown\", event => {\n if (event.defaultPrevented) return;\n if (event.composedPath().includes(this.#selectionOverlay)) return;\n const target = event.composedPath()[0];\n if (!this.#connected || event.isComposing || composing || event.key === \"Process\" || event.key === \"Dead\") return;\n if (event.getModifierState(\"AltGraph\")) return;\n this.#dispatchInput({ type: \"key\", key: event.key, code: event.code, repeat: event.repeat,\n ...inputModifiers(event) }, event, target === input || target === this.element);\n }, { ...options, capture: true });\n input.addEventListener(\"paste\", event => {\n if (event.defaultPrevented || !this.#connected || !event.clipboardData) return;\n this.#dispatchInput({ type: \"paste\", text: event.clipboardData.getData(\"text/plain\") }, event);\n input.value = \"\";\n }, options);\n input.addEventListener(\"compositionstart\", () => {\n if (!this.#canInput()) return;\n composing = true;\n compositionCommit = null;\n clearTimeout(this.#compositionTimer);\n }, options);\n input.addEventListener(\"compositionend\", event => {\n if (!composing) return;\n composing = false;\n // Accommodate browsers placing the final input before or after compositionend.\n compositionCommit = typeof event.data === \"string\" ? event.data : input.value;\n this.#compositionTimer = setTimeout(() => {\n if (compositionCommit) this.#dispatchInput({ type: \"text\", text: compositionCommit });\n compositionCommit = null;\n input.value = \"\";\n }, 0);\n }, options);\n input.addEventListener(\"input\", event => {\n const inputEvent = event instanceof InputEvent ? event : undefined;\n if (inputEvent?.isComposing || composing) return;\n clearTimeout(this.#compositionTimer);\n const text = compositionCommit ?? inputEvent?.data ?? input.value;\n compositionCommit = null;\n if (text && inputEvent?.inputType !== \"insertFromPaste\") this.#dispatchInput({ type: \"text\", text }, event);\n input.value = \"\";\n }, options);\n }\n\n focus() {\n if (this.#disposed) return;\n const target = this.#canInput() ? this.#input : this.element;\n target.focus({ preventScroll: true });\n }\n\n /** Request HMP1 primary explicitly; peer notifications confirm the result. */\n requestPrimary() {\n if (!this.#canInput()) throw new Error(\"Terminal view does not accept input\");\n if (this.#size.width <= 0 || this.#size.height <= 0) throw new Error(\"Show the terminal container before taking primary\");\n const grid = this.#fittedGrid();\n if (!grid) throw new Error(\"Show the terminal container before taking primary\");\n if (this.#peer.id === null) {\n if (!this.#peer.isPrimary) throw new Error(\"Waiting for the HMP1 connection\");\n this.resize(grid.columns, grid.rows);\n return;\n }\n this.#send({ type: \"requestPrimary\", ...grid });\n }\n\n /** Request a grid; never reflow locally before the authoritative response. */\n resize(columns: number, rows: number): void {\n const grid = dimensions(columns, rows);\n if (!this.#canInput()) throw new Error(\"Terminal view does not accept input\");\n if (!this.#peer.isPrimary) throw new Error(\"Only the primary view can request a terminal resize\");\n this.#send({ type: \"resize\", ...grid });\n this.#lastRequested = `${columns}x${rows}`;\n }\n\n /** Change the primary's sizing policy; applied grid dimensions still come from the server. */\n setSizing(sizing: TerminalSizing): void {\n const next = normalizeSizing(sizing, this.#sizing.fontSize);\n if (!this.#connected || this.#disposed) throw new Error(\"Terminal view is not connected\");\n if (this.#readOnly) throw new Error(\"Terminal view does not accept input\");\n if (!this.#peer.isPrimary) throw new Error(\"Only the primary view can change terminal sizing\");\n this.#sizing = next;\n clearTimeout(this.#resizeTimer);\n this.#resizeTimer = undefined;\n this.#lastRequested = undefined;\n this.#fit();\n this.#queueResize(true);\n this.#options.onSizingChange?.(this.sizing);\n }\n\n resync() { this.#send({ type: \"resync\" }); }\n\n #disconnect() {\n this.#inputSerial++;\n this.#connected = false;\n this.#hyperlinks.update([]);\n this.#osc8Rows.clear();\n this.#detectedLinks = [];\n this.#replacePresentedLinks([]);\n this.#linkSnapshot = undefined;\n this.#pendingLinks = undefined;\n this.#linkDetector.clear();\n if (this.#input) this.#input.disabled = true;\n this.#mouse?.update(1, 1, 0);\n this.#history.disconnect();\n this.#inspectionChanged();\n clearTimeout(this.#resizeTimer);\n this.#resizeTimer = undefined;\n }\n\n #fail(error: Error): void {\n this.#disconnect();\n this.#ready.reject(error);\n this.#options.onStatus?.(error.message, \"error\");\n this.#worker?.terminate();\n }\n\n /** Detach this view. The server-side shared terminal is not terminated. */\n dispose() {\n if (this.#disposed) return;\n this.#disposed = true;\n this.#disconnect();\n this.#linkDetector.dispose();\n this.#ready.reject(new DOMException(\"Terminal view was disposed\", \"AbortError\"));\n clearTimeout(this.#readyTimer);\n clearTimeout(this.#compositionTimer);\n this.#observer?.disconnect();\n this.#mouse?.dispose();\n this.#listeners.abort();\n this.#post({ type: \"stop\" });\n this.#worker?.terminate();\n this.element.remove();\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts deleted file mode 100644 index 8f82556cd8b..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { RenderBackend, RenderBatch, RenderColor, RenderTexture } from "./render-backend.js"; -export declare class WebGl2Backend implements RenderBackend { - private readonly canvas; - readonly gl: WebGL2RenderingContext; - private readonly onFatal; - readonly kind = "webgl2"; - private textureLimit; - private canvasLimit; - private bufferBytes; - private disposed; - private lostError?; - private program?; - private instanceBuffer?; - private vertexArray?; - private viewport?; - private readonly shaders; - private readonly textures; - private readonly fences; - private readonly onContextLost; - static create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise; - private constructor(); - get maxTextureDimension2D(): number; - get maxCanvasDimension2D(): number; - get instanceBufferBytes(): number; - private initialize; - private compile; - private assertActive; - private contextLost; - private checkErrors; - createTexture(width: number, height: number, label: string): RenderTexture; - resize(width: number, height: number): void; - submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[], background: RenderColor): void; - idle(): Promise; - private pollFence; - private settleFence; - dispose(): void; -} -//# sourceMappingURL=webgl2-backend.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts.map deleted file mode 100644 index f14d4f8c95d..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"webgl2-backend.d.ts","sourceRoot":"","sources":["../src/webgl2-backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAgB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAqHhH,qBAAa,aAAc,YAAW,aAAa;IAuC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAmB,QAAQ,CAAC,EAAE,EAAE,sBAAsB;IAC/F,OAAO,CAAC,QAAQ,CAAC,OAAO;IAvC1B,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAc;IACrC,OAAO,CAAC,WAAW,CAAC,CAAyB;IAC7C,OAAO,CAAC,QAAQ,CAAC,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAG5B;WAEW,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IAoBrG,OAAO;IAGP,IAAI,qBAAqB,IAAI,MAAM,CAA8B;IACjE,IAAI,oBAAoB,IAAI,MAAM,CAA6B;IAC/D,IAAI,mBAAmB,IAAI,MAAM,CAA6B;IAE9D,OAAO,CAAC,UAAU;IA2DlB,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IAmBnB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa;IAgC1E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAoB3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,EAC7F,UAAU,EAAE,WAAW,GAAG,IAAI;IA+C1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB3B,OAAO,CAAC,SAAS;IA4BjB,OAAO,CAAC,WAAW;IAQnB,OAAO,IAAI,IAAI;CAkBhB"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js deleted file mode 100644 index 80f37283405..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js +++ /dev/null @@ -1,456 +0,0 @@ -import { QUAD_STRIDE, RendererUnavailableError } from "./render-backend.js"; -const vertexShader = /* glsl */ `#version 300 es -precision highp float; -precision highp int; - -layout(location = 0) in vec4 rect; -layout(location = 1) in vec4 uvRect; -layout(location = 2) in vec4 color; -layout(location = 3) in float mode; -uniform vec2 viewport; -out vec2 fragmentUv; -out vec4 fragmentTint; -flat out float fragmentMode; - -void main() { - const vec2 corners[6] = vec2[6]( - vec2(0, 0), vec2(1, 0), vec2(0, 1), - vec2(0, 1), vec2(1, 0), vec2(1, 1) - ); - vec2 corner = corners[gl_VertexID]; - vec2 position = rect.xy + corner * rect.zw; - gl_Position = vec4(position / viewport * vec2(2, -2) + vec2(-1, 1), 0, 1); - fragmentUv = mix(uvRect.xy, uvRect.zw, corner); - fragmentTint = color; - fragmentMode = mode; -}`; -const fragmentShader = /* glsl */ `#version 300 es -precision highp float; -precision highp int; - -uniform highp sampler2D image; -in vec2 fragmentUv; -in vec4 fragmentTint; -flat in float fragmentMode; -out vec4 fragmentColor; - -void main() { - vec4 texel = textureLod(image, fragmentUv, 0.0); - if (fragmentMode < 0.5) { - fragmentColor = fragmentTint; - } else if (fragmentMode < 1.5) { - fragmentColor = vec4(fragmentTint.rgb, fragmentTint.a * texel.a); - } else { - fragmentColor = texel * fragmentTint; - } -}`; -function errorFrom(value) { - return value instanceof Error ? value : new Error(String(value)); -} -function positiveInteger(value) { - return Number.isSafeInteger(value) && value > 0; -} -function prepareUpload(gl) { - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); - gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); - gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0); - gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0); - gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0); -} -class WebGl2Texture { - owner; - gl; - texture; - width; - height; - assertActive; - release; - destroyed = false; - constructor(owner, gl, texture, width, height, assertActive, release) { - this.owner = owner; - this.gl = gl; - this.texture = texture; - this.width = width; - this.height = height; - this.assertActive = assertActive; - this.release = release; - } - bind() { - this.assertActive(); - if (this.destroyed) - throw new Error("WebGL2 texture is destroyed"); - this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); - prepareUpload(this.gl); - } - writePixels(pixels, width, height, x = 0, y = 0) { - if (!positiveInteger(width) || !positiveInteger(height) || - !Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 || - x + width > this.width || y + height > this.height || pixels.byteLength < width * height * 4) { - throw new Error("Invalid WebGL2 texture upload dimensions or pixel data"); - } - this.bind(); - this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, x, y, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels); - } - writeBitmap(bitmap) { - if (bitmap.width !== this.width || bitmap.height !== this.height) { - throw new Error("WebGL2 bitmap dimensions do not match the texture"); - } - this.bind(); - // ImageBitmap ignores unpack conversion flags; the shared decoder supplies straight-alpha, - // unconverted, top-down bitmaps. Raw RGBA uploads use the explicit unpack state above. - this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bitmap); - } - destroy() { - if (this.destroyed) - return; - this.destroyed = true; - this.release(this); - this.gl.deleteTexture(this.texture); - } -} -export class WebGl2Backend { - canvas; - gl; - onFatal; - kind = "webgl2"; - textureLimit = 0; - canvasLimit = 0; - bufferBytes = 0; - disposed = false; - lostError; - program; - instanceBuffer; - vertexArray; - viewport; - shaders = new Set(); - textures = new Set(); - fences = new Set(); - onContextLost = (event) => { - const message = event.statusMessage; - this.contextLost(new Error(`WebGL2 context lost${message ? `: ${message}` : ""}`)); - }; - static async create(canvas, onFatal) { - const gl = canvas.getContext("webgl2", { - alpha: false, - antialias: false, - depth: false, - stencil: false, - premultipliedAlpha: false, - preserveDrawingBuffer: false, - }); - if (!gl) - throw new RendererUnavailableError("Could not create an OffscreenCanvas WebGL2 context"); - const backend = new WebGl2Backend(canvas, gl, onFatal); - try { - backend.initialize(); - return backend; - } - catch (error) { - backend.dispose(); - throw error; - } - } - constructor(canvas, gl, onFatal) { - this.canvas = canvas; - this.gl = gl; - this.onFatal = onFatal; - } - get maxTextureDimension2D() { return this.textureLimit; } - get maxCanvasDimension2D() { return this.canvasLimit; } - get instanceBufferBytes() { return this.bufferBytes; } - initialize() { - const gl = this.gl; - this.canvas.addEventListener("webglcontextlost", this.onContextLost); - this.checkErrors("initialization"); - this.textureLimit = gl.getParameter(gl.MAX_TEXTURE_SIZE); - const viewportLimit = gl.getParameter(gl.MAX_VIEWPORT_DIMS); - this.canvasLimit = Math.min(this.textureLimit, gl.getParameter(gl.MAX_RENDERBUFFER_SIZE), viewportLimit[0], viewportLimit[1]); - if (!positiveInteger(this.textureLimit) || !positiveInteger(this.canvasLimit)) { - throw new Error("Invalid WebGL2 texture or viewport limits"); - } - const vertex = this.compile(gl.VERTEX_SHADER, vertexShader); - const fragment = this.compile(gl.FRAGMENT_SHADER, fragmentShader); - const program = gl.createProgram(); - if (!program) - throw new Error("Could not allocate a WebGL2 shader program"); - this.program = program; - gl.attachShader(program, vertex); - gl.attachShader(program, fragment); - gl.linkProgram(program); - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - throw new Error(`WebGL2 shader link failed: ${gl.getProgramInfoLog(program) || "No diagnostic available"}`); - } - for (const shader of this.shaders) { - gl.detachShader(program, shader); - gl.deleteShader(shader); - } - this.shaders.clear(); - const viewport = gl.getUniformLocation(program, "viewport"); - const image = gl.getUniformLocation(program, "image"); - if (viewport === null || image === null) - throw new Error("WebGL2 shader uniforms are unavailable"); - this.viewport = viewport; - const buffer = gl.createBuffer(); - if (!buffer) - throw new Error("Could not allocate a WebGL2 instance buffer"); - this.instanceBuffer = buffer; - const vertexArray = gl.createVertexArray(); - if (!vertexArray) - throw new Error("Could not allocate a WebGL2 vertex array"); - this.vertexArray = vertexArray; - gl.useProgram(program); - gl.uniform1i(image, 0); - gl.activeTexture(gl.TEXTURE0); - gl.bindVertexArray(vertexArray); - gl.bindBuffer(gl.ARRAY_BUFFER, buffer); - for (let attribute = 0; attribute < 4; attribute++) { - gl.enableVertexAttribArray(attribute); - gl.vertexAttribDivisor(attribute, 1); - } - gl.enable(gl.BLEND); - gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); - gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.STENCIL_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.SCISSOR_TEST); - gl.disable(gl.DITHER); - this.resize(this.canvas.width, this.canvas.height); - } - compile(type, source) { - const gl = this.gl; - const shader = gl.createShader(type); - if (!shader) - throw new Error("Could not allocate a WebGL2 shader"); - this.shaders.add(shader); - gl.shaderSource(shader, source); - gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - throw new Error(`WebGL2 shader compilation failed: ${gl.getShaderInfoLog(shader) || "No diagnostic available"}`); - } - return shader; - } - assertActive() { - if (this.disposed) - throw new Error("WebGL2 backend is disposed"); - if (this.lostError) - throw this.lostError; - } - contextLost(error) { - if (this.disposed || this.lostError) - return; - this.lostError = error; - for (const fence of this.fences) - this.settleFence(fence, error); - this.onFatal(error); - } - checkErrors(operation) { - this.assertActive(); - const gl = this.gl; - const code = gl.getError(); - if (code === gl.CONTEXT_LOST_WEBGL || gl.isContextLost()) { - const error = new Error("WebGL2 context lost"); - this.contextLost(error); - throw error; - } - if (code !== gl.NO_ERROR) { - const name = code === gl.OUT_OF_MEMORY ? "OUT_OF_MEMORY" : - code === gl.INVALID_ENUM ? "INVALID_ENUM" : - code === gl.INVALID_VALUE ? "INVALID_VALUE" : - code === gl.INVALID_OPERATION ? "INVALID_OPERATION" : - code === gl.INVALID_FRAMEBUFFER_OPERATION ? "INVALID_FRAMEBUFFER_OPERATION" : `0x${code.toString(16)}`; - throw new Error(`WebGL2 ${operation} failed: ${name}`); - } - } - createTexture(width, height, label) { - this.assertActive(); - if (!positiveInteger(width) || !positiveInteger(height) || - width > this.textureLimit || height > this.textureLimit) { - throw new Error(`${label} has invalid dimensions or exceeds the WebGL2 texture dimension limit`); - } - const gl = this.gl; - const texture = gl.createTexture(); - if (!texture) { - this.checkErrors("texture allocation"); - throw new Error(`Could not allocate WebGL2 texture: ${label}`); - } - try { - gl.bindTexture(gl.TEXTURE_2D, texture); - prepareUpload(gl); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - // WebGL guarantees zero initialization for null data, including untouched atlas padding. - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - this.checkErrors(`texture allocation (${label})`); - const resource = new WebGl2Texture(this, gl, texture, width, height, () => this.assertActive(), resource => this.textures.delete(resource)); - this.textures.add(resource); - return resource; - } - catch (error) { - gl.deleteTexture(texture); - throw error; - } - } - resize(width, height) { - this.assertActive(); - if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { - throw new Error("Invalid WebGL2 logical viewport dimensions"); - } - if (!positiveInteger(this.canvas.width) || !positiveInteger(this.canvas.height) || - this.canvas.width > this.canvasLimit || this.canvas.height > this.canvasLimit) { - throw new Error("Canvas exceeds the WebGL2 drawing buffer dimension limit"); - } - const gl = this.gl; - if (gl.drawingBufferWidth !== this.canvas.width || gl.drawingBufferHeight !== this.canvas.height) { - this.checkErrors("drawing buffer resize"); - throw new Error("WebGL2 could not allocate the requested canvas drawing buffer"); - } - gl.viewport(0, 0, this.canvas.width, this.canvas.height); - gl.useProgram(this.program); - gl.uniform2f(this.viewport, width, height); - this.checkErrors("resize"); - } - submit(instances, quadCount, batches, background) { - this.assertActive(); - if (!Number.isSafeInteger(quadCount) || quadCount < 0 || quadCount * QUAD_STRIDE > instances.length) { - throw new Error("Invalid WebGL2 instance data"); - } - for (const batch of batches) { - if (!(batch.resource instanceof WebGl2Texture) || batch.resource.owner !== this) { - throw new Error("Texture belongs to a different rendering backend"); - } - if (batch.resource.destroyed) - throw new Error("WebGL2 texture is destroyed"); - if (!Number.isSafeInteger(batch.start) || !Number.isSafeInteger(batch.count) || - batch.start < 0 || batch.count < 0 || batch.start + batch.count > quadCount) { - throw new Error("Invalid WebGL2 batch instance range"); - } - } - const gl = this.gl; - const strideBytes = QUAD_STRIDE * Float32Array.BYTES_PER_ELEMENT; - const usedBytes = quadCount * strideBytes; - gl.bindVertexArray(this.vertexArray); - gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer); - if (!this.bufferBytes || usedBytes > this.bufferBytes) { - const bytes = Math.max(256, instances.byteLength); - gl.bufferData(gl.ARRAY_BUFFER, bytes, gl.DYNAMIC_DRAW); - this.checkErrors("instance buffer allocation"); - this.bufferBytes = bytes; - } - if (usedBytes) - gl.bufferSubData(gl.ARRAY_BUFFER, 0, instances, 0, quadCount * QUAD_STRIDE); - gl.useProgram(this.program); - gl.activeTexture(gl.TEXTURE0); - gl.clearColor(background[0], background[1], background[2], 1); - gl.clear(gl.COLOR_BUFFER_BIT); - for (const batch of batches) { - if (!batch.count) - continue; - const resource = batch.resource; - gl.bindTexture(gl.TEXTURE_2D, resource.texture); - // WebGL2 has no baseInstance; rebase all per-instance attributes for each ordered batch. - const offset = batch.start * strideBytes; - gl.vertexAttribPointer(0, 4, gl.FLOAT, false, strideBytes, offset); - gl.vertexAttribPointer(1, 4, gl.FLOAT, false, strideBytes, offset + 16); - gl.vertexAttribPointer(2, 4, gl.FLOAT, false, strideBytes, offset + 32); - gl.vertexAttribPointer(3, 1, gl.FLOAT, false, strideBytes, offset + 48); - gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, batch.count); - } - // Upload errors are checked once per submission, not once per glyph. - this.checkErrors("frame submission"); - } - async idle() { - this.checkErrors("GPU completion"); - const gl = this.gl; - const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0); - if (!sync) { - this.checkErrors("GPU fence allocation"); - throw new Error("Could not allocate a WebGL2 GPU fence"); - } - return new Promise((resolve, reject) => { - const fence = { sync, resolve, reject }; - this.fences.add(fence); - try { - gl.flush(); - this.checkErrors("GPU fence submission"); - this.pollFence(fence); - } - catch (error) { - this.settleFence(fence, errorFrom(error)); - } - }); - } - pollFence(fence) { - if (!this.fences.has(fence)) - return; - try { - this.assertActive(); - const gl = this.gl; - if (gl.isContextLost()) { - const error = new Error("WebGL2 context lost"); - this.contextLost(error); - throw error; - } - const status = gl.clientWaitSync(fence.sync, 0, 0); - if (status === gl.ALREADY_SIGNALED || status === gl.CONDITION_SATISFIED) { - this.checkErrors("GPU completion"); - this.settleFence(fence); - } - else if (status === gl.TIMEOUT_EXPIRED) { - fence.timer = setTimeout(() => { - fence.timer = undefined; - this.pollFence(fence); - }, 0); - } - else { - this.checkErrors("GPU fence wait"); - throw new Error("WebGL2 GPU fence wait failed"); - } - } - catch (error) { - this.settleFence(fence, errorFrom(error)); - } - } - settleFence(fence, error) { - if (!this.fences.delete(fence)) - return; - if (fence.timer !== undefined) - clearTimeout(fence.timer); - this.gl.deleteSync(fence.sync); - if (error) - fence.reject(error); - else - fence.resolve(); - } - dispose() { - if (this.disposed) - return; - this.disposed = true; - this.canvas.removeEventListener("webglcontextlost", this.onContextLost); - for (const fence of this.fences) - this.settleFence(fence, new Error("WebGL2 backend is disposed")); - for (const texture of this.textures) - texture.destroy(); - const gl = this.gl; - if (this.instanceBuffer) - gl.deleteBuffer(this.instanceBuffer); - if (this.vertexArray) - gl.deleteVertexArray(this.vertexArray); - if (this.program) - gl.deleteProgram(this.program); - for (const shader of this.shaders) - gl.deleteShader(shader); - this.shaders.clear(); - this.instanceBuffer = undefined; - this.vertexArray = undefined; - this.program = undefined; - this.bufferBytes = 0; - if (!gl.isContextLost()) - gl.getExtension("WEBGL_lose_context")?.loseContext(); - } -} -//# sourceMappingURL=webgl2-backend.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js.map deleted file mode 100644 index c581d7fa752..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgl2-backend.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"webgl2-backend.js","sourceRoot":"","sources":["../src/webgl2-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { QUAD_STRIDE, RendererUnavailableError } from \"./render-backend.js\";\nimport type { RenderBackend, RenderBatch, RenderColor, RenderPixels, RenderTexture } from \"./render-backend.js\";\n\nconst vertexShader = /* glsl */ `#version 300 es\nprecision highp float;\nprecision highp int;\n\nlayout(location = 0) in vec4 rect;\nlayout(location = 1) in vec4 uvRect;\nlayout(location = 2) in vec4 color;\nlayout(location = 3) in float mode;\nuniform vec2 viewport;\nout vec2 fragmentUv;\nout vec4 fragmentTint;\nflat out float fragmentMode;\n\nvoid main() {\n const vec2 corners[6] = vec2[6](\n vec2(0, 0), vec2(1, 0), vec2(0, 1),\n vec2(0, 1), vec2(1, 0), vec2(1, 1)\n );\n vec2 corner = corners[gl_VertexID];\n vec2 position = rect.xy + corner * rect.zw;\n gl_Position = vec4(position / viewport * vec2(2, -2) + vec2(-1, 1), 0, 1);\n fragmentUv = mix(uvRect.xy, uvRect.zw, corner);\n fragmentTint = color;\n fragmentMode = mode;\n}`;\n\nconst fragmentShader = /* glsl */ `#version 300 es\nprecision highp float;\nprecision highp int;\n\nuniform highp sampler2D image;\nin vec2 fragmentUv;\nin vec4 fragmentTint;\nflat in float fragmentMode;\nout vec4 fragmentColor;\n\nvoid main() {\n vec4 texel = textureLod(image, fragmentUv, 0.0);\n if (fragmentMode < 0.5) {\n fragmentColor = fragmentTint;\n } else if (fragmentMode < 1.5) {\n fragmentColor = vec4(fragmentTint.rgb, fragmentTint.a * texel.a);\n } else {\n fragmentColor = texel * fragmentTint;\n }\n}`;\n\nfunction errorFrom(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction positiveInteger(value: number): boolean {\n return Number.isSafeInteger(value) && value > 0;\n}\n\nfunction prepareUpload(gl: WebGL2RenderingContext): void {\n gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);\n gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0);\n gl.pixelStorei(gl.UNPACK_SKIP_PIXELS, 0);\n gl.pixelStorei(gl.UNPACK_SKIP_ROWS, 0);\n}\n\nclass WebGl2Texture implements RenderTexture {\n destroyed = false;\n\n constructor(readonly owner: WebGl2Backend, private readonly gl: WebGL2RenderingContext,\n readonly texture: WebGLTexture, readonly width: number, readonly height: number,\n private readonly assertActive: () => void, private readonly release: (texture: WebGl2Texture) => void) {}\n\n private bind(): void {\n this.assertActive();\n if (this.destroyed) throw new Error(\"WebGL2 texture is destroyed\");\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n prepareUpload(this.gl);\n }\n\n writePixels(pixels: RenderPixels, width: number, height: number, x = 0, y = 0): void {\n if (!positiveInteger(width) || !positiveInteger(height) ||\n !Number.isSafeInteger(x) || !Number.isSafeInteger(y) || x < 0 || y < 0 ||\n x + width > this.width || y + height > this.height || pixels.byteLength < width * height * 4) {\n throw new Error(\"Invalid WebGL2 texture upload dimensions or pixel data\");\n }\n this.bind();\n this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, x, y, width, height,\n this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels);\n }\n\n writeBitmap(bitmap: ImageBitmap): void {\n if (bitmap.width !== this.width || bitmap.height !== this.height) {\n throw new Error(\"WebGL2 bitmap dimensions do not match the texture\");\n }\n this.bind();\n // ImageBitmap ignores unpack conversion flags; the shared decoder supplies straight-alpha,\n // unconverted, top-down bitmaps. Raw RGBA uploads use the explicit unpack state above.\n this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, 0, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, bitmap);\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.release(this);\n this.gl.deleteTexture(this.texture);\n }\n}\n\ninterface PendingFence {\n sync: WebGLSync;\n timer?: ReturnType;\n resolve: () => void;\n reject: (error: Error) => void;\n}\n\nexport class WebGl2Backend implements RenderBackend {\n readonly kind = \"webgl2\";\n private textureLimit = 0;\n private canvasLimit = 0;\n private bufferBytes = 0;\n private disposed = false;\n private lostError?: Error;\n private program?: WebGLProgram;\n private instanceBuffer?: WebGLBuffer;\n private vertexArray?: WebGLVertexArrayObject;\n private viewport?: WebGLUniformLocation;\n private readonly shaders = new Set();\n private readonly textures = new Set();\n private readonly fences = new Set();\n private readonly onContextLost = (event: Event): void => {\n const message = (event as WebGLContextEvent).statusMessage;\n this.contextLost(new Error(`WebGL2 context lost${message ? `: ${message}` : \"\"}`));\n };\n\n static async create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise {\n const gl = canvas.getContext(\"webgl2\", {\n alpha: false,\n antialias: false,\n depth: false,\n stencil: false,\n premultipliedAlpha: false,\n preserveDrawingBuffer: false,\n });\n if (!gl) throw new RendererUnavailableError(\"Could not create an OffscreenCanvas WebGL2 context\");\n const backend = new WebGl2Backend(canvas, gl, onFatal);\n try {\n backend.initialize();\n return backend;\n } catch (error) {\n backend.dispose();\n throw error;\n }\n }\n\n private constructor(private readonly canvas: OffscreenCanvas, readonly gl: WebGL2RenderingContext,\n private readonly onFatal: (error: Error) => void) {}\n\n get maxTextureDimension2D(): number { return this.textureLimit; }\n get maxCanvasDimension2D(): number { return this.canvasLimit; }\n get instanceBufferBytes(): number { return this.bufferBytes; }\n\n private initialize(): void {\n const gl = this.gl;\n this.canvas.addEventListener(\"webglcontextlost\", this.onContextLost);\n this.checkErrors(\"initialization\");\n this.textureLimit = gl.getParameter(gl.MAX_TEXTURE_SIZE);\n const viewportLimit = gl.getParameter(gl.MAX_VIEWPORT_DIMS) as Int32Array;\n this.canvasLimit = Math.min(this.textureLimit, gl.getParameter(gl.MAX_RENDERBUFFER_SIZE),\n viewportLimit[0], viewportLimit[1]);\n if (!positiveInteger(this.textureLimit) || !positiveInteger(this.canvasLimit)) {\n throw new Error(\"Invalid WebGL2 texture or viewport limits\");\n }\n\n const vertex = this.compile(gl.VERTEX_SHADER, vertexShader);\n const fragment = this.compile(gl.FRAGMENT_SHADER, fragmentShader);\n const program = gl.createProgram();\n if (!program) throw new Error(\"Could not allocate a WebGL2 shader program\");\n this.program = program;\n gl.attachShader(program, vertex);\n gl.attachShader(program, fragment);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(`WebGL2 shader link failed: ${gl.getProgramInfoLog(program) || \"No diagnostic available\"}`);\n }\n for (const shader of this.shaders) {\n gl.detachShader(program, shader);\n gl.deleteShader(shader);\n }\n this.shaders.clear();\n const viewport = gl.getUniformLocation(program, \"viewport\");\n const image = gl.getUniformLocation(program, \"image\");\n if (viewport === null || image === null) throw new Error(\"WebGL2 shader uniforms are unavailable\");\n this.viewport = viewport;\n const buffer = gl.createBuffer();\n if (!buffer) throw new Error(\"Could not allocate a WebGL2 instance buffer\");\n this.instanceBuffer = buffer;\n const vertexArray = gl.createVertexArray();\n if (!vertexArray) throw new Error(\"Could not allocate a WebGL2 vertex array\");\n this.vertexArray = vertexArray;\n\n gl.useProgram(program);\n gl.uniform1i(image, 0);\n gl.activeTexture(gl.TEXTURE0);\n gl.bindVertexArray(vertexArray);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n for (let attribute = 0; attribute < 4; attribute++) {\n gl.enableVertexAttribArray(attribute);\n gl.vertexAttribDivisor(attribute, 1);\n }\n gl.enable(gl.BLEND);\n gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);\n gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.disable(gl.DEPTH_TEST);\n gl.disable(gl.STENCIL_TEST);\n gl.disable(gl.CULL_FACE);\n gl.disable(gl.SCISSOR_TEST);\n gl.disable(gl.DITHER);\n this.resize(this.canvas.width, this.canvas.height);\n }\n\n private compile(type: number, source: string): WebGLShader {\n const gl = this.gl;\n const shader = gl.createShader(type);\n if (!shader) throw new Error(\"Could not allocate a WebGL2 shader\");\n this.shaders.add(shader);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(`WebGL2 shader compilation failed: ${gl.getShaderInfoLog(shader) || \"No diagnostic available\"}`);\n }\n return shader;\n }\n\n private assertActive(): void {\n if (this.disposed) throw new Error(\"WebGL2 backend is disposed\");\n if (this.lostError) throw this.lostError;\n }\n\n private contextLost(error: Error): void {\n if (this.disposed || this.lostError) return;\n this.lostError = error;\n for (const fence of this.fences) this.settleFence(fence, error);\n this.onFatal(error);\n }\n\n private checkErrors(operation: string): void {\n this.assertActive();\n const gl = this.gl;\n const code = gl.getError();\n if (code === gl.CONTEXT_LOST_WEBGL || gl.isContextLost()) {\n const error = new Error(\"WebGL2 context lost\");\n this.contextLost(error);\n throw error;\n }\n if (code !== gl.NO_ERROR) {\n const name = code === gl.OUT_OF_MEMORY ? \"OUT_OF_MEMORY\" :\n code === gl.INVALID_ENUM ? \"INVALID_ENUM\" :\n code === gl.INVALID_VALUE ? \"INVALID_VALUE\" :\n code === gl.INVALID_OPERATION ? \"INVALID_OPERATION\" :\n code === gl.INVALID_FRAMEBUFFER_OPERATION ? \"INVALID_FRAMEBUFFER_OPERATION\" : `0x${code.toString(16)}`;\n throw new Error(`WebGL2 ${operation} failed: ${name}`);\n }\n }\n\n createTexture(width: number, height: number, label: string): RenderTexture {\n this.assertActive();\n if (!positiveInteger(width) || !positiveInteger(height) ||\n width > this.textureLimit || height > this.textureLimit) {\n throw new Error(`${label} has invalid dimensions or exceeds the WebGL2 texture dimension limit`);\n }\n const gl = this.gl;\n const texture = gl.createTexture();\n if (!texture) {\n this.checkErrors(\"texture allocation\");\n throw new Error(`Could not allocate WebGL2 texture: ${label}`);\n }\n try {\n gl.bindTexture(gl.TEXTURE_2D, texture);\n prepareUpload(gl);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n // WebGL guarantees zero initialization for null data, including untouched atlas padding.\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n this.checkErrors(`texture allocation (${label})`);\n const resource = new WebGl2Texture(this, gl, texture, width, height,\n () => this.assertActive(), resource => this.textures.delete(resource));\n this.textures.add(resource);\n return resource;\n } catch (error) {\n gl.deleteTexture(texture);\n throw error;\n }\n }\n\n resize(width: number, height: number): void {\n this.assertActive();\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {\n throw new Error(\"Invalid WebGL2 logical viewport dimensions\");\n }\n if (!positiveInteger(this.canvas.width) || !positiveInteger(this.canvas.height) ||\n this.canvas.width > this.canvasLimit || this.canvas.height > this.canvasLimit) {\n throw new Error(\"Canvas exceeds the WebGL2 drawing buffer dimension limit\");\n }\n const gl = this.gl;\n if (gl.drawingBufferWidth !== this.canvas.width || gl.drawingBufferHeight !== this.canvas.height) {\n this.checkErrors(\"drawing buffer resize\");\n throw new Error(\"WebGL2 could not allocate the requested canvas drawing buffer\");\n }\n gl.viewport(0, 0, this.canvas.width, this.canvas.height);\n gl.useProgram(this.program!);\n gl.uniform2f(this.viewport!, width, height);\n this.checkErrors(\"resize\");\n }\n\n submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[],\n background: RenderColor): void {\n this.assertActive();\n if (!Number.isSafeInteger(quadCount) || quadCount < 0 || quadCount * QUAD_STRIDE > instances.length) {\n throw new Error(\"Invalid WebGL2 instance data\");\n }\n for (const batch of batches) {\n if (!(batch.resource instanceof WebGl2Texture) || batch.resource.owner !== this) {\n throw new Error(\"Texture belongs to a different rendering backend\");\n }\n if (batch.resource.destroyed) throw new Error(\"WebGL2 texture is destroyed\");\n if (!Number.isSafeInteger(batch.start) || !Number.isSafeInteger(batch.count) ||\n batch.start < 0 || batch.count < 0 || batch.start + batch.count > quadCount) {\n throw new Error(\"Invalid WebGL2 batch instance range\");\n }\n }\n const gl = this.gl;\n const strideBytes = QUAD_STRIDE * Float32Array.BYTES_PER_ELEMENT;\n const usedBytes = quadCount * strideBytes;\n gl.bindVertexArray(this.vertexArray!);\n gl.bindBuffer(gl.ARRAY_BUFFER, this.instanceBuffer!);\n if (!this.bufferBytes || usedBytes > this.bufferBytes) {\n const bytes = Math.max(256, instances.byteLength);\n gl.bufferData(gl.ARRAY_BUFFER, bytes, gl.DYNAMIC_DRAW);\n this.checkErrors(\"instance buffer allocation\");\n this.bufferBytes = bytes;\n }\n if (usedBytes) gl.bufferSubData(gl.ARRAY_BUFFER, 0, instances, 0, quadCount * QUAD_STRIDE);\n gl.useProgram(this.program!);\n gl.activeTexture(gl.TEXTURE0);\n gl.clearColor(background[0], background[1], background[2], 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n for (const batch of batches) {\n if (!batch.count) continue;\n const resource = batch.resource as WebGl2Texture;\n gl.bindTexture(gl.TEXTURE_2D, resource.texture);\n // WebGL2 has no baseInstance; rebase all per-instance attributes for each ordered batch.\n const offset = batch.start * strideBytes;\n gl.vertexAttribPointer(0, 4, gl.FLOAT, false, strideBytes, offset);\n gl.vertexAttribPointer(1, 4, gl.FLOAT, false, strideBytes, offset + 16);\n gl.vertexAttribPointer(2, 4, gl.FLOAT, false, strideBytes, offset + 32);\n gl.vertexAttribPointer(3, 1, gl.FLOAT, false, strideBytes, offset + 48);\n gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, batch.count);\n }\n // Upload errors are checked once per submission, not once per glyph.\n this.checkErrors(\"frame submission\");\n }\n\n async idle(): Promise {\n this.checkErrors(\"GPU completion\");\n const gl = this.gl;\n const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);\n if (!sync) {\n this.checkErrors(\"GPU fence allocation\");\n throw new Error(\"Could not allocate a WebGL2 GPU fence\");\n }\n return new Promise((resolve, reject) => {\n const fence: PendingFence = { sync, resolve, reject };\n this.fences.add(fence);\n try {\n gl.flush();\n this.checkErrors(\"GPU fence submission\");\n this.pollFence(fence);\n } catch (error) {\n this.settleFence(fence, errorFrom(error));\n }\n });\n }\n\n private pollFence(fence: PendingFence): void {\n if (!this.fences.has(fence)) return;\n try {\n this.assertActive();\n const gl = this.gl;\n if (gl.isContextLost()) {\n const error = new Error(\"WebGL2 context lost\");\n this.contextLost(error);\n throw error;\n }\n const status = gl.clientWaitSync(fence.sync, 0, 0);\n if (status === gl.ALREADY_SIGNALED || status === gl.CONDITION_SATISFIED) {\n this.checkErrors(\"GPU completion\");\n this.settleFence(fence);\n } else if (status === gl.TIMEOUT_EXPIRED) {\n fence.timer = setTimeout(() => {\n fence.timer = undefined;\n this.pollFence(fence);\n }, 0);\n } else {\n this.checkErrors(\"GPU fence wait\");\n throw new Error(\"WebGL2 GPU fence wait failed\");\n }\n } catch (error) {\n this.settleFence(fence, errorFrom(error));\n }\n }\n\n private settleFence(fence: PendingFence, error?: Error): void {\n if (!this.fences.delete(fence)) return;\n if (fence.timer !== undefined) clearTimeout(fence.timer);\n this.gl.deleteSync(fence.sync);\n if (error) fence.reject(error);\n else fence.resolve();\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.canvas.removeEventListener(\"webglcontextlost\", this.onContextLost);\n for (const fence of this.fences) this.settleFence(fence, new Error(\"WebGL2 backend is disposed\"));\n for (const texture of this.textures) texture.destroy();\n const gl = this.gl;\n if (this.instanceBuffer) gl.deleteBuffer(this.instanceBuffer);\n if (this.vertexArray) gl.deleteVertexArray(this.vertexArray);\n if (this.program) gl.deleteProgram(this.program);\n for (const shader of this.shaders) gl.deleteShader(shader);\n this.shaders.clear();\n this.instanceBuffer = undefined;\n this.vertexArray = undefined;\n this.program = undefined;\n this.bufferBytes = 0;\n if (!gl.isContextLost()) gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts deleted file mode 100644 index d51fc964400..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { RenderBackend, RenderBatch, RenderColor, RenderTexture } from "./render-backend.js"; -export declare class WebGpuBackend implements RenderBackend { - readonly device: GPUDevice; - readonly kind = "webgpu"; - instanceBufferBytes: number; - instanceBuffer: GPUBuffer | undefined; - disposed: boolean; - context?: GPUCanvasContext; - format: GPUTextureFormat; - uniform: GPUBuffer; - sampler: GPUSampler; - pipeline?: GPURenderPipeline; - static create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise; - constructor(device: GPUDevice); - get maxTextureDimension2D(): number; - get maxCanvasDimension2D(): number; - initialize(canvas: OffscreenCanvas): Promise; - createTexture(width: number, height: number, label: string): RenderTexture; - resize(width: number, height: number): void; - submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[], background: RenderColor): void; - idle(): Promise; - dispose(): void; -} -//# sourceMappingURL=webgpu-backend.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts.map deleted file mode 100644 index e5b010c906c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"webgpu-backend.d.ts","sourceRoot":"","sources":["../src/webgpu-backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAgB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA6DhH,qBAAa,aAAc,YAAW,aAAa;IA4CrC,QAAQ,CAAC,MAAM,EAAE,SAAS;IA3CtC,QAAQ,CAAC,IAAI,YAAY;IACzB,mBAAmB,SAAK;IACxB,cAAc,EAAE,SAAS,GAAG,SAAS,CAAC;IACtC,QAAQ,UAAS;IACjB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,SAAS,CAAC;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;WAEhB,MAAM,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;gBAiChF,MAAM,EAAE,SAAS;IAMtC,IAAI,qBAAqB,IAAI,MAAM,CAAqD;IACxF,IAAI,oBAAoB,IAAI,MAAM,CAAuC;IAEnE,UAAU,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCxD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa;IAyB1E,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,EAC7F,UAAU,EAAE,WAAW,GAAG,IAAI;IA8B1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,OAAO,IAAI,IAAI;CAQhB"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js deleted file mode 100644 index 1cadfca5bc8..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js +++ /dev/null @@ -1,234 +0,0 @@ -import { QUAD_STRIDE, RendererUnavailableError } from "./render-backend.js"; -const shader = /* wgsl */ ` -struct Viewport { size: vec2f, padding: vec2f } -@group(0) @binding(0) var viewport: Viewport; -@group(0) @binding(1) var image: texture_2d; -@group(0) @binding(2) var imageSampler: sampler; - -struct VertexOut { - @builtin(position) position: vec4f, - @location(0) uv: vec2f, - @location(1) color: vec4f, - @location(2) @interpolate(flat) mode: f32, -} - -@vertex fn vertex( - @builtin(vertex_index) index: u32, - @location(0) rect: vec4f, - @location(1) uvRect: vec4f, - @location(2) color: vec4f, - @location(3) mode: f32, -) -> VertexOut { - let corners = array( - vec2f(0, 0), vec2f(1, 0), vec2f(0, 1), - vec2f(0, 1), vec2f(1, 0), vec2f(1, 1) - ); - let corner = corners[index]; - let position = rect.xy + corner * rect.zw; - var out: VertexOut; - out.position = vec4f(position / viewport.size * vec2f(2, -2) + vec2f(-1, 1), 0, 1); - out.uv = mix(uvRect.xy, uvRect.zw, corner); - out.color = color; - out.mode = mode; - return out; -} - -@fragment fn fragment(in: VertexOut) -> @location(0) vec4f { - // Explicit LOD avoids derivative-uniformity requirements across solid/mask/image batches. - let texel = textureSampleLevel(image, imageSampler, in.uv, 0); - if (in.mode < 0.5) { return in.color; } - if (in.mode < 1.5) { return vec4f(in.color.rgb, in.color.a * texel.a); } - return texel * in.color; -}`; -class WebGpuTexture { - device; - texture; - bindGroup; - width; - height; - constructor(device, texture, bindGroup, width, height) { - this.device = device; - this.texture = texture; - this.bindGroup = bindGroup; - this.width = width; - this.height = height; - } - writePixels(pixels, width, height, x = 0, y = 0) { - this.device.queue.writeTexture({ texture: this.texture, origin: [x, y] }, pixels, { bytesPerRow: width * 4, rowsPerImage: height }, [width, height]); - } - writeBitmap(bitmap) { - this.device.queue.copyExternalImageToTexture({ source: bitmap }, { texture: this.texture, premultipliedAlpha: false }, [this.width, this.height]); - } - destroy() { this.texture.destroy(); } -} -export class WebGpuBackend { - device; - kind = "webgpu"; - instanceBufferBytes = 0; - instanceBuffer; - disposed = false; - context; - format; - uniform; - sampler; - pipeline; - static async create(canvas, onFatal) { - if (!globalThis.isSecureContext) - throw new RendererUnavailableError("WebGPU requires HTTPS or localhost"); - if (!navigator.gpu) - throw new RendererUnavailableError("WebGPU is unavailable in this browser worker"); - const adapter = await navigator.gpu.requestAdapter(); - if (!adapter) - throw new RendererUnavailableError("No WebGPU adapter is available"); - let device; - try { - device = await adapter.requestDevice(); - } - catch (error) { - if (error instanceof DOMException && error.name === "OperationError") { - throw new RendererUnavailableError(`Could not acquire a WebGPU device: ${error.message}`, { cause: error }); - } - throw error; - } - let backend; - try { - backend = new WebGpuBackend(device); - const active = backend; - device.lost.then(info => { - if (!active.disposed) - onFatal(new Error(`WebGPU device lost: ${info.message || info.reason}`)); - }); - device.addEventListener("uncapturederror", event => { - if (!active.disposed) - onFatal(new Error(`WebGPU error: ${event.error.message}`)); - }); - await backend.initialize(canvas); - return backend; - } - catch (error) { - if (backend) - backend.dispose(); - else - device.destroy(); - throw error; - } - } - constructor(device) { - this.device = device; - this.format = navigator.gpu.getPreferredCanvasFormat(); - this.uniform = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }); - this.sampler = device.createSampler({ minFilter: "linear", magFilter: "linear" }); - } - get maxTextureDimension2D() { return this.device.limits.maxTextureDimension2D; } - get maxCanvasDimension2D() { return this.maxTextureDimension2D; } - async initialize(canvas) { - const module = this.device.createShaderModule({ code: shader }); - this.pipeline = await this.device.createRenderPipelineAsync({ - layout: "auto", - vertex: { - module, - entryPoint: "vertex", - buffers: [{ - arrayStride: QUAD_STRIDE * 4, - stepMode: "instance", - attributes: [ - { shaderLocation: 0, offset: 0, format: "float32x4" }, - { shaderLocation: 1, offset: 16, format: "float32x4" }, - { shaderLocation: 2, offset: 32, format: "float32x4" }, - { shaderLocation: 3, offset: 48, format: "float32" }, - ], - }], - }, - fragment: { - module, - entryPoint: "fragment", - targets: [{ - format: this.format, - blend: { - color: { srcFactor: "src-alpha", dstFactor: "one-minus-src-alpha" }, - alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha" }, - }, - }], - }, - primitive: { topology: "triangle-list" }, - }); - // Acquire the presentation surface last. A null context leaves it usable by WebGL2. - const context = canvas.getContext("webgpu"); - if (!context) - throw new RendererUnavailableError("Could not create an OffscreenCanvas WebGPU context"); - this.context = context; - context.configure({ device: this.device, format: this.format, alphaMode: "opaque" }); - } - createTexture(width, height, label) { - if (width > this.maxTextureDimension2D || height > this.maxTextureDimension2D) { - throw new Error(`${label} exceeds the GPU texture dimension limit`); - } - if (!this.pipeline) - throw new Error("WebGPU backend is not initialized"); - const texture = this.device.createTexture({ - label, size: [width, height], format: "rgba8unorm", - usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, - }); - try { - const bindGroup = this.device.createBindGroup({ - layout: this.pipeline.getBindGroupLayout(0), - entries: [ - { binding: 0, resource: { buffer: this.uniform } }, - { binding: 1, resource: texture.createView() }, - { binding: 2, resource: this.sampler }, - ], - }); - return new WebGpuTexture(this.device, texture, bindGroup, width, height); - } - catch (error) { - texture.destroy(); - throw error; - } - } - resize(width, height) { - this.device.queue.writeBuffer(this.uniform, 0, new Float32Array([width, height, 0, 0])); - } - submit(instances, quadCount, batches, background) { - if (this.disposed || !this.context || !this.pipeline) - throw new Error("WebGPU backend is not initialized"); - const usedBytes = quadCount * QUAD_STRIDE * 4; - if (!this.instanceBuffer || usedBytes > this.instanceBufferBytes) { - this.instanceBuffer?.destroy(); - this.instanceBufferBytes = Math.max(256, instances.byteLength); - this.instanceBuffer = this.device.createBuffer({ - size: this.instanceBufferBytes, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, - }); - } - if (usedBytes) - this.device.queue.writeBuffer(this.instanceBuffer, 0, instances, 0, quadCount * QUAD_STRIDE); - const encoder = this.device.createCommandEncoder(); - const pass = encoder.beginRenderPass({ - colorAttachments: [{ - view: this.context.getCurrentTexture().createView(), - clearValue: { r: background[0], g: background[1], b: background[2], a: 1 }, - loadOp: "clear", storeOp: "store", - }], - }); - pass.setPipeline(this.pipeline); - pass.setVertexBuffer(0, this.instanceBuffer); - for (const batch of batches) { - if (!(batch.resource instanceof WebGpuTexture)) - throw new Error("Texture belongs to a different rendering backend"); - pass.setBindGroup(0, batch.resource.bindGroup); - pass.draw(6, batch.count, 0, batch.start); - } - pass.end(); - this.device.queue.submit([encoder.finish()]); - } - async idle() { await this.device.queue.onSubmittedWorkDone(); } - dispose() { - if (this.disposed) - return; - this.disposed = true; - this.instanceBuffer?.destroy(); - this.uniform.destroy(); - this.context?.unconfigure(); - this.device.destroy(); - } -} -//# sourceMappingURL=webgpu-backend.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js.map deleted file mode 100644 index 71908b76108..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/webgpu-backend.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"webgpu-backend.js","sourceRoot":"","sources":["../src/webgpu-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { QUAD_STRIDE, RendererUnavailableError } from \"./render-backend.js\";\nimport type { RenderBackend, RenderBatch, RenderColor, RenderPixels, RenderTexture } from \"./render-backend.js\";\n\nconst shader = /* wgsl */ `\nstruct Viewport { size: vec2f, padding: vec2f }\n@group(0) @binding(0) var viewport: Viewport;\n@group(0) @binding(1) var image: texture_2d;\n@group(0) @binding(2) var imageSampler: sampler;\n\nstruct VertexOut {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n @location(1) color: vec4f,\n @location(2) @interpolate(flat) mode: f32,\n}\n\n@vertex fn vertex(\n @builtin(vertex_index) index: u32,\n @location(0) rect: vec4f,\n @location(1) uvRect: vec4f,\n @location(2) color: vec4f,\n @location(3) mode: f32,\n) -> VertexOut {\n let corners = array(\n vec2f(0, 0), vec2f(1, 0), vec2f(0, 1),\n vec2f(0, 1), vec2f(1, 0), vec2f(1, 1)\n );\n let corner = corners[index];\n let position = rect.xy + corner * rect.zw;\n var out: VertexOut;\n out.position = vec4f(position / viewport.size * vec2f(2, -2) + vec2f(-1, 1), 0, 1);\n out.uv = mix(uvRect.xy, uvRect.zw, corner);\n out.color = color;\n out.mode = mode;\n return out;\n}\n\n@fragment fn fragment(in: VertexOut) -> @location(0) vec4f {\n // Explicit LOD avoids derivative-uniformity requirements across solid/mask/image batches.\n let texel = textureSampleLevel(image, imageSampler, in.uv, 0);\n if (in.mode < 0.5) { return in.color; }\n if (in.mode < 1.5) { return vec4f(in.color.rgb, in.color.a * texel.a); }\n return texel * in.color;\n}`;\n\nclass WebGpuTexture implements RenderTexture {\n constructor(readonly device: GPUDevice, readonly texture: GPUTexture, readonly bindGroup: GPUBindGroup,\n readonly width: number, readonly height: number) {}\n\n writePixels(pixels: RenderPixels, width: number, height: number, x = 0, y = 0): void {\n this.device.queue.writeTexture({ texture: this.texture, origin: [x, y] }, pixels,\n { bytesPerRow: width * 4, rowsPerImage: height }, [width, height]);\n }\n\n writeBitmap(bitmap: ImageBitmap): void {\n this.device.queue.copyExternalImageToTexture({ source: bitmap },\n { texture: this.texture, premultipliedAlpha: false }, [this.width, this.height]);\n }\n\n destroy(): void { this.texture.destroy(); }\n}\n\nexport class WebGpuBackend implements RenderBackend {\n readonly kind = \"webgpu\";\n instanceBufferBytes = 0;\n instanceBuffer: GPUBuffer | undefined;\n disposed = false;\n context?: GPUCanvasContext;\n format: GPUTextureFormat;\n uniform: GPUBuffer;\n sampler: GPUSampler;\n pipeline?: GPURenderPipeline;\n\n static async create(canvas: OffscreenCanvas, onFatal: (error: Error) => void): Promise {\n if (!globalThis.isSecureContext) throw new RendererUnavailableError(\"WebGPU requires HTTPS or localhost\");\n if (!navigator.gpu) throw new RendererUnavailableError(\"WebGPU is unavailable in this browser worker\");\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) throw new RendererUnavailableError(\"No WebGPU adapter is available\");\n let device: GPUDevice;\n try {\n device = await adapter.requestDevice();\n } catch (error) {\n if (error instanceof DOMException && error.name === \"OperationError\") {\n throw new RendererUnavailableError(`Could not acquire a WebGPU device: ${error.message}`, { cause: error });\n }\n throw error;\n }\n let backend: WebGpuBackend | undefined;\n try {\n backend = new WebGpuBackend(device);\n const active = backend;\n device.lost.then(info => {\n if (!active.disposed) onFatal(new Error(`WebGPU device lost: ${info.message || info.reason}`));\n });\n device.addEventListener(\"uncapturederror\", event => {\n if (!active.disposed) onFatal(new Error(`WebGPU error: ${event.error.message}`));\n });\n await backend.initialize(canvas);\n return backend;\n } catch (error) {\n if (backend) backend.dispose();\n else device.destroy();\n throw error;\n }\n }\n\n constructor(readonly device: GPUDevice) {\n this.format = navigator.gpu.getPreferredCanvasFormat();\n this.uniform = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });\n this.sampler = device.createSampler({ minFilter: \"linear\", magFilter: \"linear\" });\n }\n\n get maxTextureDimension2D(): number { return this.device.limits.maxTextureDimension2D; }\n get maxCanvasDimension2D(): number { return this.maxTextureDimension2D; }\n\n async initialize(canvas: OffscreenCanvas): Promise {\n const module = this.device.createShaderModule({ code: shader });\n this.pipeline = await this.device.createRenderPipelineAsync({\n layout: \"auto\",\n vertex: {\n module,\n entryPoint: \"vertex\",\n buffers: [{\n arrayStride: QUAD_STRIDE * 4,\n stepMode: \"instance\",\n attributes: [\n { shaderLocation: 0, offset: 0, format: \"float32x4\" },\n { shaderLocation: 1, offset: 16, format: \"float32x4\" },\n { shaderLocation: 2, offset: 32, format: \"float32x4\" },\n { shaderLocation: 3, offset: 48, format: \"float32\" },\n ],\n }],\n },\n fragment: {\n module,\n entryPoint: \"fragment\",\n targets: [{\n format: this.format,\n blend: {\n color: { srcFactor: \"src-alpha\", dstFactor: \"one-minus-src-alpha\" },\n alpha: { srcFactor: \"one\", dstFactor: \"one-minus-src-alpha\" },\n },\n }],\n },\n primitive: { topology: \"triangle-list\" },\n });\n // Acquire the presentation surface last. A null context leaves it usable by WebGL2.\n const context = canvas.getContext(\"webgpu\");\n if (!context) throw new RendererUnavailableError(\"Could not create an OffscreenCanvas WebGPU context\");\n this.context = context;\n context.configure({ device: this.device, format: this.format, alphaMode: \"opaque\" });\n }\n\n createTexture(width: number, height: number, label: string): RenderTexture {\n if (width > this.maxTextureDimension2D || height > this.maxTextureDimension2D) {\n throw new Error(`${label} exceeds the GPU texture dimension limit`);\n }\n if (!this.pipeline) throw new Error(\"WebGPU backend is not initialized\");\n const texture = this.device.createTexture({\n label, size: [width, height], format: \"rgba8unorm\",\n usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,\n });\n try {\n const bindGroup = this.device.createBindGroup({\n layout: this.pipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: { buffer: this.uniform } },\n { binding: 1, resource: texture.createView() },\n { binding: 2, resource: this.sampler },\n ],\n });\n return new WebGpuTexture(this.device, texture, bindGroup, width, height);\n } catch (error) {\n texture.destroy();\n throw error;\n }\n }\n\n resize(width: number, height: number): void {\n this.device.queue.writeBuffer(this.uniform, 0, new Float32Array([width, height, 0, 0]));\n }\n\n submit(instances: Float32Array, quadCount: number, batches: readonly RenderBatch[],\n background: RenderColor): void {\n if (this.disposed || !this.context || !this.pipeline) throw new Error(\"WebGPU backend is not initialized\");\n const usedBytes = quadCount * QUAD_STRIDE * 4;\n if (!this.instanceBuffer || usedBytes > this.instanceBufferBytes) {\n this.instanceBuffer?.destroy();\n this.instanceBufferBytes = Math.max(256, instances.byteLength);\n this.instanceBuffer = this.device.createBuffer({\n size: this.instanceBufferBytes, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n });\n }\n if (usedBytes) this.device.queue.writeBuffer(this.instanceBuffer, 0, instances, 0, quadCount * QUAD_STRIDE);\n const encoder = this.device.createCommandEncoder();\n const pass = encoder.beginRenderPass({\n colorAttachments: [{\n view: this.context.getCurrentTexture().createView(),\n clearValue: { r: background[0], g: background[1], b: background[2], a: 1 },\n loadOp: \"clear\", storeOp: \"store\",\n }],\n });\n pass.setPipeline(this.pipeline);\n pass.setVertexBuffer(0, this.instanceBuffer);\n for (const batch of batches) {\n if (!(batch.resource instanceof WebGpuTexture)) throw new Error(\"Texture belongs to a different rendering backend\");\n pass.setBindGroup(0, batch.resource.bindGroup);\n pass.draw(6, batch.count, 0, batch.start);\n }\n pass.end();\n this.device.queue.submit([encoder.finish()]);\n }\n\n async idle(): Promise { await this.device.queue.onSubmittedWorkDone(); }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.instanceBuffer?.destroy();\n this.uniform.destroy();\n this.context?.unconfigure();\n this.device.destroy();\n }\n}\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts deleted file mode 100644 index 4a46d255906..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts +++ /dev/null @@ -1,263 +0,0 @@ -import type { InputModifiers, PointerButton, SelectionMode, SelectionRange, TerminalLinkUnderlineStyle, TerminalBuffer, TerminalFont, TerminalGeometry, TerminalPeer, TerminalSize, TerminalStats, TerminalRendererPreference, TerminalStatusLevel, TerminalProgress, TerminalShellIntegration, TerminalWorkingDirectory, TerminalCommandMark, TerminalCloseDetails } from "./types.js"; -import type { LinkDetectionSnapshot } from "./link-detection.js"; -export type SelectionText = { - status: "valid"; - text: string; -} | { - status: "none" | "invalidated"; - text: null; -}; -export type HistorySelection = SelectionText & { - requestId: number; - mode: SelectionMode; - ranges: SelectionRange[]; -}; -export interface HistoryMetadata { - generation: string; - buffer: TerminalBuffer; - totalRows: number; - liveTop: number; - top: number; - following: boolean; - requestId: number; - rowIds: string[]; - selection: HistorySelection; - copy: (SelectionText & { - requestId: number; - }) | null; -} -export interface TerminalCell { - index: number; - foreground: number; - background: number; - underlineColor: number; - attributes: number; - width: number; - underlineStyle: number; - text: string; -} -export interface HyperlinkRange { - row: number; - startColumn: number; - endColumn: number; - uri: string; -} -export interface ImageMetadata { - key: string; - width: number; - height: number; - byteLength: number; - format: "rgba" | "png"; -} -export interface FrameImage extends ImageMetadata { - bytes: Uint8Array; -} -export interface ImagePlacement { - key: string; - kind: "kgp" | "sixel"; - x: number; - y: number; - width: number; - height: number; - sourceX: number; - sourceY: number; - sourceWidth: number; - sourceHeight: number; - clipX: number; - clipY: number; - clipWidth: number; - clipHeight: number; - z: number; -} -export interface FrameMetadata extends TerminalGeometry { - version: 1; - full: boolean; - revision: number; - baseRevision: number; - peer: TerminalPeer; - history: HistoryMetadata | null; - title: string; - progress: TerminalProgress; - shellIntegration: TerminalShellIntegration; - workingDirectory: TerminalWorkingDirectory; - commandMark: TerminalCommandMark | null; - defaultBackground?: number; - defaultForeground?: number; - cursor: { - visible: boolean; - x: number; - y: number; - shape: number; - }; - images: ImageMetadata[]; - retainedImages: string[]; - placements: ImagePlacement[]; - warnings: string[]; - hyperlinks: HyperlinkRange[]; - stats: { - workloadBytes: number; - outputBatches: number; - captureMs: number; - elapsedMs: number; - }; -} -export interface TerminalFrame { - metadata: FrameMetadata; - cells: TerminalCell[]; - images: FrameImage[]; -} -export type CellPosition = { - x: number; - y: number; -} & Partial>; -export type MouseButton = PointerButton | "none" | "wheelUp" | "wheelDown" | "wheelLeft" | "wheelRight"; -export type MouseCommand = { - type: "mouse"; - action: "down" | "up" | "move" | "wheel"; - button: MouseButton; - count?: number; -} & CellPosition; -export type InputCommand = { - type: "input" | "paste"; - text: string; -} | { - type: "key"; - key: string; - ctrl: boolean; - alt: boolean; - shift: boolean; -} | MouseCommand; -export type TerminalCommand = InputCommand | { - type: "viewport"; - requestId: number; - delta?: number; - live?: boolean; - extend?: { - row: number; - column: number; - }; -} | { - type: "selection"; - action: "clear"; - requestId: number; -} | { - type: "selection"; - action: "start" | "extend"; - mode: SelectionMode; - requestId: number; - generation: string; - rowId: string; - column: number; -} | { - type: "copy"; - requestId: number; - selectionRequestId: number; - generation: string; -} | { - type: "resize" | "requestPrimary"; - columns: number; - rows: number; -} | { - type: "resync"; -} | { - type: "ack"; - revision: number; -}; -export type WorkerInputMessage = { - type: "init"; - canvas: OffscreenCanvas; - url: string; - scale: number; - font: TerminalFont; - renderer: TerminalRendererPreference; -} | ({ - type: "viewport"; -} & TerminalSize) | { - type: "linkDetection"; - enabled: boolean; - generation: number; -} | { - type: "linkDecorations"; - revision: number; - generation: number; - serial: number; - ranges: readonly SelectionRange[]; - underlineStyle?: TerminalLinkUnderlineStyle; -} | { - type: "command"; - command: TerminalCommand; -} | { - type: "stop"; -}; -export interface WorkerStats extends TerminalStats { - revision: number; - fullFrames: number; - frames: number; - presentations: number; - changedCells: number; - lastChangedCells: number; - discardedFrames: number; - imageCount: number; - textureBytes: number; - atlasGlyphs: number; - atlasBytes: number; - bytesReceived: number; - imageUploadBytes: number; - imagePayloadBytes: number; - gpu: "initializing" | "ready" | "error" | "stopped"; - connected: boolean; - warnings: string[]; - fps: number; - receivedKBps: number; - workloadMBps: number; - captureMs: number; - rendererCpuMs: number; - preparationCpuMs: number; - workloadBytes: number; - outputBatches: number; - serverElapsedMs: number; - history?: HistoryMetadata | null; -} -export type WorkerOutputMessage = { - type: "connected"; -} | { - type: "closed"; - details: TerminalCloseDetails; -} | { - type: "status"; - message: string; - level: TerminalStatusLevel; -} | ({ - type: "geometry"; - peer: TerminalPeer; - history: HistoryMetadata | null; - revision: number; - title: string; - progress: TerminalProgress; - shellIntegration: TerminalShellIntegration; - workingDirectory: TerminalWorkingDirectory; - commandMark: TerminalCommandMark | null; - linkGeneration?: number; - linkSnapshot?: LinkDetectionSnapshot; - text: string; - hyperlinks: HyperlinkRange[]; -} & TerminalGeometry) | { - type: "linkSnapshot"; - generation: number; - snapshot: LinkDetectionSnapshot; -} | { - type: "linkDecorations"; - revision: number; - generation: number; - serial: number; -} | { - type: "history"; - history: HistoryMetadata | null; - revision: number; - text: string; -} | { - type: "stats"; - stats: WorkerStats; - text?: string; -}; -//# sourceMappingURL=wire-types.d.ts.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts.map deleted file mode 100644 index 3a0fb4a4ef0..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"wire-types.d.ts","sourceRoot":"","sources":["../src/wire-types.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js deleted file mode 100644 index 8c3c20d277f..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=wire-types.js.map \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js.map b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js.map deleted file mode 100644 index cfb94c1af1c..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/wire-types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"wire-types.js","sourceRoot":"","sources":["../src/wire-types.ts"],"names":[],"mappings":"","sourcesContent":["import type { InputModifiers, PointerButton, SelectionMode, SelectionRange, TerminalLinkUnderlineStyle,\n TerminalBuffer, TerminalFont, TerminalGeometry, TerminalPeer, TerminalSize, TerminalStats,\n TerminalRendererPreference, TerminalStatusLevel, TerminalProgress, TerminalShellIntegration,\n TerminalWorkingDirectory, TerminalCommandMark, TerminalCloseDetails } from \"./types.js\";\nimport type { LinkDetectionSnapshot } from \"./link-detection.js\";\n\nexport type SelectionText =\n | { status: \"valid\"; text: string }\n | { status: \"none\" | \"invalidated\"; text: null };\nexport type HistorySelection = SelectionText & {\n requestId: number; mode: SelectionMode; ranges: SelectionRange[];\n};\nexport interface HistoryMetadata {\n generation: string; buffer: TerminalBuffer; totalRows: number; liveTop: number; top: number;\n following: boolean; requestId: number; rowIds: string[];\n selection: HistorySelection;\n copy: (SelectionText & { requestId: number }) | null;\n}\nexport interface TerminalCell {\n index: number; foreground: number; background: number; underlineColor: number;\n attributes: number; width: number; underlineStyle: number; text: string;\n}\nexport interface HyperlinkRange {\n row: number; startColumn: number; endColumn: number; uri: string;\n}\nexport interface ImageMetadata {\n key: string; width: number; height: number; byteLength: number; format: \"rgba\" | \"png\";\n}\nexport interface FrameImage extends ImageMetadata { bytes: Uint8Array }\nexport interface ImagePlacement {\n key: string; kind: \"kgp\" | \"sixel\";\n x: number; y: number; width: number; height: number;\n sourceX: number; sourceY: number; sourceWidth: number; sourceHeight: number;\n clipX: number; clipY: number; clipWidth: number; clipHeight: number; z: number;\n}\nexport interface FrameMetadata extends TerminalGeometry {\n version: 1; full: boolean; revision: number; baseRevision: number;\n peer: TerminalPeer; history: HistoryMetadata | null;\n title: string;\n progress: TerminalProgress;\n shellIntegration: TerminalShellIntegration;\n workingDirectory: TerminalWorkingDirectory;\n commandMark: TerminalCommandMark | null;\n defaultBackground?: number; defaultForeground?: number;\n cursor: { visible: boolean; x: number; y: number; shape: number };\n images: ImageMetadata[]; retainedImages: string[]; placements: ImagePlacement[]; warnings: string[];\n hyperlinks: HyperlinkRange[];\n stats: { workloadBytes: number; outputBatches: number; captureMs: number; elapsedMs: number };\n}\nexport interface TerminalFrame { metadata: FrameMetadata; cells: TerminalCell[]; images: FrameImage[] }\nexport type CellPosition = { x: number; y: number } & Partial>;\nexport type MouseButton = PointerButton | \"none\" | \"wheelUp\" | \"wheelDown\" | \"wheelLeft\" | \"wheelRight\";\nexport type MouseCommand = {\n type: \"mouse\"; action: \"down\" | \"up\" | \"move\" | \"wheel\"; button: MouseButton; count?: number;\n} & CellPosition;\nexport type InputCommand =\n | { type: \"input\" | \"paste\"; text: string }\n | { type: \"key\"; key: string; ctrl: boolean; alt: boolean; shift: boolean }\n | MouseCommand;\nexport type TerminalCommand = InputCommand\n | { type: \"viewport\"; requestId: number; delta?: number; live?: boolean;\n extend?: { row: number; column: number } }\n | { type: \"selection\"; action: \"clear\"; requestId: number }\n | { type: \"selection\"; action: \"start\" | \"extend\"; mode: SelectionMode;\n requestId: number; generation: string; rowId: string; column: number }\n | { type: \"copy\"; requestId: number; selectionRequestId: number; generation: string }\n | { type: \"resize\" | \"requestPrimary\"; columns: number; rows: number }\n | { type: \"resync\" }\n | { type: \"ack\"; revision: number };\nexport type WorkerInputMessage =\n | { type: \"init\"; canvas: OffscreenCanvas; url: string; scale: number; font: TerminalFont;\n renderer: TerminalRendererPreference }\n | ({ type: \"viewport\" } & TerminalSize)\n | { type: \"linkDetection\"; enabled: boolean; generation: number }\n | { type: \"linkDecorations\"; revision: number; generation: number; serial: number;\n ranges: readonly SelectionRange[]; underlineStyle?: TerminalLinkUnderlineStyle }\n | { type: \"command\"; command: TerminalCommand }\n | { type: \"stop\" };\nexport interface WorkerStats extends TerminalStats {\n revision: number; fullFrames: number; frames: number; presentations: number;\n changedCells: number; lastChangedCells: number; discardedFrames: number;\n imageCount: number; textureBytes: number; atlasGlyphs: number; atlasBytes: number;\n bytesReceived: number; imageUploadBytes: number; imagePayloadBytes: number;\n gpu: \"initializing\" | \"ready\" | \"error\" | \"stopped\"; connected: boolean; warnings: string[];\n fps: number; receivedKBps: number; workloadMBps: number;\n captureMs: number; rendererCpuMs: number; preparationCpuMs: number;\n workloadBytes: number; outputBatches: number; serverElapsedMs: number;\n history?: HistoryMetadata | null;\n}\nexport type WorkerOutputMessage =\n | { type: \"connected\" }\n | { type: \"closed\"; details: TerminalCloseDetails }\n | { type: \"status\"; message: string; level: TerminalStatusLevel }\n | ({ type: \"geometry\"; peer: TerminalPeer; history: HistoryMetadata | null;\n revision: number; title: string; progress: TerminalProgress; shellIntegration: TerminalShellIntegration;\n workingDirectory: TerminalWorkingDirectory; commandMark: TerminalCommandMark | null;\n linkGeneration?: number; linkSnapshot?: LinkDetectionSnapshot;\n text: string; hyperlinks: HyperlinkRange[] } & TerminalGeometry)\n | { type: \"linkSnapshot\"; generation: number; snapshot: LinkDetectionSnapshot }\n | { type: \"linkDecorations\"; revision: number; generation: number; serial: number }\n | { type: \"history\"; history: HistoryMetadata | null; revision: number; text: string }\n | { type: \"stats\"; stats: WorkerStats; text?: string };\n"]} \ No newline at end of file diff --git a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/package.json b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/package.json deleted file mode 100644 index 1a85e685509..00000000000 --- a/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@hex1b/web-terminal", - "version": "0.168.0", - "description": "First-party WebGPU and WebGL2 browser terminal for Hex1b", - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } - }, - "files": [ - "dist", - "README.md", - "LICENSE" - ], - "sideEffects": false, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/mitchdenny/hex1b.git", - "directory": "src/web-terminal" - }, - "publishConfig": { - "access": "public" - }, - "engines": { - "node": ">=22" - }, - "scripts": { - "build": "node scripts/clean.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs", - "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "node --test tests/*.test.mjs && tsc -p tests/types/tsconfig.json && tsc -p tests/types/tsconfig.bundler.json", - "prepack": "npm run build" - }, - "devDependencies": { - "@webgpu/types": "0.1.69", - "typescript": "5.9.3" - } -} diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs new file mode 100644 index 00000000000..e33936908cd --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Components.Controls; +using Aspire.Dashboard.Components.Tests.Shared; +using Bunit; +using Xunit; + +namespace Aspire.Dashboard.Components.Tests.Controls; + +[UseCulture("en-US")] +public class TerminalTitleTests : DashboardTestContext +{ + public TerminalTitleTests() + { + FluentUISetupHelpers.AddCommonDashboardServices(this); + FluentUISetupHelpers.SetupFluentButton(this); + TerminalSetupHelpers.SetupTerminalTitle(this); + } + + [Fact] + public void Metadata_IsRenderedAsTextAndClearsToFallback() + { + var cut = RenderComponent(builder => builder + .Add(p => p.FallbackTitle, "shell") + .Add(p => p.State, new TerminalToolbarState + { + Title = "", + WorkingDirectory = "/work/", + WorkingDirectoryUri = "file://remote/work/%3Capp%3E" + })); + + Assert.Equal("", cut.Find(".terminal-title").TextContent); + var button = cut.Find(".terminal-directory"); + Assert.Equal("/work/", cut.Find(".terminal-directory-measure").TextContent); + Assert.Equal("/work/", button.GetAttribute("data-text")); + Assert.Equal("true", button.GetAttribute("data-copybutton")); + Assert.Equal(Resources.ControlsStrings.GridValueCopyToClipboard, button.GetAttribute("data-precopy")); + Assert.Equal(Resources.ControlsStrings.GridValueCopied, button.GetAttribute("data-postcopy")); + Assert.False(button.HasAttribute("disabled")); + Assert.Equal("Copy working directory: /work/", button.GetAttribute("aria-label")); + Assert.Empty(cut.FindAll("fluent-tooltip")); + Assert.False(button.HasAttribute("title")); + Assert.Single(cut.FindAll(".terminal-directory .copy-icon")); + Assert.Empty(cut.FindAll("script, a")); + cut.SetParametersAndRender(builder => builder.Add(p => p.State, new TerminalToolbarState + { + WorkingDirectory = "/a longer path/with spaces/and Unicode \u03bb", + })); + Assert.Equal("/a longer path/with spaces/and Unicode \u03bb", cut.Find(".terminal-directory").GetAttribute("data-text")); + Assert.Equal("/a longer path/with spaces/and Unicode \u03bb", cut.Find(".terminal-directory-measure").TextContent); + Assert.Equal("Copy working directory: /a longer path/with spaces/and Unicode \u03bb", cut.Find(".terminal-directory").GetAttribute("aria-label")); + Assert.Equal(button.Id, cut.Find(".terminal-directory").Id); + cut.SetParametersAndRender(builder => builder.Add(p => p.State, new TerminalToolbarState())); + Assert.Equal("shell", cut.Find(".terminal-title").TextContent); + Assert.Empty(cut.FindAll(".terminal-directory")); + } + + [Theory] + [InlineData("/work/app")] + [InlineData("C:\\src\\app")] + [InlineData("ab\U0001F680cd")] + [InlineData("abe\u0301cd")] + [InlineData("/")] + public void Directory_PreservesFullMeasurementAndCopyValue(string path) + { + var cut = RenderComponent(builder => builder.Add(p => p.State, new TerminalToolbarState + { + WorkingDirectory = path + })); + + Assert.Equal(path, cut.Find(".terminal-metadata").GetAttribute("data-directory")); + Assert.Equal(path, cut.Find(".terminal-directory-measure").TextContent); + Assert.Empty(cut.Find(".terminal-directory-display").TextContent); + Assert.Equal(path, cut.Find(".terminal-directory").GetAttribute("data-text")); + } + + [Theory] + [InlineData("normal", 0, "0", "0%", "TerminalProgress")] + [InlineData("normal", 10, "10", "10%", "TerminalProgress")] + [InlineData("normal", 100, "100", "100%", "TerminalProgress")] + [InlineData("indeterminate", null, null, "", "TerminalProgress")] + [InlineData("error", 25, "25", "25%", "TerminalProgressError")] + [InlineData("warning", 75, "75", "75%", "TerminalProgressWarning")] + public void Progress_ExposesAccessibleStateAndValue(string state, int? percentage, string? expectedValue, string expectedText, string label) + { + var cut = RenderComponent(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Connected = true, ProgressState = state, ProgressPercentage = percentage + })); + var progress = cut.Find("[role=progressbar]"); + Assert.Equal(expectedValue, progress.GetAttribute("aria-valuenow")); + Assert.Equal(Resources.TerminalStrings.ResourceManager.GetString(label), progress.GetAttribute("aria-label")); + var indicator = cut.Find(".terminal-progress"); + Assert.Equal(state, indicator.GetAttribute("data-state")); + Assert.Equal(Resources.TerminalStrings.ResourceManager.GetString(label), indicator.GetAttribute("title")); + Assert.Equal(expectedText, indicator.TextContent.Trim()); + Assert.Equal("terminal-progress", cut.Find(".terminal-metadata").Children[0].ClassName); + Assert.Equal("terminal-title", cut.Find(".terminal-metadata").Children[1].ClassName); + if (state == "indeterminate") + { + Assert.Empty(cut.FindAll(".terminal-progress-percentage")); + } + else + { + Assert.Single(cut.FindAll(".terminal-progress-percentage")); + } + } + + [Theory] + [InlineData(true, "none")] + [InlineData(false, "normal")] + [InlineData(false, "indeterminate")] + public void InactiveProgress_IsHidden(bool connected, string state) + { + var cut = RenderComponent(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Connected = connected, ProgressState = state, ProgressPercentage = 42 + })); + Assert.Empty(cut.FindAll("[role=progressbar]")); + } +} diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs index 37eb113d8bd..cdc983b6770 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs @@ -100,6 +100,29 @@ public void NewWindowAction_IsOnlyOfferedInAnEnabledResourceTitlebar(bool chrome } } + [Fact] + public async Task Titlebar_UsesWorkloadMetadataAndHidesDisconnectedProgress() + { + var module = TerminalSetupHelpers.SetupTerminalViewModule(this, "/Components/Controls/TerminalView.razor.js"); + module.Setup("initTerminal", _ => true).SetResult(1); + var cut = RenderComponent(builder => builder.Add(p => p.ResourceName, "shell")); + var state = new TerminalToolbarState + { + TerminalId = 1, Generation = 2, Connected = true, + Title = "building", WorkingDirectory = "/work/app", + WorkingDirectoryUri = "file:///work/app", ProgressState = "normal", ProgressPercentage = 42 + }; + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state)); + Assert.Equal("building", cut.Find(".terminal-titlebar .terminal-title").TextContent); + Assert.Equal("/work/app", cut.Find(".terminal-titlebar .terminal-directory").GetAttribute("data-text")); + Assert.Equal("42", cut.Find(".terminal-titlebar [role=progressbar]").GetAttribute("aria-valuenow")); + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state with { Generation = 1, Title = "stale" })); + Assert.Equal("building", cut.Find(".terminal-title").TextContent); + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state with { Connected = false })); + Assert.Empty(cut.FindAll("[role=progressbar]")); + Assert.Equal("building", cut.Find(".terminal-title").TextContent); + } + [Fact] public async Task InitialFontSize_SeedsMountWithoutResettingCurrentFont() { diff --git a/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs b/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs index d91a43e294a..b418f64613f 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs @@ -18,6 +18,26 @@ namespace Aspire.Dashboard.Components.Tests.Dialogs; [UseCulture("en-US")] public sealed class InteractionsTerminalDialogTests : DashboardTestContext { + [Fact] + public async Task WorkloadMetadata_IsShownAboveTheInteractiveTerminal() + { + TerminalSetupHelpers.SetupTerminalView(this); + var getCut = InteractionsSetupHelpers.SetupDialog(this, p => p.Content, out var dialogService); + var viewModel = new InteractionsTerminalDialogViewModel { TerminalId = "terminal", Message = "Message" }; + await dialogService.ShowDialogAsync(viewModel, new DialogParameters { Title = "Interaction" }); + var cut = getCut(); + var terminal = cut.FindComponent().Instance; + await cut.InvokeAsync(() => terminal.OnTerminalStateChanged(new TerminalToolbarState + { + TerminalId = 1, Generation = 1, Connected = true, + Title = "deploy", WorkingDirectory = "/work/app", ProgressState = "warning", ProgressPercentage = 50 + })); + Assert.Equal("deploy", cut.Find(".interaction-terminal-titlebar .terminal-title").TextContent); + Assert.Equal("/work/app", cut.Find(".interaction-terminal-titlebar .terminal-directory").GetAttribute("data-text")); + Assert.Equal("50", cut.Find(".interaction-terminal-titlebar [role=progressbar]").GetAttribute("aria-valuenow")); + Assert.Equal("Message", cut.Find(".interaction-message").TextContent.Trim()); + } + [Theory] [InlineData("", "terminal", "terminal")] [InlineData("/aspire/nested", "terminal", "terminal")] diff --git a/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs new file mode 100644 index 00000000000..9468833f490 --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import { test } from "node:test"; + +const source = await readFile(new URL("../../../src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js", import.meta.url), "utf8"); +const { compactPath, observePath, disconnectPath } = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`); + +test("paths that fit retain every character, including roots and trailing separators", () => { + for (const path of ["/", "/home/user/app/", "C:\\work\\app", "\\\\server\\share\\app", "relative/path", "/a//b", "/user/e\u0301/\u{1f680}"]) { + assert.equal(compactPath(path, value => value.length <= path.length), path); + } +}); + +test("middle omissions preserve whole leading and trailing components", () => { + for (const [path, expected] of [ + ["/home/user/projects/service/src", "/home/user/\u2026/service/src"], + ["/home/a\\b/projects/service/src", "/home/a\\b/\u2026/service/src"], + ["C:\\work\\projects\\service\\src", "C:\\work\\\u2026\\service\\src"], + ["/home/user/projects/service/src/", "/\u2026/src/"], + ["/one-very-long-component", "/\u2026"], + ["one-very-long-component", "\u2026"], + ]) { + assert.equal(compactPath(path, value => value.length <= expected.length), expected); + } +}); + +test("every width omits only complete path segments, never partial names", () => { + for (const path of [ + "/Users/name/projects/aspire/src", + "C:\\Users\\name\\projects\\aspire\\src", + "\\\\server\\share\\projects\\aspire\\", + "/user/e\u0301/\u{1f469}\u200d\u{1f4bb}/项目/a long final directory", + "a-single-component-without-separators", + ]) { + const original = new Set([...path.split(/[\\/]/), "\u2026", ""]); + for (let width = 0; width <= path.length; width++) { + const display = compactPath(path, value => value.length <= width); + assert.ok(display.length <= width); + assert.ok(display.split(/[\\/]/).every(segment => original.has(segment)), display); + } + } +}); + +test("measurement follows resize, path changes and fonts, and releases observers", async () => { + const originals = new Map(); + const install = (name, value) => { + originals.set(name, Object.getOwnPropertyDescriptor(globalThis, name)); + Object.defineProperty(globalThis, name, { value, configurable: true }); + }; + const resizes = []; + const mutations = []; + const fonts = new EventTarget(); + fonts.ready = Promise.resolve(); + const context = { font: "", measureText: value => ({ width: value ? value.length + 0.2 : 0 }) }; + const display = { textContent: "" }; + const textBox = width => ({ + clientWidth: width, + getBoundingClientRect() { return { width: this.clientWidth + 0.25 }; }, + querySelector: () => display, + }); + let text = textBox(6); + let path = "/home/user/project/src"; + const element = { querySelector: () => text, getAttribute: () => path }; + try { + install("document", { fonts, createElement: () => ({ getContext: () => context }) }); + install("getComputedStyle", () => ({ font: "12px monospace" })); + install("ResizeObserver", class { + constructor(callback) { this.callback = callback; this.targets = new Set(); resizes.push(this); } + observe(target) { this.targets.add(target); } + unobserve(target) { this.targets.delete(target); } + disconnect() { this.targets.clear(); } + }); + install("MutationObserver", class { + constructor(callback) { this.callback = callback; mutations.push(this); } + observe(target) { this.target = target; } + disconnect() { this.target = null; } + }); + + observePath(element); + assert.equal(display.textContent, "/\u2026/src"); + assert.equal(context.font, "12px monospace"); + text.clientWidth = 100; + resizes[0].callback(); + assert.equal(display.textContent, path); + text.clientWidth = path.length; + resizes[0].callback(); + assert.equal(display.textContent, path, "A fractional-width path that fits must remain complete."); + path = "/other/project"; + mutations[0].callback(); + assert.equal(display.textContent, path); + text.clientWidth = 2; + fonts.dispatchEvent(new Event("loadingdone")); + assert.equal(display.textContent, "/\u2026"); + text = null; + path = ""; + mutations[0].callback(); + assert.equal(resizes[0].targets.size, 0); + text = textBox(100); + path = "C:\\work\\restored"; + mutations[0].callback(); + assert.equal(display.textContent, path); + assert.deepEqual([...resizes[0].targets], [text]); + + disconnectPath(element); + assert.equal(resizes[0].targets.size, 0); + assert.equal(mutations[0].target, null); + display.textContent = "disposed"; + await fonts.ready; + fonts.dispatchEvent(new Event("loadingdone")); + resizes[0].callback(); + assert.equal(display.textContent, "disposed"); + } finally { + disconnectPath(element); + for (const [name, original] of originals) { + if (original) { + Object.defineProperty(globalThis, name, original); + } else { + delete globalThis[name]; + } + } + } +}); diff --git a/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalView.test.mjs b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalView.test.mjs index 7e94f8e9945..94c39d215ee 100644 --- a/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalView.test.mjs +++ b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalView.test.mjs @@ -3,16 +3,18 @@ import assert from "node:assert/strict"; import { afterEach, beforeEach, mock, test } from "node:test"; -import { readFile } from "node:fs/promises"; +import { readFile, readdir } from "node:fs/promises"; +import { join, relative, sep } from "node:path"; +import { fileURLToPath } from "node:url"; const dashboard = new URL("../../../src/Aspire.Dashboard/", import.meta.url); const assets = new URL("wwwroot/js/hex1b-web-terminal/", dashboard); -const { WebTerminal, MIN_FONT_SIZE, MAX_FONT_SIZE } = await import(new URL("dist/index.js", assets)); +const { WebTerminal, MIN_FONT_SIZE, MAX_FONT_SIZE } = await import(new URL("dist/index.min.js", assets)); const source = await readFile(new URL("Components/Controls/TerminalView.razor.js", dashboard), "utf8"); // Remap the public browser asset import to its checked-in location for Node, // without changing the adapter implementation under test. const terminal = await import(`data:text/javascript;base64,${Buffer.from(source.replace( - '"../../js/hex1b-web-terminal/dist/index.js"', JSON.stringify(new URL("dist/index.js", assets).href) + '"../../js/hex1b-web-terminal/dist/index.min.js"', JSON.stringify(new URL("dist/index.min.js", assets).href) )).toString("base64")}`); let attempts; @@ -24,6 +26,8 @@ let snapshots; let serial; const globals = new Map(); let originalMount; +let themeObservers; +let mediaQueries; function setGlobal(name, value) { globals.set(name, Object.getOwnPropertyDescriptor(globalThis, name)); @@ -40,10 +44,41 @@ beforeEach(() => { ids = []; snapshots = []; serial = 0; - setGlobal("window", { isSecureContext: true }); + themeObservers = []; + mediaQueries = new Map(); + setGlobal("window", { isSecureContext: true, matchMedia(query) { + if (!mediaQueries.has(query)) { + mediaQueries.set(query, Object.assign(new EventTarget(), { matches: false })); + } + return mediaQueries.get(query); + } }); setGlobal("navigator", { gpu: {} }); - setGlobal("document", { activeElement: null, body: {}, hasFocus: () => true, visibilityState: "visible" }); - setGlobal("getComputedStyle", element => ({ visibility: element.visibility ?? "visible" })); + setGlobal("document", { + activeElement: null, + body: { append() {} }, + documentElement: {}, + createElement: tag => tag === "canvas" + ? { getContext: () => ({ fillStyle: "" }) } + : { style: {}, remove() {} }, + hasFocus: () => true, + visibilityState: "visible", + }); + setGlobal("getComputedStyle", element => ({ + visibility: element.visibility ?? "visible", color: "rgb(100, 100, 100)", + getPropertyValue: name => ({ + "--terminal-background": "#0d1117", + "--colorNeutralForeground1": "#ffffff", + })[name] ?? "none", + })); + setGlobal("MutationObserver", class { + constructor(callback) { + this.callback = callback; + this.disconnected = false; + themeObservers.push(this); + } + observe(element, options) { this.element = element; this.options = options; } + disconnect() { this.disconnected = true; } + }); setGlobal("requestAnimationFrame", callback => { frames.set(++serial, callback); return serial; @@ -66,14 +101,28 @@ beforeEach(() => { originalMount = WebTerminal.mount; WebTerminal.mount = (element, options) => { const ready = Promise.withResolvers(); + const status = { + textContent: "", dataset: { level: "info" }, attributes: new Set(), + toggleAttribute(name, enabled) { + if (enabled) this.attributes.add(name); + else this.attributes.delete(name); + }, + }; + const shadow = { + styles: [], + querySelector: selector => selector === ".inspection-message" ? status : {}, + append(style) { this.styles.push(style.textContent); }, + }; const client = { - element: { parentElement: element, contains: value => value === client.element }, + element: { parentElement: element, shadowRoot: shadow, dataset: {}, contains: value => value === client.element }, connected: true, peer: { id: "browser-1", primaryId: "cli-1", isPrimary: false }, geometry: { columns: 100, rows: 30 }, sizing: { ...options.sizing }, readOnly: options.readOnly, readOnlyCalls: [], + scrollbar: options.scrollbar, + scrollbarCalls: [], sizingCalls: [], primaryRequests: 0, focusCalls: 0, @@ -93,6 +142,10 @@ beforeEach(() => { this.readOnly = readOnly; this.readOnlyCalls.push(readOnly); }, + setScrollbar(scrollbar) { + this.scrollbar = scrollbar; + this.scrollbarCalls.push(scrollbar); + }, focus() { this.focusCalls++; document.activeElement = this.element; }, clearSelection() { this.selectionClears++; @@ -179,12 +232,12 @@ function selectionEvent(attempt, overrides = {}) { } function mount({ visible = true, dotNetRef, options = {} } = {}) { - const element = { + const element = Object.assign(new EventTarget(), { clientWidth: visible ? 800 : 0, clientHeight: visible ? 600 : 0, contains: value => value === element || value?.parentElement === element, closest: () => null, - }; + }); const controls = []; const template = { firstElementChild: { cloneNode() { const control = selectionControl(); @@ -231,6 +284,42 @@ function retry() { return delay; } +test("pointer focus styling ends on keyboard input without changing terminal focus", async () => { + const { element } = mount(); + const { client } = attempts[0]; + attempts[0].resolve(); + await settle(); + const focusCalls = client.focusCalls; + const pointer = new Event("pointerdown", { cancelable: true }); + element.dispatchEvent(pointer); + assert.equal(client.element.dataset.aspirePointerInput, "true"); + assert.equal(pointer.defaultPrevented, false); + + const keyboard = new Event("keydown", { cancelable: true }); + element.dispatchEvent(keyboard); + assert.equal(client.element.dataset.aspirePointerInput, undefined); + assert.equal(keyboard.defaultPrevented, false); + assert.equal(client.focusCalls, focusCalls); +}); + +test("pointer focus listeners follow replacement clients and are removed on disposal", async () => { + const { id, element } = mount(); + attempts[0].resolve(); + await settle(); + const original = attempts[0].client; + terminal.reconnectTerminal(id, "wss://dashboard/api/terminal?resource=other&replica=1"); + attempts[1].resolve(); + await settle(); + const replacement = attempts[1].client; + element.dispatchEvent(new Event("pointerdown")); + assert.equal(original.element.dataset.aspirePointerInput, undefined); + assert.equal(replacement.element.dataset.aspirePointerInput, "true"); + + terminal.disposeTerminal(id); + element.dispatchEvent(new Event("keydown")); + assert.equal(replacement.element.dataset.aspirePointerInput, "true"); +}); + for (const [name, rects, position] of [ ["single line", [{ left: 20, top: 10, width: 60, height: 20 }], { left: "86px", top: "36px" }], ["last line rather than bounding box", [ @@ -274,6 +363,53 @@ for (const [name, ranges, text, visible] of [ }); } +test("theme and contrast changes replace the complete overlay configuration without reconnecting", async () => { + const { id } = mount(); + const attempt = attempts[0]; + const initial = attempt.options.scrollbar; + assert.equal(initial.placement, "overlay"); + assert.equal(initial.markers, true); + assert.equal(typeof initial.render, "function"); + assert.equal(themeObservers[0].element, document.documentElement); + assert.deepEqual(themeObservers[0].options, { attributes: true, attributeFilter: ["data-theme"] }); + + // Include a theme change before the asynchronous mount has returned its handle. + themeObservers[0].callback(); + attempt.resolve(); + await settle(); + assert.notEqual(attempt.client.scrollbar.render, initial.render); + for (const query of ["(forced-colors: active)", "(prefers-contrast: more)"]) { + const previous = attempt.client.scrollbar; + const media = mediaQueries.get(query); + media.matches = true; + media.dispatchEvent(new Event("change")); + assert.notEqual(attempt.client.scrollbar.render, previous.render); + assert.equal(attempt.client.scrollbar.placement, "overlay"); + assert.equal(attempt.client.scrollbar.markers, true); + } + assert.equal(attempts.length, 1); + assert.deepEqual(attempt.client.sizingCalls, []); + assert.equal(attempt.client.selectionClears, 0); + terminal.reconnectTerminal(id, "wss://dashboard/api/terminal?resource=app&replica=1"); + assert.equal(attempts[1].options.scrollbar, attempt.client.scrollbar); +}); + +test("theme observers and accessibility listeners are released on disposal", async () => { + const { id } = mount(); + const attempt = attempts[0]; + attempt.resolve(); + await settle(); + terminal.disposeTerminal(id); + const calls = attempt.client.scrollbarCalls.length; + assert.equal(themeObservers[0].disconnected, true); + themeObservers[0].callback(); + for (const media of mediaQueries.values()) { + media.dispatchEvent(new Event("change")); + } + assert.equal(attempt.client.scrollbarCalls.length, calls); + assert.equal(attempt.client.disposed, true); +}); + test("selection copy control follows the single-cell threshold in both directions", () => { const { controls } = mount(); for (const cells of [1, 2, 1, 0, 3]) { @@ -486,12 +622,103 @@ test("reconnect removes selection controls, listeners and stale clipboard callba assert.equal(controls[1].actions.removed, true); }); +test("metadata before mount completion is coalesced and updates read-only views", async () => { + const { id } = mount({ options: { readOnly: true } }); + const { options } = attempts[0]; + options.onTitleChange("build "); + options.onWorkingDirectoryChange({ uri: "file://host/work/my%20app", host: "host", path: "/work/my app" }); + options.onProgressChange({ state: "normal", percentage: 42 }); + attempts[0].resolve(); + await settle(); + assert.equal(snapshots.length, 1); + assert.equal(snapshots[0].title, "build "); + assert.equal(snapshots[0].workingDirectory, "/work/my app"); + assert.equal(snapshots[0].workingDirectoryUri, "file://host/work/my%20app"); + assert.equal(snapshots[0].progressState, "normal"); + assert.equal(snapshots[0].progressPercentage, 42); + for (const progress of [ + { state: "indeterminate", percentage: null }, + { state: "error", percentage: 30 }, + { state: "warning", percentage: 50 }, + { state: "none", percentage: null }, + ]) { + options.onProgressChange(progress); + await settle(); + assert.equal(snapshots.at(-1).progressState, progress.state); + assert.equal(snapshots.at(-1).progressPercentage, progress.percentage); + } + options.onTitleChange(""); + options.onWorkingDirectoryChange({ uri: null, host: null, path: null }); + await settle(); + assert.equal(terminal.getToolbarState(id).title, ""); + assert.equal(terminal.getToolbarState(id).workingDirectory, null); + assert.equal(attempts[0].client.primaryRequests, 0); +}); + +test("metadata survives transport loss but not endpoint replacement or stale callbacks", async () => { + const { id } = mount(); + const old = attempts[0]; + old.options.onTitleChange("old title"); + old.options.onWorkingDirectoryChange({ uri: "file:///old", host: "", path: "/old" }); + old.options.onProgressChange({ state: "normal", percentage: 20 }); + old.resolve(); + await settle(); + old.close(1006); + await settle(); + assert.equal(terminal.getToolbarState(id).title, "old title"); + assert.equal(terminal.getToolbarState(id).connected, false); + terminal.reconnectTerminal(id, "wss://dashboard/api/terminal?resource=new"); + const expected = terminal.getToolbarState(id); + assert.equal(expected.title, ""); + assert.equal(expected.workingDirectory, null); + assert.equal(expected.progressState, "none"); + old.options.onTitleChange("stale title"); + old.options.onWorkingDirectoryChange({ uri: "file:///stale", host: "", path: "/stale" }); + old.options.onProgressChange({ state: "error", percentage: 99 }); + assert.deepEqual(terminal.getToolbarState(id), expected); + terminal.disposeTerminal(id); + attempts[1].options.onTitleChange("disposed"); + assert.equal(terminal.getToolbarState(id), null); +}); + +test("history chrome suppression preserves errors and selection feedback and disconnects on release", async () => { + const { id } = mount(); + attempts[0].resolve(); + await settle(); + const shadow = attempts[0].client.element.shadowRoot; + const status = shadow.querySelector(".inspection-message"); + const observer = themeObservers.find(observer => observer.element === status); + assert.ok(observer); + assert.match(shadow.styles[0], /\.return-live \{ display: none !important; \}/); + for (const [text, level, hidden] of [ + ["42 rows above live", "info", true], + ["0 rows above live", "info", true], + ["Selection copied", "info", false], + ["Navigation rejected", "error", false], + ["42 rows above live", "error", false], + ["", "info", false], + ]) { + status.textContent = text; + status.dataset.level = level; + observer.callback(); + assert.equal(status.attributes.has("data-aspire-history-position"), hidden); + } + terminal.reconnectTerminal(id, attempts[0].options.url); + assert.equal(observer.disconnected, true); + attempts[1].resolve(); + await settle(); + const replacement = themeObservers.at(-1); + terminal.disposeTerminal(id); + assert.equal(replacement.disconnected, true); +}); + test("init returns an id while mount waits for its first connected frame", async () => { const { id } = mount(); assert.equal(terminal.getToolbarState(id).connected, false); assert.equal(attempts[0].options.label, "Localized terminal input"); assert.equal(attempts[0].options.url, "wss://dashboard/api/terminal?resource=app&replica=1"); assert.equal(attempts[0].options.renderer, "auto"); + assert.equal(attempts[0].options.padding, 3); attempts[0].options.onStatus("Socket open", "ready"); attempts[0].role(false); await settle(); @@ -500,6 +727,8 @@ test("init returns an id while mount waits for its first connected frame", async await settle(); assert.deepEqual(snapshots.at(-1), { terminalId: id, generation: 1, status: "viewer", connected: true, + title: "", workingDirectory: null, workingDirectoryUri: null, + progressState: "none", progressPercentage: null, isPrimary: false, canTakeControl: true, sizeMode: "font", sizeKey: "100x30", fontPx: 13, fontControlsEnabled: true, sizeSelectEnabled: true, fitEnabled: true, @@ -1458,18 +1687,18 @@ test("disposing a view ignores later native close callbacks", async () => { assert.equal(attempts.length, 1); }); -test("frontend manifest, lockfile, vendored package and backend use the exact paired version", async () => { +test("frontend manifest, lockfile, minified bundle and backend use the exact paired version", async () => { const manifest = JSON.parse(await readFile(new URL("package.json", dashboard), "utf8")); const lockfile = JSON.parse(await readFile(new URL("package-lock.json", dashboard), "utf8")); - const vendored = JSON.parse(await readFile(new URL("package.json", assets), "utf8")); + const bundle = await readFile(new URL("dist/index.min.js", assets), "utf8"); const version = manifest.dependencies["@hex1b/web-terminal"]; - assert.equal(version, "0.168.0"); - assert.equal(vendored.version, version); + assert.equal(version, "0.169.0-alpha.1608.1.d6a20d4"); + assert.ok(bundle.startsWith(`// @hex1b/web-terminal ${version}; minified with Terser. See ../LICENSE.\n`)); assert.equal(lockfile.packages[""].dependencies["@hex1b/web-terminal"], version); assert.equal(lockfile.packages["node_modules/@hex1b/web-terminal"].version, version); // Central package rows have the form: - // + // // Match the exact Include value, not Hex1b.Tool or Hex1b.McpServer; // whitespace, attribute order and either XML quote style are allowed. const packages = await readFile(new URL("../../Directory.Packages.props", dashboard), "utf8"); @@ -1482,39 +1711,45 @@ test("frontend manifest, lockfile, vendored package and backend use the exact pa assert.equal(backendVersion[1], version); }); -test("checked-in deployment includes the worker and licensed font without npm installation", async () => { - for (const name of [ - "dist/index.js", - "dist/terminal-worker.js", - "dist/webgpu-backend.js", - "dist/webgl2-backend.js", - "dist/hyperlinks.js", +test("checked-in deployment contains only the minified bundle, font and required licenses without npm installation", async () => { + const entries = await readdir(assets, { recursive: true, withFileTypes: true }); + const files = entries.filter(entry => entry.isFile()) + .map(entry => relative(fileURLToPath(assets), join(entry.parentPath, entry.name)).split(sep).join("/")) + .sort(); + assert.deepEqual(files, [ + "LICENSE", "dist/fonts/cascadia-mono-nf/CascadiaMonoNF.woff2", "dist/fonts/cascadia-mono-nf/LICENSE.txt", - "dist/fonts/cascadia-mono-nf/README.md", - "LICENSE", - "README.md", - ]) { + "dist/index.min.js", + ]); + for (const name of files) { assert.ok((await readFile(new URL(name, assets))).length > 0, `Missing or empty vendored asset: ${name}`); } }); -test("entry, module worker and bundled font URLs preserve PathBase and same origin", async () => { +test("entry, both bundled module workers and font URLs preserve PathBase and same origin", async () => { // Inspect the emitted forms: - // import { WebTerminal, ... } from "../../js/.../dist/index.js"; - // new Worker(new URL("./terminal-worker.js", import.meta.url), ...); - // new URL("./fonts/.../CascadiaMonoNF.woff2", import.meta.url).href; + // import { WebTerminal, ... } from "../../js/.../dist/index.min.js"; + // new URL(import.meta.url); t.hash=`hex1b-${e}-worker`; + // new URL("./fonts/.../CascadiaMonoNF.woff2",import.meta.url).href; + // Minification renames local functions and parameters, not these URL shapes. // Keeping these module-relative URLs avoids both PathBase escapes and // blob/cross-origin worker URLs that require relaxing the dashboard CSP. const entryReference = source.match(/from "([^"]+)"/)[1]; const entryUrl = new URL(entryReference, "https://dashboard.example/nested/aspire/Components/Controls/TerminalView.razor.js"); - assert.equal(entryUrl.href, "https://dashboard.example/nested/aspire/js/hex1b-web-terminal/dist/index.js"); - const clientSource = await readFile(new URL("dist/web-terminal.js", assets), "utf8"); - const workerReference = clientSource.match(/new Worker\(new URL\("([^"]+)", import\.meta\.url\)/)[1]; - assert.equal(new URL(workerReference, entryUrl).href, - "https://dashboard.example/nested/aspire/js/hex1b-web-terminal/dist/terminal-worker.js"); - const fontSource = await readFile(new URL("dist/terminal-font.js", assets), "utf8"); - const fontReference = fontSource.match(/new URL\("([^"]+)", import\.meta\.url\)/)[1]; + assert.equal(entryUrl.href, "https://dashboard.example/nested/aspire/js/hex1b-web-terminal/dist/index.min.js"); + const clientSource = await readFile(new URL("dist/index.min.js", assets), "utf8"); + assert.match(clientSource, /new URL\(import\.meta\.url\)/); + assert.match(clientSource, /\.hash=`hex1b-\$\{[\w$]+\}-worker`/); + assert.match(clientSource, /globalThis instanceof DedicatedWorkerGlobalScope/); + for (const kind of ["terminal", "link-detection"]) { + assert.match(clientSource, new RegExp(`case\\s*"#hex1b-${kind}-worker":`)); + const workerUrl = new URL(`${entryUrl.href}?v=alpha`); + workerUrl.hash = `hex1b-${kind}-worker`; + assert.equal(workerUrl.href, + `https://dashboard.example/nested/aspire/js/hex1b-web-terminal/dist/index.min.js?v=alpha#hex1b-${kind}-worker`); + } + const fontReference = clientSource.match(/new URL\("([^"]+\.woff2)",import\.meta\.url\)/)[1]; assert.equal(new URL(fontReference, entryUrl).href, "https://dashboard.example/nested/aspire/js/hex1b-web-terminal/dist/fonts/cascadia-mono-nf/CascadiaMonoNF.woff2"); }); diff --git a/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs b/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs index f6bae239c30..a936c76ac9b 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs @@ -24,6 +24,37 @@ namespace Aspire.Dashboard.Components.Tests.Layout; [UseCulture("en-US")] public partial class TerminalDockTests : DashboardTestContext { + [Fact] + public async Task WorkloadMetadata_FollowsTheActiveTerminalWithoutRemounting() + { + var updates = Channel.CreateUnbounded(); + TerminalSetupHelpers.SetupTerminalComponents(this, + TerminalSetupHelpers.CreateTerminalDashboardClient(terminalChannelProvider: () => updates)); + var cut = RenderComponent(); + await cut.InvokeAsync(cut.Instance.ToggleAsync); + await updates.Writer.WriteAsync(TerminalSetupHelpers.Snapshot("first", "second")); + cut.WaitForAssertion(() => Assert.Equal(2, cut.FindComponents().Count)); + var views = cut.FindComponents().Select(view => view.Instance).ToArray(); + for (var i = 0; i < views.Length; i++) + { + var index = i; + await cut.InvokeAsync(() => views[index].OnTerminalStateChanged(new TerminalToolbarState + { + TerminalId = 1, Generation = 1, Connected = true, + Title = $"title-{index}", WorkingDirectory = $"/work/{index}", + ProgressState = "normal", ProgressPercentage = 20 + index + })); + } + Assert.Equal("title-0", cut.Find(".terminal-dock-tabstrip .terminal-title").TextContent); + Assert.Equal("/work/0", cut.Find(".terminal-dock-tabstrip .terminal-directory").GetAttribute("data-text")); + await cut.FindAll(".terminal-dock-tab-select")[1].ClickAsync(new()); + Assert.Equal("title-1", cut.Find(".terminal-dock-tabstrip .terminal-title").TextContent); + Assert.Equal("/work/1", cut.Find(".terminal-dock-tabstrip .terminal-directory").GetAttribute("data-text")); + Assert.Equal("21", cut.Find(".terminal-dock-tabstrip [role=progressbar]").GetAttribute("aria-valuenow")); + Assert.Equal(views, cut.FindComponents().Select(view => view.Instance)); + Assert.Equal(2, JSInterop.Invocations.Count(i => i.Identifier == "initTerminal")); + } + [Theory] [InlineData("")] [InlineData("/aspire/nested")] diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs index 050cb2c098d..ffb77a4d28f 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs @@ -19,6 +19,30 @@ namespace Aspire.Dashboard.Components.Tests.Pages; public class TerminalWindowTests : DashboardTestContext { + [Fact] + public async Task WorkloadMetadata_UpdatesWindowTitleAndTitlebar() + { + TerminalSetupHelpers.SetupTerminalComponents(this, new TestDashboardClient()); + var head = RenderComponent(); + var cut = RenderComponent(builder => builder.Add(p => p.ResourceName, "shell")); + var terminal = cut.FindComponent().Instance; + var state = new TerminalToolbarState + { + TerminalId = 1, Generation = 1, Connected = true, + Title = "build", WorkingDirectory = "/work/app", ProgressState = "indeterminate" + }; + await cut.InvokeAsync(() => terminal.OnTerminalStateChanged(state)); + Assert.Equal("build", head.Find("title").TextContent); + Assert.Equal("build", cut.Find(".terminal-window-titlebar .terminal-title").TextContent); + Assert.Equal("/work/app", cut.Find(".terminal-window-titlebar .terminal-directory").GetAttribute("data-text")); + Assert.Single(cut.FindAll(".terminal-window-titlebar [role=progressbar]")); + + await cut.InvokeAsync(() => terminal.OnTerminalStateChanged(state with { Title = "", ProgressState = "none" })); + Assert.Equal("shell", head.Find("title").TextContent); + Assert.Equal("shell", cut.Find(".terminal-title").TextContent); + Assert.Empty(cut.FindAll("[role=progressbar]")); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs b/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs index 89325c72156..3fb10994ed5 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs @@ -68,6 +68,7 @@ public static void SetupTerminalView(TestContext context, string pathBase = "") public static BunitJSModuleInterop SetupTerminalViewModule(TestContext context, string modulePath) { + SetupTerminalTitle(context); context.Services.TryAddSingleton(); FluentUISetupHelpers.SetupFluentList(context); var module = context.JSInterop.SetupModule(modulePath); @@ -82,6 +83,13 @@ public static BunitJSModuleInterop SetupTerminalViewModule(TestContext context, return module; } + public static void SetupTerminalTitle(TestContext context) + { + var module = context.JSInterop.SetupModule("./Components/Controls/TerminalTitle.razor.js"); + module.SetupVoid("observePath", _ => true).SetVoidResult(); + module.SetupVoid("disconnectPath", _ => true).SetVoidResult(); + } + public static void SetupTerminalDock(TestContext context, string pathBase = "") { var dock = context.JSInterop.SetupModule("./Components/Layout/TerminalDock.razor.js"); diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs index a282539ca66..ba420ad3ad2 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs @@ -54,7 +54,8 @@ await RunTestAsync(async page => connection.Workload.Write("\u001b[?25lOutput while read-only\r\n"); await connection.WaitForProducerTextAsync("Output while read-only", CancellationToken.None).DefaultTimeout(); await ExpectObserverTextAsync(page, "Output while read-only"); - await Assertions.Expect(terminal.Locator("canvas")).ToBeVisibleAsync(); + // The overlay scrollbar has its own canvas after the terminal surface. + await Assertions.Expect(terminal.Locator("canvas").First).ToBeVisibleAsync(); await page.EvaluateAsync("() => window.moduleTerminal.focus()"); await page.Keyboard.TypeAsync("blocked-keyboard"); await PasteAsync(input, "blocked-paste"); @@ -242,7 +243,7 @@ await RunTestAsync(async page => await MountObserverAsync(page, requestPrimary: false); connection.Workload.Write("\u001b[?25lready"); await ExpectObserverTextAsync(page, "ready"); - var canvas = page.Locator(".terminal-view canvas"); + var canvas = page.Locator(".terminal-view canvas").First; var before = await canvas.ScreenshotAsync(); connection.Workload.Write("\r\nTerminal browser output is visible"); await ExpectObserverTextAsync(page, "Terminal browser output is visible"); @@ -351,7 +352,7 @@ private static Task MountModuleAsync(IPage page, TerminalViewSession sessio url.searchParams.set('viewId', viewId); // Capture the public client returned by the real mount, without replacing // its input implementation, to exercise direct paste/action entry points. - const { WebTerminal } = await import('/js/hex1b-web-terminal/dist/index.js'); + const { WebTerminal } = await import('/js/hex1b-web-terminal/dist/index.min.js'); const mount = WebTerminal.mount; WebTerminal.mount = async (...args) => { const client = await mount.call(WebTerminal, ...args); @@ -372,7 +373,7 @@ private static async Task MountObserverAsync(IPage page, bool requestPrimary) { await page.EvaluateAsync(""" async ({ endpoint, requestPrimary, columns, rows }) => { - const { WebTerminal } = await import('/js/hex1b-web-terminal/dist/index.js'); + const { WebTerminal } = await import('/js/hex1b-web-terminal/dist/index.min.js'); const container = document.createElement('div'); container.dataset.testid = 'observer-terminal'; container.style.cssText = 'position:fixed;right:0;top:0;width:400px;height:200px'; diff --git a/tests/Aspire.Dashboard.Tests/Terminal/TerminalWebSocketTests.cs b/tests/Aspire.Dashboard.Tests/Terminal/TerminalWebSocketTests.cs index bdf78372c74..0b78a4551a0 100644 --- a/tests/Aspire.Dashboard.Tests/Terminal/TerminalWebSocketTests.cs +++ b/tests/Aspire.Dashboard.Tests/Terminal/TerminalWebSocketTests.cs @@ -14,6 +14,79 @@ namespace Aspire.Dashboard.Tests.Terminal; public class TerminalWebSocketTests(ITestOutputHelper output) { + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BrowserView_RestoresHistoryProducedBeforeAttachment(bool useGrpc) + { + await using var host = new TerminalTestHost(output, requireAuthentication: false, useGrpc); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await host.StartAsync(timeout.Token); + var lines = Enumerable.Range(0, 100).Select(i => $"Retained output {i:D3}").ToArray(); + host.Workload.Write(string.Join("\r\n", lines) + "\r\nready"); + await host.WaitForProducerTextAsync("ready", timeout.Token); + + for (var attachment = 0; attachment < 2; attachment++) + { + using var browser = await host.ConnectBrowserAsync(timeout.Token); + var initial = await ReadUntilAsync(browser, _ => true, timeout.Token); + Assert.Equal(101, initial.GetProperty("history").GetProperty("totalRows").GetInt32()); + Assert.Equal(lines[0], await ReadFirstLogicalLineAsync(browser, 1, timeout.Token)); + await browser.CloseAsync(WebSocketCloseStatus.NormalClosure, "Detach", timeout.Token); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task BrowserView_RestoresCommandMarksAndNavigationAfterReattachment(bool useGrpc) + { + await using var host = new TerminalTestHost(output, requireAuthentication: false, useGrpc); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await host.StartAsync(timeout.Token); + // OSC 133 uses A/B/C/D for prompt, command line, execution and completion. + // Retain the executing mark on row zero, well outside the live screen. + // https://github.com/mitchdenny/hex1b/blob/d6a20d4/docs/web-terminal.md + host.Workload.Write("\u001b]133;A\u0007$ \u001b]133;B\u0007echo retained\u001b]133;C;cmdline_url=echo%20retained\u0007\r\n"); + host.Workload.Write(string.Join("\r\n", Enumerable.Range(0, 100).Select(i => $"Output {i:D3}"))); + host.Workload.Write("\r\n\u001b]133;D;0\u0007ready"); + await host.WaitForProducerTextAsync("ready", timeout.Token); + + string? originalId = null; + for (var attachment = 0; attachment < 2; attachment++) + { + using var browser = await host.ConnectBrowserAsync(timeout.Token); + var initial = await ReadUntilAsync(browser, frame => + frame.GetProperty("history").TryGetProperty("markers", out var marks) && + marks.EnumerateArray().Any(mark => mark.GetProperty("phase").GetString() == "executing"), timeout.Token); + var mark = Assert.Single(initial.GetProperty("history").GetProperty("markers").EnumerateArray(), + mark => mark.GetProperty("phase").GetString() == "executing"); + var id = mark.GetProperty("id").GetString(); + Assert.NotNull(id); + Assert.Equal("command", mark.GetProperty("source").GetString()); + Assert.Equal(0, mark.GetProperty("row").GetInt32()); + originalId ??= id; + Assert.Equal(originalId, id); + + await SendAsync(browser, JsonSerializer.Serialize(new { type = "marker", action = "details", requestId = 1, id }), timeout.Token); + var detailsFrame = await ReadUntilAsync(browser, frame => + frame.GetProperty("history").GetProperty("markerResult") is { ValueKind: JsonValueKind.Object } result && + result.GetProperty("requestId").GetInt32() == 1, timeout.Token); + var detailsResult = detailsFrame.GetProperty("history").GetProperty("markerResult"); + Assert.True(detailsResult.GetProperty("success").GetBoolean()); + Assert.Equal("executing", detailsResult.GetProperty("details").GetProperty("phase").GetString()); + Assert.Equal("cmdline_url=echo%20retained", detailsResult.GetProperty("details").GetProperty("rawParameters").GetString()); + + await SendAsync(browser, JsonSerializer.Serialize(new { type = "marker", action = "jump", requestId = 2, id }), timeout.Token); + var jumped = await ReadUntilAsync(browser, frame => + frame.GetProperty("history").GetProperty("markerResult") is { ValueKind: JsonValueKind.Object } result && + result.GetProperty("requestId").GetInt32() == 2, timeout.Token); + Assert.True(jumped.GetProperty("history").GetProperty("markerResult").GetProperty("success").GetBoolean()); + Assert.Equal(0, jumped.GetProperty("history").GetProperty("top").GetInt32()); + await browser.CloseAsync(WebSocketCloseStatus.NormalClosure, "Detach", timeout.Token); + } + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/Infrastructure.Tests/NuGetConfigTests.cs b/tests/Infrastructure.Tests/NuGetConfigTests.cs index f5141a51193..01dd65120ba 100644 --- a/tests/Infrastructure.Tests/NuGetConfigTests.cs +++ b/tests/Infrastructure.Tests/NuGetConfigTests.cs @@ -53,13 +53,28 @@ public void AuditSourceIsSeparateFromPackageSources() }); var sources = root.Element("packageSources")!.Elements("add").ToArray(); - Assert.All(sources, source => AssertApprovedSource(source.Attribute("value")!.Value)); + Assert.All(sources.Where(source => (string?)source.Attribute("key") != "nuget-hex1b"), + source => AssertApprovedSource(source.Attribute("value")!.Value)); Assert.Equal( sources.Select(source => source.Attribute("key")!.Value).Order(StringComparer.Ordinal), root.Element("packageSourceMapping")!.Elements("packageSource") .Select(source => source.Attribute("key")!.Value).Order(StringComparer.Ordinal)); } + [Fact] + public void NuGetOrgIsLimitedToHex1bPackages() + { + var root = XDocument.Load(Path.Combine(RepoRoot.Path, "NuGet.config")).Root!; + var source = Assert.Single(root.Element("packageSources")!.Elements("add"), + source => (string?)source.Attribute("key") == "nuget-hex1b"); + Assert.Equal("https://api.nuget.org/v3/index.json", (string?)source.Attribute("value")); + + var mapping = Assert.Single(root.Element("packageSourceMapping")!.Elements("packageSource"), + mapping => (string?)mapping.Attribute("key") == "nuget-hex1b"); + Assert.Equal(["Hex1b", "Hex1b.McpServer", "Hex1b.Tool"], + mapping.Elements("package").Select(package => (string?)package.Attribute("pattern"))); + } + [Fact] public void DiagnosticsPackagesAreMappedToPublicAndToolsFeeds() { From d032bfd568cc320461b71a04513645f592c76249 Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Sat, 19 Sep 2026 23:10:40 +1000 Subject: [PATCH 02/55] Make terminal titles copyable like working directories Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6bb79b75-08ac-4fcb-a432-921948a3a205 --- .../Components/Controls/TerminalTitle.razor | 18 ++++++++- .../Controls/TerminalTitle.razor.cs | 13 ++++-- .../Controls/TerminalTitle.razor.css | 30 +++++++++++--- .../Resources/TerminalStrings.Designer.cs | 6 +++ .../Resources/TerminalStrings.resx | 4 ++ .../Resources/xlf/TerminalStrings.cs.xlf | 5 +++ .../Resources/xlf/TerminalStrings.de.xlf | 5 +++ .../Resources/xlf/TerminalStrings.es.xlf | 5 +++ .../Resources/xlf/TerminalStrings.fr.xlf | 5 +++ .../Resources/xlf/TerminalStrings.it.xlf | 5 +++ .../Resources/xlf/TerminalStrings.ja.xlf | 5 +++ .../Resources/xlf/TerminalStrings.ko.xlf | 5 +++ .../Resources/xlf/TerminalStrings.pl.xlf | 5 +++ .../Resources/xlf/TerminalStrings.pt-BR.xlf | 5 +++ .../Resources/xlf/TerminalStrings.ru.xlf | 5 +++ .../Resources/xlf/TerminalStrings.tr.xlf | 5 +++ .../Resources/xlf/TerminalStrings.zh-Hans.xlf | 5 +++ .../Resources/xlf/TerminalStrings.zh-Hant.xlf | 5 +++ src/Aspire.Dashboard/wwwroot/js/README.md | 2 + .../Controls/TerminalTitleTests.cs | 40 ++++++++++++++++++- 20 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor index b5745bd81d3..d618aea6c55 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor @@ -11,7 +11,23 @@ } } - @DisplayTitle + @if (!string.IsNullOrEmpty(DisplayTitle)) + { +
+ + + @DisplayTitle + + + +
+ } @if (!string.IsNullOrEmpty(State?.WorkingDirectory)) {
diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs index aba29e59b7f..8003be4ebee 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs @@ -12,6 +12,7 @@ namespace Aspire.Dashboard.Components.Controls; /// Displays the workload title, directory and progress without changing terminal state. public partial class TerminalTitle : IAsyncDisposable { + private readonly string _titleButtonId = $"terminal-title-{Guid.NewGuid():N}"; private readonly string _directoryButtonId = $"terminal-directory-{Guid.NewGuid():N}"; private ElementReference _metadataElement; private IJSObjectReference? _jsModule; @@ -86,11 +87,17 @@ public async ValueTask DisposeAsync() } } - private Dictionary DirectoryCopyAttributes => FluentUIExtensions.GetClipboardCopyAdditionalAttributes( - State?.WorkingDirectory, + private Dictionary TitleCopyAttributes => + GetCopyAttributes(DisplayTitle, nameof(Resources.TerminalStrings.TerminalCopyTitle)); + + private Dictionary DirectoryCopyAttributes => + GetCopyAttributes(State?.WorkingDirectory ?? string.Empty, nameof(Resources.TerminalStrings.TerminalCopyWorkingDirectory)); + + private Dictionary GetCopyAttributes(string value, string labelResourceName) => FluentUIExtensions.GetClipboardCopyAdditionalAttributes( + value, ControlsLoc[nameof(Resources.ControlsStrings.GridValueCopyToClipboard)], ControlsLoc[nameof(Resources.ControlsStrings.GridValueCopied)], - ("aria-label", Loc[nameof(Resources.TerminalStrings.TerminalCopyWorkingDirectory), State?.WorkingDirectory ?? string.Empty].Value)); + ("aria-label", Loc[labelResourceName, value].Value)); private string DisplayTitle => State is { Title.Length: > 0 } ? State.Title : FallbackTitle; diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css index 859b0a27541..f43a13d5ea6 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css @@ -8,35 +8,50 @@ font-size: var(--fontSizeBase200); } -.terminal-title { +.terminal-title-container { flex: 1; + /* Keep the copy target usable even when a long directory competes for space. */ + min-width: clamp(3rem, 25%, 6rem); +} + +.terminal-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.terminal-title:empty { - display: none; -} - .terminal-directory-container { margin-inline-start: auto; max-width: min(32rem, 60%); min-width: 0; } +.terminal-title-container ::deep .terminal-title-button, .terminal-directory-container ::deep .terminal-directory { max-width: 100%; min-width: 0; - font-family: var(--fontFamilyMonospace); + padding-inline: 4px; + font-family: inherit; font-size: inherit; } +.terminal-directory-container ::deep .terminal-directory { + font-family: var(--fontFamilyMonospace); +} + +.terminal-title-container ::deep .terminal-title-button:focus-visible, +.terminal-directory-container ::deep .terminal-directory:focus-visible { + /* The compact detached header has no room for an external focus outline. */ + outline-offset: -2px; +} + +.terminal-title-container ::deep .terminal-title-button::part(content), .terminal-directory-container ::deep .terminal-directory::part(content) { min-width: 0; } +.terminal-title-content, .terminal-directory-content { display: flex; align-items: center; @@ -62,6 +77,7 @@ inset: 0; } +.terminal-title-copy-icons, .terminal-directory-copy-icons { flex: 0 0 16px; width: 16px; @@ -69,6 +85,8 @@ opacity: 0; } +.terminal-title-container:has(.terminal-title-button:hover) .terminal-title-copy-icons, +.terminal-title-container:has(:focus-visible) .terminal-title-copy-icons, .terminal-directory-container:hover .terminal-directory-copy-icons, .terminal-directory-container:has(:focus-visible) .terminal-directory-copy-icons { opacity: 1; diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs index 5e68f3c2407..85b1b94d742 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs @@ -57,6 +57,12 @@ public static string TerminalCopyWorkingDirectory { } } + public static string TerminalCopyTitle { + get { + return ResourceManager.GetString("TerminalCopyTitle", resourceCulture); + } + } + public static string TerminalProgressError { get { return ResourceManager.GetString("TerminalProgressError", resourceCulture); diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.resx b/src/Aspire.Dashboard/Resources/TerminalStrings.resx index 9f3f831494a..c972a155a72 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.resx +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.resx @@ -124,6 +124,10 @@ Copy working directory: {0} {0} is the working directory path. + + Copy terminal title: {0} + {0} is the terminal title. + Error diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf index 443dedf8b6b..41e2e6c27e4 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf index c56a734dc18..2245fb39a74 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf index 5d46cd79fb0..ae7e477e30c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf index 3dd729d9d73..d109b9c05cd 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf index 839e388ed11..2a44b9c8aa3 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf index 500b55d2956..87515a03f14 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf index 36eed236e93..5174477933c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf index 0d410f5ed30..243b058395c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf index cdde1e08400..f032142467e 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf index 5df6c7d4331..d50005718cd 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf index c9a07ca4477..1e2aca66683 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf index 4f6df34306e..403e6544163 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf index 5f235a672af..9ceb86b1880 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf @@ -7,6 +7,11 @@ Toggle terminal (`) + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + Copy working directory: {0} Copy working directory: {0} diff --git a/src/Aspire.Dashboard/wwwroot/js/README.md b/src/Aspire.Dashboard/wwwroot/js/README.md index 3684c08f4ee..15f6b78ec56 100644 --- a/src/Aspire.Dashboard/wwwroot/js/README.md +++ b/src/Aspire.Dashboard/wwwroot/js/README.md @@ -106,6 +106,8 @@ is displayed at the right of the title bar as a copy button. Clicking anywhere on the path copies the full value using the Dashboard's shared client-side clipboard handler; the copy icon appears on hover or keyboard focus without changing the layout. +Titles use the same borderless, hover-icon copy interaction, including the fallback +resource name when no workload title is present. Both buttons copy the full text. Long paths omit whole middle segments to retain leading and trailing context, while the clipboard retains the full decoded path. Cleared titles fall back to the surface's original name. Progress supports determinate, diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs index e33936908cd..623ff199cc2 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs @@ -56,6 +56,42 @@ public void Metadata_IsRenderedAsTextAndClearsToFallback() Assert.Empty(cut.FindAll(".terminal-directory")); } + [Fact] + public void Title_CopiesFullLiveTitleAndFallbackWithIndependentFeedback() + { + var title = "build \u03bb with a long title"; + var cut = RenderComponent(builder => builder + .Add(p => p.FallbackTitle, "shell") + .Add(p => p.State, new TerminalToolbarState + { + Title = title, + WorkingDirectory = "/work/app" + })); + + var button = cut.Find(".terminal-title-button"); + var id = button.Id; + Assert.NotEqual(cut.Find(".terminal-directory").Id, id); + Assert.Equal(title, cut.Find(".terminal-title").TextContent); + Assert.Equal(title, button.GetAttribute("data-text")); + Assert.Equal("true", button.GetAttribute("data-copybutton")); + Assert.Equal($"Copy terminal title: {title}", button.GetAttribute("aria-label")); + Assert.Equal(Resources.ControlsStrings.GridValueCopied, button.GetAttribute("data-postcopy")); + Assert.Single(button.QuerySelectorAll(".copy-icon")); + Assert.Single(button.QuerySelectorAll(".checkmark-icon")); + Assert.False(button.HasAttribute("title")); + Assert.False(button.HasAttribute("disabled")); + Assert.Empty(cut.FindAll("fluent-tooltip")); + + cut.SetParametersAndRender(builder => builder.Add(p => p.State, new TerminalToolbarState { Title = "updated" })); + Assert.Equal("updated", cut.Find(".terminal-title-button").GetAttribute("data-text")); + Assert.Equal(id, cut.Find(".terminal-title-button").Id); + cut.SetParametersAndRender(builder => builder.Add(p => p.State, new TerminalToolbarState())); + Assert.Equal("shell", cut.Find(".terminal-title-button").GetAttribute("data-text")); + Assert.Equal("Copy terminal title: shell", cut.Find(".terminal-title-button").GetAttribute("aria-label")); + cut.SetParametersAndRender(builder => builder.Add(p => p.FallbackTitle, "")); + Assert.Empty(cut.FindAll(".terminal-title-button")); + } + [Theory] [InlineData("/work/app")] [InlineData("C:\\src\\app")] @@ -86,7 +122,7 @@ public void Progress_ExposesAccessibleStateAndValue(string state, int? percentag { var cut = RenderComponent(builder => builder.Add(p => p.State, new TerminalToolbarState { - Connected = true, ProgressState = state, ProgressPercentage = percentage + Title = "shell", Connected = true, ProgressState = state, ProgressPercentage = percentage })); var progress = cut.Find("[role=progressbar]"); Assert.Equal(expectedValue, progress.GetAttribute("aria-valuenow")); @@ -96,7 +132,7 @@ public void Progress_ExposesAccessibleStateAndValue(string state, int? percentag Assert.Equal(Resources.TerminalStrings.ResourceManager.GetString(label), indicator.GetAttribute("title")); Assert.Equal(expectedText, indicator.TextContent.Trim()); Assert.Equal("terminal-progress", cut.Find(".terminal-metadata").Children[0].ClassName); - Assert.Equal("terminal-title", cut.Find(".terminal-metadata").Children[1].ClassName); + Assert.Equal("terminal-title-container", cut.Find(".terminal-metadata").Children[1].ClassName); if (state == "indeterminate") { Assert.Empty(cut.FindAll(".terminal-progress-percentage")); From 1a0180520a8a97876b303628df1cbb669dcc2d8f Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Sat, 19 Sep 2026 23:24:17 +1000 Subject: [PATCH 03/55] Right-align terminal paths beside copy indicators Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6bb79b75-08ac-4fcb-a432-921948a3a205 --- src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css index f43a13d5ea6..482e7e80ae3 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css @@ -75,6 +75,7 @@ .terminal-directory-display { position: absolute; inset: 0; + text-align: right; } .terminal-title-copy-icons, From a807ee2298e12afdf3b83c4e810811984183dddf Mon Sep 17 00:00:00 2001 From: Mitch Denny Date: Sat, 19 Sep 2026 23:36:40 +1000 Subject: [PATCH 04/55] Use Fluent font sizing and fit icons in terminal footer Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6bb79b75-08ac-4fcb-a432-921948a3a205 --- .../Components/Controls/TerminalView.razor | 18 ++++++---- .../Resources/TerminalStrings.resx | 2 +- .../Resources/xlf/TerminalStrings.cs.xlf | 4 +-- .../Resources/xlf/TerminalStrings.de.xlf | 4 +-- .../Resources/xlf/TerminalStrings.es.xlf | 4 +-- .../Resources/xlf/TerminalStrings.fr.xlf | 4 +-- .../Resources/xlf/TerminalStrings.it.xlf | 4 +-- .../Resources/xlf/TerminalStrings.ja.xlf | 4 +-- .../Resources/xlf/TerminalStrings.ko.xlf | 4 +-- .../Resources/xlf/TerminalStrings.pl.xlf | 4 +-- .../Resources/xlf/TerminalStrings.pt-BR.xlf | 4 +-- .../Resources/xlf/TerminalStrings.ru.xlf | 4 +-- .../Resources/xlf/TerminalStrings.tr.xlf | 4 +-- .../Resources/xlf/TerminalStrings.zh-Hans.xlf | 4 +-- .../Resources/xlf/TerminalStrings.zh-Hant.xlf | 4 +-- .../Controls/TerminalViewTests.cs | 35 ++++++++++++++++++- 16 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor index 95185128f5e..ef0d91bde7a 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor +++ b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor @@ -47,21 +47,27 @@