diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 82bf44e506d..c98f9be0d0a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "hex1b.tool": { - "version": "0.165.0", + "version": "0.171.0", "commands": [ "hex1b" ] diff --git a/.mcp.json b/.mcp.json index 0418e51ba28..4ed4d3cb865 100644 --- a/.mcp.json +++ b/.mcp.json @@ -4,7 +4,7 @@ "type": "stdio", "command": "dnx", "args": [ - "Hex1b.McpServer@0.133.0", + "Hex1b.McpServer@0.171.0", "--yes" ] }, diff --git a/Directory.Packages.props b/Directory.Packages.props index 4c69072792c..404c5bcadc5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -119,9 +119,9 @@ - - - + + + diff --git a/docs/specs/cli-backchannel.md b/docs/specs/cli-backchannel.md index 779adf44a59..f6cc0ea4248 100644 --- a/docs/specs/cli-backchannel.md +++ b/docs/specs/cli-backchannel.md @@ -38,13 +38,17 @@ privileged administrators. Process-backed AppHost terminals (both docked and interaction prompts) also configure Hex1b's Windows PTY helper socket directory before creating the deferred workload. Aspire -secures `%USERPROFILE%\.aspire\pty` and sets `HEX1B_PTY_SHIM_SOCKET_DIR` in the AppHost's -process environment. An existing override is preserved, normalized to an absolute path, +secures `%USERPROFILE%\.aspire\pty` and supplies a unique explicit +`WindowsPtyProxySocketPath` to each workload. An existing `HEX1B_PTY_SHIM_SOCKET_DIR` +override is read without changing the process environment, normalized to an absolute path, and validated with the same override policy; unsafe directories or permission failures prevent -terminal creation. Hex1b may additionally grant Windows SYSTEM access to this directory. -The override remains set for the process lifetime because Hex1b reads it at PTY startup, -not when the process-options callback runs. Setting only the child process environment -would not control this socket. Linux and macOS use direct PTYs and need no such override. +terminal creation. Socket paths are checked against the native endpoint length limit +before creating directories. Sockets live in an Aspire-owned `proxy` subdirectory +so Hex1b's startup ACL enforcement does not rewrite the override directory's permissions. +The shorter unique filenames keep paths within the previous allocation's length budget. +Hex1b snapshots the explicit path before deferred startup, +so later parent or child environment changes cannot redirect it. Hex1b owns socket cleanup. +Linux and macOS use direct PTYs and need no socket path. ## Philosophy diff --git a/docs/specs/with-terminal.md b/docs/specs/with-terminal.md index 60cbb79ece1..699afbd99cb 100644 --- a/docs/specs/with-terminal.md +++ b/docs/specs/with-terminal.md @@ -336,10 +336,14 @@ it does not lock the terminal, its creator's automation, or other viewers. ### Browser requirements and package pairing The dashboard uses `@hex1b/web-terminal` and the `Hex1b` NuGet package at -exactly `0.168.0`. HWT1 is experimental state transfer +exactly `0.171.0`. HWT1 is experimental state transfer between these paired packages, not a stable wire contract implemented by -Aspire. Upgrade both together. The full npm `dist` tree is vendored, including -module workers, relative imports, fonts and licenses. +Aspire. Upgrade both together. The vendored runtime consists of one bundled +JavaScript file for the client and both workers, a font, and two licenses. +Worker roles use fragments on the bundled file URL rather than separate worker +modules; the dashboard references the standalone vendored bundle for both. +The 0.171 producer retains at most 200 command marks even when older backing +text is still present; scrollback text is retained independently. The dashboard uses the package's automatic renderer selection: WebGPU is preferred, with WebGL2 used when WebGPU capabilities or device acquisition are @@ -376,7 +380,10 @@ The package handles Ctrl/Cmd+click on authoritative OSC 8 hyperlinks in live output and history. HMP state replay preserves link destinations across late attachment and reconnect. It only opens absolute HTTP, HTTPS and mailto destinations with `noopener,noreferrer`; plain clicks and drags retain selection/application -behavior. Aspire adds no custom opener or plain-text URL detection. See the +behavior. Aspire also enables Hex1b's per-view plain-text HTTP/HTTPS URL detector +with dashed underlines and a Ctrl/Cmd-click action that opens a new tab with +`noopener,noreferrer`. Remote file paths and custom URI schemes are not enabled. +See the [hyperlink PR](https://github.com/mitchdenny/hex1b/pull/489), [renderer PR](https://github.com/mitchdenny/hex1b/pull/491), and [hyperlink replay fix](https://github.com/mitchdenny/hex1b/pull/493). @@ -450,7 +457,31 @@ input to the footer controls; Shift+F6 moves focus to the preceding dashboard control. Hiding the hint preserves its space so focus changes do not resize the terminal or move the footer controls. -Dock tabs share the Resources/Parameters tab styling. The dock resize handle +Outside the dock, terminal headers keep a fixed icon slot before the workload title. Active progress +uses a ring with the percentage in its tooltip, not inline text; otherwise the +resource page shows its resource icon, and other surfaces use a terminal icon. +Error and warning progress retain their accessible labels and theme colors. +Working directories shorten in the middle when space is limited; UNC paths keep +their server and share together or show no path if even the root cannot fit. +The terminal's Light/Dark palette is an explicit browser preference, independent +of the Dashboard theme. It defaults to Dark and can be changed in the terminal +footer; Dashboard chrome still follows Settings theme changes. + +Dock tabs use the titles supplied when the terminals are created, not live workload +titles. The dock does not show a separate workload title, icon, progress, or working +directory alongside the tabs. + +Dock tabs share the Resources/Parameters tab styling. Drag a tab to reorder it, +or focus its title and press Alt+Shift+Left/Right. Reordering keeps the active +terminal and mounted viewers unchanged. The order is local to the dashboard +session and survives recovery snapshots; newly discovered terminals append. +Tabs stay on one row, with horizontal scrolling. Left/right buttons remain +visible beside **Open in window**, disabled when there are no more tabs in +that direction. A thin line between tabs indicates the drop position. +Activating a tab brings its title and close button into view, +and dragging near either edge scrolls the strip to reach off-screen tabs. + +The dock resize handle uses the dashboard's Fluent splitter styling, including neutral gray hover, drag and keyboard-focus feedback. diff --git a/eng/github-ci/test-trigger-map.yml b/eng/github-ci/test-trigger-map.yml index cff7dcbeb4f..5dd790cbfd6 100644 --- a/eng/github-ci/test-trigger-map.yml +++ b/eng/github-ci/test-trigger-map.yml @@ -134,6 +134,7 @@ path_rules: - paths: - src/Aspire.Dashboard/Components/Controls/TerminalView.razor.js + - src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js - src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js - src/Aspire.Dashboard/wwwroot/js/app.js - src/Aspire.Dashboard/wwwroot/js/app-terminalwindow.js diff --git a/playground/Terminals/Terminals.AppHost/AppHost.cs b/playground/Terminals/Terminals.AppHost/AppHost.cs index 8d828c7daab..bedbcb42f7a 100644 --- a/playground/Terminals/Terminals.AppHost/AppHost.cs +++ b/playground/Terminals/Terminals.AppHost/AppHost.cs @@ -1,14 +1,29 @@ // 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("palette-test", "Scripts/palette-test.cs") + .WithTerminal(options => + { + options.Columns = 120; + options.Rows = 50; + options.ShowTerminalHost = true; + }); + +builder.AddCSharpApp("terminal-features", "Scripts/terminal-features.cs") + .WithTerminal(options => + { + options.Columns = 120; + options.Rows = 32; + options.ShowTerminalHost = true; + }); + builder.AddPostgres("postgres").WithRepl(); builder.AddRedis("redis").WithRepl(); builder.AddValkey("valkey").WithRepl(); diff --git a/playground/Terminals/Terminals.AppHost/Scripts/palette-test.cs b/playground/Terminals/Terminals.AppHost/Scripts/palette-test.cs new file mode 100644 index 00000000000..739995c46ee --- /dev/null +++ b/playground/Terminals/Terminals.AppHost/Scripts/palette-test.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// Open palette-test in the Terminals playground, then use the terminal footer's +// Terminal palette selector to compare Aspire Light and Aspire Dark. +// Run directly: dotnet run --file palette-test.cs (add -- --once to emit the chart and exit). +// Original chart inspired by the block/foreground views in https://github.com/eikenb/terminal-colors. +// SGR reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + +using System.Text; + +const string Reset = "\u001b[0m"; +string[] names = ["Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White"]; +var chart = new StringBuilder(); + +// Keep default and indexed colors as SGR references (e.g. ESC[31;44m), not resolved RGB. +// This lets the browser recolor existing content and selection without rerunning the script. +string Foreground(int index) => index < 8 ? $"{30 + index}" : $"{90 + index - 8}"; +string Background(int index) => index < 8 ? $"{40 + index}" : $"{100 + index - 8}"; +string Label(int index) => $"{index:00} {(index < 8 ? "" : "Bright ")}{names[index % 8]}"; + +void Cell(string text, string foreground, string background, int width) +{ + chart.Append("\u001b[").Append(foreground).Append(';').Append(background).Append('m'); + chart.Append(text.PadRight(width)); + chart.Append(Reset); +} + +chart.AppendLine("ASPIRE TERMINAL PALETTE REVIEW"); +chart.AppendLine("Switch Aspire Light / Dark using the Terminal palette selector in the footer. Select text to compare selection colors."); +chart.AppendLine("ANSI names identify slots, not fixed RGB values. Low-contrast matrix cells are intentional."); +chart.AppendLine(); +chart.AppendLine("DEFAULT SURFACE (uncolored cells use the palette background)"); +chart.AppendLine("+--------------------------------------------------------------------------------------------------+"); +chart.AppendLine("| |"); +chart.AppendLine("| Default foreground: The quick brown fox jumps over the lazy dog. 0123456789 !? [] {} |"); +chart.AppendLine("+--------------------------------------------------------------------------------------------------+"); +chart.AppendLine(); +chart.AppendLine("ANSI SWATCHES: blank fill, default text, black text (00), bright-white text (15)"); + +for (var start = 0; start < 16; start += 8) +{ + chart.AppendLine(start == 0 ? "Normal 00-07" : "Bright 08-15 (explicit bright slots, not bold)"); + for (var index = start; index < start + 8; index++) + { + chart.Append($"{index:00} {names[index % 8]}".PadRight(12)); + } + chart.AppendLine(); + + foreach (var (text, foreground) in new[] { ("", "39"), (" Default", "39"), (" Black", "30"), (" White", "97") }) + { + for (var index = start; index < start + 8; index++) + { + Cell(text, foreground, Background(index), 11); + chart.Append(' '); + } + chart.AppendLine(); + } + chart.AppendLine(); +} + +chart.AppendLine("TEXT MATRIX: rows = foreground; columns = background; D = palette default"); +chart.Append("Foreground".PadRight(18)); +foreach (var column in new[] { "D", "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15" }) +{ + chart.Append(column.PadRight(5)); +} +chart.AppendLine(); + +for (var foreground = -1; foreground < 16; foreground++) +{ + chart.Append((foreground < 0 ? "D Default" : Label(foreground)).PadRight(18)); + for (var background = -1; background < 16; background++) + { + Cell(" Aa0 ", foreground < 0 ? "39" : Foreground(foreground), background < 0 ? "49" : Background(background), 5); + } + chart.AppendLine(); +} + +chart.AppendLine(); +chart.AppendLine($"ATTRIBUTES: Normal Aa0 \u001b[1mBold Aa0{Reset} \u001b[2mDim Aa0{Reset} \u001b[4mUnderline Aa0{Reset} \u001b[7mReverse Aa0{Reset}"); +chart.AppendLine($"ANSI TEXT: \u001b[31mRed{Reset} \u001b[32mGreen{Reset} \u001b[33mYellow{Reset} \u001b[34mBlue{Reset} \u001b[35mMagenta{Reset} \u001b[36mCyan{Reset}"); +chart.AppendLine($"TRUE RGB CONTROL (does not follow palette): \u001b[38;2;255;128;64mOrange #FF8040{Reset}"); +chart.Append("Chart stays still for selection and screenshots. Resize or scroll as needed. Ctrl+C exits."); + +using var stopping = new CancellationTokenSource(); +Console.CancelKeyPress += (_, e) => +{ + e.Cancel = true; + stopping.Cancel(); +}; + +try +{ + Console.Write("\u001b]2;Aspire palette review\u0007"); + Console.Write(Reset); + Console.Write(chart); + if (!args.Contains("--once")) + { + await Task.Delay(Timeout.Infinite, stopping.Token); + } +} +catch (OperationCanceledException) when (stopping.IsCancellationRequested) +{ + // Leave the chart available until the user stops the resource or presses Ctrl+C. +} +finally +{ + Console.Write($"{Reset}\u001b]2;\u0007"); +} 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..5d03dae56cc --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor @@ -0,0 +1,53 @@ +@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..8ca82b40e2c --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; +using Aspire.Dashboard.Extensions; +using Aspire.Dashboard.Utils; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Localization; +using Microsoft.FluentUI.AspNetCore.Components; +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 static readonly Icon s_terminalIcon = new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.WindowConsole(); + private readonly string _titleButtonId = $"terminal-title-{Guid.NewGuid():N}"; + 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; + + /// Gets or sets the resource icon shown when progress is inactive; defaults to the terminal icon. + [Parameter] + public Icon? IdleIcon { get; set; } + + [Inject] + public required IStringLocalizer Loc { get; init; } + + [Inject] + public required IStringLocalizer ControlsLoc { get; init; } + + [Inject] + public required IJSRuntime JS { get; init; } + + [Inject] + public required ILogger Logger { get; init; } + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _initializationTask = InitializeAsync(); + } + + return _initializationTask ?? Task.CompletedTask; + } + + private async Task InitializeAsync() + { + try + { + _jsModule = await JS.InvokeAsync("import", $"./{Assets["Components/Controls/TerminalTitle.razor.js"]}"); + if (!_disposed) + { + await _jsModule.InvokeVoidAsync("observePath", _metadataElement); + } + } + catch (JSDisconnectedException) + { + // The circuit is gone; there is no metadata observer to keep alive. + } + catch (OperationCanceledException) when (_disposed) + { + // Disposal can interrupt the optional observer setup. + } + catch (JSException ex) + { + Logger.LogWarning(ex, "Could not initialize terminal path measurement."); + } + } + + /// 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 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)], + ("data-copyfailed", Loc[nameof(Resources.TerminalStrings.TerminalCopyFailed)].Value), + ("aria-label", Loc[labelResourceName, value].Value)); + + private string DisplayTitle => State is { Title.Length: > 0 } ? State.Title : FallbackTitle; + + private double? ProgressValue => State?.ProgressState is "indeterminate" ? null : State?.ProgressPercentage; + + private bool HasProgress => State is { Connected: true, ProgressState: "normal" or "indeterminate" or "error" or "warning" }; + + private string ProgressTooltip => ProgressValue is { } percentage ? (percentage / 100).ToString("P0", CultureInfo.CurrentCulture) : ProgressLabel; + + 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..ec1dab02511 --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.css @@ -0,0 +1,131 @@ +.terminal-metadata { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + flex: 1; + color: var(--colorNeutralForeground1); + font-family: var(--fontFamilyBase); + font-size: var(--fontSizeBase200); +} + +.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-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 { + --colorNeutralForeground2BrandPressed: var(--colorBrandForegroundLinkPressed); + max-width: 100%; + min-width: 0; + padding-inline: 4px; + font-family: inherit; + font-size: inherit; +} + +.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; + 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; + text-align: right; +} + +.terminal-copy-status { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} + +.terminal-title-copy-icons, +.terminal-directory-copy-icons { + flex: 0 0 16px; + width: 16px; + height: 16px; + 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; +} + +.terminal-icon[data-state] { + display: flex; + align-items: center; + box-sizing: content-box; + width: 16px; + color: var(--colorBrandForegroundLink); + padding-inline-end: 8px; + border-inline-end: 1px solid var(--terminal-titlebar-border-color, var(--colorNeutralStroke1)); + flex-shrink: 0; +} + +.terminal-icon[data-state="error"] { + color: var(--error); + --colorBrandStroke1: var(--error); +} + +.terminal-icon[data-state="warning"] { + color: var(--warning); + --colorBrandStroke1: var(--warning); +} + +@media (forced-colors: active) { + .terminal-icon[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..5b6d9a79ff0 --- /dev/null +++ b/src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js @@ -0,0 +1,94 @@ +// 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. The UNC server and share belong to the root; + // \\server\…\src would identify a different location. 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 uncRoot = windows ? path.match(/^\\\\[^\\/]+[\\/]+[^\\/]+[\\/]*/u)?.[0] : null; + const root = uncRoot ?? 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 uncRoot ? [root + ellipsis, ""] : [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 10adcc5d33f..d6bb2e1123e 100644 --- a/src/Aspire.Dashboard/Components/Controls/TerminalView.razor +++ b/src/Aspire.Dashboard/Components/Controls/TerminalView.razor @@ -1,6 +1,6 @@ @namespace Aspire.Dashboard.Components.Controls -
+
- +
+ +
+
+ +
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 4d5393bacf7..ab4947b725a 100644 --- a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor +++ b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor @@ -9,7 +9,6 @@
0) { -
- @foreach (var terminal in _terminals) - { -
- - - - -
- } +
+
+ @foreach (var terminal in _terminals) + { +
+ + + + +
+ } +
+ }
@if (_popupBlocked) { @Loc[nameof(Resources.TerminalStrings.TerminalDockDetachBlocked)] } +
+ + + + + + +
_terminals.Count == 0; + /// + /// Moves a dock tab relative to another tab without changing the active terminal or its viewer. + /// + /// The terminal being moved. + /// The terminal that anchors the new position. + /// Whether to insert after the target instead of before it. + /// A task that completes when the tab order is updated. + [JSInvokable] + public Task ReorderTerminalAsync(string terminalId, string targetId, bool after) => InvokeAsync(() => + { + if (_disposed || terminalId == targetId) + { + return; + } + + var sourceIndex = _terminals.FindIndex(t => t.TerminalId == terminalId); + var targetIndex = _terminals.FindIndex(t => t.TerminalId == targetId); + if (sourceIndex < 0 || targetIndex < 0) + { + Logger.LogDebug("Ignored reordering removed dock terminal {TerminalId} relative to {TargetId}.", terminalId, targetId); + return; + } + + var terminal = _terminals[sourceIndex]; + _terminals.RemoveAt(sourceIndex); + targetIndex = _terminals.FindIndex(t => t.TerminalId == targetId); + _terminals.Insert(targetIndex + (after ? 1 : 0), terminal); + StateHasChanged(); + }); + private bool IsPaneActive(string terminalId) => terminalId == _activeTerminalId; private string GetTabId(string terminalId) => $"{_elementIdPrefix}-tab-{terminalId}"; @@ -399,8 +429,14 @@ await InvokeAsync(async () => if (update.KindCase == WatchTerminalsUpdate.KindOneofCase.Snapshot) { var previousActiveIndex = _terminals.FindIndex(t => t.TerminalId == _activeTerminalId); + // Keep this browser's tab order across recovery snapshots; append newly discovered terminals. + var snapshotById = update.Snapshot.Terminals.ToDictionary(t => t.TerminalId, StringComparer.Ordinal); + var ordered = _terminals.Where(t => snapshotById.ContainsKey(t.TerminalId)) + .Select(t => snapshotById[t.TerminalId]).ToList(); + var existingIds = ordered.Select(t => t.TerminalId).ToHashSet(StringComparer.Ordinal); + ordered.AddRange(update.Snapshot.Terminals.Where(t => !existingIds.Contains(t.TerminalId))); _terminals.Clear(); - _terminals.AddRange(update.Snapshot.Terminals); + _terminals.AddRange(ordered); if (!_terminals.Any(t => t.TerminalId == _activeTerminalId)) { // Snapshots can also remove the active tab; use the same adjacent fallback as removal. diff --git a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css index 48ef77e1711..1777ba8f4c1 100644 --- a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css +++ b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.css @@ -1,5 +1,5 @@ /* The dock is fixed to the viewport bottom and taken out of layout flow so collapsing it cannot reflow the page. - Collapsing translates it fully off-screen instead of hiding it: the element keeps its box dimensions, so xterm's + Collapsing translates it fully off-screen instead of hiding it: the element keeps its box dimensions, so the terminal's measured cell metrics and column count stay valid and reopening does not need a re-fit or a repaint. */ .terminal-dock { position: fixed; @@ -11,32 +11,10 @@ flex-direction: column; box-sizing: border-box; max-height: 100vh; - background-color: #0d1117; + background-color: var(--colorNeutralBackground1); transition: transform 120ms ease-out; --fluent-multi-splitter-bar-size: 5px; --fluent-multi-splitter-background-color: var(--colorNeutralStroke2); - /* The dock stays dark in both dashboard themes. Pin the Fluent tokens its buttons and icons - consume so a light dashboard theme cannot paint dark icons or light controls on the tabstrip. */ - --colorNeutralForeground1: #c9d1d9; - --colorNeutralForeground1Hover: #f0f6fc; - --colorNeutralForeground1Pressed: #8b949e; - --colorNeutralForeground2: #c9d1d9; - --colorNeutralForeground2Hover: #f0f6fc; - --colorNeutralForeground2Pressed: #8b949e; - --colorNeutralForeground2BrandHover: #58a6ff; - --colorNeutralForeground2BrandPressed: #58a6ff; - --colorNeutralForeground3: #8b949e; - --colorTransparentBackground: transparent; - --colorTransparentBackgroundPressed: #30363d; - --colorNeutralBackground1: #21262d; - --colorNeutralBackground1Hover: #30363d; - --colorNeutralBackground1Pressed: #282e33; - --colorNeutralBackground2: #21262d; - --colorNeutralBackground3: #30363d; - --colorNeutralStroke1: #30363d; - /* Fluent's purple focus ring is close to unreadable on #0d1117; reuse the dock's own accent instead. */ - --colorBrandStroke1: #58a6ff; - --colorStrokeFocus2: #58a6ff; } .terminal-dock.visible { @@ -60,21 +38,91 @@ align-items: center; gap: 2px; padding: 2px 4px; - background-color: #161b22; + background-color: var(--colorNeutralBackground2); border-top: 1px solid var(--colorNeutralStroke1); border-bottom: 1px solid var(--colorNeutralStroke1); flex: 0 0 auto; + height: 36px; +} + +.terminal-dock-tabs { + position: relative; + display: flex; + align-items: center; + min-width: 0; + flex: 0 1 auto; +} + +.terminal-dock-tabs[data-overflow-right]::after { + content: ""; + position: absolute; + inset: 0 0 0 auto; + width: 32px; + background: linear-gradient(to right, transparent, var(--colorNeutralBackground2)); + pointer-events: none; } .terminal-dock-tablist { + flex: 0 1 auto; + flex-wrap: nowrap; + overflow-x: auto; + scrollbar-width: none; border-color: transparent; } +.terminal-dock-tablist::-webkit-scrollbar { + display: none; +} + +.terminal-dock-tab-scroll { + display: flex; + flex: 0 0 auto; +} + +.terminal-dock-tab[data-drop-position]::after { + content: ""; + position: absolute; + top: 3px; + bottom: 3px; + width: 2px; + background-color: var(--colorBrandStroke1); + pointer-events: none; +} + +/* Center the insertion line in the shared tab list's 4px gap, outside the tab border. */ +.terminal-dock-tab[data-drop-position="before"]::after { + left: -4px; +} + +.terminal-dock-tab[data-drop-position="after"]::after { + right: -4px; +} + +.terminal-dock-tab.dragging { + opacity: 0.5; +} + +@media (forced-colors: active) { + .terminal-dock-tabs[data-overflow-right]::after { + display: none; + } + + .terminal-dock-tab[data-drop-position]::after { + background-color: Highlight; + forced-color-adjust: none; + } +} + .terminal-dock-tab { + position: relative; display: flex; align-items: center; + font-size: var(--fontSizeBase200); + /* Include the close button's inset around its 12px icon in the tab's trailing spacing. */ + padding-inline-end: calc(12px - (var(--aspire-control-height) - 12px) / 2); max-width: 220px; flex: 0 0 auto; + height: auto; } .terminal-dock-tab-select { @@ -101,8 +149,14 @@ white-space: nowrap; } +.terminal-dock-tab ::deep .terminal-dock-tab-close { + --aspire-control-height: 24px; +} + .terminal-dock-filler { flex: 1 1 auto; + min-width: 0; + padding-inline: 8px; } .terminal-dock-body { @@ -116,7 +170,7 @@ inset: 0; } -/* visibility, not display: an inactive pane must keep real dimensions or xterm refits itself to zero columns. */ +/* visibility, not display: an inactive pane must keep real dimensions or the terminal refits itself to zero columns. */ .terminal-dock-pane.inactive { visibility: hidden; } @@ -126,7 +180,7 @@ inset: 0; display: flex; container: terminal-dock-panel / size; - background-color: #0d1117; + background-color: var(--colorNeutralBackground1); overflow: auto; } @@ -142,14 +196,14 @@ margin: auto; padding: 24px; text-align: center; - color: #8b949e; + color: var(--colorNeutralForeground2); font-size: 12px; line-height: 1.5; } .terminal-dock-panel-heading { margin: 0; - color: #c9d1d9; + color: var(--colorNeutralForeground1); font-size: 14px; line-height: 1.5; font-weight: 600; @@ -162,12 +216,11 @@ } .terminal-dock-panel-hint { - /* Keep the hint above the 4.5:1 AA floor against #0d1117 (#6e7681 measured 4.12:1). */ - color: #7d8590; + color: var(--colorNeutralForeground2); } .terminal-dock-panel-more-info { - color: #58a6ff; + color: var(--colorBrandForegroundLink); text-decoration: none; } @@ -226,7 +279,7 @@ justify-content: center; gap: 12px; height: 100%; - color: #8b949e; + color: var(--colorNeutralForeground2); font-size: 13px; } diff --git a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js index 5623639bfbc..1c6b5267fed 100644 --- a/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js +++ b/src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js @@ -196,9 +196,10 @@ export function unregisterResizeHandle(dockElement) { const tabNavigationRegistrations = new WeakMap(); -export function registerTabNavigation(dockElement) { +export function registerTabNavigation(dockElement, dotNetRef) { unregisterTabNavigation(dockElement); let focusedTabGroup = null; + const disposeTabStrip = registerTabStrip(dockElement, dotNetRef); const onFocusIn = (event) => { const group = event.target.closest?.('.terminal-dock-tab'); @@ -244,7 +245,7 @@ export function registerTabNavigation(dockElement) { event.preventDefault(); event.stopPropagation(); tabs[nextIndex].focus({ preventScroll: true }); - tabs[nextIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' }); + tabs[nextIndex].closest('.terminal-dock-tab').scrollIntoView({ block: 'nearest', inline: 'nearest' }); tabs[nextIndex].click(); }; @@ -264,7 +265,7 @@ export function registerTabNavigation(dockElement) { const target = dockElement.querySelector('.terminal-dock-tab-select[aria-selected="true"]') || dockElement.querySelector('.terminal-dock-collapse'); target.focus({ preventScroll: true }); - target.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + (target.closest('.terminal-dock-tab') ?? target).scrollIntoView({ block: 'nearest', inline: 'nearest' }); }); document.addEventListener('focusin', onFocusIn); @@ -272,6 +273,7 @@ export function registerTabNavigation(dockElement) { onFocusIn({ target: document.activeElement }); observer.observe(dockElement, { childList: true, subtree: true }); tabNavigationRegistrations.set(dockElement, () => { + disposeTabStrip(); document.removeEventListener('focusin', onFocusIn); dockElement.removeEventListener('keydown', onKeyDown); observer.disconnect(); @@ -282,3 +284,180 @@ export function unregisterTabNavigation(dockElement) { tabNavigationRegistrations.get(dockElement)?.(); tabNavigationRegistrations.delete(dockElement); } + +function registerTabStrip(dock, dotNetRef) { + let list = null; + let selected = null; + let dragged = null; + let dropTarget = null; + let dropAfter = false; + let dragX = null; + let scrollTimer = null; + let pending = false; + let disposed = false; + const listeners = new AbortController(); + const groups = () => Array.from(list?.querySelectorAll('.terminal-dock-tab') ?? []); + const isRtl = () => getComputedStyle(list).direction === 'rtl'; + + const updateOverflow = () => { + const controls = dock.querySelector('.terminal-dock-tab-scroll'); + const tabs = groups(); + const viewport = list?.getBoundingClientRect(); + const boxes = tabs.map(tab => tab.getBoundingClientRect()); + for (const button of controls.querySelectorAll('[data-tab-scroll]')) { + const canScroll = button.dataset.tabScroll === '-1' + ? boxes.some(box => box.left < viewport.left - 1) + : boxes.some(box => box.right > viewport.right + 1); + button.toggleAttribute('disabled', !canScroll); + button.setAttribute('aria-disabled', String(!canScroll)); + if (button.dataset.tabScroll === '1') { + dock.querySelector('.terminal-dock-tabs')?.toggleAttribute('data-overflow-right', canScroll); + } + } + }; + const resizeObserver = new ResizeObserver(updateOverflow); + const refresh = () => { + const current = dock.querySelector('.terminal-dock-tablist'); + if (current !== list) { + resizeObserver.disconnect(); + list = current; + if (list) { + resizeObserver.observe(list); + resizeObserver.observe(dock.querySelector('.terminal-dock-tabs')); + } + } + const active = list?.querySelector('[aria-selected="true"]'); + if (active && active !== selected && !dock.inert) { + active.closest('.terminal-dock-tab').scrollIntoView({ block: 'nearest', inline: 'nearest' }); + } + selected = dock.inert ? null : active; + if (dragged && (!dragged.isConnected || dock.inert)) clearDrag(); + updateOverflow(); + }; + const observer = new MutationObserver(refresh); + + const clearDrop = () => { + dropTarget?.removeAttribute('data-drop-position'); + dropTarget = null; + }; + const clearDrag = () => { + clearDrop(); + dragged?.classList.remove('dragging'); + dragged = null; + dragX = null; + clearTimeout(scrollTimer); + scrollTimer = null; + }; + const updateDrop = x => { + clearDrop(); + const candidates = groups().filter(group => group !== dragged); + const rtl = isRtl(); + dropTarget = candidates.find(group => { + const box = group.getBoundingClientRect(); + return rtl ? x > box.left + box.width / 2 : x < box.left + box.width / 2; + }) ?? candidates.at(-1); + if (dropTarget) { + const box = dropTarget.getBoundingClientRect(); + dropAfter = rtl ? x < box.left + box.width / 2 : x > box.left + box.width / 2; + dropTarget.dataset.dropPosition = dropAfter !== rtl ? 'after' : 'before'; + } + }; + const reorder = async (source, target, after, restoreFocus) => { + if (pending || disposed) return; + pending = true; + try { + // Blazor owns node order and keyed terminal viewers; never move their DOM nodes directly. + await dotNetRef.invokeMethodAsync('ReorderTerminalAsync', source.dataset.terminalId, target.dataset.terminalId, after); + if (!disposed && !dock.inert && source.isConnected) { + const tab = source.querySelector('.terminal-dock-tab-select'); + if (restoreFocus && (document.activeElement === document.body || source.contains(document.activeElement))) { + tab.focus({ preventScroll: true }); + } + source.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + } + } catch (error) { + if (!disposed) console.error('Failed to reorder terminal dock tabs.', error); + } finally { + pending = false; + } + }; + const autoScroll = () => { + scrollTimer = null; + if (!dragged || dragX === null || !list || dock.inert) return; + const box = list.getBoundingClientRect(); + const delta = dragX < box.left + 28 ? -12 : dragX > box.right - 28 ? 12 : 0; + if (delta) { + list.scrollBy({ left: delta, behavior: 'instant' }); + // The pointer can stay still while scrolling moves a different tab underneath it. + updateDrop(dragX); + } + scrollTimer = setTimeout(autoScroll, 50); + }; + + dock.addEventListener('click', event => { + const button = event.target.closest?.('[data-tab-scroll]'); + if (!button || !list || dock.inert || button.hasAttribute('disabled')) return; + const distance = Math.max(80, list.clientWidth * 0.75); + list.scrollBy({ left: Number(button.dataset.tabScroll) * distance, behavior: 'instant' }); + }, { signal: listeners.signal }); + dock.addEventListener('scroll', updateOverflow, { capture: true, passive: true, signal: listeners.signal }); + dock.addEventListener('dragstart', event => { + const tab = event.target.closest?.('.terminal-dock-tab-select'); + if (!tab || !list?.contains(tab) || dock.inert || pending) { + if (tab) event.preventDefault(); + return; + } + dragged = tab.closest('.terminal-dock-tab'); + dragged.classList.add('dragging'); + event.dataTransfer.effectAllowed = 'move'; + // A native drag needs a payload in Firefox. Identity is kept locally, never accepted from another document. + event.dataTransfer.setData('text/plain', dragged.dataset.terminalId); + }, { signal: listeners.signal }); + dock.addEventListener('dragover', event => { + if (!dragged || !list || dock.inert || !list.contains(event.target)) return; + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + dragX = event.clientX; + if (scrollTimer === null) autoScroll(); + updateDrop(dragX); + }, { signal: listeners.signal }); + dock.addEventListener('dragleave', event => { + if (list && !list.contains(event.relatedTarget)) { + dragX = null; + clearDrop(); + } + }, { signal: listeners.signal }); + dock.addEventListener('drop', event => { + if (!dragged || !dropTarget || !list?.contains(event.target) || dock.inert) { + clearDrag(); + return; + } + event.preventDefault(); + const source = dragged, target = dropTarget, after = dropAfter; + const restoreFocus = source.contains(document.activeElement); + clearDrag(); + void reorder(source, target, after, restoreFocus); + }, { signal: listeners.signal }); + dock.addEventListener('dragend', clearDrag, { signal: listeners.signal }); + dock.addEventListener('keydown', event => { + const tab = event.target.closest?.('.terminal-dock-tab-select'); + if (!tab || !list || dock.inert || !event.altKey || !event.shiftKey || event.ctrlKey || event.metaKey || + event.isComposing || !['ArrowLeft', 'ArrowRight'].includes(event.key)) return; + event.preventDefault(); + event.stopPropagation(); + const tabs = groups(); + const source = tab.closest('.terminal-dock-tab'); + const forward = (event.key === 'ArrowRight') !== isRtl(); + const target = tabs[tabs.indexOf(source) + (forward ? 1 : -1)]; + if (target && !event.repeat) void reorder(source, target, forward, true); + }, { signal: listeners.signal }); + observer.observe(dock, { childList: true, subtree: true, attributes: true, attributeFilter: ['aria-selected', 'inert'] }); + refresh(); + return () => { + disposed = true; + clearDrag(); + listeners.abort(); + resizeObserver.disconnect(); + observer.disconnect(); + }; +} diff --git a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor index f5950487ca3..ac0890475ca 100644 --- a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor @@ -170,6 +170,7 @@ @ref="_terminalViewRef" ShowOpenInWindow="@(_activeView == ConsoleLogsView.Terminal)" ResourceName="@_terminalResourceName" + ResourceIcon="@(GetSelectedResource() is { } terminalResource ? ResourceIconHelpers.GetIconForResource(IconResolver, terminalResource, IconSize.Size16) : null)" ReplicaIndex="@_terminalReplicaIndex" />
} diff --git a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor index a334dded8d9..07a2c753ec5 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 41f461cfcd7..fb9f8a631f8 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..a6d045a79d0 100644 --- a/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css +++ b/src/Aspire.Dashboard/Components/Pages/TerminalWindow.razor.css @@ -5,21 +5,30 @@ position: fixed; inset: 0; display: flex; - background: #0d1117; + flex-direction: column; + background: var(--colorNeutralBackground1); 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; align-items: center; justify-content: center; - color: #8b949e; + color: var(--colorNeutralForeground2); font-size: 13px; } diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs index 397f43d6196..95b6df72da1 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.Designer.cs @@ -45,6 +45,78 @@ public static System.Globalization.CultureInfo Culture { } } + public static string TerminalProgress { + get { + return ResourceManager.GetString("TerminalProgress", resourceCulture); + } + } + + public static string TerminalPalette { + get { + return ResourceManager.GetString("TerminalPalette", resourceCulture); + } + } + + public static string TerminalPaletteLabel { + get { + return ResourceManager.GetString("TerminalPaletteLabel", resourceCulture); + } + } + + public static string TerminalPaletteLight { + get { + return ResourceManager.GetString("TerminalPaletteLight", resourceCulture); + } + } + + public static string TerminalPaletteDark { + get { + return ResourceManager.GetString("TerminalPaletteDark", resourceCulture); + } + } + + public static string TerminalSizeLabel { + get { + return ResourceManager.GetString("TerminalSizeLabel", resourceCulture); + } + } + + public static string TerminalPaletteSaveFailed { + get { + return ResourceManager.GetString("TerminalPaletteSaveFailed", resourceCulture); + } + } + + public static string TerminalCopyWorkingDirectory { + get { + return ResourceManager.GetString("TerminalCopyWorkingDirectory", resourceCulture); + } + } + + public static string TerminalCopyTitle { + get { + return ResourceManager.GetString("TerminalCopyTitle", resourceCulture); + } + } + + public static string TerminalCopyFailed { + get { + return ResourceManager.GetString("TerminalCopyFailed", 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); @@ -267,6 +339,33 @@ public static string TerminalDockTabs { } } + /// + /// Looks up a localized string similar to Scroll terminal tabs left. + /// + public static string TerminalDockScrollLeft { + get { + return ResourceManager.GetString("TerminalDockScrollLeft", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Scroll terminal tabs right. + /// + public static string TerminalDockScrollRight { + get { + return ResourceManager.GetString("TerminalDockScrollRight", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused.. + /// + public static string TerminalDockReorderHelp { + get { + return ResourceManager.GetString("TerminalDockReorderHelp", resourceCulture); + } + } + /// /// Looks up a localized string similar to No docked terminals. /// diff --git a/src/Aspire.Dashboard/Resources/TerminalStrings.resx b/src/Aspire.Dashboard/Resources/TerminalStrings.resx index ec1bcdf8531..3d2491f279a 100644 --- a/src/Aspire.Dashboard/Resources/TerminalStrings.resx +++ b/src/Aspire.Dashboard/Resources/TerminalStrings.resx @@ -117,6 +117,44 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Terminal progress + + + Terminal palette + + + Palette + + + Light + + + Dark + + + Size + + + Unable to save the terminal palette. Check that browser storage is available and try again. + + + Copy working directory: {0} + {0} is the working directory path. + + + Copy terminal title: {0} + {0} is the terminal title. + + + Could not copy to clipboard. + + + Error + + + Warning + Terminal Default terminal title when no resource name is available. @@ -153,7 +191,7 @@ Terminal dimensions - Fit + Fit terminal to available space Open in new window @@ -174,6 +212,15 @@ Close terminal '{0}' {0} is the terminal's display title. + + Scroll terminal tabs left + + + Scroll terminal tabs right + + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Terminal close timed out diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf index b7728c7cfb2..8fa4cb1e068 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.cs.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf index da3b320d1b7..a6194e9b22a 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.de.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf index 9a3b9070c1c..2f96f94f38b 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.es.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf index b86a61af6f7..ce9d7a8523e 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.fr.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf index f9a30920acf..c407c43718c 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.it.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf index cd32910aa97..77c2277ecd2 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ja.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf index 51238154705..9c8c3b1021e 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ko.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf index 3ce1e563fbd..6fc1cd52102 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pl.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf index 6f942118183..667ff91eabb 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.pt-BR.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf index 32d2032fb90..a5e696198f9 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.ru.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf index 4c5cffaea53..e32260a6ae4 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.tr.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf index f8af38bfe8b..7f97fee6eeb 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hans.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf index 9cb80b83beb..36d5da18b0d 100644 --- a/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf +++ b/src/Aspire.Dashboard/Resources/xlf/TerminalStrings.zh-Hant.xlf @@ -7,6 +7,21 @@ Toggle terminal (`) + + Could not copy to clipboard. + Could not copy to clipboard. + + + + Copy terminal title: {0} + Copy terminal title: {0} + {0} is the terminal title. + + + 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. @@ -82,6 +97,11 @@ This terminal may still be open in a separate window. Focus it or return it to the panel to continue. + + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + Drag to reorder tabs, or use Alt+Shift+Left or Alt+Shift+Right while a tab is focused. + + Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. Use Up or Down to resize, Shift for larger steps, Home for minimum height, and End for maximum height. @@ -92,6 +112,16 @@ Return to panel + + Scroll terminal tabs left + Scroll terminal tabs left + + + + Scroll terminal tabs right + Scroll terminal tabs right + + Terminals Terminals @@ -117,11 +147,56 @@ 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 palette + Terminal palette + + + + Dark + Dark + + + + Palette + Palette + + + + Light + Light + + + + Unable to save the terminal palette. Check that browser storage is available and try again. + Unable to save the terminal palette. Check that browser storage is available and try again. + + + + Terminal progress + Terminal progress + + + + Error + Error + + + + Warning + Warning + + Reconnect terminal Reconnect terminal + + Size + Size + + The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. The terminal could not take resize control or change its dimensions. Try selecting the dimensions again. @@ -143,8 +218,8 @@ - Fit - Fit + Fit terminal to available space + Fit terminal to available space diff --git a/src/Aspire.Dashboard/package-lock.json b/src/Aspire.Dashboard/package-lock.json index 02849df0d25..e53023847c9 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.171.0" + }, + "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.171.0", + "resolved": "https://registry.npmjs.org/@hex1b/web-terminal/-/web-terminal-0.171.0.tgz", + "integrity": "sha512-MIqGy/JPvgPoECWuYvIUJqKEFR8h+qDRl6XU158CSMDhpR3V1WjEXCdkWOfco42teFh9UawwpsKNu4Y8Kxuoag==", "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..2e1178813f0 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.171.0" + }, + "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/css/controls.css b/src/Aspire.Dashboard/wwwroot/css/controls.css index 728ff390293..c606454e380 100644 --- a/src/Aspire.Dashboard/wwwroot/css/controls.css +++ b/src/Aspire.Dashboard/wwwroot/css/controls.css @@ -525,8 +525,7 @@ fluent-dropdown fluent-option:focus-visible { outline: none; } -fluent-text-input[appearance="outline"]:focus-within::part(root), -.terminal-container:focus-within::after { +fluent-text-input[appearance="outline"]:focus-within::part(root) { border-color: var(--colorStrokeFocus1); outline: var(--strokeWidthThick) solid var(--colorStrokeFocus1); outline-offset: calc(-1 * var(--strokeWidthThick)); diff --git a/src/Aspire.Dashboard/wwwroot/js/README.md b/src/Aspire.Dashboard/wwwroot/js/README.md index 1d357832441..aa9444e0a02 100644 --- a/src/Aspire.Dashboard/wwwroot/js/README.md +++ b/src/Aspire.Dashboard/wwwroot/js/README.md @@ -18,8 +18,9 @@ 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 the published `@hex1b/web-terminal` **0.171.0** release, +paired with the Hex1b, Hex1b.McpServer, and Hex1b.Tool NuGet packages and the +repository-local `hex1b` tool at the same version. 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 +32,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 +45,23 @@ 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 matching NuGet packages are available through the approved `dotnet-public` feed; +no additional package source is required. Desktop and Android Firefox terminals use `renderer: "webgl2"` until the [WebGPU performance issue](https://bugzilla.mozilla.org/show_bug.cgi?id=1870699) @@ -77,13 +91,145 @@ 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 palettes + +The terminal offers explicit Aspire light and Aspire dark palettes using Aspire variants +of **Hex1b Light** and **Hex1b Dark**: light lavender `#d5d0df` and dark +purple-neutral `#312e3c` backgrounds, with richer chromatic ANSI slots. These +increase OKLCH chroma by up to 25% in dark mode and 10% in light mode, preserving +hue and reducing chroma to fit sRGB rather than clipping channels. Lightness is +adjusted where needed; the rounded colors provide at least 5:1 normal and 6:1 +bright chromatic text contrast against the default background. These targets do +not cover arbitrary foreground/background pairs or dim text. +Default foreground, neutral ANSI slots and selection colors retain the Hex1b +defaults. Colors are precomputed constants, not runtime transformations. +The frame uses the active palette background; the overlay track uses Hex1b's +foreground/background blend. Mounting passes both palettes and `colorMode`; +palette changes call `setColorMode` on the existing client, including changes that +occur while mounting or while a dock pane is hidden. + +Unused space around the terminal grid has a subtle diagonal hatch: 1px lines every +8px, using the active palette foreground at 10% opacity over a base that mixes +92% palette background with 8% black. +The grid and its padding remain solid, including transparent default cells. +A 0.5px pinstripe at the padding's outer edge blends 10% foreground with the +background to distinguish the terminal boundary without changing its dimensions. +Forced-colors mode suppresses the decorative pattern and uses a system-color edge. + +Plain-text HTTP/HTTPS URLs use Hex1b's per-view link detection with dashed +underlines to distinguish them from explicit OSC 8 links, and open in a new +tab on Ctrl/Cmd-click with `noopener,noreferrer`. Explicit OSC 8 links retain +Hex1b's default HTTP/HTTPS/mailto allowlist. Plain clicks and drags retain terminal +input and selection behavior; links also work in read-only views. Remote file +paths and custom URI schemes are not enabled. + +The **Terminal palette** dropdown to the right of the footer's dimensions selector +selects **Dark** or **Light** independently of the site. **Dark** is the default, +including for the former **Follow Dashboard** preference. Site theme changes do +not change the selected terminal palette. This non-sensitive +preference is stored in browser local storage and applies to all terminal surfaces, +including detached windows. Changes update existing clients without reconnecting; +other windows observe storage events. The terminal frame and overlay track follow +the selected palette, while toolbars, dock tabs, headers and popups retain the site +theme. The dropdown also remains available on surfaces without a dimensions +selector and in read-only views because palette changes do not affect the workload. +Failed saves show a dismissible error rather than applying an unpersisted preference. + +Hex1b preserves default and indexed ANSI colors through the negotiated +`indexed-v1` HWT extension, so existing content and retained scrollback recolor +without reconnecting, resizing, clearing selection or taking focus. Explicit RGB +colors and image pixels remain unchanged. Matching client/server package versions +are required; legacy RGBA frames cannot be recolored. + +Both GPU backends paint opaque selection foreground/background from the active +palette. The adapter does not tint the transparent selection geometry with CSS. +Aspire palettes are supplied through `lightModePalette` and +`darkModePalette` at mount and can be updated through `setPalette`, independently of +the Dashboard controls and scrollbar styling. + +### 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, detached window and interaction dialog share the same +title/directory/progress presentation. Dock panes are chromeless and omit this +header; their tabs retain the titles captured when they were created. +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. +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, +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 pinned renderer draws rectangular markers on a flat translucent track, +with upstream marker navigation and a thumb focus outline. +A scoped shadow-DOM override 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 explicitly uses the active-palette foreground/background blend at 35% opacity. The thumb +uses that palette's foreground, so it remains contrasting in either theme. +Markers use the selected terminal palette's purple and red ANSI colors at full +opacity, with system colors in forced-color mode. The track remains +translucent in both themes. + +Tooltip colors are resolved in the Dashboard theme; marker colors follow the terminal palette. 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. Hex1b 0.171 retains at most 200 command +marks per producer even when their backing text is still retained; older marks +are evicted first. 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 @@ -94,6 +240,10 @@ take resize ownership. Public font-size limits are 8–32 pixels. Opening an interactive terminal or activating its view focuses its keyboard input once it is ready. Inactive dock panes do not take focus, and an asynchronous mount does not take focus back from a control the user selected while it was loading. +The terminal's inset focus outline adds a subtle brand-purple tint to a muted +neutral base independently of textbox and button focus colors. Increased contrast +restores the stronger control focus color, and forced-colors mode uses the system +highlight color. Mouse clicks on the font stepper, Fit button, or a dimensions option return focus to terminal input; keyboard activation keeps focus on the control for repeated adjustments. Opening the dimensions picker keeps focus until an option is chosen. @@ -102,7 +252,7 @@ banner. Diagnostics include the exception, selection status, document focus, and clipboard permissions policy, never clipboard or selected text. These failures can include pending selection resolution before the browser clipboard API is called; they do not necessarily mean clipboard permission was denied. -Hex1b 0.168.0 also displays its own inspection status inside its shadow root. +Hex1b also displays its own inspection status inside its shadow root. Its public API does not currently expose an option to suppress that native message. Other terminal status and sizing errors offer **Dismiss**, which clears the local error and returns focus without reconnecting or discarding terminal history. @@ -135,20 +285,24 @@ Ctrl/Cmd+click opens server-authoritative OSC 8 hyperlinks using the package's built-in routing, including links in history and read-only views. Only absolute HTTP, HTTPS and mailto destinations are allowed, and tabs use `noopener,noreferrer`. Plain clicks and drags retain selection/application -behavior; Shift and Alt reserve selection gestures. Aspire does not add its own -opener, custom-scheme support, or plain-text URL detection. HMP state replay +behavior; Shift and Alt reserve selection gestures. Aspire also enables Hex1b's +per-view plain-text HTTP/HTTPS URL detector with dashed underlines and a +Ctrl/Cmd-click action that opens a new tab with `noopener,noreferrer`. +Remote file paths and custom URI schemes are not enabled. HMP state replay preserves link destinations when a browser attaches or reconnects. See [the hyperlink PR](https://github.com/mitchdenny/hex1b/pull/489) and [the replay fix](https://github.com/mitchdenny/hex1b/pull/493). The public API supports auto/fixed sizing, primary requests, keyboard and mouse input, paste/copy, selection, and producer-backed history. It exposes workload -title, progress, and shell-integration state with change callbacks; the dashboard -does not yet consume these and still labels the terminal with the resource name. -It has no terminal theme setter, search API, or clear-buffer API. Terminal -colors and content are server-authoritative; inspection UI uses the package's -theme defaults/tokens. The previous terminal hardcoded dark xterm colors rather -than offering a theme control. Search, filtering, clearing the log display, and +title, working directory, progress, and shell-integration state with change +callbacks. The dashboard consumes title, directory and progress updates for its +metadata headers, falling back to the resource name when no workload title is +present. Dock panes omit the header and retain their creation-time tab titles. +The terminal palette selector uses `setColorMode` to switch between Dark and +Light independently of the Dashboard theme, recoloring default and indexed +colors while preserving explicit RGB colors and image pixels. There is no +terminal search API or clear-buffer API. Search, filtering, clearing the log display, and downloads belong to the separate Blazor `LogViewer`, which does not use xterm and is unchanged by this migration. @@ -165,7 +319,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 e973bc47b4d..b7fcf1607c7 100644 --- a/src/Aspire.Dashboard/wwwroot/js/app.js +++ b/src/Aspire.Dashboard/wwwroot/js/app.js @@ -8,6 +8,7 @@ fluentDropdownStyleSheet.replaceSync(` background-color: var(--colorNeutralBackground1); border: 1px solid var(--colorNeutralStroke1); box-shadow: none !important; + min-width: var(--aspire-dropdown-min-width, 160px); } .control:hover { @@ -162,13 +163,33 @@ window.copyTextToClipboard = function (id, text, precopy, postcopy) { delete button.dataset.copyTimeout; } + // An earlier clipboard request may still be pending when the same button is clicked again. + const request = Symbol(); + button.copyRequest = request; const copyIcon = button.querySelector('.copy-icon'); const checkmarkIcon = button.querySelector('.checkmark-icon'); - + const copyStatus = button.nextElementSibling?.classList.contains('terminal-copy-status') + ? button.nextElementSibling : null; + if (copyStatus) { + copyStatus.textContent = ''; + } const anchoredTooltip = document.querySelector(`fluent-tooltip[anchor="${id}"]`); const tooltipDiv = anchoredTooltip ? anchoredTooltip.children[0] : null; + if (tooltipDiv) { + tooltipDiv.innerText = precopy; + } + if (copyIcon && checkmarkIcon) { + copyIcon.style.display = ''; + checkmarkIcon.style.display = 'none'; + } navigator.clipboard.writeText(text) .then(() => { + if (button.copyRequest !== request) { + return; + } + if (copyStatus) { + copyStatus.textContent = postcopy; + } if (tooltipDiv) { tooltipDiv.innerText = postcopy; } @@ -177,23 +198,38 @@ window.copyTextToClipboard = function (id, text, precopy, postcopy) { checkmarkIcon.style.display = ''; } }) - .catch(() => { + .catch(error => { + if (button.copyRequest !== request) { + return; + } + if (copyStatus) { + copyStatus.textContent = button.getAttribute('data-copyfailed'); + } if (tooltipDiv) { - tooltipDiv.innerText = 'Could not access clipboard'; + tooltipDiv.innerText = button.getAttribute('data-copyfailed') || 'Could not access clipboard'; + } else { + console.warn("Dashboard clipboard copy failed.", error); } - }); - - button.dataset.copyTimeout = setTimeout(function () { - if (tooltipDiv) { - tooltipDiv.innerText = precopy; - } + }) + .finally(() => { + if (button.copyRequest !== request) { + return; + } + button.dataset.copyTimeout = setTimeout(function () { + if (copyStatus) { + copyStatus.textContent = ''; + } + if (tooltipDiv) { + tooltipDiv.innerText = precopy; + } - if (copyIcon && checkmarkIcon) { - copyIcon.style.display = ''; - checkmarkIcon.style.display = 'none'; - } - delete button.dataset.copyTimeout; - }, 1500); + if (copyIcon && checkmarkIcon) { + copyIcon.style.display = ''; + checkmarkIcon.style.display = 'none'; + } + delete button.dataset.copyTimeout; + }, 1500); + }); }; window.copyText = function (text) { 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..b3598f395c1 --- /dev/null +++ b/src/Aspire.Dashboard/wwwroot/js/hex1b-web-terminal/dist/index.min.js @@ -0,0 +1,2 @@ +// @hex1b/web-terminal 0.171.0; 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,g=i({"src/renderer-options.ts"(){}});function w(e="dark"){if("light"===e||"dark"===e||"system"===e)return e;throw new TypeError('colorMode must be "light", "dark", or "system"')}function y(e,t){if("string"!=typeof e||!/^#[0-9a-f]{6}$/iu.test(e))throw new TypeError(`${t} must be a #RRGGBB color`);return e.toLowerCase()}function v(e){if(!r(e))throw new TypeError("A terminal palette object is required");if(!Array.isArray(e.ansi)||16!==e.ansi.length)throw new TypeError("palette.ansi must contain exactly 16 colors");const t=Array.from(e.ansi,(e,t)=>y(e,`palette.ansi[${t}]`)),i={};if(void 0!==e.extended){if(!r(e.extended))throw new TypeError("palette.extended must be an object");for(const[t,r]of Object.entries(e.extended)){const e=Number(t);if(!Number.isInteger(e)||e<16||e>255||String(e)!==t)throw new TypeError("palette.extended indices must be integers from 16 to 255");i[e]=y(r,`palette.extended[${t}]`)}}return Object.freeze({foreground:y(e.foreground,"palette.foreground"),background:y(e.background,"palette.background"),ansi:Object.freeze(t),...void 0===e.cursor?{}:{cursor:y(e.cursor,"palette.cursor")},...void 0===e.selectionForeground?{}:{selectionForeground:y(e.selectionForeground,"palette.selectionForeground")},...void 0===e.selectionBackground?{}:{selectionBackground:y(e.selectionBackground,"palette.selectionBackground")},...void 0===e.extended?{}:{extended:Object.freeze(i)}})}function b(e,t,i){return(e|t<<8|i<<16|4278190080)>>>0}function k(e){const t=Number.parseInt(e.slice(1),16);return b(t>>16,t>>8&255,255&t)}function x(e){const t=new Uint32Array(256);for(let i=0;i<16;i++)t[i]=k(e.ansi[i]);const i=[0,95,135,175,215,255];for(let e=16;e<232;e++){const r=e-16;t[e]=b(i[Math.floor(r/36)],i[Math.floor(r/6)%6],i[r%6])}for(let e=232;e<256;e++){const i=8+10*(e-232);t[e]=b(i,i,i)}for(const[i,r]of Object.entries(e.extended??{}))t[Number(i)]=k(r);return{foreground:k(e.foreground),background:k(e.background),selectionForeground:k(e.selectionForeground??e.background),selectionBackground:k(e.selectionBackground??e.foreground),...void 0===e.cursor?{}:{cursor:k(e.cursor)},indexed:t}}function E(e,t=!1){if(!(e>>>24==255||e>=16777216&&e<=16777471||33554432===e||50331648===e||t&&67108864===e))throw new Error("Invalid indexed-v1 color reference")}function T(e,t){switch(e>>>24){case 1:return t.indexed[255&e];case 2:return t.foreground;case 3:return t.background;default:return e}}function C(e,t,i){if("indexed-v1"!==t.colorEncoding)return e.foreground;const r=T(32&e.attributes?e.background:e.foreground,i);return 2&e.attributes?((255&r)>>1|(r>>>8&255)>>1<<8|(r>>>16&255)>>1<<16|4278190080)>>>0:r}function S(e,t,i){return"indexed-v1"!==t.colorEncoding?e.background:32&e.attributes||50331648!==e.background?T(32&e.attributes?e.foreground:e.background,i):16777215&i.background}var I,M=i({"src/terminal-palette.ts"(){s(),f=Object.freeze({foreground:"#d4d0c8",background:"#323232",selectionForeground:"#323232",selectionBackground:"#d4d0c8",ansi:Object.freeze(["#242424","#ed888f","#8dae82","#c3a150","#7baad1","#bc99c5","#64b1b5","#b7b4ae","#9a9a9a","#ff99a0","#9bc08e","#d7b156","#87bbe7","#d0a8da","#6cc4c8","#dedad3"])}),m=Object.freeze({foreground:"#323232",background:"#d4d0c8",selectionForeground:"#d4d0c8",selectionBackground:"#323232",ansi:Object.freeze(["#24262b","#9e3141","#3e6131","#705402","#255c86","#704b7a","#006367","#b7b4ae","#595959","#982338","#355a27","#664d01","#175480","#694273","#015a5e","#e3dfd7"])})}});function L(e){return"string"==typeof e&&/^[1-9][0-9]{0,18}$/u.test(e)&&BigInt(e)<=9223372036854775807n}function A(e){if("string"!=typeof e||!/^(?:custom|command):[A-Za-z0-9-]{1,100}$/u.test(e))throw new TypeError("Invalid terminal marker ID")}var R,P,O,z=i({"src/marker-state.ts"(){c(),I=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||!L(t.generation)||!L(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){A(e),await this.#c({action:"remove",id:e}),this.#r.delete(e)}async jump(e){A(e),await this.#c({action:"jump",id:e})}async details(e){A(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 U(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 D(e,t,i){if(!Array.isArray(e)||e.length>i)throw new Error(`Invalid ${t}`)}function B(e){if("string"!=typeof e||!e.length||e.length>1024)throw new Error("Invalid image key")}function q(e,t){if("string"!=typeof e||!/^[1-9][0-9]{0,18}$/u.test(e)||BigInt(e)>9223372036854775807n)throw new Error(`Invalid ${t}`)}function j(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&&(U(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>R.commandMarkParameterUnits))throw new Error("Invalid terminal command mark parameters")}function N(e){if("string"!=typeof e.status||!["none","valid","invalidated"].includes(e.status)||("valid"===e.status?"string"!=typeof e.text||e.text.length>R.metadataBytes:null!==e.text))throw new Error("Invalid selection text")}function $(e){if(!r(e)||1!==e.version||"boolean"!=typeof e.full)throw new Error("Unsupported frame metadata version");if(U(e.revision,"revision",1),U(e.baseRevision,"base revision"),void 0!==e.colorEncoding&&null!==e.colorEncoding&&"indexed-v1"!==e.colorEncoding)throw new Error("Unsupported color encoding");if(void 0!==e.colorEncodings&&(D(e.colorEncodings,"color encodings",16),e.colorEncodings.some(e=>"string"!=typeof e||e.length>64)))throw new Error("Invalid color encodings");if("string"!=typeof e.title||e.title.length>R.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 U(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&&U(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>R.metadataBytes))throw new Error("Invalid terminal working directory");const o=e.commandMark;null!==o&&j(o);const a=U(e.columns,"columns",1,1024),l=U(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");U(a*l,"cell count",1,R.cells),function(e,t,i){if(null===e)return;if(!r(e))throw new Error("Missing history metadata");if(q(e.generation,"history generation"),"main"!==e.buffer&&"alternate"!==e.buffer)throw new Error("Invalid history buffer");const n=U(e.totalRows,"history total rows",i,2147483647),s=U(e.liveTop,"history live top",0,n-i);if(s!==n-i)throw new Error("Inconsistent history extent");if(U(e.top,"history top",0,s),"boolean"!=typeof e.following||e.following&&e.top!==e.liveTop)throw new Error("Invalid history following state");if(U(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(D(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)q(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(U(o.requestId,"selection request id"),"string"!=typeof o.mode||!["character","word","line","rectangle"].includes(o.mode))throw new Error("Invalid selection mode");D(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=U(e.row,"selection range row",a+1,i-1),s=U(e.startColumn,"selection start column",0,t-1);U(e.endColumn,"selection end column",s+1,t),a=n}if(N(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");U(e.copy.requestId,"copy request id",1),N(e.copy)}if(void 0!==e.markers){D(e.markers,"history markers",R.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(A(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(U(i.column,"marker column",0,1024),null!==i.row&&(U(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)j({...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=U(t.total,"marker inventory count",1,Math.floor(R.metadataBytes/48)),n=U(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(U(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");A(t.markerId)}void 0!==t.details&&null!==t.details&&j(t.details)}}(e.history,a,l),D(e.hyperlinks,"hyperlinks",a*l);let c=0;for(const t of e.hyperlinks){if(!r(t))throw new Error("Invalid hyperlink");const e=U(t.row,"hyperlink row",0,l-1),i=U(t.startColumn,"hyperlink start column",0,a-1),n=U(t.endColumn,"hyperlink end column",i+1,a);if(e*a+iR.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]&&U(e[t],t,0,4294967295);if(!r(e.cursor)||"boolean"!=typeof e.cursor.visible)throw new Error("Invalid cursor");U(e.cursor.x,"cursor x",-1,1024),U(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)),U(e.cursor.shape,"cursor shape",0,6),D(e.images,"images",R.images),D(e.retainedImages,"retained image keys",R.images),D(e.placements,"placements",R.placements),D(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(B(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(B(t.key),d.has(t.key)||!h.has(t.key))throw new Error("Inconsistent new image keys");d.add(t.key);const e=U(t.width,"image width",1,16384),i=U(t.height,"image height",1,16384),n=U(t.byteLength,"image byte length",1,R.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>R.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(B(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 F,G=i({"src/protocol.ts"(){s(),z(),M(),R=Object.freeze({commandBytes:65536,frameBytes:100663296,metadataBytes:8388608,titleUnits:4096,commandMarkParameterUnits:8192,cells:262144,images:4096,placements:16384,textureBytes:268435456}),P=new TextDecoder("utf-8",{fatal:!0}),O=new TextEncoder}});function W(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 _(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function H(e,t){if(!1===e)return!1;if(void 0===e)return{};if(!_(e))throw new TypeError("Invalid links configuration");const i={};if(!1===e.osc8)i.osc8=!1;else if(void 0!==e.osc8){if(!_(e.osc8))throw new TypeError("Invalid OSC 8 configuration");W(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(!_(r)||!Array.isArray(r.rules))throw new TypeError("Invalid detection configuration");if(r.rules.length>F.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(!_(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");W(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>F.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 K(e){if("function"!=typeof e)throw new TypeError("Expected a link action callback");return(t,i,r)=>{if(!function(e){if(!_(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=>_(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 V=i({"src/link-options.ts"(){F=Object.freeze({rules:32,pattern:8192,chunk:65536,totalText:262144,cells:262144,matches:2048,resultText:262144,cacheEntries:2048,cacheText:1048576,timeoutMs:250})}});function X(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 Y,J,Z,Q,ee,te,ie=i({"src/link-worker-protocol.ts"(){}}),re=i({"src/render-backend.ts"(){Y=16,J=class extends Error{}}}),ne=i({"src/webgpu-backend.ts"(){re(),Z=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()}},Q=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 J("WebGPU requires HTTPS or localhost");if(!navigator.gpu)throw new J("WebGPU is unavailable in this browser worker");const r=await navigator.gpu.requestAdapter();if(!r)throw new J("No WebGPU adapter is available");let n,s;try{n=await r.requestDevice()}catch(e){if(e instanceof DOMException&&"OperationError"===e.name)throw new J(`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*Y,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 J("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 Z(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*Y*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*Y);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 Z))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 se(e){return e instanceof Error?e:new Error(String(e))}function oe(e){return Number.isSafeInteger(e)&&e>0}function ae(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 le=i({"src/webgl2-backend.ts"(){re(),ee=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),ae(this.gl)}writePixels(e,t,i,r=0,n=0){if(!oe(t)||!oe(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 J("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]),!oe(this.textureLimit)||!oe(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(),!oe(e)||!oe(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),ae(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 ee(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(!oe(this.canvas.width)||!oe(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*Y>e.length)throw new Error("Invalid WebGL2 instance data");for(const e of i){if(!(e.resource instanceof ee)||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=Y*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*Y),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,se(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,se(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 ce,he,de,ue,pe,fe,me,ge,we=i({"src/backend-selection.ts"(){re(),g(),ne(),le()}});function ye(e){return[(255&e)/255,(e>>>8&255)/255,(e>>>16&255)/255,(e>>>24&255)/255]}function ve(e){return`${5&e.attributes}/${e.width}/${e.text}`}function be(e){return 1109742===e.text.codePointAt(0)}function ke(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*ce*i)+4,c=Math.ceil(he*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 xe,Ee,Te=i({"src/renderer.ts"(){G(),u(),we(),re(),M(),ce=10,he=20,de=1048576,ue=1048576,pe=Y,fe=[1,1,1,1],me=x(f),ge=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 te.create(e,t)};try{return{backend:await Q.create(e,t)}}catch(i){if("auto"!==r||!(i instanceof J))throw i;try{return{backend:await te.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*pe),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*ce,n=t*he,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>R.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||be(i)||t.set(ve(i),i);const i=e=>[...e.keys()].reduce((e,t)=>e+t.length,0);if(t.size>16384||i(t)>ue)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)<=ue?ke(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=ke(t,e,this.scale))&&e=de)throw new Error("Frame exceeds bounded quad budget");const p=this.quadCount*pe;if(p+pe>this.instances.length){const e=new Float32Array(Math.min(2*this.instances.length,de*pe));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,s){this.quad(this.atlas,e,t,i,r,n,0,void 0,s)}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,fe,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,s=e.underlineColor,o){128&e.attributes&&this.solid(t,i+10,r,1,n,o),256&e.attributes&&this.solid(t,i+1,r,1,n,o);const a=e.underlineStyle||(8&e.attributes?1:0);this.underline(a,t,i,r,ye(s),o)}underline(e,t,i,r,n,s){if(1===e)this.solid(t,i+18,r,1,n,s);else if(2===e)this.solid(t,i+16,r,1,n,s),this.solid(t,i+18,r,1,n,s);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 o)e.z<-1073741824&&this.placement(e.placement);for(let i=0;i=-1073741824&&e.z<0&&this.placement(e.placement);for(let s=0;s=0&&this.placement(e.placement);const a=t.history?.selection;if("valid"===a?.status)for(const s of a.ranges){const o=[s.startColumn*ce,s.row*he,(s.endColumn-s.startColumn)*ce,he];this.solid(...o,ye(n.selectionBackground));const a=s.row*this.columns;let l=a+s.startColumn;for(;l>a&&0===e[l]?.width;)l--;for(let c=l;c=0&&l.x=0&&l.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}}}}}),et=i({"src/marker-pages.ts"(){G(),Se=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>R.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{ze=!1,Re||Pe||Le||Ae||Ke.pending||Ve||(Fe=async function(){if(!Ue||!qe||!Ie)return;Ue=!1,Pe=!0;const e=$e;try{e&&He.prepare(je,qe);const t=He.submission();Ie.resize(qe.columns,qe.rows,_e);const i=Math.floor(performance.now()/600)%2==0,r=Ie.render(je,qe,i,t.mask,Ye);if(await Ie.idle(),Le||Ae)return;if(Be=i,Je.presentations++,Je.rendererCpuMs=r.cpuMs,Je.quads=r.quads,Je.drawCalls=r.drawCalls,Je.warnings=Ie.canvasLimited?[...qe.warnings,`Canvas resolution capped by the GPU; terminal grid remains ${qe.columns}x${qe.rows}`]:qe.warnings,e){$e=void 0,Oe=!1,Je.frames++,e.full&&Je.fullFrames++,Je.revision=e.revision,Je.changedCells+=e.changedCells,Je.lastChangedCells=e.changedCells,Je.captureMs=qe.stats.captureMs,Je.workloadBytes=qe.stats.workloadBytes,Je.outputBatches=qe.stats.outputBatches,Je.serverElapsedMs=qe.stats.elapsedMs,Je.columns=qe.columns,Je.rows=qe.rows,Je.mouseTracking=qe.mouseTracking,Je.peer=qe.peer,Je.history=qe.history;const t=function(e,t,i){const r=[];for(let n=0;nR.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=U(n(),"metadata length",2,R.metadataBytes);r(s);const o=JSON.parse(P.decode(new Uint8Array(e,i,s)));i+=s,$(o);const a=o.columns*o.rows,l=U(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 l=n(),d=n(),u=n();"indexed-v1"===o.colorEncoding&&(E(l),E(d),E(u,!0));const p=t.getUint16(i,!0),f=t.getUint8(i+2),m=t.getUint8(i+3),g=t.getUint16(i+4,!0);if(i+=6,m>5)throw new Error("Unsupported underline style");r(g);const w=P.decode(new Uint8Array(e,i,g));i+=g,c.push({index:s,foreground:l,background:d,underlineColor:u,attributes:p,width:f,underlineStyle:m,text:w})}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(!Xe&&i.colorEncodings?.includes("indexed-v1")&&(Xe=!0,rt({type:"colorEncoding",value:"indexed-v1"})),!i.full&&(Ve||i.baseRevision!==Ne||i.revision<=Ne||!qe||i.columns!==qe.columns||i.rows!==qe.rows||(i.colorEncoding??null)!==(qe.colorEncoding??null)))return Je.discardedFrames++,Oe=!1,Ke.reset(),Ve=!0,Ue=!1,$e=void 0,rt({type:"ack",revision:i.revision}),rt({type:"resync"}),void it(`Revision mismatch (local ${Ne}, base ${i.baseRevision}); requesting a full frame`);if(await Fe,Le||Ae)return;if(await Ie.idle(),await Ie.updateImages(t.images,i.retainedImages),Le||Ae)return;const r=performance.now(),n=i.full?new Array(i.columns*i.rows):je.slice();for(const e of t.cells)n[e.index]=e;Ie.prepareGlyphs(n),je=n,qe=i,Ne=i.revision;const s=Ke.pending&&!i.full,o=Ke.accept(i.history);if(Ve=!1,$e={revision:i.revision,full:i.full||s&&!!$e?.full,changedCells:t.cells.length+(s?$e?.changedCells??0:0)},De=je.some(e=>e&&16&e.attributes&&!(64&e.attributes))||i.cursor.visible&&(0===i.cursor.shape||i.cursor.shape%2==1),Je.preparationCpuMs=performance.now()-r,void 0===o)return Ue=!1,Oe=!1,void rt({type:"ack",revision:i.revision});i.history=o,Ue=!0}finally{Re=!1,Ue&&ot()}}}var lt=i({"src/terminal-worker.ts"(){G(),Te(),Qe(),et(),s(),M(),Le=!1,Ae=!1,Re=!1,Pe=!1,Oe=!1,ze=!1,Ue=!1,De=!1,Be=!0,je=[],Ne=0,Fe=Promise.resolve(),He=new Ee,Ke=new Se,Ve=!1,Xe=!1,Ye=x(f),Je={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},Ze={time:performance.now(),presentations:0,bytes:0,workload:0},self.addEventListener("error",e=>{e.preventDefault(),st(e.error||new Error(e.message))}),self.addEventListener("unhandledrejection",e=>{e.preventDefault(),st(e.reason)}),self.addEventListener("message",e=>{const t=e.data;if("init"===t.type)(async function(e){if(Ie||Me)throw new Error("Worker is already initialized");if(Ye=x(v(void 0===e.palette?f:e.palette)),"function"!=typeof self.requestAnimationFrame)throw new Error("This browser does not support requestAnimationFrame in a dedicated OffscreenCanvas worker");if(it("Loading terminal font and initializing renderer..."),Ie=await ge.create(e.canvas,e.scale,st,e.font,e.renderer),Le||Ae)return void Ie?.dispose();Je.gpu="ready",Je.backingScale=e.scale,nt();const t="webgpu"===Ie.backend.kind?"WebGPU":"WebGL2";Ie.fallbackReason&&it(`Using WebGL2: ${Ie.fallbackReason}`),it(`${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:");(Me=new WebSocket(i)).binaryType="arraybuffer",Me.addEventListener("open",()=>{Le||Ae||(Je.connected=!0,self.postMessage({type:"connected"}),it(`Connected · ${t} worker · server-authoritative cells and graphics`,"ready"),nt())}),Me.addEventListener("message",e=>{e.data instanceof ArrayBuffer?at(e.data).catch(st):st(new Error(`Expected binary HWT1 frame, received ${String(e.data).slice(0,200)}`))}),Me.addEventListener("close",e=>{Je.connected=!1,Le||Ae||(Je.gpu="stopped",Je.fps=0,Ae=!0,clearInterval(Ge),clearInterval(We),Ie?.dispose(),self.postMessage({type:"closed",details:{code:e.code,reason:e.reason,wasClean:e.wasClean}}),it(`View disconnected (${e.code}${e.reason?`: ${e.reason}`:""}). Attach another view to reconnect.`,"error"),nt())}),Ge=setInterval(()=>{const e=performance.now(),t=(e-Ze.time)/1e3;Je.fps=(Je.presentations-Ze.presentations)/t,Je.receivedKBps=(Je.bytesReceived-Ze.bytes)/t/1e3,Je.workloadMBps=Math.max(0,Je.workloadBytes-Ze.workload)/t/1e6,Ze={time:e,presentations:Je.presentations,bytes:Je.bytesReceived,workload:Je.workloadBytes},nt()},1e3),We=setInterval(()=>{const e=Math.floor(performance.now()/600)%2==0;De&&e!==Be&&ot()},100)})(t).catch(st);else if("stop"===t.type)Ae=!0,clearInterval(Ge),clearInterval(We),Me?.close(1e3,"View detached"),Ie?.dispose(),self.close();else if("viewport"!==t.type||Le||Ae)if("command"!==t.type||Le||Ae)if("palette"!==t.type||Le||Ae)if("linkDetection"!==t.type||Le||Ae){if("linkDecorations"===t.type&&!Le&&!Ae)try{He.accept(t.revision,t.generation,t.serial,t.ranges,Re||Oe||!!$e,t.underlineStyle)&&ot()}catch(e){st(e)}}else try{if(!He.configure(t.enabled,t.generation))return;if(!Re&&!Pe&&!$e){const e=He.snapshot();e&&self.postMessage({type:"linkSnapshot",generation:He.generation,snapshot:e})}ot()}catch(e){st(e)}else try{Ye=x(v(t.palette)),ot()}catch(e){st(e)}else rt(t.command);else{if(!Number.isFinite(t.width)||!Number.isFinite(t.height)||t.width<0||t.height<0)return void st(new Error("Invalid mounted viewport dimensions"));if(_e?.width===t.width&&_e?.height===t.height)return;_e={width:t.width,height:t.height},ot()}})}}),ct={};function ht(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 dt(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>F.chunk||s>F.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>F.totalText||r>=F.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=ht(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+=X(a),o>F.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})})(ct,{scanLinks:()=>dt});var ut=i({"src/link-detection-worker.ts"(){V(),ie(),"undefined"!=typeof self&&"function"==typeof self.postMessage&&self.addEventListener("message",e=>{self.postMessage(dt(e.data))})}});var pt=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))}}},ft=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 mt=Object.freeze({Continue:"continue",Consume:"consume",Application:"application",Browser:"browser"}),gt=Object.freeze({CopySelection:"copySelection",PasteClipboard:"pasteClipboard",CopyOrPaste:"copyOrPaste",ClearSelection:"clearSelection",ScrollToLive:"scrollToLive",ScrollLines:"scrollLines"}),wt=new Set(["l","t","v","w","+","-","=","0"]),yt=new Set(["F1","F3","F5","F6","F7","F10","F11","F12"]),vt=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}`)]),bt=new Set(Object.values(mt));function kt(){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:gt.CopySelection},{id:"browser.shortcuts",match:e=>"key"===e.type&&(e.meta||yt.has(e.key)||e.ctrl&&("Tab"===e.key||"F4"===e.key||wt.has(e.key.toLowerCase())||e.shift&&1===e.key.length)||e.alt&&!e.ctrl),route:mt.Browser},{id:"terminal.keys",match:e=>"key"===e.type&&(vt.has(e.key)||e.ctrl&&1===e.key.length),route:mt.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:gt.CopyOrPaste}]}function xt(e,t){if("function"!=typeof e||"AsyncFunction"===e.constructor.name)throw new TypeError(`${t} must be a synchronous function`)}var Et=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(gt));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&&xt(t,"onInput"),this.#p=t;const r=kt(),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 xt(e.match,`Binding ${e.id}.match`),void 0!==e.when&&xt(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(Tt(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(!Tt(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!==mt.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!==mt.Continue)return n}return{route:mt.Continue}}};function Tt(e){return"string"==typeof e&&[...bt].some(t=>t===e)}function Ct(e){return{ctrl:!!e.ctrlKey,alt:!!e.altKey,shift:!!e.shiftKey,meta:!!e.metaKey}}var St=[["left",1],["middle",4],["right",2]],It=["left","middle","right"];function Mt(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 pt,g=new ft;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 z(){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)),z()}},60))}function U(){void 0!==f&&cancelAnimationFrame(f),f=void 0,u&&t(u),u=void 0}function D(e,i){for(const[r,n]of St){const s=0!==(e.buttons&n);s!==h.has(r)&&(U(),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?U():(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!==mt.Browser&&t.preventDefault(),void("app"!==g.owner&&x!==mt.Application||c!==t.pointerId||D(t,n));i();const s={type:"pointer",button:It[t.button],point:Object.freeze({...n}),...Ct(t)},o=r.resolve?.(s)??{route:mt.Continue};let a=void 0!==o.action?mt.Consume:o.route;if(a===mt.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=mt.Consume)}if(E=2===t.button?a:void 0,T&&(E=mt.Consume),C=t,O(t),a!==mt.Continue)return k=b=void 0,M.count=0,x=a,c=t.pointerId,d=n,e.setPointerCapture(c),a!==mt.Browser&&t.preventDefault(),a===mt.Application?U():(void 0!==f&&cancelAnimationFrame(f),f=void 0,u=void 0),a===mt.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!==mt.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 z()}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:St.find(([e])=>h.has(e))?.[0]??"none",...t},n=JSON.stringify(i);n!==p&&(p=n,u=i,void 0===f&&(f=requestAnimationFrame(U)))},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===mt.Application&&t&&D(e,t),void(0===e.buttons&&B(x===mt.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!==mt.Continue){const t=E;return E=void 0,void(t!==mt.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:mt.Continue}:{route:x??mt.Continue};if(s.route===mt.Browser)return;if(void 0!==s.action||s.route===mt.Consume)return i.preventDefault(),void(void 0!==s.action&&r.execute?.(s,n));if(s.route!==mt.Application&&(i.ctrlKey||i.metaKey))return;const o=s.route===mt.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(),U();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 Lt(e){const t=new URL(import.meta.url);return t.hash=`hex1b-${e}-worker`,t}c(),u(),g(),M();var At=8,Rt=32;function Pt(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 Ot(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,...Pt(e.columns,e.rows)}:{mode:"auto",fontSize:i}}var zt="Text history is not available for this terminal view",Ut="Selection expired: its text was changed, evicted, reset, or resized. Select the text again.",Dt="Selection changed before copy completed. Copy the new selection again.",Bt=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?Ut:""}}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(Ut)));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(Ut)):(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(zt);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(Dt)),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(Dt)),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 qt(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 jt(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 Nt(e,t,i,r,n,s){const o=jt(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})}z(),s();var $t=/\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 Ft(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 Gt(e){const t=function(e={}){let t;Ft(e,"scrollbar.appearance");const i=(e,i)=>{if(void 0===e)return;if("string"!=typeof e||e.length>256||!e.trim()||/\\|\/\*|--/.test(e)||$t.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&&Ft(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 Wt=Gt();function _t(e){Wt(e)}var Ht=Object.freeze({track:"#202020",thumb:"#999999",marker:"#aaaaaa",error:"#eeeeee",prompt:"#666666","command-line":"#888888",executing:"#bbbbbb",success:"#999999",custom:"#dddddd"});function Kt(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 Vt(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 Xt=(e,t,i)=>Math.max(t,Math.min(i,e)),Yt=(e,t,i,r=0)=>t>=e.left-r&&t=e.top-r&&iYt(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 Zt=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)*Xt(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&&Yt(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(),Yt(t.thumb,i.x,i.y))return void(this.dragOffset=i.y-t.thumb.top);const r=Jt(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=Yt(t.track,i.x,i.y,s?s.proximity:0),a=Yt(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(!Yt(e.track,t.x,t.y))return null;const i=Jt(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(Xt(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=Xt("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:Xt(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}`,Ht[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:Kt(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??_t)(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 Qt(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 ei(e){return e?Qt(e.rawParameters).get("cmdline_url")??null:null}function ti(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=ei(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 ii=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=ti){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()}))}},ri="\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",ni="\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",si=`\n :host {\n ${ri}\n --cp-terminal-font-family: "Segoe UI", Aptos, Calibri, -apple-system, BlinkMacSystemFont, sans-serif;\n }\n @media (prefers-color-scheme: dark) { :host { ${ni} } }\n :host-context([data-theme="light"]) { ${ri} }\n :host-context([data-theme="dark"]) { ${ni} }\n :host([data-theme="light"]) { ${ri} }\n :host([data-theme="dark"]) { ${ni} }\n .viewport {\n ${Object.entries(Ht).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`;G(),s();var oi=["status","mode","requestId","active","pending","canExtend","text","message","copying","copyError"],ai=["available","following","pending","generation","buffer","top","liveTop","totalRows","requestId","navigationError"],li=["columns","rows","cellWidth","cellHeight","mouseTracking"],ci=["row","startColumn","endColumn"],hi=(e,t,i)=>i.every(i=>e?.[i]===t?.[i]);function di(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 ui=class{#L;#A;#R;#P;#O;#z;#U;#D;#B;#q=!1;#j=!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.#z=o,this.#U=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.#j||=e,this.#q||this.#P.aborted||(this.#q=!0,queueMicrotask(()=>{if(this.#q=!1,this.#P.aborted)return;const e=this.#O(),t=this.#j;if(this.#j=!1,!t&&(i=this.#D,r=e,i&&i.connected===r.connected&&i.readOnly===r.readOnly&&hi(i.selection,r.selection,oi)&&hi(i.viewport,r.viewport,ai)&&hi(i.geometry,r.geometry,li)&&hi(i.canvasSize,r.canvasSize,["width","height"])&&i.selection.ranges.length===r.selection.ranges.length&&i.selection.ranges.every((e,t)=>hi(e,r.selection.ranges[t],ci))))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.#z,rects:Object.freeze(di(n.selection.ranges,n.geometry,n.canvasSize))}),o=new CustomEvent("selectionui",{cancelable:!0,detail:s});if(this.#B=o,this.#U(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 pi(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 fi=class{#N=new Map;update(e){this.#N.clear();const t=new Map;for(const i of e){t.has(i.uri)||t.set(i.uri,pi(i.uri));const e=t.get(i.uri);if(!e)continue;const r=this.#N.get(i.row)??[];r.push({...i,uri:e}),this.#N.set(i.row,r)}}at(e){return this.#N.get(e.y)?.find(t=>e.x>=t.startColumn&&e.xF.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 mi.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>F.totalText||o.length+d.length>F.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>F.totalText||o.length+1>F.chunk)throw new RangeError("Link text exceeds limit");o+="\n"}}return s}function wi(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}ie();var yi=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=H({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??Lt("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 ${F.timeoutMs} ms`),this.#se())},F.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>F.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+=X(e),n>F.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+X(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=wi(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(W(d,this.#$.actions),i.length+s.length>=F.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>F.cacheEntries||this.#J>F.cacheText;){const e=this.#X.keys().next().value;this.#X.delete(e),this.#J-=this.#Y.get(e),this.#Y.delete(e)}}};function vi(e,t,i){const r=e.querySelector(t);if(!(r instanceof i))throw new Error(`Missing terminal element: ${t}`);return r}V(),s();var bi=class e{element;#$;#ue;#pe;#fe;#me;#ge=()=>{this.#ee||"system"!==this.#pe||this.#we()};#G;#ye;#ve;#be;#ke;#xe;#Ee=new AbortController;#Te={width:0,height:0};#Ce=Ot();#Se={columns:80,rows:24,cellWidth:10,cellHeight:20,mouseTracking:0};#Ie={id:null,primaryId:null,isPrimary:!1};#Me=!1;#Le=!1;#Ae;#ee=!1;#Re=!1;#Pe;#Oe;#ze;#Ue;#De=Promise.withResolvers();#Be;#qe={};#je="";#Ne="";#$e=!1;#Fe={state:"none",percentage:null};#Ge={phase:"unknown",lastExitCode:null};#We={uri:null,host:null,path:null};#_e=null;#He=!1;#g;#Ke;#Ve;#Xe;#Ye;#Je;#Ze;#Qe=!1;#et=!1;#tt;#it;#rt="";#nt=0;#st=!1;#ot;#f;#at=!1;#lt=0;#ct;#ht;#dt="";#ut={width:0,height:0};#pt=new fi;#K;#ft;#mt=1;#gt=0;#wt=0;#yt;#vt=new Map;#bt=[];#kt=[];#xt=new Map;#Et;#Tt;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.#De.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.#Ae=e.readOnly??!1,this.#ue=p(e.renderer),this.#pe=w(e.colorMode),this.#fe={light:v(void 0===e.lightModePalette?m:e.lightModePalette),dark:v(void 0===e.darkModePalette?f:e.darkModePalette)},this.#Ve=qt(e.padding),this.#Xe=Vt(e.scrollbar),this.#Ye=Nt(this.#Te,this.#Se,!1,this.#Ce,this.#Ve,this.#Xe),!(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.#ot=new Et(e),this.#f=new Map(Object.entries(e.actions??{})),this.#K=H(e.links,new Set(this.#f.keys())),this.#ft=new yi({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.#Me&&e===this.#gt&&(this.#bt=t,this.#Ct([]),this.#St())},onError:e=>{if(!this.#ee)try{this.#$.onStatus?.(`Link detection: ${e.message}`,"error")}finally{this.#$.onLinkDetectionError?.(e)}}}),this.#ft.configure(!!this.#K&&this.#K.detection),this.#g=new Bt(e=>this.#e(e),()=>this.#It()),this.#Ke=new I(e=>this.#e(e),()=>this.#Mt()),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.#Se}}get peer(){return{...this.#Ie}}get connected(){return this.#Me}get readOnly(){return this.#Ae}get title(){return this.#Ne}get progress(){return{...this.#Fe}}get shellIntegration(){return{...this.#Ge}}get workingDirectory(){return{...this.#We}}get commandMark(){return this.#_e?{...this.#_e}:null}get stats(){return{...this.#qe}}get screenText(){return this.#je}get sizing(){return{...this.#Ce}}get layout(){return this.#Ye}get padding(){return this.#Ve}get scrollbar(){return this.#Xe}get markers(){return this.#Ke.markers}get inputBindings(){return this.#ot.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.#st,copyError:this.#rt}}#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.#Ce=Ot(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.#ye=vi(s,".surface",HTMLDivElement),this.#ve=vi(s,"canvas",HTMLCanvasElement),this.#be=vi(s,"textarea",HTMLTextAreaElement),this.#tt=vi(s,".highlights",HTMLDivElement),this.#it=vi(s,".inspection",HTMLDivElement),this.#ve.id=`hex1b-content-${o()}`;const a=vi(s,".scrollbar-accessibility",HTMLDivElement);a.setAttribute("aria-controls",this.#ve.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.#Ze=new ii({overlay:l,getDetails:e=>this.getCommandMarkDetails(e),reportError:e=>{this.#rt=`Scrollbar tooltip: ${n(e)}`,this.#Lt(),this.#$.onStatus?.(this.#rt,"error")}}),this.#Je=new Zt({element:vi(s,".viewport",HTMLDivElement),canvas:vi(s,".scrollbar-canvas",HTMLCanvasElement),accessibility:a,getState:()=>({layout:this.layout,viewport:this.viewport,markers:this.markers,connected:this.#Me,configuration:this.#Xe}),scrollToRow:e=>this.scrollToRow(e),scrollToLive:()=>this.scrollToLive(),scrollToMarker:e=>this.scrollToMarker(e),onMarkerHover:e=>this.#Ze?.update(e,this.layout,!!this.#Xe&&this.#Xe.tooltip),reportError:e=>{this.#rt=`Scrollbar: ${n(e)}`,this.#Lt(),this.#$.onStatus?.(this.#rt,"error")}}),this.#ht=document.createElement("div"),this.#ht.slot="selection-ui",this.#ht.className="hex1b-selection-overlay",this.#ht.style.cssText="position:relative;width:100%;height:100%;pointer-events:none",this.element.append(this.#ht),this.#ct=new ui({element:this.element,overlay:this.#ht,button:vi(this.#it,".copy-selection",HTMLButtonElement),signal:this.#Ee.signal,getState:()=>({selection:this.selection,viewport:this.viewport,geometry:this.geometry,canvasSize:this.#ut,connected:this.#Me,readOnly:this.#Ae}),runAction:this.runAction.bind(this),onSelectionUI:this.#$.onSelectionUI,reportError:e=>{this.#dt=e?`Selection UI failed: ${n(e)}`:"",this.#ht.hidden=!!e,this.#Lt(),e&&this.#$.onStatus?.(this.#dt,"error")}}),this.#ct.refresh(),this.#be.setAttribute("aria-label",this.#$.label||"Terminal input. Click outside to use page controls."),this.#be.disabled=!0,e.append(this.element);const c=e=>{try{this.#rt="",e()}catch(e){this.#rt=n(e),this.#It()}};this.#ke=Mt(this.#ve,e=>this.#At(e),()=>this.focus(),{state:()=>({historical:!this.viewport.following||this.viewport.pending,readOnly:this.#Ae,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.#Je?.activity()}),end:e=>this.#g.endGesture(e),resolve:e=>this.#Rt(e),execute:(e,t)=>this.#Pt(e,t),hyperlink:e=>this.#Ot(e),hoverHyperlink:e=>{this.#Et=e?.id,this.#K&&this.#K.detection&&"hover"===this.#K.detection.decoration&&this.#St()},openHyperlink:(e,t)=>this.#zt(e,t)}),this.#Ut(),vi(this.#it,".return-live",HTMLButtonElement).addEventListener("click",()=>{this.runAction(gt.ScrollToLive).catch(e=>this.#Dt(e))},{signal:this.#Ee.signal}),vi(this.#it,".copy-selection",HTMLButtonElement).addEventListener("click",()=>{this.runAction(gt.CopySelection).catch(e=>this.#Dt(e))},{signal:this.#Ee.signal}),vi(s,".viewport",HTMLDivElement).addEventListener("pointerdown",e=>{e.composedPath().includes(this.#ht)||e.target instanceof Element&&e.target.closest("button")||e.target===this.#ve||e.target===this.#be||(e.preventDefault(),this.focus())},{signal:this.#Ee.signal}),this.#xe=new ResizeObserver(e=>{const{width:t,height:i}=e[0].contentRect,r=t!==this.#Te.width||i!==this.#Te.height;this.#Te={width:t,height:i},this.#Bt(),r&&this.#qt()}),this.#xe.observe(e),window.addEventListener("pagehide",()=>this.dispose(),{signal:this.#Ee.signal}),this.#$.signal?.addEventListener("abort",()=>this.dispose(),{once:!0,signal:this.#Ee.signal}),this.#Be=setTimeout(()=>this.#jt(new Error("Timed out waiting for the terminal's first frame")),3e4),this.#G=void 0===this.#$.workerUrl?new Worker(Lt("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.#Nt(e.data)),this.#G.addEventListener("error",e=>{e.preventDefault(),this.#jt(new Error(e.message||"Terminal worker failed"))}),this.#G.addEventListener("messageerror",()=>this.#jt(new Error("Terminal worker message could not be decoded")));const d=this.#ve.transferControlToOffscreen();this.#me=window.matchMedia?.("(prefers-color-scheme: dark)"),this.#me?.addEventListener("change",this.#ge),this.#$t({type:"init",canvas:d,url:t.href,scale:i,font:r,renderer:this.#ue,palette:this.#fe[this.resolvedColorMode]},[d]),this.#we(),this.#Ft()}#Nt(e){if(!this.#ee)if("connected"===e.type)this.#Me=!0,this.#be.disabled=!this.#Gt();else if("closed"===e.type){if(this.#Le)return;this.#Le=!0,clearTimeout(this.#Be);try{this.#Wt(),this.#ee||this.#$.onClose?.(Object.freeze({...e.details}))}finally{this.#De.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.#Wt(),this.#De.reject(new Error(e.message))),this.#$.onStatus?.(e.message,e.level);else if("geometry"===e.type){this.#gt=e.revision,this.#vt.clear();for(const t of e.hyperlinks){const e=this.#vt.get(t.row)??[];e.push(t),this.#vt.set(t.row,e)}this.#Ct([]),this.#Tt=void 0;const t=!this.#Re,i=t||["columns","rows","cellWidth","cellHeight","mouseTracking"].some(t=>this.#Se[t]!==e[t]);this.#Se={columns:e.columns,rows:e.rows,cellWidth:e.cellWidth,cellHeight:e.cellHeight,mouseTracking:e.mouseTracking},this.#Re=!0;const r=this.#Ie;if(this.#Ie=e.peer,this.#be.disabled=!this.#Gt(),this.#Gt()&&document.activeElement===this.element&&!this.element.shadowRoot?.activeElement&&this.focus(),this.#pt.update(e.hyperlinks),(i||r.isPrimary!==this.#Ie.isPrimary)&&this.#Bt(),this.#Ie.isPrimary||(clearTimeout(this.#Pe),this.#Pe=void 0,this.#Oe=void 0),(t||!r.isPrimary&&this.#Ie.isPrimary)&&this.#qt(!0),this.#Oe===`${e.columns}x${e.rows}`&&(this.#Oe=void 0),Object.hasOwn(e,"history")&&(this.#je=e.text,this.#Ke.accept(e.history,e.revision),this.#g.accept(e.history,e.revision)),e.linkGeneration===this.#mt&&(e.linkSnapshot?this.#_t(e.linkSnapshot):(this.#yt&&(this.#yt={...this.#yt,revision:e.revision}),this.#ft.advance(e.revision))),this.#ke?.update(e.columns,e.rows,e.mouseTracking),i&&this.#$.onGeometry?.(this.geometry),t&&this.#$.onSizingChange?.(this.sizing),r.id===this.#Ie.id&&r.primaryId===this.#Ie.primaryId&&r.isPrimary===this.#Ie.isPrimary||this.#$.onRoleChange?.(this.peer),!this.#ee&&this.#Me&&(null!==this.#Ie.id||this.#Ie.isPrimary)){const t=!this.#$e||this.#Ne!==e.title,i=!this.#He||this.#Fe.state!==e.progress.state||this.#Fe.percentage!==e.progress.percentage,r=!this.#He||this.#Ge.phase!==e.shellIntegration.phase||this.#Ge.lastExitCode!==e.shellIntegration.lastExitCode,n=!this.#He||this.#We.uri!==e.workingDirectory.uri,s=!this.#He||this.#_e?.phase!==e.commandMark?.phase||this.#_e?.exitCode!==e.commandMark?.exitCode||this.#_e?.rawParameters!==e.commandMark?.rawParameters;this.#Ne=e.title,this.#$e=!0,this.#Fe={...e.progress},this.#Ge={...e.shellIntegration},this.#We={...e.workingDirectory},this.#_e=e.commandMark?{...e.commandMark}:null,this.#He=!0,t&&this.#$.onTitleChange?.(this.#Ne),!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.#mt&&e.snapshot.revision===this.#gt&&this.#_t(e.snapshot);else if("linkDecorations"===e.type){const t=this.#Tt;this.#Me&&t&&t.serial===e.serial&&t.generation===e.generation&&e.generation===this.#mt&&t.revision===e.revision&&e.revision===this.#gt&&(this.#Tt=void 0,this.#Ct(t.links),this.#ke?.refresh())}else"history"===e.type?(this.#je=e.text,this.#Ke.accept(e.history,e.revision),this.#g.accept(e.history,e.revision)):"stats"===e.type&&(this.#qe=e.stats,void 0!==e.text&&(this.#je=e.text),e.stats.revision>0&&this.#$e&&this.#He&&this.#Me&&(null!==this.#Ie.id||this.#Ie.isPrimary)&&(clearTimeout(this.#Be),this.#De.resolve(this)),this.#$.onStats?.(this.stats,e.text))}#Bt(){const e=Nt(this.#Te,this.#Se,this.#Ie.isPrimary,this.#Ce,this.#Ve,this.#Xe),t=JSON.stringify(e)!==JSON.stringify(this.#Ye);this.#Ye=e;const{width:i,height:r,left:n,top:s}=e.content;this.#ye.style.width=`${i}px`,this.#ye.style.height=`${r}px`,this.#ye.style.left=`${n}px`,this.#ye.style.top=`${s}px`;const o=getComputedStyle(this.#ye);this.#ut={width:Number.parseFloat(o.width),height:Number.parseFloat(o.height)},this.#ct?.refresh(),this.#Je?.refresh(),t&&!this.#Qe&&(this.#Qe=!0,queueMicrotask(()=>{this.#Qe=!1,this.#ee||this.#$.onLayoutChange?.(this.layout)}));const a=window.devicePixelRatio||1;this.#$t({type:"viewport",width:Math.ceil(i*a),height:Math.ceil(r*a)})}#Ht(){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))))}}(jt(this.#Te,this.#Ve,this.#Xe),this.#Se,this.#Ce)}#qt(e=!1){("fixed"!==this.#Ce.mode||e)&&this.#Gt()&&this.#Ie.isPrimary&&void 0===this.#Pe&&!this.#ee&&(this.#Pe=setTimeout(()=>{this.#Pe=void 0;const e=this.#Ht();if(!e||!this.#Ie.isPrimary||!this.#Gt())return;const t=`${e.columns}x${e.rows}`;e.columns===this.#Se.columns&&e.rows===this.#Se.rows||t===this.#Oe||this.resize(e.columns,e.rows)},50))}#$t(e,t=[]){this.#G?.postMessage(e,t)}#e(e){if(!this.#Me||this.#ee)throw new Error("Terminal view is not connected");if(this.#Ae&&["input","paste","key","mouse","resize","requestPrimary"].includes(e.type))throw new Error("Terminal view does not accept input");this.#$t({type:"command",command:e})}#At(e){!this.#ee&&this.#Gt()&&(!function(e){if(O.encode(JSON.stringify(e)).byteLength>R.commandBytes)throw new RangeError("Terminal input exceeds the 64 KiB message limit. Send a smaller amount of text.")}(e),this.#lt++,["input","paste","key"].includes(e.type)&&this.viewport.available&&(this.#ke?.cancel(),"none"!==this.selection.status&&this.clearSelection(),this.viewport.following&&!this.viewport.pending||this.scrollToLive()),this.#e(e))}#It(){const e=this.viewport,t=this.selection;if(this.#ke?.refresh(),"invalidated"===t.status&&this.#ke?.cancel(),this.#tt){this.#tt.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.#Se.columns*100}%;top:${e.row/this.#Se.rows*100}%;width:${(e.endColumn-e.startColumn)/this.#Se.columns*100}%;height:${100/this.#Se.rows}%`,t}));const i=vi(this.#it,".return-live",HTMLButtonElement);i.hidden=!e.available||e.following&&!e.pending,i.disabled=!this.#Me,this.#Lt()}this.#ee||this.#ct?.refresh(),this.#Je?.refresh(),this.#$.onViewportChange?.(e),this.#$.onSelectionChange?.(t)}scrollLines(e){this.#g.scroll(e),this.#Je?.activity()}scrollToRow(e){this.#g.scrollTo(e),this.#Je?.activity()}scrollToLive(){this.#g.live(),this.#Je?.activity()}scrollToMarker(e){return this.#Je?.activity(),this.#Ke.jump(e)}addMarker(e){return this.#Ke.add(e)}removeMarker(e){return this.#Ke.remove(e)}getCommandMarkDetails(e){return this.#Ke.details(e)}#Mt(){this.#Je?.activity(),this.#et||(this.#et=!0,queueMicrotask(()=>{this.#et=!1,this.#ee||this.#$.onMarkersChange?.(this.markers)}))}setPadding(e){if(this.#ee)throw new Error("Terminal view is disposed");this.#Ve=qt(e),this.#Bt(),this.#qt(!0)}setScrollbar(e){if(this.#ee)throw new Error("Terminal view is disposed");this.#Xe=Vt(e),this.#Je?.cancel(),this.#Bt(),this.#qt(!0)}refreshScrollbar(){if(this.#ee)throw new Error("Terminal view is disposed");this.#Je?.invalidate()}clearSelection(){this.#rt="",this.#g.clear()}refreshSelectionUI(){if(this.#ee)throw new Error("Terminal view is disposed");this.#ct?.refresh(!0)}#Lt(){if(!this.#it)return;const e=this.selection,t=this.viewport,i=vi(this.#it,".inspection-message",HTMLSpanElement);i.textContent=this.#dt||this.#rt||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.#dt||this.#rt||t.navigationError||"invalidated"===e.status?"error":"info"}#Dt(e){const t=e instanceof Error?e:new Error(String(e));this.#rt=`Input action failed: ${t.message}`,this.#It(),this.#$.onInputError?.(t)}async copySelection({clear:e=!1}={}){if("boolean"!=typeof e)throw new TypeError("Copy clear must be a boolean");const t=++this.#nt,i=this.selection.requestId,r=this.viewport.generation;this.#rt="";try{if(!this.#Me||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.#st=!0,this.#It();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.#nt&&"valid"===this.selection.status&&this.selection.requestId===i&&this.viewport.generation===r&&this.clearSelection(),o}catch(e){throw t===this.#nt&&(this.#g.cancelCopy(e),this.#rt=`Copy failed: ${n(e)}`),e}finally{t===this.#nt&&(this.#st=!1),this.#It()}}#Gt(){return this.#Me&&this.#Re&&!this.#Ae&&(null!==this.#Ie.id||this.#Ie.isPrimary)}get inputContext(){return Object.freeze({terminal:this,selection:this.selection,viewport:this.viewport,buffer:this.viewport.buffer??null,mouseCaptured:!this.#Ae&&0!==this.#Se.mouseTracking,historical:!this.viewport.following||this.viewport.pending,readOnly:this.#Ae,connected:this.#Me,peer:this.peer})}#Rt(e){try{const t=this.#ot.resolve(Object.freeze(e),this.inputContext);return this.#Ae&&t.route===mt.Application?{route:"pointer"===e.type||"wheel"===e.type?mt.Continue:mt.Consume}:t}catch(e){return this.#Dt(e),{route:mt.Consume}}}#Pt(e,t){this.#Kt(e.action,e.args,t).catch(e=>this.#Dt(e))}async runAction(e,t,i){return this.#Kt(e,t,i)}async#Kt(e,t,i){if(this.#ee)throw new Error("Terminal view is disposed");if(this.#rt="","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 gt.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 gt.PasteClipboard:return this.pasteClipboard();case gt.ClearSelection:return this.clearSelection();case gt.ScrollToLive:return this.scrollToLive();case gt.ScrollLines:if("number"!=typeof t)throw new RangeError("Scroll delta must be a signed 32-bit integer");return this.scrollLines(t);case gt.CopyOrPaste:if(this.#at)throw new Error("A clipboard action is still in progress. Try again when it finishes.");this.#at=!0;try{return this.selection.active||this.selection.pending&&this.selection.canExtend?await this.copySelection({clear:!0}):this.#Ae?void 0:await this.pasteClipboard()}finally{this.#at=!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.#Gt())throw new Error("Terminal view does not accept input");e&&this.#At({type:"paste",text:e})}async pasteClipboard(){if(!this.#Gt())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.#lt,t=this.viewport.generation,i=this.selection.requestId,r=document.activeElement,n=await navigator.clipboard.readText();if(!this.#Gt()||e!==this.#lt||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}get colorMode(){return this.#pe}get resolvedColorMode(){return"system"===this.#pe?this.#me?.matches?"dark":"light":this.#pe}setColorMode(e){if(this.#ee)throw new Error("Terminal view is disposed");if(void 0===e)throw new TypeError("A color mode is required");this.#pe=w(e),this.#we()}setPalette(e,t){if(this.#ee)throw new Error("Terminal view is disposed");if("light"!==e&&"dark"!==e)throw new TypeError('Palette mode must be "light" or "dark"');this.#fe[e]=v(t),this.resolvedColorMode===e&&this.#we()}#we(){const e=this.#fe[this.resolvedColorMode];this.element.dataset.theme=this.resolvedColorMode,this.element.style.colorScheme=this.resolvedColorMode,this.element.style.backgroundColor=e.background,this.#$t({type:"palette",palette:e})}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.#Ae===e)return;const t=document.activeElement===this.element&&(!this.element.shadowRoot?.activeElement||this.element.shadowRoot.activeElement===this.#be);this.#Ae=e,this.#lt++,this.#Ue?.(),this.#be&&(this.#be.value="",this.#be.disabled=!this.#Gt()),this.#ke?.cancel(),this.#ke?.refresh(),clearTimeout(this.#Pe),this.#Pe=void 0,this.#Oe=void 0,this.#qt(!0),t&&this.focus(),this.#ct?.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=H(e,new Set(this.#f.keys()));this.#K=t,this.#mt++,this.#bt=[],this.#Ct([]),this.#Tt=void 0,this.#yt=void 0,this.#Et=void 0,this.#ft.configure(!!t&&t.detection),this.#ke?.cancel(),this.#ke?.refresh(),this.#Ft()}#Ft(){const e=!(!this.#K||!(this.#K.osc8||this.#K.detection&&this.#K.detection.rules.some(e=>!1!==e.enabled)));this.#$t({type:"linkDetection",enabled:e,generation:this.#mt})}#_t(e){this.#yt=e,this.#bt=[],this.#Ct([]),this.#Tt=void 0,this.#ft.update(e),this.#ke?.refresh()}#Vt(e){return`detected/${this.#mt}/${this.#gt}/${e.id}`}#Ct(e){this.#kt=e,this.#xt.clear();for(const t of e)for(const e of t.activation.ranges){const i=this.#xt.get(e.row)??[];i.push({startColumn:e.startColumn,endColumn:e.endColumn,link:t}),this.#xt.set(e.row,i)}}#St(){if(!this.#G||!this.#Me||!this.#gt)return;const e=this.#K&&this.#K.detection;if(!e)return;const t="none"===e.decoration?[]:"hover"===e.decoration?this.#bt.filter(e=>this.#Vt(e)===this.#Et):this.#bt;this.#Tt={revision:this.#gt,generation:this.#mt,serial:++this.#wt,links:this.#bt},this.#$t({type:"linkDecorations",revision:this.#gt,generation:this.#mt,serial:this.#wt,ranges:t.flatMap(e=>e.activation.ranges),underlineStyle:e.underlineStyle??"solid"})}#Ot(e){if(!this.#Me||this.viewport.pending||!this.#K)return null;const t=this.#vt.get(e.y)?.find(t=>e.x>=t.startColumn&&e.xe.x>=t.startColumn&&e.xthis.#Vt(t)===e.id);if(i)return void this.#Kt(i.action,i.activation,t).catch(e=>this.#Dt(e));if(!this.#K.osc8){try{window.open(e.target,"_blank","noopener,noreferrer")}catch(e){this.#Dt(e)}return}const r=this.#vt.get(t.point.y)?.find(e=>t.point.x>=e.startColumn&&t.point.xthis.#Dt(e))}#Xt(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.#At({type:"key",key:e.key,ctrl:e.ctrl,alt:e.alt,shift:e.shift})}else"paste"===e.type?this.#Gt()&&this.paste(e.text):"text"===e.type&&this.#At({type:"input",text:e.text})}#Yt(e,t,i=!0){const r=this.#Rt(e);if((i||void 0!==r.action||r.route===mt.Consume)&&r.route!==mt.Browser&&(r.route!==mt.Continue||"key"!==e.type))if(t?.preventDefault(),t?.stopPropagation(),void 0!==r.action)this.#Pt(r,e);else if(r.route!==mt.Consume)try{this.#Xt(e)}catch(e){this.#Dt(e)}}#Ut(){const e=this.#be,t={signal:this.#Ee.signal};let i=!1,r=null;this.#Ue=()=>{i=!1,r=null,clearTimeout(this.#ze)},this.element.addEventListener("keydown",t=>{if(t.defaultPrevented)return;if(t.composedPath().includes(this.#ht))return;const r=t.composedPath()[0];!this.#Me||t.isComposing||i||"Process"===t.key||"Dead"===t.key||t.getModifierState("AltGraph")||this.#Yt({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.#Me&&t.clipboardData&&(this.#Yt({type:"paste",text:t.clipboardData.getData("text/plain")},t),e.value="")},t),e.addEventListener("compositionstart",()=>{this.#Gt()&&(i=!0,r=null,clearTimeout(this.#ze))},t),e.addEventListener("compositionend",t=>{i&&(i=!1,r="string"==typeof t.data?t.data:e.value,this.#ze=setTimeout(()=>{r&&this.#Yt({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.#ze);const s=r??n?.data??e.value;r=null,s&&"insertFromPaste"!==n?.inputType&&this.#Yt({type:"text",text:s},t),e.value=""},t)}focus(){if(this.#ee)return;(this.#Gt()?this.#be:this.element).focus({preventScroll:!0})}requestPrimary(){if(!this.#Gt())throw new Error("Terminal view does not accept input");if(this.#Te.width<=0||this.#Te.height<=0)throw new Error("Show the terminal container before taking primary");const e=this.#Ht();if(!e)throw new Error("Show the terminal container before taking primary");if(null!==this.#Ie.id)this.#e({type:"requestPrimary",...e});else{if(!this.#Ie.isPrimary)throw new Error("Waiting for the HMP1 connection");this.resize(e.columns,e.rows)}}resize(e,t){const i=Pt(e,t);if(!this.#Gt())throw new Error("Terminal view does not accept input");if(!this.#Ie.isPrimary)throw new Error("Only the primary view can request a terminal resize");this.#e({type:"resize",...i}),this.#Oe=`${e}x${t}`}setSizing(e){const t=Ot(e,this.#Ce.fontSize);if(!this.#Me||this.#ee)throw new Error("Terminal view is not connected");if(this.#Ae)throw new Error("Terminal view does not accept input");if(!this.#Ie.isPrimary)throw new Error("Only the primary view can change terminal sizing");this.#Ce=t,clearTimeout(this.#Pe),this.#Pe=void 0,this.#Oe=void 0,this.#Bt(),this.#qt(!0),this.#$.onSizingChange?.(this.sizing)}resync(){this.#e({type:"resync"})}#Wt(){this.#lt++,this.#Me=!1,this.#pt.update([]),this.#vt.clear(),this.#bt=[],this.#Ct([]),this.#yt=void 0,this.#Tt=void 0,this.#ft.clear(),this.#be&&(this.#be.disabled=!0),this.#ke?.update(1,1,0),this.#g.disconnect(),this.#Ke.disconnect(),this.#Je?.cancel(),this.#It(),clearTimeout(this.#Pe),this.#Pe=void 0}#jt(e){this.#Wt(),this.#De.reject(e),this.#$.onStatus?.(e.message,"error"),this.#G?.terminate()}dispose(){this.#ee||(this.#ee=!0,this.#Wt(),this.#ft.dispose(),this.#De.reject(new DOMException("Terminal view was disposed","AbortError")),clearTimeout(this.#Be),clearTimeout(this.#ze),this.#xe?.disconnect(),this.#me?.removeEventListener("change",this.#ge),this.#ke?.dispose(),this.#Je?.dispose(),this.#Ze?.dispose(),this.#Ee.abort(),this.#$t({type:"stop"}),this.#G?.terminate(),this.element.remove())}};if(M(),V(),"undefined"!=typeof DedicatedWorkerGlobalScope&&globalThis instanceof DedicatedWorkerGlobalScope)switch(new URL(import.meta.url).hash){case"#hex1b-terminal-worker":await Promise.resolve().then(()=>(lt(),tt));break;case"#hex1b-link-detection-worker":await Promise.resolve().then(()=>(ut(),ct))}export{mt as InputRoute,Rt as MAX_FONT_SIZE,At as MIN_FONT_SIZE,gt as TerminalAction,bi as WebTerminal,Gt as createDefaultScrollbarRenderer,f as defaultDarkPalette,kt as defaultInputBindings,m as defaultLightPalette,ei as getCmdlineUrl,K as linkAction,Qt as parseCommandMarkParameters,_t as renderDefaultScrollbar,ti 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":"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","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":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAChC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,IAAY;IACtD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,EAAE,IAAI,OAAO,GAAG,GAAG;QAC3D,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,UAAU,CAAC,sDAAsD,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAyB,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,gBAAgB,GAAG,gBAAgB;IAC5G,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,gBAAgB,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,aAAa,IAAI,QAAQ,GAAG,aAAa,EAAE,CAAC;QACxF,MAAM,IAAI,UAAU,CAAC,qCAAqC,aAAa,OAAO,aAAa,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,KAAK,OAAO;QAC5B,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;QACzE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAkB,EAAE,QAA0B,EAAE,MAA2B;IACvG,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACnF,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,GAAG,gBAAgB,CAAC;IACjD,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;KAC3F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAkB,EAAE,QAA0B,EAAE,SAAkB,EAAE,MAA2B;IACzH,MAAM,OAAO,GAAG,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpG,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EACjC,IAAI,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,SAAS,CAAC,EACpD,IAAI,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC","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":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACvF,mBAAmB,iBAAiB,CAAC;AAErC,2FAA2F;AAC3F,MAAM,WAAW,YAAY;IAAG,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC/D,MAAM,WAAW,YAAY;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC/D,MAAM,WAAW,aAAa;IAAG,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AACvD,MAAM,MAAM,iBAAiB,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC3D,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,iBAAiB,CAAC;CAClC;AACD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB;AACD,8FAA8F;AAC9F,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACxE,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACvE,MAAM,WAAW,gBAAgB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAClF,iGAAiG;AACjG,MAAM,WAAW,YAAY;IAAG,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAAE;AACrF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,WAAW,CAAC;AAClD,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,CAAC;AACxE,MAAM,WAAW,cAAc;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;AACvF,MAAM,MAAM,gBAAgB,GAAG,CAC3B;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAC/E,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChG;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,UAAU,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IACpF,OAAO,CAAC,EAAE,SAAS,CAAC;IAAC,GAAG,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CACpH,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAClF,MAAM,MAAM,iBAAiB,GAAG,CAC5B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,GAC1E;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CACrF,GAAG;IACF,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,SAAS,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AACF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAC7D,6FAA6F;AAC7F,MAAM,WAAW,oBAAoB;IACnC,uGAAuG;IACvG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AACD,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACvD,+FAA+F;AAC/F,MAAM,MAAM,0BAA0B,GAAG,MAAM,GAAG,oBAAoB,CAAC;AACvE,wGAAwG;AACxG,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,2FAA2F;IAC3F,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IACtF,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC9E,GAAG,CAAC,EAAE,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxG,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACtE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IACvG,IAAI,CAAC,EAAE,YAAY,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACzD;AACD,MAAM,WAAW,cAAc;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC9F,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AACxD,+FAA+F;AAC/F,MAAM,MAAM,aAAa,GACrB,CAAC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,CAAC,GAC9E,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAA;CAAE,GAAG,cAAc,CAAC,GAC7F,CAAC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IACjE,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,cAAc,CAAC,GAC5D;IAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7C,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAAC;AACjF,MAAM,MAAM,kBAAkB,GAAG,eAAe,GAAG,gBAAgB,GAAG,aAAa,GAC/E,gBAAgB,GAAG,cAAc,GAAG,aAAa,CAAC;AACtD,MAAM,WAAW,oBAAoB;IAAG,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AACzD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AACD,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAC5B,CAAC,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC;AACxG,MAAM,MAAM,aAAa,GACrB;IAAE,KAAK,EAAE,eAAe,CAAC;IAAC,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,GACxD;IAAE,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAC3E,MAAM,MAAM,gBAAgB,GAC1B,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,KAAK,eAAe,GAAG,aAAa,GAAG,SAAS,CAAC;AACjH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC;IAClF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,OAAO,CAAC;IAClF,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,GAAG,aAAa,CAAC;AAClB,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC;AAC/E,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAChD,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC,CAAC;CACxD;AACD,wGAAwG;AACxG,MAAM,WAAW,iBAAiB;IAChC,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/F,CAAC,MAAM,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrF,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC9F,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpG,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,IAAI,SAAS,kBAAkB,GAAG,KAAK,GAAG,IAAI,EAC1E,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACvF;AACD,MAAM,WAAW,kBAAkB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAChG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC5C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AACD,6FAA6F;AAC7F,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;CACzD;AACD,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;AAC9D,8EAA8E;AAC9E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAC9F,yFAAyF;AACzF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AACD,6FAA6F;AAC7F,MAAM,MAAM,6BAA6B,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;AAC5G,sFAAsF;AACtF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,6BAA6B,CAAC;IAC9C,4FAA4F;IAC5F,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AACD,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACvC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AACD;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,6BAA6B,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,8FAA8F;IAC9F,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AACD,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;IAClB,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACzB,2EAA2E;IAC3E,sBAAsB,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACtC,iGAAiG;IACjG,KAAK,CAAC,EAAE,KAAK,GAAG,mBAAmB,CAAC;IACpC,uFAAuF;IACvF,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACnE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjE;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAClD,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,gBAAgB,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAChF;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,gBAAgB,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAChF;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,mBAAmB,GAAG,IAAI,KAAK,IAAI,CAAC;IACxE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACnE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACxD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC3D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACtC,6FAA6F;IAC7F,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,SAAS,CAAC;CACxD;AACD,sFAAsF;AACtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,kGAAkG;IAClG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mGAAmG;IACnG,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;IACpD,+FAA+F;IAC/F,QAAQ,CAAC,gBAAgB,EAAE,wBAAwB,CAAC;IACpD,mGAAmG;IACnG,QAAQ,CAAC,WAAW,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,YAAY,EAAE,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;IACtC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,IAAI,IAAI,CAAC;IACrB,cAAc,IAAI,IAAI,CAAC;IACvB,kBAAkB,IAAI,IAAI,CAAC;IAC3B,aAAa,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,mBAAmB,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,IAAI,CAAC;IACd,cAAc,IAAI,IAAI,CAAC;IACvB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;CACjB"} \ 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":"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"} \ 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/src/Aspire.Hosting/ApplicationModel/TerminalService.cs b/src/Aspire.Hosting/ApplicationModel/TerminalService.cs index c6738bed148..cfec6a95de3 100644 --- a/src/Aspire.Hosting/ApplicationModel/TerminalService.cs +++ b/src/Aspire.Hosting/ApplicationModel/TerminalService.cs @@ -139,7 +139,7 @@ private static Hex1bTerminalBuilder CreateBuilder(TerminalLaunchOptions options) ? new Dictionary(options.EnvironmentVariables, StringComparer.Ordinal) : null; - Hex1bPtySocketHelper.Configure(); + var socketPath = Hex1bPtySocketHelper.CreateSocketPath(); return Hex1bTerminal.CreateBuilder() .WithPtyProcess(process => @@ -147,6 +147,7 @@ private static Hex1bTerminalBuilder CreateBuilder(TerminalLaunchOptions options) process.FileName = executable; process.Arguments = arguments; process.WorkingDirectory = workingDirectory; + process.WindowsPtyProxySocketPath = socketPath; if (environment is not null) { diff --git a/src/Aspire.Hosting/Utils/Hex1bPtySocketHelper.cs b/src/Aspire.Hosting/Utils/Hex1bPtySocketHelper.cs index 9d2bb9a5f99..f7a7b1b64d6 100644 --- a/src/Aspire.Hosting/Utils/Hex1bPtySocketHelper.cs +++ b/src/Aspire.Hosting/Utils/Hex1bPtySocketHelper.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 System.Net.Sockets; using Aspire.Shared; namespace Aspire.Hosting.Utils; @@ -12,40 +13,38 @@ internal static class Hex1bPtySocketHelper { internal const string SocketDirectoryEnvironmentVariable = "HEX1B_PTY_SHIM_SOCKET_DIR"; - private static readonly object s_lock = new(); - - internal static void Configure() + internal static string? CreateSocketPath() { // Unix PTYs do not use the hex1bpty helper or its filesystem socket. if (!OperatingSystem.IsWindows()) { - return; + return null; } - lock (s_lock) + var directory = Environment.GetEnvironmentVariable(SocketDirectoryEnvironmentVariable); + var useDefaultDirectory = string.IsNullOrWhiteSpace(directory); + if (string.IsNullOrWhiteSpace(directory)) { - var directory = Environment.GetEnvironmentVariable(SocketDirectoryEnvironmentVariable); - var useDefaultDirectory = string.IsNullOrWhiteSpace(directory); - if (string.IsNullOrWhiteSpace(directory)) + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + if (string.IsNullOrWhiteSpace(home)) { - var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - if (string.IsNullOrWhiteSpace(home)) - { - throw new InvalidOperationException("Cannot configure the PTY socket directory without a user profile directory."); - } - - directory = Path.Combine(home, SocketDirectoryNames.Aspire, SocketDirectoryNames.Pty); + throw new InvalidOperationException("Cannot configure the PTY socket directory without a user profile directory."); } - directory = SocketPermissionHelper.CreateDirectory(directory, repairExisting: useDefaultDirectory).FullName; - - // Hex1b reads the parent's process environment when the deferred PTY workload starts, - // then passes the full socket path to hex1bpty via --socket. The workload's Environment - // dictionary is too late. Keep this process-wide override after terminal/AppHost disposal - // so concurrent and lazily started terminals continue using the secured directory. - // Replace with per-workload configuration when Hex1b exposes a socket-path option. - // https://github.com/mitchdenny/hex1b/blob/v0.168.0/src/Hex1b/WindowsPtySocketPaths.cs - Environment.SetEnvironmentVariable(SocketDirectoryEnvironmentVariable, directory); + directory = Path.Combine(home, SocketDirectoryNames.Aspire, SocketDirectoryNames.Pty); } + + directory = Path.GetFullPath(directory); + // Hex1b secures its immediate parent again at startup. Isolate that ACL rewrite + // from a caller's validated override (which may also allow SYSTEM). + var proxyDirectory = Path.Combine(directory, "proxy"); + var socketPath = Path.Combine(proxyDirectory, $"{Guid.NewGuid():N}.socket"); + // Validate the native endpoint length before creating directories. Hex1b snapshots this + // exact path, so later environment changes cannot redirect a deferred workload. + _ = new UnixDomainSocketEndPoint(socketPath); + SocketPermissionHelper.CreateDirectory(directory, repairExisting: useDefaultDirectory); + SocketPermissionHelper.CreateDirectory(proxyDirectory, repairExisting: true); + + return socketPath; } } 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..2107a48f356 --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalTitleTests.cs @@ -0,0 +1,198 @@ +// 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 Microsoft.FluentUI.AspNetCore.Components; +using Microsoft.JSInterop; +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 = Render(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.Equal(Resources.TerminalStrings.TerminalCopyFailed, button.GetAttribute("data-copyfailed")); + Assert.Equal("polite", cut.Find(".terminal-directory-container [role=status]").GetAttribute("aria-live")); + 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.Render(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.Render(builder => builder.Add(p => p.State, new TerminalToolbarState())); + Assert.Equal("shell", cut.Find(".terminal-title").TextContent); + Assert.Empty(cut.FindAll(".terminal-directory")); + } + + [Fact] + public void Title_CopiesFullLiveTitleAndFallbackWithIndependentFeedback() + { + var title = "build \u03bb with a long title"; + var cut = Render(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.Equal(Resources.TerminalStrings.TerminalCopyFailed, button.GetAttribute("data-copyfailed")); + Assert.Equal("polite", cut.Find(".terminal-title-container [role=status]").GetAttribute("aria-live")); + 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.Render(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.Render(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.Render(builder => builder.Add(p => p.FallbackTitle, "")); + Assert.Empty(cut.FindAll(".terminal-title-button")); + } + + [Fact] + public void PathObserverFailure_DoesNotPreventCopyControlsFromRendering() + { + var module = JSInterop.SetupModule("./Components/Controls/TerminalTitle.razor.js"); + module.SetupVoid("observePath", _ => true).SetException(new JSException("Canvas unavailable")); + + var cut = Render(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Title = "shell", + WorkingDirectory = "/work/app" + })); + + Assert.Equal("shell", cut.Find(".terminal-title").TextContent); + Assert.Equal("/work/app", cut.Find(".terminal-directory").GetAttribute("data-text")); + Assert.Single(module.Invocations, i => i.Identifier == "observePath"); + } + + [Theory] + [InlineData("/work/app")] + [InlineData("C:\\src\\app")] + [InlineData("ab\U0001F680cd")] + [InlineData("abe\u0301cd")] + [InlineData("/")] + public void Directory_PreservesFullMeasurementAndCopyValue(string path) + { + var cut = Render(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 = Render(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Title = "shell", 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-icon"); + Assert.Equal(state, indicator.GetAttribute("data-state")); + Assert.Equal(expectedValue is null ? Resources.TerminalStrings.ResourceManager.GetString(label) : expectedText, indicator.GetAttribute("title")); + Assert.Empty(indicator.TextContent.Trim()); + Assert.Equal("terminal-icon", cut.Find(".terminal-metadata").Children[0].ClassName); + Assert.Equal("terminal-title-container", cut.Find(".terminal-metadata").Children[1].ClassName); + Assert.Single(indicator.Children); + } + + [Theory] + [InlineData(true, "none")] + [InlineData(false, "normal")] + [InlineData(false, "indeterminate")] + public void InactiveProgress_ShowsTerminalIcon(bool connected, string state) + { + var cut = Render(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Connected = connected, ProgressState = state, ProgressPercentage = 42 + })); + Assert.Empty(cut.FindAll("[role=progressbar]")); + Assert.Equal("none", cut.Find(".terminal-icon").GetAttribute("data-state")); + Assert.IsType(cut.FindComponent>().Instance.Value); + } + + [Fact] + public void ResourceIcon_ReturnsAfterProgressAndDisconnect() + { + var icon = new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Database(); + var cut = Render(builder => builder.Add(p => p.IdleIcon, icon)); + Assert.Same(icon, cut.FindComponent>().Instance.Value); + + cut.Render(builder => builder.Add(p => p.State, new TerminalToolbarState + { + Connected = true, ProgressState = "normal", ProgressPercentage = 42 + })); + Assert.Single(cut.FindAll("[role=progressbar]")); + Assert.Equal("42%", cut.Find(".terminal-icon").GetAttribute("title")); + Assert.Empty(cut.FindComponents>()); + + cut.Render(builder => builder.Add(p => p.State, new TerminalToolbarState { Connected = true, ProgressState = "none" })); + Assert.Same(icon, cut.FindComponent>().Instance.Value); + cut.Render(builder => builder.Add(p => p.State, new TerminalToolbarState { Connected = false, ProgressState = "normal", ProgressPercentage = 42 })); + Assert.Same(icon, cut.FindComponent>().Instance.Value); + Assert.Null(cut.Find(".terminal-icon").GetAttribute("title")); + } +} diff --git a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs index 830fcde0c87..d326df8e249 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Controls/TerminalViewTests.cs @@ -66,12 +66,90 @@ public void ChromeAndFooter_RespectSurfaceParameters(bool chromeless, bool showD Assert.Equal(chromeless, cut.Find(".terminal-view").ClassList.Contains("terminal-chromeless")); Assert.Equal(chromeless ? 0 : 1, cut.FindAll(".terminal-titlebar").Count); Assert.Equal(showDimensions ? 1 : 0, cut.FindAll(".terminal-size-select").Count); + Assert.Equal(showDimensions ? 2 : 1, cut.FindAll(".terminal-select-label").Count); + foreach (var label in cut.FindAll(".terminal-select-label")) + { + Assert.NotNull(cut.Find($"#{label.GetAttribute("for")}")); + } Assert.Equal(showDimensions ? 1 : 0, cut.FindAll(".terminal-fit").Count); Assert.Single(cut.FindAll(".terminal-font-minus")); Assert.Single(cut.FindAll(".terminal-font-plus")); Assert.Equal(Resources.TerminalStrings.TerminalFocusControlsHint, cut.Find(".terminal-focus-hint").TextContent); Assert.Equal(Resources.TerminalStrings.TerminalToolbarDecreaseFontSize, cut.Find(".terminal-font-minus").GetAttribute("aria-label")); Assert.Equal(Resources.TerminalStrings.TerminalToolbarIncreaseFontSize, cut.Find(".terminal-font-plus").GetAttribute("aria-label")); + foreach (var button in cut.FindAll(".terminal-controls fluent-button")) + { + Assert.Single(button.QuerySelectorAll("svg")); + Assert.False(string.IsNullOrEmpty(button.GetAttribute("aria-label"))); + Assert.Equal(button.GetAttribute("aria-label"), button.GetAttribute("title")); + } + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, false)] + public async Task PaletteSelector_UsesSavedStateAndRemainsAvailableWithoutResizeAuthority(bool readOnly, bool showDimensions) + { + var module = TerminalSetupHelpers.SetupTerminalViewModule(this, "/Components/Controls/TerminalView.razor.js"); + module.Setup("initTerminal", _ => true).SetResult(1); + var save = module.Setup("setPaletteFromHost", 1, "light"); + save.SetResult(true); + var cut = Render(builder => builder + .Add(p => p.ResourceName, "shell") + .Add(p => p.ReadOnly, readOnly) + .Add(p => p.ShowDimensionsPicker, showDimensions)); + var state = new TerminalToolbarState { TerminalId = 1, Generation = 1, Palette = "dark" }; + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state)); + var select = cut.FindComponent>(); + Assert.Equal("dark", select.Instance.Value); + Assert.False(select.Instance.Disabled); + Assert.Equal(Resources.TerminalStrings.TerminalPalette, select.Instance.AriaLabel); + Assert.Equal(["Light", "Dark"], select.Instance.Items!.Select(select.Instance.OptionText!)); + Assert.Equal("terminal-palette-select aspire-input", cut.Find(".terminal-controls").LastElementChild!.ClassName); + + await cut.InvokeAsync(() => select.Instance.ValueChanged.InvokeAsync("light")); + Assert.Single(save.Invocations); + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state with { Palette = "light" })); + Assert.Equal("light", select.Instance.Value); + + await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(state with { Error = "palette-failed" })); + Assert.Equal("dark", select.Instance.Value); + Assert.Equal(Resources.TerminalStrings.TerminalPaletteSaveFailed, cut.Find("[role=alert]").TextContent); + Assert.Equal(Resources.TerminalStrings.TerminalDismissError, cut.Find(".terminal-error fluent-button").TextContent.Trim()); + var failedSave = module.Setup("setPaletteFromHost", 1, "light"); + failedSave.SetResult(false); + var originalSelect = select.Instance; + await cut.InvokeAsync(() => select.Instance.ValueChanged.InvokeAsync("light")); + var restoredSelect = cut.FindComponent>(); + Assert.NotSame(originalSelect, restoredSelect.Instance); + Assert.Equal("dark", restoredSelect.Instance.Value); + } + + [Fact] + public void Footer_UsesCustomLabelsForIconTooltipsAndAccessibleNames() + { + TerminalSetupHelpers.SetupTerminalView(this); + var cut = Render(builder => builder + .Add(p => p.DecreaseFontSizeLabel, "Smaller text") + .Add(p => p.IncreaseFontSizeLabel, "Larger text") + .Add(p => p.FitLabel, "Fit to bounds")); + + Assert.Collection(cut.FindAll(".terminal-controls fluent-button"), + button => + { + Assert.Equal("Smaller text", button.GetAttribute("title")); + Assert.Equal("Smaller text", button.GetAttribute("aria-label")); + }, + button => + { + Assert.Equal("Larger text", button.GetAttribute("title")); + Assert.Equal("Larger text", button.GetAttribute("aria-label")); + }, + button => + { + Assert.Equal("Fit to bounds", button.GetAttribute("title")); + Assert.Equal("Fit to bounds", button.GetAttribute("aria-label")); + }); } [Theory] @@ -100,6 +178,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 = Render(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() { @@ -136,7 +237,7 @@ await cut.InvokeAsync(() => cut.Instance.OnTerminalStateChanged(new TerminalTool Cols = 97, Rows = 38, SizeKey = "97x38", SizeSelectEnabled = true })); Assert.False(cut.Find(".terminal-fit").HasAttribute("disabled")); - Assert.Equal(Resources.TerminalStrings.TerminalToolbarGridSizeAuto, cut.Find(".terminal-fit").TextContent.Trim()); + Assert.Equal(Resources.TerminalStrings.TerminalToolbarGridSizeAuto, cut.Find(".terminal-fit").GetAttribute("title")); var items = cut.FindComponent>().Instance.Items; Assert.NotNull(items); Assert.Equal([new("97x38", "97\u00d738", 97, 38), new TerminalSizePreset("80x24", "80\u00d724", 80, 24)], items); diff --git a/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs b/tests/Aspire.Dashboard.Components.Tests/Dialogs/InteractionsTerminalDialogTests.cs index 9dca6920ca9..e3c2c472467 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/Clipboard.test.mjs b/tests/Aspire.Dashboard.Components.Tests/JavaScript/Clipboard.test.mjs new file mode 100644 index 00000000000..bf5835ee2b8 --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/JavaScript/Clipboard.test.mjs @@ -0,0 +1,124 @@ +// 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"; +import { runInNewContext } from "node:vm"; + +const source = await readFile(new URL("../../../src/Aspire.Dashboard/wwwroot/js/app.js", import.meta.url), "utf8"); + +function createClipboard() { + const requests = []; + const timers = new Map(); + let nextTimer = 1; + const copyIcon = { style: { display: "" } }; + const checkmarkIcon = { style: { display: "none" } }; + const status = { classList: { contains: name => name === "terminal-copy-status" }, textContent: "" }; + const tooltip = { innerText: "Copy" }; + const button = { + dataset: {}, + nextElementSibling: status, + querySelector: selector => selector === ".copy-icon" ? copyIcon : checkmarkIcon, + getAttribute: name => name === "data-copyfailed" ? "Copy failed" : null, + }; + const document = { + getElementById: () => button, + querySelector: () => ({ children: [tooltip] }), + addEventListener() {}, + }; + const window = { document }; + runInNewContext(source, { + window, document, + navigator: { + clipboard: { + writeText(text) { + return new Promise((resolve, reject) => requests.push({ text, resolve, reject })); + }, + }, + }, + customElements: { define() {} }, + CSSStyleSheet: class { replaceSync() {} }, + setTimeout(callback, delay) { + assert.equal(delay, 1500); + const id = nextTimer++; + timers.set(id, callback); + return id; + }, + clearTimeout: id => timers.delete(Number(id)), + }); + return { + requests, timers, status, tooltip, copyIcon, checkmarkIcon, button, + copy: text => window.copyTextToClipboard("copy-button", text, "Copy", "Copied"), + reset() { + assert.equal(timers.size, 1); + const [id, callback] = timers.entries().next().value; + timers.delete(id); + callback(); + }, + }; +} + +const flush = () => new Promise(resolve => setImmediate(resolve)); + +test("clipboard success receives a full feedback interval after the write finishes", async () => { + const clipboard = createClipboard(); + clipboard.copy("first"); + assert.equal(clipboard.requests[0].text, "first"); + assert.equal(clipboard.timers.size, 0); + clipboard.requests[0].resolve(); + await flush(); + assert.equal(clipboard.status.textContent, "Copied"); + assert.equal(clipboard.tooltip.innerText, "Copied"); + assert.equal(clipboard.copyIcon.style.display, "none"); + assert.equal(clipboard.checkmarkIcon.style.display, ""); + assert.equal(clipboard.timers.size, 1); + + clipboard.reset(); + assert.equal(clipboard.status.textContent, ""); + assert.equal(clipboard.tooltip.innerText, "Copy"); + assert.equal(clipboard.copyIcon.style.display, ""); + assert.equal(clipboard.checkmarkIcon.style.display, "none"); + assert.equal(clipboard.button.dataset.copyTimeout, undefined); +}); + +test("clipboard failure also resets after the write finishes", async () => { + const clipboard = createClipboard(); + clipboard.copy("first"); + assert.equal(clipboard.timers.size, 0); + clipboard.requests[0].reject(new Error("Permission denied")); + await flush(); + assert.equal(clipboard.status.textContent, "Copy failed"); + assert.equal(clipboard.tooltip.innerText, "Copy failed"); + assert.equal(clipboard.timers.size, 1); + clipboard.reset(); + assert.equal(clipboard.status.textContent, ""); + assert.equal(clipboard.tooltip.innerText, "Copy"); +}); + +test("a late clipboard result cannot overwrite feedback from a newer request", async () => { + const clipboard = createClipboard(); + clipboard.copy("old"); + clipboard.copy("new"); + clipboard.requests[1].resolve(); + await flush(); + assert.equal(clipboard.status.textContent, "Copied"); + assert.equal(clipboard.timers.size, 1); + clipboard.requests[0].reject(new Error("Old request failed")); + await flush(); + assert.equal(clipboard.status.textContent, "Copied"); + assert.equal(clipboard.tooltip.innerText, "Copied"); + assert.equal(clipboard.timers.size, 1); + + clipboard.copy("third"); + assert.equal(clipboard.timers.size, 0); + assert.equal(clipboard.status.textContent, ""); + assert.equal(clipboard.tooltip.innerText, "Copy"); + assert.equal(clipboard.checkmarkIcon.style.display, "none"); + clipboard.requests[2].reject(new Error("New request failed")); + await flush(); + assert.equal(clipboard.status.textContent, "Copy failed"); + assert.equal(clipboard.timers.size, 1); + clipboard.reset(); + assert.equal(clipboard.status.textContent, ""); +}); diff --git a/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalDock.test.mjs b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalDock.test.mjs index 253c9b27f8a..8e6c6f8bdf7 100644 --- a/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalDock.test.mjs +++ b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalDock.test.mjs @@ -31,6 +31,7 @@ beforeEach(() => { afterEach(() => { if (dockElement) { + terminalDock.unregisterTabNavigation(dockElement); terminalDock.unregisterResizeHandle(dockElement); dockElement = null; } @@ -85,6 +86,173 @@ function runPendingTimer() { timer.callback(); } +function createTabRegistration() { + const observers = []; + class Observer { + constructor(callback) { this.callback = callback; observers.push(this); } + observe() {} + disconnect() { this.disconnected = true; } + } + setGlobal("ResizeObserver", Observer); + setGlobal("MutationObserver", Observer); + setGlobal("getComputedStyle", () => ({ direction: "ltr" })); + const calls = []; + const scrolls = []; + const makeButton = direction => ({ + dataset: { tabScroll: direction }, + disabled: false, + closest: selector => selector === "[data-tab-scroll]" ? buttonByDirection[direction] : null, + toggleAttribute(name, value) { this.disabled = value; }, + setAttribute(name, value) { this[name] = value; }, + hasAttribute() { return this.disabled; }, + }); + const buttonByDirection = { "-1": makeButton("-1"), "1": makeButton("1") }; + const controls = { hidden: false, querySelectorAll: () => Object.values(buttonByDirection) }; + const wrapper = { clientWidth: 200, toggleAttribute() {} }; + const tabs = ["one", "two", "three"].map((id, index) => { + const group = { + dataset: { terminalId: id }, + isConnected: true, + scrollIntoView() {}, + classList: { add() {}, remove() {} }, + removeAttribute() { delete this.dataset.dropPosition; }, + getBoundingClientRect: () => ({ left: index * 100, right: index * 100 + 100, width: 100 }), + contains: element => element === group || element === tab, + querySelector: () => tab, + }; + const tab = { + closest: selector => selector === ".terminal-dock-tab-select" ? tab : selector === ".terminal-dock-tab" ? group : null, + scrollIntoView() {}, + focus() { document.activeElement = tab; }, + }; + return { group, tab }; + }); + const list = { + clientWidth: 144, + scrollWidth: 300, + querySelectorAll: () => tabs.map(t => t.group), + querySelector: () => tabs[0].tab, + contains: element => element === list || tabs.some(t => t.tab === element || t.group === element), + getBoundingClientRect: () => ({ left: 0, right: 144 }), + scrollBy: options => scrolls.push(options), + }; + document.activeElement = { closest: () => null }; + dockElement = Object.assign(new EventTarget(), { + isConnected: true, + inert: false, + querySelector: selector => ({ + ".terminal-dock-tablist": list, + ".terminal-dock-tab-scroll": controls, + ".terminal-dock-tabs": wrapper, + })[selector], + contains: element => list.contains(element), + }); + const emit = (type, target, properties = {}) => { + const event = new Event(type, { cancelable: true }); + Object.defineProperty(event, "target", { value: target }); + Object.assign(event, properties); + dockElement.dispatchEvent(event); + return event; + }; + terminalDock.registerTabNavigation(dockElement, { + invokeMethodAsync(...args) { calls.push(args); return Promise.resolve(); }, + }); + return { calls, scrolls, controls, wrapper, list, tabs, emit, observers, buttonByDirection }; +} + +test("overflow arrows remain visible and disable when tabs fit or the list is removed", () => { + const { controls, buttonByDirection, emit, scrolls, list, observers } = createTabRegistration(); + assert.equal(controls.hidden, false); + assert.equal(buttonByDirection["-1"].disabled, true); + assert.equal(buttonByDirection["-1"]["aria-disabled"], "true"); + assert.equal(buttonByDirection["1"].disabled, false); + assert.equal(buttonByDirection["1"]["aria-disabled"], "false"); + emit("click", buttonByDirection["1"]); + assert.deepEqual(scrolls, [{ left: 108, behavior: "instant" }]); + list.getBoundingClientRect = () => ({ left: 0, right: 500 }); + observers[0].callback(); + assert.equal(controls.hidden, false); + assert.equal(buttonByDirection["-1"].disabled, true); + assert.equal(buttonByDirection["1"].disabled, true); + const querySelector = dockElement.querySelector; + dockElement.querySelector = selector => selector === ".terminal-dock-tablist" ? null : querySelector(selector); + observers[1].callback(); + assert.equal(controls.hidden, false); + assert.equal(buttonByDirection["-1"].disabled, true); + assert.equal(buttonByDirection["1"].disabled, true); +}); + +test("local drag commits relative order, ignores foreign drops and cleans up edge scrolling", async () => { + const { emit, tabs, calls, observers } = createTabRegistration(); + const dataTransfer = { setData() {} }; + emit("drop", tabs[0].tab, { dataTransfer }); + assert.deepEqual(calls, []); + emit("dragstart", tabs[2].tab, { dataTransfer }); + assert.equal(dataTransfer.effectAllowed, "move"); + emit("dragover", tabs[0].tab, { dataTransfer, clientX: 5 }); + assert.equal(tabs[0].group.dataset.dropPosition, "before"); + emit("drop", tabs[0].tab, { dataTransfer }); + await Promise.resolve(); + assert.deepEqual(calls, [["ReorderTerminalAsync", "three", "one", false]]); + assert.equal(timers.size, 0); + emit("dragstart", tabs[0].tab, { dataTransfer }); + emit("dragover", tabs[2].tab, { dataTransfer, clientX: 140 }); + assert.equal(timers.size, 1); + terminalDock.unregisterTabNavigation(dockElement); + assert.equal(timers.size, 0); + assert.ok(observers.every(observer => observer.disconnected)); +}); + +test("keyboard reorder is confined to modified tab-header keys", async () => { + const { emit, tabs, calls } = createTabRegistration(); + emit("keydown", tabs[0].tab, { key: "ArrowRight", altKey: true, shiftKey: true }); + await Promise.resolve(); + assert.deepEqual(calls, [["ReorderTerminalAsync", "one", "two", true]]); + calls.length = 0; + emit("keydown", tabs[0].tab, { key: "ArrowLeft", altKey: true, shiftKey: true }); + emit("keydown", { closest: () => null }, { key: "ArrowRight", altKey: true, shiftKey: true }); + dockElement.inert = true; + emit("keydown", tabs[0].tab, { key: "ArrowRight", altKey: true, shiftKey: true }); + assert.deepEqual(calls, []); +}); + +test("stationary edge drag updates the drop target as tabs scroll underneath", async () => { + const { emit, tabs, calls, list } = createTabRegistration(); + const dataTransfer = { setData() {} }; + emit("dragstart", tabs[0].tab, { dataTransfer }); + emit("dragover", tabs[1].tab, { dataTransfer, clientX: 140 }); + assert.equal(tabs[1].group.dataset.dropPosition, "before"); + list.scrollBy = () => { + tabs[1].group.getBoundingClientRect = () => ({ left: 20, right: 120, width: 100 }); + tabs[2].group.getBoundingClientRect = () => ({ left: 120, right: 220, width: 100 }); + }; + const [id, timer] = timers.entries().next().value; + timers.delete(id); + timer.callback(); + assert.equal(tabs[2].group.dataset.dropPosition, "before"); + emit("drop", tabs[2].tab, { dataTransfer }); + await Promise.resolve(); + assert.deepEqual(calls, [["ReorderTerminalAsync", "one", "three", false]]); + assert.equal(timers.size, 0); +}); + +test("removal or collapse cancels a drag without reordering", () => { + const { emit, tabs, calls, observers } = createTabRegistration(); + const dataTransfer = { setData() {} }; + for (const collapse of [false, true]) { + tabs[0].group.isConnected = true; + emit("dragstart", tabs[0].tab, { dataTransfer }); + emit("dragover", tabs[1].tab, { dataTransfer, clientX: 140 }); + if (collapse) dockElement.inert = true; + else tabs[0].group.isConnected = false; + observers[1].callback(); + emit("drop", tabs[1].tab, { dataTransfer }); + assert.equal(timers.size, 0); + assert.equal(tabs[1].group.dataset.dropPosition, undefined); + } + assert.deepEqual(calls, []); +}); + test("drag and viewport changes share one throttled update", () => { const { grabber, invocations } = createRegistration(); 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..4f0308a2270 --- /dev/null +++ b/tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs @@ -0,0 +1,135 @@ +// 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"], + ["\\\\server\\share\\projects\\aspire", "\\\\server\\share\\\u2026\\aspire"], + ["\\\\server\\share\\projects\\aspire\\", "\\\\server\\share\\\u2026\\aspire\\"], + ["/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("UNC paths never abbreviate the server or share", () => { + const path = "\\\\server\\share\\projects\\aspire"; + const root = "\\\\server\\share\\"; + assert.equal(compactPath(path, value => value.length <= root.length + 1), `${root}\u2026`); + assert.equal(compactPath(path, value => value.length < root.length), ""); + assert.equal(compactPath("\\\\server\\share\\", value => value.length <= root.length - 1), ""); +}); + +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 1e7dda95357..43582268003 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, defaultDarkPalette, defaultLightPalette } = 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,48 @@ beforeEach(() => { ids = []; snapshots = []; serial = 0; - setGlobal("window", { isSecureContext: true }); + themeObservers = []; + mediaQueries = new Map(); + const storage = new Map(); + setGlobal("localStorage", { + getItem: key => storage.get(key) ?? null, + setItem: (key, value) => storage.set(key, value), + removeItem: key => storage.delete(key), + clear: () => storage.clear(), + }); + setGlobal("window", Object.assign(new EventTarget(), { 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: { dataset: { theme: "dark" } }, + 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 +108,30 @@ 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: [], + colorMode: options.colorMode, + colorModeCalls: [], + scrollbar: options.scrollbar, + scrollbarCalls: [], sizingCalls: [], primaryRequests: 0, focusCalls: 0, @@ -93,6 +151,14 @@ beforeEach(() => { this.readOnly = readOnly; this.readOnlyCalls.push(readOnly); }, + setScrollbar(scrollbar) { + this.scrollbar = scrollbar; + this.scrollbarCalls.push(scrollbar); + }, + setColorMode(colorMode) { + this.colorMode = colorMode; + this.colorModeCalls.push(colorMode); + }, focus() { this.focusCalls++; document.activeElement = this.element; }, clearSelection() { this.selectionClears++; @@ -179,12 +245,13 @@ function selectionEvent(attempt, overrides = {}) { } function mount({ visible = true, dotNetRef, options = {} } = {}) { - const element = { + const view = { style: { setProperty(name, value) { this[name] = value; } } }; + const element = Object.assign(new EventTarget(), { clientWidth: visible ? 800 : 0, clientHeight: visible ? 600 : 0, contains: value => value === element || value?.parentElement === element, - closest: () => null, - }; + closest: selector => selector === ".terminal-view" ? view : null, + }); const controls = []; const template = { firstElementChild: { cloneNode() { const control = selectionControl(); @@ -198,6 +265,7 @@ function mount({ visible = true, dotNetRef, options = {} } = {}) { focus() { document.activeElement = this; }, })); const footer = Object.assign(new EventTarget(), { + querySelector: () => null, querySelectorAll: () => footerControls, focus() { document.activeElement = this; }, }); @@ -209,9 +277,7 @@ function mount({ visible = true, dotNetRef, options = {} } = {}) { } }, { label: "Localized terminal input", ...options, viewId }, template, footer); ids.push(id); - return { - id, element, controls, footer, footerControls, viewId, - }; + return { id, element, view, controls, footer, footerControls, viewId }; } async function settle() { @@ -233,6 +299,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", [ @@ -276,6 +378,320 @@ for (const [name, ranges, text, visible] of [ }); } +for (const [theme, background] of [["light", "#d5d0df"], ["dark", "#312e3c"]]) { + test(`terminal mounts with the Aspire ${theme} palette and unchanged neutral and selection colors`, async () => { + document.documentElement.dataset.theme = theme; + terminal.setTerminalPalette(theme); + const { view } = mount(); + const attempt = attempts[0]; + assert.equal(attempt.options.colorMode, theme); + for (const [palette, defaults] of [ + [attempt.options.lightModePalette, defaultLightPalette], + [attempt.options.darkModePalette, defaultDarkPalette], + ]) { + const { ansi, background: paletteBackground, ...unchanged } = palette; + const { ansi: defaultAnsi, background: defaultBackground, ...defaultUnchanged } = defaults; + assert.deepEqual(unchanged, defaultUnchanged); + assert.equal(ansi.length, 16); + for (const index of [0, 7, 8, 15]) { + assert.equal(ansi[index], defaultAnsi[index]); + } + for (const color of [paletteBackground, ...ansi]) { + assert.match(color, /^#[0-9a-f]{6}$/); + } + } + assert.equal(view.style["--terminal-background"], background); + attempt.resolve(); + await settle(); + assert.equal(attempt.client.colorMode, theme); + }); +} + +test("chromatic ANSI text meets contrast targets on both Aspire backgrounds", () => { + // WCAG relative luminance uses linearized sRGB, not perceptual OKLCH lightness. + // https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html + const luminance = hex => { + const channels = hex.slice(1).match(/../g).map(channel => parseInt(channel, 16) / 255) + .map(channel => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4); + return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722; + }; + mount(); + for (const palette of [attempts[0].options.lightModePalette, attempts[0].options.darkModePalette]) { + const background = luminance(palette.background); + for (const index of [1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14]) { + const foreground = luminance(palette.ansi[index]); + const contrast = (Math.max(foreground, background) + 0.05) / (Math.min(foreground, background) + 0.05); + assert.ok(contrast >= (index < 8 ? 5 : 6), + `ANSI ${index} on ${palette.background} has contrast ${contrast}`); + } + } +}); + +for (const [preference, pageTheme] of [["dark", "light"], ["light", "dark"]]) { + test(`saved ${preference} palette overrides ${pageTheme} Dashboard on mount and theme changes`, async () => { + terminal.setTerminalPalette(preference); + assert.equal(localStorage.getItem("Aspire.TerminalPalette"), JSON.stringify(preference)); + assert.equal(terminal.getTerminalPalette(), preference); + document.documentElement.dataset.theme = pageTheme; + mount(); + const attempt = attempts[0]; + assert.equal(attempt.options.colorMode, preference); + attempt.resolve(); + await settle(); + for (const theme of ["light", "dark"]) { + document.documentElement.dataset.theme = theme; + themeObservers[0].callback(); + assert.equal(attempt.client.colorMode, preference); + } + }); +} + +test("palette override updates every view including pending and hidden mounts without disrupting input", async () => { + const first = mount(); + const pending = mount(); + const hidden = mount({ visible: false }); + attempts[0].resolve(); + await settle(); + const client = attempts[0].client; + const focus = client.focusCalls; + const selection = client.selection; + terminal.setTerminalPalette("light"); + attempts[1].resolve(); + await settle(); + assert.equal(client.colorMode, "light"); + assert.equal(attempts[1].client.colorMode, "light"); + assert.equal(hidden.view.style["--terminal-background"], attempts[0].options.lightModePalette.background); + assert.equal(client.focusCalls, focus); + assert.equal(client.selection, selection); + assert.equal(client.selectionClears, 0); + assert.deepEqual(client.sizingCalls, []); + assert.equal(attempts.length, 2); + terminal.setTerminalPalette("dark"); + assert.equal(client.colorMode, "dark"); + terminal.reconnectTerminal(first.id, "wss://dashboard/api/terminal?resource=app&replica=1"); + assert.equal(attempts[2].options.colorMode, "dark"); + assert.equal(document.documentElement.dataset.theme, "dark"); +}); + +test("cross-window storage changes and cleared preferences update palettes and stop after disposal", async () => { + const { id } = mount(); + attempts[0].resolve(); + await settle(); + const client = attempts[0].client; + const changed = (key, storageArea = localStorage) => { + const event = new Event("storage"); + Object.assign(event, { key, storageArea }); + window.dispatchEvent(event); + }; + localStorage.setItem("Aspire.TerminalPalette", '"light"'); + changed("another-setting"); + changed("Aspire.TerminalPalette", {}); + assert.equal(client.colorMode, "dark"); + changed("Aspire.TerminalPalette"); + assert.equal(client.colorMode, "light"); + localStorage.clear(); + changed(null); + assert.equal(client.colorMode, "dark"); + terminal.disposeTerminal(id); + const calls = [...client.colorModeCalls]; + localStorage.setItem("Aspire.TerminalPalette", '"light"'); + changed("Aspire.TerminalPalette"); + terminal.setTerminalPalette("dark"); + assert.deepEqual(client.colorModeCalls, calls); +}); + +for (const stored of [null, '"dashboard"']) { + test(`missing or former theme-following preference (${stored}) uses Dark independently of site theme`, async () => { + if (stored !== null) { + localStorage.setItem("Aspire.TerminalPalette", stored); + } + document.documentElement.dataset.theme = "light"; + const { id } = mount(); + attempts[0].resolve(); + await settle(); + assert.equal(terminal.getToolbarState(id).palette, "dark"); + assert.equal(attempts[0].client.colorMode, "dark"); + for (const theme of ["dark", "light"]) { + document.documentElement.dataset.theme = theme; + themeObservers[0].callback(); + assert.equal(attempts[0].client.colorMode, "dark"); + } + assert.equal(console.warn.mock.calls.length, 0); + }); +} + +test("unavailable or corrupt palette storage logs a warning and uses Dark", () => { + for (const value of ["invalid-json", '"unknown"', "null", "1"]) { + localStorage.setItem("Aspire.TerminalPalette", value); + assert.equal(terminal.getTerminalPalette(), "dark"); + } + mock.method(localStorage, "getItem", () => { throw new Error("Storage disabled"); }); + assert.equal(terminal.getTerminalPalette(), "dark"); + assert.equal(console.warn.mock.calls.length, 5); +}); + +test("failed or invalid palette writes do not change the mounted palette", async () => { + mount(); + attempts[0].resolve(); + await settle(); + assert.throws(() => terminal.setTerminalPalette("invalid"), TypeError); + assert.throws(() => terminal.setTerminalPalette("dashboard"), TypeError); + mock.method(localStorage, "setItem", () => { throw new Error("Storage disabled"); }); + assert.throws(() => terminal.setTerminalPalette("light"), /Storage disabled/); + assert.equal(attempts[0].client.colorMode, "dark"); +}); + +test("footer labels the asynchronously generated palette combobox and disconnects its observer", () => { + const { id, footer } = mount(); + const button = { setAttribute: mock.fn() }; + footer.querySelector = () => ({ + getAttribute: () => "Terminal palette", + querySelector: () => button, + }); + const observer = themeObservers.find(o => o.element === footer); + observer.callback(); + assert.deepEqual(button.setAttribute.mock.calls[0].arguments, ["aria-label", "Terminal palette"]); + terminal.disposeTerminal(id); + assert.equal(observer.disconnected, true); +}); + +test("footer palette changes update toolbar selections even for read-only and disconnected views", async () => { + const first = mount({ readOnly: true }); + const second = mount({ visible: false }); + terminal.setPaletteFromHost(first.id, "light"); + assert.equal(terminal.getToolbarState(first.id).palette, "light"); + assert.equal(terminal.getToolbarState(second.id).palette, "light"); + assert.equal(attempts.length, 1); + assert.deepEqual(attempts[0].client.sizingCalls, []); + assert.equal(localStorage.getItem("Aspire.TerminalPalette"), '"light"'); +}); + +test("footer palette save failure preserves selection, surfaces a dismissible error and allows retry", () => { + const { id } = mount(); + const setter = mock.method(localStorage, "setItem", () => { throw new Error("Storage disabled"); }); + terminal.setPaletteFromHost(id, "light"); + assert.equal(terminal.getToolbarState(id).palette, "dark"); + assert.equal(terminal.getToolbarState(id).error, "palette-failed"); + terminal.dismissError(id); + assert.equal(terminal.getToolbarState(id).error, null); + terminal.setPaletteFromHost(id, "light"); + setter.mock.restore(); + terminal.setPaletteFromHost(id, "light"); + assert.equal(terminal.getToolbarState(id).palette, "light"); + assert.equal(terminal.getToolbarState(id).error, null); + assert.equal(attempts.length, 1); +}); + +test("palette, theme and contrast changes replace the complete overlay without reconnecting", async () => { + const { id, view } = 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. + document.documentElement.dataset.theme = "light"; + themeObservers[0].callback(); + terminal.setTerminalPalette("light"); + attempt.resolve(); + await settle(); + assert.notEqual(attempt.client.scrollbar.render, initial.render); + assert.equal(attempt.options.colorMode, "dark"); + assert.equal(attempt.client.colorMode, "light"); + assert.equal(view.style["--terminal-background"], attempt.options.lightModePalette.background); + const focusCalls = attempt.client.focusCalls; + const selection = attempt.client.selection; + function paintedTrack() { + const painted = []; + const context = { + globalAlpha: 1, + save() {}, + restore() {}, + fillRect() { painted.push({ color: this.fillStyle, opacity: this.globalAlpha }); }, + }; + attempt.client.scrollbar.render({ + context, opacity: 1, + track: { left: 0, top: 0, width: 8, height: 40 }, + thumb: { left: 0, top: 0, width: 0, height: 0 }, + markers: [], + colors: { track: "#202020" }, + interaction: { focused: false, dragging: false }, + }); + return painted; + } + for (const [theme, palette] of [["dark", attempt.options.darkModePalette], ["light", attempt.options.lightModePalette]]) { + terminal.setTerminalPalette(theme); + document.documentElement.dataset.theme = theme; + themeObservers[0].callback(); + assert.equal(attempt.client.colorMode, theme); + assert.equal(view.style["--terminal-background"], palette.background); + assert.deepEqual(paintedTrack(), [{ color: theme === "dark" ? "#837f82" : "#848189", opacity: 0.35 }]); + } + 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(attempt.client.colorMode, "light"); + assert.deepEqual(paintedTrack(), [{ color: "rgb(100, 100, 100)", opacity: 1 }]); + } + mediaQueries.get("(forced-colors: active)").matches = false; + mediaQueries.get("(forced-colors: active)").dispatchEvent(new Event("change")); + assert.deepEqual(paintedTrack(), [{ color: "#848189", opacity: 1 }]); + assert.equal(attempts.length, 1); + assert.deepEqual(attempt.client.sizingCalls, []); + assert.equal(attempt.client.selectionClears, 0); + assert.equal(attempt.client.selection, selection); + assert.equal(attempt.client.focusCalls, focusCalls); + terminal.reconnectTerminal(id, "wss://dashboard/api/terminal?resource=app&replica=1"); + assert.equal(attempts[1].options.scrollbar, attempt.client.scrollbar); + assert.equal(attempts[1].options.colorMode, "light"); + assert.deepEqual(attempts[1].options.lightModePalette, attempt.options.lightModePalette); + assert.deepEqual(attempts[1].options.darkModePalette, attempt.options.darkModePalette); + attempts[1].resolve(); + await settle(); + attempts[1].close(1006); + retry(); + assert.equal(attempts[2].options.colorMode, "light"); +}); + +test("a hidden terminal mounts with the latest selected palette when revealed", () => { + const { element, view } = mount({ visible: false }); + document.documentElement.dataset.theme = "light"; + terminal.setTerminalPalette("light"); + themeObservers[0].callback(); + assert.equal(attempts.length, 0); + assert.equal(view.style["--terminal-background"], "#d5d0df"); + element.clientWidth = 800; + element.clientHeight = 600; + observers[0].callback(); + assert.equal(attempts[0].options.colorMode, "light"); +}); + +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; + const colorModeCalls = [...attempt.client.colorModeCalls]; + 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.deepEqual(attempt.client.colorModeCalls, colorModeCalls); + 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]) { @@ -488,12 +904,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(); @@ -502,7 +1009,10 @@ 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", + palette: "dark", fontPx: 13, fontControlsEnabled: true, sizeSelectEnabled: true, fitEnabled: true, canDecreaseFontSize: true, canIncreaseFontSize: true, @@ -515,6 +1025,41 @@ test("init returns an id while mount waits for its first connected frame", async assert.equal(attempts[0].options.readOnly, false); }); +test("web link detection requires modifier clicks and preserves default OSC 8 handling", () => { + mount(); + const links = attempts[0].options.links; + assert.equal(links.osc8, undefined); + assert.equal(links.detection.activation, "modifierClick"); + assert.equal(links.detection.rules.length, 1); + assert.equal(links.detection.rules[0].builtin, "url"); +}); + +for (const target of ["https://aspire.dev/docs?view=terminal#links", "http://localhost:5000/"]) { + test(`detected web link opens safely: ${target}`, () => { + mount(); + const opened = []; + window.open = (...args) => opened.push(args); + attempts[0].options.links.detection.rules[0].action({}, { + source: "detected", ruleId: "web", kind: "uri", text: target, target, + ranges: [{ row: 0, startColumn: 0, endColumn: target.length }], revision: 1, + }); + assert.deepEqual(opened, [[target, "_blank", "noopener,noreferrer"]]); + }); +} + +for (const target of ["javascript:alert(1)", "data:text/html,test", "file:///tmp/test", "/relative", "custom://host", "mailto:test@example.com"]) { + test(`detected web link rejects non-web destination: ${target}`, () => { + mount(); + const opened = []; + window.open = (...args) => opened.push(args); + assert.throws(() => attempts[0].options.links.detection.rules[0].action({}, { + source: "detected", ruleId: "web", kind: "uri", text: target, target, + ranges: [{ row: 0, startColumn: 0, endColumn: target.length }], revision: 1, + })); + assert.deepEqual(opened, []); + }); +} + test("opening a terminal focuses input after the first frame without taking primary", async () => { document.activeElement = { tagName: "BUTTON" }; mount(); @@ -1503,18 +2048,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.171.0"); + assert.equal(bundle.split(/\r?\n/, 1)[0], `// @hex1b/web-terminal ${version}; minified with Terser. See ../LICENSE.`); 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"); @@ -1527,39 +2072,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 13ff0661527..a62207c96b3 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Layout/TerminalDockTests.cs @@ -23,6 +23,74 @@ namespace Aspire.Dashboard.Components.Tests.Layout; [UseCulture("en-US")] public partial class TerminalDockTests : DashboardTestContext { + [Fact] + public async Task Reorder_PreservesSelectionViewersAndOrderAcrossSnapshots() + { + var updates = Channel.CreateUnbounded(); + TerminalSetupHelpers.SetupTerminalComponents(this, + TerminalSetupHelpers.CreateTerminalDashboardClient(terminalChannelProvider: () => updates)); + var cut = Render(); + await cut.InvokeAsync(cut.Instance.ToggleAsync); + await updates.Writer.WriteAsync(TerminalSetupHelpers.Snapshot("first", "second", "third")); + cut.WaitForAssertion(() => Assert.Equal(3, cut.FindComponents().Count)); + var views = cut.FindComponents().Select(view => view.Instance).ToArray(); + await cut.FindAll("[role=tab]")[1].ClickAsync(new()); + + await cut.Instance.ReorderTerminalAsync("first", "third", after: true); + Assert.Equal(["second", "third", "first"], cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim())); + Assert.Equal("second", cut.Find("[aria-selected=true]").TextContent.Trim()); + Assert.Equal([views[1], views[2], views[0]], cut.FindComponents().Select(view => view.Instance)); + + await cut.Instance.ReorderTerminalAsync("first", "second", after: false); + Assert.Equal(["first", "second", "third"], cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim())); + await cut.Instance.ReorderTerminalAsync("third", "first", after: false); + await updates.Writer.WriteAsync(TerminalSetupHelpers.Snapshot("first", "second", "third", "fourth")); + cut.WaitForAssertion(() => Assert.Equal(["third", "first", "second", "fourth"], + cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim()))); + Assert.Equal("second", cut.Find("[aria-selected=true]").TextContent.Trim()); + cut.WaitForAssertion(() => Assert.Equal(4, JSInterop.Invocations.Count(i => i.Identifier == "initTerminal"))); + + await updates.Writer.WriteAsync(TerminalSetupHelpers.Change(TerminalChangeType.Removed, "second")); + cut.WaitForAssertion(() => Assert.Equal("fourth", cut.Find("[aria-selected=true]").TextContent.Trim())); + await cut.Instance.ReorderTerminalAsync("second", "first", after: false); + await cut.Instance.ReorderTerminalAsync("first", "second", after: false); + await cut.Instance.ReorderTerminalAsync("first", "first", after: true); + Assert.Equal(["third", "first", "fourth"], cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim())); + } + + [Fact] + public async Task WorkloadMetadata_PreservesStaticTabTitlesWithoutExtraHeaderOrRemounting() + { + var updates = Channel.CreateUnbounded(); + TerminalSetupHelpers.SetupTerminalComponents(this, + TerminalSetupHelpers.CreateTerminalDashboardClient(terminalChannelProvider: () => updates)); + var cut = Render(); + 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(["first", "second"], cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim())); + Assert.Empty(cut.FindComponents()); + Assert.Equal(string.Empty, cut.Find(".terminal-dock-filler").TextContent); + await cut.FindAll(".terminal-dock-tab-select")[1].ClickAsync(new()); + Assert.Equal(["first", "second"], cut.FindAll("[role=tab]").Select(tab => tab.TextContent.Trim())); + Assert.Equal("second", cut.Find("[aria-selected=true]").TextContent.Trim()); + Assert.Empty(cut.FindComponents()); + Assert.Equal(string.Empty, cut.Find(".terminal-dock-filler").TextContent); + Assert.Equal(views, cut.FindComponents().Select(view => view.Instance)); + Assert.Equal(2, JSInterop.Invocations.Count(i => i.Identifier == "initTerminal")); + } + [Fact] public async Task EmptyDock_ShowsHeadingDocumentationAndKeyboardHint() { @@ -156,7 +224,7 @@ public async Task WatchUpdates_ReplaceSnapshotAndSelectAppHostTerminals() Assert.Equal("https://aka.ms/aspire/dashboard-terminal", helpLink.GetAttribute("href")); Assert.Equal("_blank", helpLink.GetAttribute("target")); Assert.Equal("noopener noreferrer", helpLink.GetAttribute("rel")); - Assert.Equal(["Open terminal in a new window", "Hide terminal panel (`)"], + Assert.Equal(["Scroll terminal tabs left", "Scroll terminal tabs right", "Open terminal in a new window", "Hide terminal panel (`)"], cut.FindAll(".terminal-dock-tabstrip fluent-button").Select(button => button.GetAttribute("aria-label"))); await updates.Writer.WriteAsync(TerminalSetupHelpers.Snapshot("first", "second")); cut.WaitForAssertion(() => Assert.Equal("first", cut.Find(".terminal-dock-tab.active").TextContent.Trim())); @@ -373,7 +441,7 @@ public async Task LastTabRemoved_EmptyDockCanReceiveAnotherAppHostTerminal() Assert.Empty(cut.FindAll("[role=tablist]")); Assert.Empty(cut.FindAll("[role=tabpanel]")); Assert.Equal("No docked terminals", cut.Find(".terminal-dock-panel-heading").TextContent); - Assert.Equal(["Open terminal in a new window", "Hide terminal panel (`)"], + Assert.Equal(["Scroll terminal tabs left", "Scroll terminal tabs right", "Open terminal in a new window", "Hide terminal panel (`)"], cut.FindAll(".terminal-dock-tabstrip fluent-button").Select(button => button.GetAttribute("aria-label"))); }); diff --git a/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs b/tests/Aspire.Dashboard.Components.Tests/Pages/TerminalWindowTests.cs index f9c1cb94ac6..cbcb0775124 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 = Render(); + var cut = Render(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 53633bbe5d5..ac34e299cb8 100644 --- a/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs +++ b/tests/Aspire.Dashboard.Components.Tests/Shared/TerminalSetupHelpers.cs @@ -69,6 +69,7 @@ public static void SetupTerminalView(BunitContext context, string pathBase = "") public static BunitJSModuleInterop SetupTerminalViewModule(BunitContext context, string modulePath) { + SetupTerminalTitle(context); context.Services.TryAddSingleton(); FluentUISetupHelpers.SetupFluentList(context); var module = context.JSInterop.SetupModule(modulePath); @@ -83,6 +84,13 @@ public static BunitJSModuleInterop SetupTerminalViewModule(BunitContext context, return module; } + public static void SetupTerminalTitle(BunitContext 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(BunitContext context, string pathBase = "") { var dock = context.JSInterop.SetupModule("./Components/Layout/TerminalDock.razor.js"); diff --git a/tests/Aspire.Dashboard.Tests/DashboardJsonSerializerContextTests.cs b/tests/Aspire.Dashboard.Tests/DashboardJsonSerializerContextTests.cs index ba284e06a81..3ba2deda320 100644 --- a/tests/Aspire.Dashboard.Tests/DashboardJsonSerializerContextTests.cs +++ b/tests/Aspire.Dashboard.Tests/DashboardJsonSerializerContextTests.cs @@ -57,6 +57,28 @@ public void TerminalInteropType_UsesGeneratedMetadata(Type interopType) Assert.NotNull(DashboardJsonSerializerContext.Default.GetTypeInfo(interopType)); } + [Fact] + public void TerminalToolbarState_RoundTripsMetadataAndPalette() + { + var value = new TerminalToolbarState + { + TerminalId = 1, + Generation = 2, + Connected = true, + Title = "build", + WorkingDirectory = "/work/app", + WorkingDirectoryUri = "file:///work/app", + ProgressState = "normal", + ProgressPercentage = 42, + Palette = "light" + }; + + var json = JsonSerializer.Serialize(value, DashboardJsonSerializerContext.Default.TerminalToolbarState); + var result = JsonSerializer.Deserialize(json, DashboardJsonSerializerContext.Default.TerminalToolbarState); + + Assert.Equal(value, result); + } + [Theory] [InlineData(typeof(ConsoleLogs.ConsoleLogsPageState))] [InlineData(typeof(Metrics.MetricsPageState))] diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalDockTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalDockTests.cs index efb2c70c815..e9c7103b9d6 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalDockTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalDockTests.cs @@ -18,6 +18,86 @@ namespace Aspire.Dashboard.Tests.Integration.Playwright; public sealed class TerminalDockTests(TerminalDockTests.TerminalDockDashboardServerFixture fixture) : PlaywrightTestsBase(fixture) { + [Fact] + [OuterloopTest("Resource-intensive Playwright browser test")] + public async Task Tabs_ReorderWithoutRemountingAndScrollWithoutWrapping() + { + await RunTestAsync(async page => + { + var (updates, _) = await OpenDockAsync(page); + var viewers = await page.Locator(".terminal-dock textarea").ElementHandlesAsync(); + await Tab(page, "third").DragToAsync(Tab(page, "first"), new() { TargetPosition = new() { X = 2, Y = 10 } }); + var tabs = page.Locator(".terminal-dock-tab-title"); + await Assertions.Expect(tabs).ToHaveTextAsync(["third", "first", "second"]); + await Assertions.Expect(Tab(page, "first")).ToHaveAttributeAsync("aria-selected", "true"); + await Tab(page, "third").FocusAsync(); + await page.Keyboard.PressAsync("Alt+Shift+ArrowRight"); + await Assertions.Expect(tabs).ToHaveTextAsync(["first", "third", "second"]); + await Assertions.Expect(Tab(page, "third")).ToBeFocusedAsync(); + foreach (var viewer in viewers) + { + Assert.True(await viewer.EvaluateAsync("element => element.isConnected")); + } + + for (var i = 0; i < 12; i++) + { + await updates.Writer.WriteAsync(Change(TerminalChangeType.Added, $"terminal-{i:00}")); + } + await page.SetViewportSizeAsync(700, 800); + await Assertions.Expect(tabs).ToHaveCountAsync(15); + var left = page.GetByRole(AriaRole.Button, new() { Name = "Scroll terminal tabs left", Exact = true }); + var right = page.GetByRole(AriaRole.Button, new() { Name = "Scroll terminal tabs right", Exact = true }); + await Assertions.Expect(right).ToBeEnabledAsync(); + await right.ClickAsync(); + await Assertions.Expect(left).ToBeEnabledAsync(); + await left.ClickAsync(); + await Assertions.Expect(left).ToBeDisabledAsync(); + Assert.True(await tabs.EvaluateAllAsync("tabs => tabs.every(t => Math.abs(t.getBoundingClientRect().top - tabs[0].getBoundingClientRect().top) < 1)")); + + await Tab(page, "first").FocusAsync(); + await page.Keyboard.PressAsync("End"); + await Assertions.Expect(Tab(page, "terminal-11")).ToBeFocusedAsync(); + await Assertions.Expect(right).ToBeDisabledAsync(); + await page.Keyboard.PressAsync("Home"); + await Assertions.Expect(Tab(page, "first")).ToBeFocusedAsync(); + await Assertions.Expect(left).ToBeDisabledAsync(); + await updates.Writer.WriteAsync(Change(TerminalChangeType.Activated, "terminal-11")); + await Assertions.Expect(Tab(page, "terminal-11")).ToHaveAttributeAsync("aria-selected", "true"); + await Assertions.Expect(right).ToBeDisabledAsync(); + + for (var i = 0; i < 12; i++) + { + await updates.Writer.WriteAsync(Change(TerminalChangeType.Removed, $"terminal-{i:00}")); + } + await page.SetViewportSizeAsync(1280, 900); + await Assertions.Expect(tabs).ToHaveCountAsync(3); + await Assertions.Expect(left).ToBeVisibleAsync(); + await Assertions.Expect(right).ToBeVisibleAsync(); + await Assertions.Expect(left).ToBeDisabledAsync(); + await Assertions.Expect(right).ToBeDisabledAsync(); + Assert.True(await page.Locator(".terminal-dock-tab-scroll").EvaluateAsync( + "element => element.nextElementSibling.classList.contains('terminal-dock-detach')")); + foreach (var id in new[] { "first", "second", "third" }) + { + await updates.Writer.WriteAsync(Change(TerminalChangeType.Removed, id)); + } + await Assertions.Expect(tabs).ToHaveCountAsync(0); + await Assertions.Expect(left).ToBeVisibleAsync(); + await Assertions.Expect(right).ToBeVisibleAsync(); + await Assertions.Expect(left).ToBeDisabledAsync(); + await Assertions.Expect(right).ToBeDisabledAsync(); + await page.SetViewportSizeAsync(900, 800); + for (var i = 0; i < 5; i++) + { + await updates.Writer.WriteAsync(Change(TerminalChangeType.Added, $"terminal-{i:00}")); + } + await Assertions.Expect(tabs).ToHaveCountAsync(5); + await Assertions.Expect(left).ToBeDisabledAsync(); + await Assertions.Expect(right).ToBeDisabledAsync(); + Assert.Empty(fixture.Client.ClosedTerminals); + }); + } + [Fact] [OuterloopTest("Resource-intensive Playwright browser test")] public async Task EmptyDock_ResizingPreservesContentInPriorityOrder() diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs index ce20752483d..34fb0d6ff0e 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/TerminalTests.cs @@ -55,7 +55,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("q"); await PasteAsync(input, "blocked-paste"); @@ -245,7 +246,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"); @@ -275,13 +276,183 @@ await RunTestAsync(async page => }); } + [Fact] + [OuterloopTest("Resource-intensive Playwright browser test")] + public async Task HeaderCopy_CopiesFullTruncatedValuesWithIndependentFeedback() + { + await RunTestAsync(async page => + { + await page.SetViewportSizeAsync(800, 700); + await using var connection = await OpenTerminalAsync(page); + await GrantClipboardPermissionsAsync(page); + var title = string.Concat(Enumerable.Repeat("Build
& \u03bb project ", 10)); + const string directory = "/workspace/team/shared projects/distributed applications/services/backend/build output/project"; + await WriteMetadataAsync(page, connection, title, directory); + + var titleButton = page.Locator(".terminal-title-button"); + var directoryButton = page.Locator(".terminal-directory"); + await page.WaitForFunctionAsync(""" + () => { + const title = document.querySelector('.terminal-title'); + const path = document.querySelector('.terminal-directory-display'); + return title.scrollWidth > title.clientWidth && path.textContent.includes('\u2026'); + } + """).DefaultTimeout(); + await Assertions.Expect(titleButton).ToHaveAttributeAsync("aria-label", $"Copy terminal title: {title}"); + await Assertions.Expect(directoryButton).ToHaveAttributeAsync("aria-label", $"Copy working directory: {directory}"); + + // Freeze browser time after the real terminal and metadata have settled. + // Overlapping copies must not reset the other button's independent 1500 ms timer. + await page.Clock.InstallAsync(new() { Time = "2026-01-01T00:00:00Z" }); + await page.Clock.PauseAtAsync("2026-01-01T00:00:01Z"); + await titleButton.ClickAsync(); + await ExpectClipboardTextAsync(page, title); + await Assertions.Expect(titleButton.Locator(".checkmark-icon")).ToBeVisibleAsync(); + await Assertions.Expect(directoryButton.Locator(".checkmark-icon")).ToBeHiddenAsync(); + + await page.Clock.RunForAsync(500); + await directoryButton.ClickAsync(); + await ExpectClipboardTextAsync(page, directory); + await Assertions.Expect(titleButton.Locator(".checkmark-icon")).ToBeVisibleAsync(); + await Assertions.Expect(directoryButton.Locator(".checkmark-icon")).ToBeVisibleAsync(); + + await page.Clock.RunForAsync(1000); + await Assertions.Expect(titleButton.Locator(".checkmark-icon")).ToBeHiddenAsync(); + await Assertions.Expect(titleButton.Locator(".copy-icon")).ToBeVisibleAsync(); + await Assertions.Expect(directoryButton.Locator(".checkmark-icon")).ToBeVisibleAsync(); + + await page.Clock.RunForAsync(500); + await Assertions.Expect(directoryButton.Locator(".checkmark-icon")).ToBeHiddenAsync(); + await Assertions.Expect(directoryButton.Locator(".copy-icon")).ToBeVisibleAsync(); + }); + } + + [Theory] + [InlineData("Enter")] + [InlineData("Space")] + [OuterloopTest("Resource-intensive Playwright browser test")] + public async Task HeaderCopy_KeyboardActivationPreservesFocusAndDoesNotForwardInput(string key) + { + await RunTestAsync(async page => + { + await using var connection = await OpenTerminalAsync(page); + await GrantClipboardPermissionsAsync(page); + var title = $"Keyboard {key} & \u03bb"; + const string directory = "/work/project with spaces/\u03bb"; + await WriteMetadataAsync(page, connection, title, directory); + var titleButton = page.Locator(".terminal-title-button"); + var directoryButton = page.Locator(".terminal-directory"); + + await titleButton.FocusAsync(); + await page.Keyboard.PressAsync(key); + await ExpectClipboardTextAsync(page, title); + await Assertions.Expect(titleButton).ToBeFocusedAsync(); + await page.Keyboard.PressAsync("Tab"); + await Assertions.Expect(directoryButton).ToBeFocusedAsync(); + await page.Keyboard.PressAsync(key); + await ExpectClipboardTextAsync(page, directory); + await Assertions.Expect(directoryButton).ToBeFocusedAsync(); + await page.Keyboard.PressAsync("Shift+Tab"); + await Assertions.Expect(titleButton).ToBeFocusedAsync(); + + var input = page.GetByRole(AriaRole.Textbox, new() { Name = "Interactive terminal input", Exact = true }); + await input.FocusAsync(); + await page.Keyboard.TypeAsync("x"); + Assert.Equal("x", await connection.ReadInputTextAsync(1, CancellationToken.None).DefaultTimeout()); + }); + } + + [Fact] + [OuterloopTest("Resource-intensive Playwright browser test")] + public async Task HeaderMetadata_LiveUpdatesAndFallbackPreserveFocusAndCopyCurrentValues() + { + await RunTestAsync(async page => + { + await using var connection = await OpenTerminalAsync(page); + await GrantClipboardPermissionsAsync(page); + var titleButton = page.Locator(".terminal-title-button"); + var directoryButton = page.Locator(".terminal-directory"); + var input = page.GetByRole(AriaRole.Textbox, new() { Name = "Interactive terminal input", Exact = true }); + var inputElement = await input.ElementHandleAsync(); + Assert.NotNull(inputElement); + await titleButton.ClickAsync(); + await ExpectClipboardTextAsync(page, ResourceName); + await Assertions.Expect(page.Locator(".terminal-title-container [role=status]")).ToHaveTextAsync("Copied!"); + + await input.FocusAsync(); + await WriteMetadataAsync(page, connection, "Building <app> & \u03bb", "/work/source files"); + connection.Workload.Write("\u001b]9;4;1;42\u0007"); + await Assertions.Expect(page.Locator(".terminal-icon")).ToHaveAttributeAsync("data-state", "normal"); + await Assertions.Expect(page.Locator(".terminal-icon")).ToHaveAttributeAsync("title", "42%"); + await Assertions.Expect(page.Locator(".terminal-icon [role=progressbar]")).ToHaveAttributeAsync("aria-valuenow", "42"); + await Assertions.Expect(input).ToBeFocusedAsync(); + await titleButton.ClickAsync(); + await ExpectClipboardTextAsync(page, "Building <app> & \u03bb"); + await directoryButton.ClickAsync(); + await ExpectClipboardTextAsync(page, "/work/source files"); + + await directoryButton.FocusAsync(); + await WriteMetadataAsync(page, connection, "Build failed", "/work/build output"); + connection.Workload.Write("\u001b]9;4;2;75\u0007"); + await Assertions.Expect(page.Locator(".terminal-icon")).ToHaveAttributeAsync("data-state", "error"); + await Assertions.Expect(page.Locator(".terminal-icon")).ToHaveAttributeAsync("title", "75%"); + await Assertions.Expect(page.Locator(".terminal-icon [role=progressbar]")).ToHaveAttributeAsync("aria-valuenow", "75"); + await Assertions.Expect(directoryButton).ToBeFocusedAsync(); + await page.Keyboard.PressAsync("Enter"); + await ExpectClipboardTextAsync(page, "/work/build output"); + await titleButton.ClickAsync(); + await ExpectClipboardTextAsync(page, "Build failed"); + + await input.FocusAsync(); + connection.Workload.Write("\u001b]2;\u0007\u001b]9;4;0\u0007"); + await Assertions.Expect(page.Locator(".terminal-title")).ToHaveTextAsync(ResourceName); + await Assertions.Expect(page.Locator(".terminal-icon")).ToHaveAttributeAsync("data-state", "none"); + await Assertions.Expect(page.Locator(".terminal-icon [role=progressbar]")).ToHaveCountAsync(0); + await Assertions.Expect(input).ToBeFocusedAsync(); + Assert.True(await inputElement.EvaluateAsync<bool>("element => element.isConnected")); + Assert.Equal(1, connection.ConnectionCount); + await titleButton.ClickAsync(); + await ExpectClipboardTextAsync(page, ResourceName); + await page.EvaluateAsync(""" + () => Object.defineProperty(navigator.clipboard, "writeText", { + configurable: true, + value: () => Promise.reject(new Error("Clipboard denied")) + }) + """); + await titleButton.ClickAsync(); + await Assertions.Expect(page.Locator(".terminal-title-container [role=status]")) + .ToHaveTextAsync("Could not copy to clipboard."); + }); + } + + private static Task GrantClipboardPermissionsAsync(IPage page) => + page.Context.GrantPermissionsAsync(["clipboard-read", "clipboard-write"], new() + { + Origin = new Uri(page.Url).GetLeftPart(UriPartial.Authority) + }); + + private static Task ExpectClipboardTextAsync(IPage page, string expected) => + AsyncTestHelpers.AssertIsTrueRetryAsync( + async () => await page.EvaluateAsync<string>("() => navigator.clipboard.readText()") == expected, + $"The browser clipboard should contain the full value '{expected}'."); + + private static async Task WriteMetadataAsync(IPage page, TestTerminalConnection connection, string title, string directory) + { + // OSC 2;title BEL and OSC 7;file:///percent-encoded/path BEL travel through + // the real producer, transport and browser client rather than mocked toolbar state. + var directoryUri = new Uri("file://" + directory).AbsoluteUri; + connection.Workload.Write($"\u001b]2;{title}\u0007\u001b]7;{directoryUri}\u0007"); + await Assertions.Expect(page.Locator(".terminal-title")).ToHaveTextAsync(title); + await Assertions.Expect(page.Locator(".terminal-metadata")).ToHaveAttributeAsync("data-directory", directory); + } + private async Task<TestTerminalConnection> OpenTerminalAsync(IPage page) { await fixture.TerminalResolver.DiscardPendingConnectionsAsync(); await page.GotoAsync($"/consolelogs/resource/{ResourceName}").DefaultTimeout(); var connection = await fixture.TerminalResolver.AcceptConnectionAsync(CancellationToken.None).DefaultTimeout(); await connection.WaitForPeerHandshakesAsync(CancellationToken.None).DefaultTimeout(); - await ExpectProducerDimensionsAsync(page); + await Assertions.Expect(page.GetByRole(AriaRole.Button, new() { Name = "Decrease font size", Exact = true })).ToBeEnabledAsync(); return connection; } @@ -357,7 +528,7 @@ private static Task<int> 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); @@ -378,7 +549,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/Aspire.Hosting.Tests/Terminals/Hex1bPtySocketTests.cs b/tests/Aspire.Hosting.Tests/Terminals/Hex1bPtySocketTests.cs index 081e00866ba..33344ddbbf0 100644 --- a/tests/Aspire.Hosting.Tests/Terminals/Hex1bPtySocketTests.cs +++ b/tests/Aspire.Hosting.Tests/Terminals/Hex1bPtySocketTests.cs @@ -20,20 +20,20 @@ public class Hex1bPtySocketTests [Theory] [InlineData(false)] [InlineData(true)] - public void Configure_OnUnix_LeavesEnvironmentAndFilesystemUnchanged(bool hasOverride) + public void CreateSocketPath_OnUnix_LeavesEnvironmentAndFilesystemUnchanged(bool hasOverride) { Assert.SkipUnless(!OperatingSystem.IsWindows(), "Unix PTYs do not use the Windows proxy socket."); RemoteExecutor.Invoke(static hasOverrideValue => { - var root = Directory.CreateTempSubdirectory(); + var root = CreateSocketTestDirectory(); try { var directory = Path.Combine(root.FullName, "custom"); var value = bool.Parse(hasOverrideValue) ? directory : null; Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, value); - Hex1bPtySocketHelper.Configure(); + Assert.Null(Hex1bPtySocketHelper.CreateSocketPath()); Assert.Equal(value, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); Assert.False(Directory.Exists(directory)); @@ -57,7 +57,7 @@ public void CreateTerminal_SecuresDirectoryBeforeDeferredStartup(TerminalPlaceme RemoteExecutor.Invoke(static async (placementValue, existingDirectoryValue) => { - var root = Directory.CreateTempSubdirectory(); + var root = CreateSocketTestDirectory(); try { var directory = Path.Combine(root.FullName, "custom"); @@ -66,9 +66,9 @@ public void CreateTerminal_SecuresDirectoryBeforeDeferredStartup(TerminalPlaceme SocketPermissionHelper.CreateDirectory(directory, repairExisting: false); } - // Normalize an existing override without confusing it with the terminal child's environment. - Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, - Path.Combine(directory, "..", "custom")); + // Normalize the socket path, not the caller's environment override. + var originalOverride = Path.Combine(directory, "..", "custom"); + Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, originalOverride); await using (var service = TestTerminalService.Create()) { await using var terminal = service.CreateTerminal(new TerminalLaunchOptions @@ -79,7 +79,7 @@ public void CreateTerminal_SecuresDirectoryBeforeDeferredStartup(TerminalPlaceme EnvironmentVariables = { [Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable] = Path.Combine(root.FullName, "child") } }); - Assert.Equal(directory, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + Assert.Equal(originalOverride, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); var security = new DirectoryInfo(directory).GetAccessControl(); Assert.True(security.AreAccessRulesProtected); using var identity = WindowsIdentity.GetCurrent(); @@ -89,10 +89,10 @@ public void CreateTerminal_SecuresDirectoryBeforeDeferredStartup(TerminalPlaceme Assert.Equal(AccessControlType.Allow, rule.AccessControlType); Assert.Equal(FileSystemRights.FullControl, rule.FileSystemRights); Assert.Equal(InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, rule.InheritanceFlags); - Assert.Empty(Directory.EnumerateFileSystemEntries(directory)); + Assert.Empty(Directory.EnumerateFileSystemEntries(Path.Combine(directory, "proxy"))); } - Assert.Equal(directory, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + Assert.Equal(originalOverride, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); Assert.True(Directory.Exists(directory)); } finally @@ -111,7 +111,7 @@ public void CreateTerminal_InvalidDirectory_FailsBeforeRegistration(bool sharedR RemoteExecutor.Invoke(static async sharedRootValue => { - var root = Directory.CreateTempSubdirectory(); + var root = CreateSocketTestDirectory(); try { Directory.CreateDirectory(Path.Combine(root.FullName, ".aspire")); @@ -146,7 +146,7 @@ public void CreateTerminal_PermissiveOverride_FailsWithoutChangingPermissions() RemoteExecutor.Invoke(static async () => { - var root = Directory.CreateTempSubdirectory(); + var root = CreateSocketTestDirectory(); try { var directory = Directory.CreateDirectory(Path.Combine(root.FullName, "custom")); @@ -179,6 +179,55 @@ public void CreateTerminal_PermissiveOverride_FailsWithoutChangingPermissions() }).Dispose(); } + [Fact] + public void CreateSocketPath_Default_DoesNotSetEnvironment() + { + Assert.SkipUnless(OperatingSystem.IsWindows(), "Windows PTY socket paths."); + + RemoteExecutor.Invoke(static () => + { + Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, null); + var socketPath = Hex1bPtySocketHelper.CreateSocketPath(); + + Assert.NotNull(socketPath); + Assert.Equal(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".aspire", "pty", "proxy"), + Path.GetDirectoryName(socketPath)); + Assert.False(Path.Exists(socketPath)); + Assert.Null(Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + }).Dispose(); + } + + [Fact] + public void CreateTerminal_OverlongSocketPath_FailsBeforeCreatingDirectory() + { + Assert.SkipUnless(OperatingSystem.IsWindows(), "Windows PTY socket paths."); + + RemoteExecutor.Invoke(static async () => + { + var root = CreateSocketTestDirectory(); + try + { + var directory = Path.Combine(root.FullName, new string('a', 108)); + Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, directory); + await using var service = TestTerminalService.Create(); + + Assert.Throws<ArgumentOutOfRangeException>(() => service.CreateTerminal(new TerminalLaunchOptions + { + Title = "Rejected terminal", + Executable = "not-started" + })); + + Assert.Empty(service.ListAll()); + Assert.False(Directory.Exists(directory)); + Assert.Equal(directory, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + } + finally + { + root.Delete(recursive: true); + } + }).Dispose(); + } + [Fact] [SupportedOSPlatform("windows")] public void Start_ConcurrentDeferredTerminals_UsePrivateSockets() @@ -187,16 +236,21 @@ public void Start_ConcurrentDeferredTerminals_UsePrivateSockets() RemoteExecutor.Invoke(static async () => { - var root = Directory.CreateTempSubdirectory(); + var root = CreateSocketTestDirectory(); try { - // Windows adds a 12-character temporary directory name. Under the runner's - // AppData\Local\Temp this leaves too little room for Hex1b's 47-character - // "hex1bpty-{32 hex digits}.socket" name. Move the allocated directory to the - // shorter profile path; MoveTo fails rather than reusing an existing directory. - root.MoveTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), root.Name)); var directory = Path.Combine(root.FullName, ".aspire", "pty"); _ = new UnixDomainSocketEndPoint(Path.Combine(directory, $"hex1bpty-{new string('0', 32)}.socket")); + var overrideDirectory = SocketPermissionHelper.CreateDirectory(directory, repairExisting: false); + var overrideSecurity = overrideDirectory.GetAccessControl(); + overrideSecurity.AddAccessRule(new FileSystemAccessRule( + new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), + FileSystemRights.FullControl, + InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, + PropagationFlags.None, + AccessControlType.Allow)); + overrideDirectory.SetAccessControl(overrideSecurity); + var originalPermissions = overrideDirectory.GetAccessControl().GetSecurityDescriptorSddlForm(AccessControlSections.All); Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, directory); await using (var service = TestTerminalService.Create()) { @@ -205,6 +259,7 @@ public void Start_ConcurrentDeferredTerminals_UsePrivateSockets() Title = "Dock", Executable = "cmd.exe", Arguments = ["/d", "/q", "/k", "echo dock-ready"], + EnvironmentVariables = { [Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable] = Path.Combine(root.FullName, "child") }, Placement = TerminalPlacement.Dock }); await using var prompt = service.CreateTerminal(new TerminalLaunchOptions @@ -215,18 +270,25 @@ public void Start_ConcurrentDeferredTerminals_UsePrivateSockets() Placement = TerminalPlacement.Dialog }); - Assert.Empty(Directory.EnumerateFileSystemEntries(directory)); + Assert.Equal(directory, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + // Deferred startup must use the snapshotted paths, not reread the environment. + var laterDirectory = Path.Combine(root.FullName, "later"); + Environment.SetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable, laterDirectory); + Assert.Empty(Directory.EnumerateFileSystemEntries(Path.Combine(directory, "proxy"))); await Task.WhenAll( dock.WaitForTextAsync("dock-ready"), prompt.WaitForTextAsync("prompt-ready")).DefaultTimeout(); - var sockets = Directory.GetFiles(directory, "*.socket"); + var sockets = Directory.GetFiles(Path.Combine(directory, "proxy"), "*.socket"); Assert.Equal(2, sockets.Length); + Assert.False(Directory.Exists(laterDirectory)); + Assert.False(Directory.Exists(Path.Combine(root.FullName, "child"))); + Assert.Equal(originalPermissions, overrideDirectory.GetAccessControl().GetSecurityDescriptorSddlForm(AccessControlSections.All)); using var identity = WindowsIdentity.GetCurrent(); var system = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); foreach (var socket in sockets) { - // The pinned Hex1b helper adds SYSTEM but must never grant other ordinary users access. + // The helper must never grant other ordinary users access. var rules = new FileInfo(socket).GetAccessControl() .GetAccessRules(true, true, typeof(SecurityIdentifier)).Cast<FileSystemAccessRule>().ToArray(); Assert.Contains(rules, rule => rule.IdentityReference.Equals(identity.User)); @@ -239,8 +301,8 @@ await Task.WhenAll( } } - Assert.Empty(Directory.EnumerateFileSystemEntries(directory)); - Assert.Equal(directory, Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); + Assert.Empty(Directory.EnumerateFileSystemEntries(Path.Combine(directory, "proxy"))); + Assert.Equal(Path.Combine(root.FullName, "later"), Environment.GetEnvironmentVariable(Hex1bPtySocketHelper.SocketDirectoryEnvironmentVariable)); } finally { @@ -248,4 +310,17 @@ await Task.WhenAll( } }).Dispose(); } + + private static DirectoryInfo CreateSocketTestDirectory() + { + var directory = Directory.CreateTempSubdirectory(); + if (OperatingSystem.IsWindows()) + { + // Leave room for "hex1bpty-{32 hex digits}.socket" within sockaddr_un. + // MoveTo fails rather than reusing an existing directory. + directory.MoveTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), directory.Name)); + } + + return directory; + } } diff --git a/tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs b/tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs index 4e6f83ab1a1..39c369ab477 100644 --- a/tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs +++ b/tests/Infrastructure.Tests/TestTriggerMap/TestTriggerMapTests.cs @@ -728,9 +728,12 @@ public void ExtensionJavaE2eDiscoveryInputsAreNotGitIgnored() } [Theory] + [InlineData("src/Aspire.Dashboard/Components/Controls/TerminalTitle.razor.js")] [InlineData("src/Aspire.Dashboard/Components/Layout/TerminalDock.razor.js")] [InlineData("src/Aspire.Dashboard/wwwroot/js/app-terminalwindow.js")] [InlineData("tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalWindow.test.mjs")] + [InlineData("tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalTitle.test.mjs")] + [InlineData("tests/Aspire.Dashboard.Components.Tests/JavaScript/TerminalDock.test.mjs")] public void DashboardTerminalScriptInputsSelectInfrastructureTests(string path) { var result = SelectWithRealMap(path); diff --git a/tests/Infrastructure.Tests/WorkflowScripts/DashboardTerminalScriptTests.cs b/tests/Infrastructure.Tests/WorkflowScripts/DashboardTerminalScriptTests.cs index 4ef11943998..4e313bfac2c 100644 --- a/tests/Infrastructure.Tests/WorkflowScripts/DashboardTerminalScriptTests.cs +++ b/tests/Infrastructure.Tests/WorkflowScripts/DashboardTerminalScriptTests.cs @@ -29,6 +29,27 @@ public async Task DetachedTerminalWindowsKeepDistinctNames() await RunScriptAsync("TerminalWindow.test.mjs"); } + [Fact] + [RequiresTools(["node"])] + public async Task TerminalTitleCompactionAndObserverCleanup() + { + await RunScriptAsync("TerminalTitle.test.mjs"); + } + + [Fact] + [RequiresTools(["node"])] + public async Task ClipboardFeedbackWaitsForWrite() + { + await RunScriptAsync("Clipboard.test.mjs"); + } + + [Fact] + [RequiresTools(["node"])] + public async Task TerminalDockNavigationAndCleanup() + { + await RunScriptAsync("TerminalDock.test.mjs"); + } + private async Task RunScriptAsync(string script) { using var command = new NodeCommand(output)