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
+
+
_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. *@
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
+
TerminalDefault terminal title when no resource name is available.
@@ -153,7 +191,7 @@
Terminal dimensions
- Fit
+ Fit terminal to available spaceOpen 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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
+
+ TerminalsTerminals
@@ -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 terminalReconnect 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":"AAAA,MAAM,WAAW,GAAG,sDAAsD,CAAC;AAC3E,MAAM,OAAO,GAAG,6FAA6F,CAAC;AAC9G,MAAM,iBAAiB,GAAG,wEAAwE,CAAC;AAEnG,mGAAmG;AACnG,MAAM,OAAO,YAAY;IACvB,KAAK,CAAqC;IAC1C,OAAO,CAAa;IACpB,QAAQ,GAA2B,IAAI,CAAC;IACxC,SAAS,GAAG,CAAC,CAAC;IACd,YAAY,GAAG,CAAC,CAAC;IACjB,iBAAiB,GAAG,CAAC,CAAC;IACtB,gBAAgB,GAAG,CAAC,CAAC;IACrB,YAAY,GAAkB,WAAW,CAAC;IAC1C,cAAc,GAAG,OAAO,CAAC;IACzB,YAAY,CAA0B;IACtC,iBAAiB,CAA4B;IAE7C,YAAY,IAAwC,EAAE,SAAqB,GAAG,EAAE,GAAE,CAAC;QACjF,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ;QACV,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACjF,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;QACvD,OAAO,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS;YAC3F,OAAO,EAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAC1D,CAAC;IAED,IAAI,SAAS;QACX,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QACtH,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1C,IAAI,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3E,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY;gBACpF,SAAS,EAAE,IAAI,CAAC,cAAc,KAAK,OAAO;gBAC1C,MAAM,EAAE,IAAI,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI;gBACxG,OAAO,EAAE,sBAAsB,EAAE,CAAC;QACtC,CAAC;QACD,OAAO,EAAE,GAAG,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,MAAM,KAAK,OAAO;YAC5D,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS;YAC/E,OAAO,EAAE,SAAS,CAAC,MAAM,KAAK,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,CAAC,OAA+B,EAAE,QAAgB;QACtD,IAAI,QAAQ,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAC7C,MAAM,kBAAkB,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC;QACrD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,OAAO,IAAI,CAAC,kBAAkB,IAAI,kBAAkB,KAAK,OAAO,CAAC,UAAU,CAAC;YAC7E,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACtB,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC;QAClC,IAAI,OAAO,IAAI,OAAO,EAAE,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;YAC9D,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,OAAO;gBACvE,OAAO,CAAC,SAAS,CAAC,SAAS,KAAK,OAAO,CAAC,kBAAkB;gBAC1D,OAAO,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,kBAAkB;gBAChG,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBACjD,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAC5B,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;gBAC9B,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QACD,IAAI,IAAI,CAAC,iBAAiB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,IAAI,CAAC,iBAAiB,CAAC;YACrC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,eAAe;QACb,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,WAAW,CAAC,KAAoB;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,IAAI;YACrE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAC;YACzC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;YACnF,MAAM,IAAI,KAAK,CAAC,iGAAiG,CAAC,CAAC;QACrH,CAAC;QACD,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,iBAAiB,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;QAC7D,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC;QAC7B,IAAI,CAAC,iBAAiB,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC;QAC7C,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,KAAoB,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,EAA6C;QAC7F,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC5E,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS;YAClF,UAAU,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,MAAM,CAAC,KAAoB;QACzB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC1B,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAC/C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YACxC,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC;YAC/B,IAAI,CAAC,iBAAiB,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;YACtC,IAAI,CAAC,OAAO,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,CAAC,KAAa,EAAE,QAAwB;QAC5C,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,UAAU,IAAI,KAAK,GAAG,UAAU;YAAE,MAAM,IAAI,UAAU,CAAC,8CAA8C,CAAC,CAAC;QACpJ,IAAI,QAAQ;YAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO;QAChC,IAAI,QAAQ;YAAE,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;;YAC5C,IAAI,CAAC,sBAAsB,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC;QAC5E,IAAI,CAAC,gBAAgB,GAAG,SAAS,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK;YAC7C,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9E,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC9B,IAAI,CAAC,gBAAgB,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC;QAC5C,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;QACnC,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvE,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAED,UAAU,CAAC,SAAS,GAAG,KAAK;QAC1B,IAAI,SAAS,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;YACnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,sBAAsB;QACpB,IAAI,CAAC,IAAI,CAAC,iBAAiB;YAAE,OAAO;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC;QACxC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;QACnC,+EAA+E;QAC/E,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3B,CAAC;IACD,UAAU,CAAC,KAAc,IAAU,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAE7D,IAAI;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QACjC,IAAI,SAAS,CAAC,MAAM,KAAK,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,IAAI,6BAA6B,CAAC,CAAC;QACtG,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC,CAAC;QAC3E,MAAM,SAAS,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC;QACtC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,EAAU,CAAC;QACrE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACjH,IAAI,CAAC,YAAY,GAAG,EAAE,SAAS,EAAE,kBAAkB,EAAE,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU;YACtG,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACnH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,WAAW,CAAC,KAAc;QACxB,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO;QAC/B,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;IAChC,CAAC;IAED,UAAU;QACR,IAAI,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,CAAC;QACjF,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;IACrC,CAAC;CACF","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