@swifttui/web 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/src/WebHostSceneRuntime.d.ts +38 -1
- package/dist/src/WebHostSceneRuntime.js +59 -1
- package/dist/src/WebHostSceneRuntime.js.map +1 -1
- package/dist/src/WebHostSurfaceTransport.d.ts +18 -1
- package/dist/src/WebHostSurfaceTransport.js +20 -1
- package/dist/src/WebHostSurfaceTransport.js.map +1 -1
- package/dist/src/WebSocketSceneBridge.d.ts +1 -0
- package/dist/src/WebSocketSceneBridge.js +4 -1
- package/dist/src/WebSocketSceneBridge.js.map +1 -1
- package/dist/src/wasi/BrowserWASIBridge.d.ts +1 -0
- package/dist/src/wasi/BrowserWASIBridge.js +4 -1
- package/dist/src/wasi/BrowserWASIBridge.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, WebHostANSIColors, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
2
|
-
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
2
|
+
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
3
3
|
import { SurfaceMetrics, WebHostSurfacePainter, WebHostSurfaceRendererKind, resolvedSurfaceBackground, resolvedSurfaceForeground } from "./src/SurfaceRenderer.js";
|
|
4
4
|
import { WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
5
|
-
import { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode } from "./src/WebHostSceneRuntime.js";
|
|
5
|
+
import { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode, coarsePointerQuery, coarsePrimaryPointer } from "./src/WebHostSceneRuntime.js";
|
|
6
6
|
import { WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
7
7
|
import { WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostVisibilityDocument, createWebHostApp } from "./src/WebHostApp.js";
|
|
8
8
|
import { DomSurfacePainter, DomSurfacePainterOptions } from "./src/DomSurfacePainter.js";
|
|
9
9
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
10
10
|
import { JSPIConstructors, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, classifyWasmEngineFamily, collectWasmEngineProbeSignals, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./src/wasi/WasmEngineCapabilities.js";
|
|
11
11
|
import { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink } from "./src/wasi/BrowserWASIBridge.js";
|
|
12
|
-
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, DomSurfacePainter, DomSurfacePainterOptions, JSPIConstructors, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, SUPPORTED_SURFACE_VERSION, StdIOPipe, SurfaceMetrics, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfacePainter, WebHostSurfaceRect, WebHostSurfaceRendererKind, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebHostVisibilityDocument, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
12
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, DomSurfacePainter, DomSurfacePainterOptions, JSPIConstructors, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, SUPPORTED_SURFACE_VERSION, StdIOPipe, SurfaceMetrics, WasmEngineCapabilities, WasmEngineFamily, WasmEngineProbeSignals, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfacePainter, WebHostSurfaceRect, WebHostSurfaceRendererKind, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebHostVisibilityDocument, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, classifyWasmEngineFamily, coarsePointerQuery, coarsePrimaryPointer, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
2
2
|
import { classifyWasmEngineFamily, collectWasmEngineProbeSignals, jspiConstructors, mainThreadStackProfileEnvironmentDefaults, resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./src/wasi/WasmEngineCapabilities.js";
|
|
3
3
|
import { applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
4
|
-
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
4
|
+
import { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId } from "./src/WebHostSurfaceTransport.js";
|
|
5
5
|
import { BrowserWASIBridge } from "./src/wasi/BrowserWASIBridge.js";
|
|
6
6
|
import { WebSocketSceneBridge, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
7
7
|
import { loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
8
8
|
import { resolvedSurfaceBackground, resolvedSurfaceForeground } from "./src/SurfaceRenderer.js";
|
|
9
9
|
import { DomSurfacePainter } from "./src/DomSurfacePainter.js";
|
|
10
|
-
import { WebHostSceneRuntime } from "./src/WebHostSceneRuntime.js";
|
|
10
|
+
import { WebHostSceneRuntime, coarsePointerQuery, coarsePrimaryPointer } from "./src/WebHostSceneRuntime.js";
|
|
11
11
|
import { createWebHostApp } from "./src/WebHostApp.js";
|
|
12
|
-
export { BrowserWASIBridge, DomSurfacePainter, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, classifyWasmEngineFamily, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
12
|
+
export { BrowserWASIBridge, DomSurfacePainter, MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, classifyWasmEngineFamily, coarsePointerQuery, coarsePrimaryPointer, collectWasmEngineProbeSignals, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, encodeWebHostTerminalRenderStyleBase64, isWebHostImageRecoveryId, jspiConstructors, loadWebHostSceneManifest, mainThreadStackProfileEnvironmentDefaults, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWasmEngineCapabilities, resolveWebHostTerminalRenderStyle, resolvedSurfaceBackground, resolvedSurfaceForeground, stackProfileEnvironmentDefaults, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
@@ -10,6 +10,12 @@ interface WebHostSceneBridge {
|
|
|
10
10
|
sendInput(chunk: Uint8Array): void;
|
|
11
11
|
/** Optional for compatibility with custom bridges predating image recovery. */
|
|
12
12
|
requestImagePayloads?: WebHostImagePayloadRequestHandler;
|
|
13
|
+
/**
|
|
14
|
+
* Declares whether this client scrolls by dragging content directly.
|
|
15
|
+
* Optional for compatibility with custom bridges predating the record; an
|
|
16
|
+
* app that never receives it keeps the desktop paradigm.
|
|
17
|
+
*/
|
|
18
|
+
updatePointerCapabilities?(supportsScrollPanning: boolean): void;
|
|
13
19
|
dispose(): void;
|
|
14
20
|
}
|
|
15
21
|
interface WebHostSceneRuntimeOptions {
|
|
@@ -67,6 +73,18 @@ interface WebHostSceneRuntimeOptions {
|
|
|
67
73
|
renderer?: WebHostSurfaceRendererKind;
|
|
68
74
|
}
|
|
69
75
|
type WheelMode = "capture" | "chain" | "passive";
|
|
76
|
+
/**
|
|
77
|
+
* The media query for "the primary pointing device is coarse", i.e. a finger.
|
|
78
|
+
* Deliberately `pointer` and not `any-pointer`: a touch-capable laptop has a
|
|
79
|
+
* coarse pointer *available* but is driven by a trackpad, and it should get
|
|
80
|
+
* the desktop paradigm.
|
|
81
|
+
*
|
|
82
|
+
* Returns `undefined` where `matchMedia` is unavailable (SSR, older embedding
|
|
83
|
+
* hosts, some test environments), which callers read as "desktop".
|
|
84
|
+
*/
|
|
85
|
+
declare function coarsePointerQuery(): MediaQueryList | undefined;
|
|
86
|
+
/** Whether the primary pointing device is a finger rather than a pointer. */
|
|
87
|
+
declare function coarsePrimaryPointer(): boolean;
|
|
70
88
|
/**
|
|
71
89
|
* Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM
|
|
72
90
|
* mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy
|
|
@@ -108,6 +126,14 @@ declare class WebHostSceneRuntime {
|
|
|
108
126
|
private documentVisible;
|
|
109
127
|
private runtimeSuspended;
|
|
110
128
|
private readonly suspendWhenHidden;
|
|
129
|
+
/**
|
|
130
|
+
* Seeded to `false` rather than left unset because absence of the record
|
|
131
|
+
* already means the desktop paradigm on the Swift side: a mouse-driven
|
|
132
|
+
* client therefore says nothing, and only a client that actually pans by
|
|
133
|
+
* dragging puts a record on the wire.
|
|
134
|
+
*/
|
|
135
|
+
private lastSentPointerCapabilities;
|
|
136
|
+
private detachPointerParadigmObserver?;
|
|
111
137
|
constructor(options: WebHostSceneRuntimeOptions);
|
|
112
138
|
mount(): Promise<void>;
|
|
113
139
|
setVisible(visible: boolean): void;
|
|
@@ -133,6 +159,17 @@ declare class WebHostSceneRuntime {
|
|
|
133
159
|
writeClipboard(text: string): Promise<void>;
|
|
134
160
|
sendInput(chunk: Uint8Array): void;
|
|
135
161
|
dispose(): void;
|
|
162
|
+
/**
|
|
163
|
+
* Tells the app whether this client scrolls by dragging content directly.
|
|
164
|
+
*
|
|
165
|
+
* The app cannot see the browsing device, and one page bundle serves both a
|
|
166
|
+
* phone and a desktop, so the paradigm has to be declared from here. It is
|
|
167
|
+
* also not fixed for the session: a tablet can be docked to a mouse, so the
|
|
168
|
+
* media query is watched and every real pointer press refines the answer
|
|
169
|
+
* from `pointerType`. Only changes are sent.
|
|
170
|
+
*/
|
|
171
|
+
private sendPointerCapabilitiesIfChanged;
|
|
172
|
+
private installPointerParadigmObserver;
|
|
136
173
|
private presentSurface;
|
|
137
174
|
/**
|
|
138
175
|
* The current frame's preferred grid size in cells, when the app published
|
|
@@ -176,5 +213,5 @@ declare class WebHostSceneRuntime {
|
|
|
176
213
|
private rawCellLocation;
|
|
177
214
|
}
|
|
178
215
|
//#endregion
|
|
179
|
-
export { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode };
|
|
216
|
+
export { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode, coarsePointerQuery, coarsePrimaryPointer };
|
|
180
217
|
//# sourceMappingURL=WebHostSceneRuntime.d.ts.map
|
|
@@ -17,6 +17,27 @@ function legacyWheelMode(captureWheelInput) {
|
|
|
17
17
|
return captureWheelInput ? "capture" : "passive";
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
+
* The media query for "the primary pointing device is coarse", i.e. a finger.
|
|
21
|
+
* Deliberately `pointer` and not `any-pointer`: a touch-capable laptop has a
|
|
22
|
+
* coarse pointer *available* but is driven by a trackpad, and it should get
|
|
23
|
+
* the desktop paradigm.
|
|
24
|
+
*
|
|
25
|
+
* Returns `undefined` where `matchMedia` is unavailable (SSR, older embedding
|
|
26
|
+
* hosts, some test environments), which callers read as "desktop".
|
|
27
|
+
*/
|
|
28
|
+
function coarsePointerQuery() {
|
|
29
|
+
if (typeof globalThis.matchMedia !== "function") return;
|
|
30
|
+
try {
|
|
31
|
+
return globalThis.matchMedia("(pointer: coarse)");
|
|
32
|
+
} catch {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Whether the primary pointing device is a finger rather than a pointer. */
|
|
37
|
+
function coarsePrimaryPointer() {
|
|
38
|
+
return coarsePointerQuery()?.matches ?? false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
20
41
|
* Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM
|
|
21
42
|
* mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy
|
|
22
43
|
* responsibilities to focused collaborators — {@link CanvasSurfacePainter} for
|
|
@@ -57,6 +78,14 @@ var WebHostSceneRuntime = class {
|
|
|
57
78
|
documentVisible = true;
|
|
58
79
|
runtimeSuspended = false;
|
|
59
80
|
suspendWhenHidden;
|
|
81
|
+
/**
|
|
82
|
+
* Seeded to `false` rather than left unset because absence of the record
|
|
83
|
+
* already means the desktop paradigm on the Swift side: a mouse-driven
|
|
84
|
+
* client therefore says nothing, and only a client that actually pans by
|
|
85
|
+
* dragging puts a record on the wire.
|
|
86
|
+
*/
|
|
87
|
+
lastSentPointerCapabilities = false;
|
|
88
|
+
detachPointerParadigmObserver;
|
|
60
89
|
constructor(options) {
|
|
61
90
|
this.descriptor = options.descriptor;
|
|
62
91
|
this.currentStyle = normalizeWebHostTerminalStyle(options.style);
|
|
@@ -114,6 +143,8 @@ var WebHostSceneRuntime = class {
|
|
|
114
143
|
writeError: (text) => this.writeOutput(text)
|
|
115
144
|
});
|
|
116
145
|
this.applyStyle(this.currentStyle);
|
|
146
|
+
this.installPointerParadigmObserver();
|
|
147
|
+
this.sendPointerCapabilitiesIfChanged(coarsePrimaryPointer());
|
|
117
148
|
this.measureCells();
|
|
118
149
|
this.resizeToMount();
|
|
119
150
|
this.draw();
|
|
@@ -193,9 +224,35 @@ var WebHostSceneRuntime = class {
|
|
|
193
224
|
}
|
|
194
225
|
dispose() {
|
|
195
226
|
this.detachInputHandlers?.();
|
|
227
|
+
this.detachPointerParadigmObserver?.();
|
|
196
228
|
this.resizeObserver?.disconnect();
|
|
197
229
|
this.element.remove();
|
|
198
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Tells the app whether this client scrolls by dragging content directly.
|
|
233
|
+
*
|
|
234
|
+
* The app cannot see the browsing device, and one page bundle serves both a
|
|
235
|
+
* phone and a desktop, so the paradigm has to be declared from here. It is
|
|
236
|
+
* also not fixed for the session: a tablet can be docked to a mouse, so the
|
|
237
|
+
* media query is watched and every real pointer press refines the answer
|
|
238
|
+
* from `pointerType`. Only changes are sent.
|
|
239
|
+
*/
|
|
240
|
+
sendPointerCapabilitiesIfChanged(supportsScrollPanning) {
|
|
241
|
+
if (this.lastSentPointerCapabilities === supportsScrollPanning) return;
|
|
242
|
+
this.lastSentPointerCapabilities = supportsScrollPanning;
|
|
243
|
+
this.bridge?.updatePointerCapabilities?.(supportsScrollPanning);
|
|
244
|
+
}
|
|
245
|
+
installPointerParadigmObserver() {
|
|
246
|
+
const query = coarsePointerQuery();
|
|
247
|
+
if (!query?.addEventListener) return;
|
|
248
|
+
const handleChange = (event) => {
|
|
249
|
+
this.sendPointerCapabilitiesIfChanged(event.matches);
|
|
250
|
+
};
|
|
251
|
+
query.addEventListener("change", handleChange);
|
|
252
|
+
this.detachPointerParadigmObserver = () => {
|
|
253
|
+
query.removeEventListener?.("change", handleChange);
|
|
254
|
+
};
|
|
255
|
+
}
|
|
199
256
|
presentSurface(frame, recoveredImagePayloadIds) {
|
|
200
257
|
const previousFrame = this.currentFrame;
|
|
201
258
|
this.currentFrame = frame;
|
|
@@ -296,6 +353,7 @@ var WebHostSceneRuntime = class {
|
|
|
296
353
|
event.preventDefault();
|
|
297
354
|
};
|
|
298
355
|
const handlePointerDown = (event) => {
|
|
356
|
+
if (event.pointerType === "touch" || event.pointerType === "mouse") this.sendPointerCapabilitiesIfChanged(event.pointerType === "touch");
|
|
299
357
|
if (this.allowsNativeTextSelection(event)) return;
|
|
300
358
|
const location = this.cellLocation(event);
|
|
301
359
|
if (!location) return;
|
|
@@ -458,6 +516,6 @@ var WebHostSceneRuntime = class {
|
|
|
458
516
|
}
|
|
459
517
|
};
|
|
460
518
|
//#endregion
|
|
461
|
-
export { WebHostSceneRuntime };
|
|
519
|
+
export { WebHostSceneRuntime, coarsePointerQuery, coarsePrimaryPointer };
|
|
462
520
|
|
|
463
521
|
//# sourceMappingURL=WebHostSceneRuntime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebHostSceneRuntime.js","names":[],"sources":["../../src/WebHostSceneRuntime.ts"],"sourcesContent":["import {\n applyWebHostTerminalStyle,\n normalizeWebHostTerminalStyle,\n type ResolvedWebHostTerminalStyle,\n type WebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport {\n CanvasSurfacePainter,\n fontForStyle,\n type CanvasSurfaceMetrics,\n} from \"./CanvasSurfacePainter.ts\";\nimport { DomSurfacePainter } from \"./DomSurfacePainter.ts\";\nimport type { WebHostSurfaceRendererKind } from \"./SurfaceRenderer.ts\";\nimport {\n InputEventEncoder,\n type CellLocation,\n type PointerButton,\n} from \"./InputEventEncoder.ts\";\nimport {\n cellLocationForEvent,\n linkTargetAt,\n rawCellLocationForEvent,\n wheelTargetCanScroll,\n type PointerGeometryMetrics,\n} from \"./PointerGeometry.ts\";\nimport { AccessibilityTreeMounter } from \"./AccessibilityTree.ts\";\nimport { normalizeSemantics } from \"./normalizeWireTokens.ts\";\nimport {\n type WebHostFocusPresentation,\n type WebHostFrameDiagnosticRecord,\n type WebHostImagePayloadRequestHandler,\n type WebHostOutputSink,\n type WebHostRuntimeIssue,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostSceneDescriptor } from \"./WebHostSceneManifest.ts\";\n\nexport interface WebHostSceneBridge {\n bindOutput(sink: WebHostOutputSink): void;\n resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;\n updateRenderStyle(style: WebHostTerminalStyle): void;\n sendInput(chunk: Uint8Array): void;\n /** Optional for compatibility with custom bridges predating image recovery. */\n requestImagePayloads?: WebHostImagePayloadRequestHandler;\n dispose(): void;\n}\n\nexport interface WebHostSceneRuntimeOptions {\n mount: HTMLElement;\n descriptor: WebHostSceneDescriptor;\n style: WebHostTerminalStyle;\n bridge?: WebHostSceneBridge;\n onInput(chunk: Uint8Array): void;\n onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n synchronizeAccessibilityFocus?: boolean;\n /**\n * How the embedded view treats mouse-wheel input.\n * - `\"chain\"` (default): forward the wheel only while a scrollable region\n * under the pointer can still scroll in that direction; otherwise let it\n * fall through so the page (or parent iframe) scrolls — iframe-like nested\n * scrolling. A scene with no `ScrollView` never traps the wheel. Uses the\n * `scrollRegions` the app publishes in its frames.\n * - `\"capture\"`: always forward the wheel to the app while the pointer is over\n * the surface (and `preventDefault` page scroll). Best for full-screen apps\n * where there is no page to scroll past.\n * - `\"passive\"`: never capture; the page always scrolls.\n *\n * Takes precedence over the legacy `captureWheelInput` flag.\n */\n wheelMode?: WheelMode;\n /**\n * Legacy boolean wheel gate. `true` → `\"capture\"`, `false` → `\"passive\"`.\n * Prefer `wheelMode`. Ignored when `wheelMode` is set. When neither is set the\n * mode defaults to `\"chain\"`.\n */\n captureWheelInput?: boolean;\n /**\n * Called when the user clicks a hyperlink cell (a click is a pointer-down\n * and pointer-up over the same link target). When unset, `http(s)` targets\n * open in a new tab via `window.open(url, \"_blank\", \"noopener,noreferrer\")`\n * and other schemes are ignored. Mirrors the Android host's tap-to-open.\n */\n onOpenHyperlink?: (url: string) => void;\n /**\n * Whether to suspend the scene's app while it cannot be seen — when the\n * scene is switched to the background (`setVisible(false)`) or the whole\n * document is hidden (`setDocumentVisible(false)`). Suspension parks the\n * app's run loop and freezes its monotonic clock, so a hidden scene costs\n * no CPU and resumes exactly where it left off. Defaults to `true`; set\n * `false` to let background scenes keep running (pre-suspension behavior).\n */\n suspendWhenHidden?: boolean;\n /**\n * Which surface presenter draws the scene's frames. `\"canvas\"` (default)\n * paints onto a 2D `<canvas>`; `\"dom\"` renders cells as absolutely\n * positioned text elements — native font rendering and, uniquely, real\n * text selection: hold Alt/Option and drag to select instead of sending\n * pointer input to the app. See {@link WebHostSurfaceRendererKind}.\n */\n renderer?: WebHostSurfaceRendererKind;\n}\n\nexport type WheelMode = \"capture\" | \"chain\" | \"passive\";\n\n/**\n * Resolves the legacy `captureWheelInput` flag to a {@link WheelMode}. When the\n * flag is unset the mode defaults to `\"chain\"`, so embeds never trap a visitor\n * who is merely scrolling past the view; `true` maps to `\"capture\"` and `false`\n * to `\"passive\"` to preserve the old boolean behavior.\n */\nfunction legacyWheelMode(captureWheelInput: boolean | undefined): WheelMode {\n if (captureWheelInput === undefined) {\n return \"chain\";\n }\n return captureWheelInput ? \"capture\" : \"passive\";\n}\n\n/**\n * Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM\n * mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy\n * responsibilities to focused collaborators — {@link CanvasSurfacePainter} for\n * canvas drawing, {@link InputEventEncoder} for wire-message encoding, and the\n * {@link PointerGeometry} helpers for pixel→cell hit-testing and wheel chaining.\n */\nexport class WebHostSceneRuntime {\n readonly descriptor: WebHostSceneDescriptor;\n readonly element: HTMLElement;\n readonly terminalMount: HTMLElement;\n\n private readonly bridge?: WebHostSceneBridge;\n private readonly onInput: (chunk: Uint8Array) => void;\n private readonly onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n private readonly synchronizeAccessibilityFocus: boolean;\n private readonly wheelMode: WheelMode;\n private readonly rendererKind: WebHostSurfaceRendererKind;\n private readonly painter: CanvasSurfacePainter | DomSurfacePainter;\n private readonly inputEncoder = new InputEventEncoder();\n private currentStyle: ResolvedWebHostTerminalStyle;\n private canvas?: HTMLCanvasElement;\n private domSurfaceRoot?: HTMLElement;\n private lastDomSurfaceSize?: { width: number; height: number };\n private accessibilityTree?: AccessibilityTreeMounter;\n private diagnosticText?: HTMLElement;\n private resizeObserver?: ResizeObserver;\n private detachInputHandlers?: () => void;\n private currentFrame?: WebHostSurfaceFrame;\n private columns = 80;\n private rows = 24;\n private cellWidth = 8;\n private cellHeight = 18;\n private activePointerButton: PointerButton = \"primary\";\n private hasCapturedPointer = false;\n private readonly onOpenHyperlink?: (url: string) => void;\n private pointerDownLinkTarget?: string;\n private lastSentResize?: {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n };\n private isVisible = false;\n private documentVisible = true;\n private runtimeSuspended = false;\n private readonly suspendWhenHidden: boolean;\n\n constructor(options: WebHostSceneRuntimeOptions) {\n this.descriptor = options.descriptor;\n this.currentStyle = normalizeWebHostTerminalStyle(options.style);\n this.bridge = options.bridge;\n this.onInput = options.onInput;\n this.onFrameDiagnostic = options.onFrameDiagnostic;\n this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;\n this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);\n this.rendererKind = options.renderer ?? \"canvas\";\n const onImagePayloadMiss = (\n ids: readonly string[]\n ): readonly string[] | void => {\n return this.bridge?.requestImagePayloads?.(ids);\n };\n this.painter = this.rendererKind === \"dom\"\n ? new DomSurfacePainter({ onImagePayloadMiss })\n : new CanvasSurfacePainter({ onImagePayloadMiss });\n this.onOpenHyperlink = options.onOpenHyperlink;\n this.suspendWhenHidden = options.suspendWhenHidden ?? true;\n this.element = document.createElement(\"section\");\n this.element.className = \"webhost-scene\";\n this.element.dataset.sceneId = options.descriptor.id;\n this.element.hidden = true;\n\n const header = document.createElement(\"div\");\n header.className = \"webhost-scene__header\";\n header.textContent = options.descriptor.title ?? options.descriptor.id;\n\n this.terminalMount = document.createElement(\"div\");\n this.terminalMount.className = \"webhost-scene__terminal\";\n this.terminalMount.tabIndex = 0;\n\n this.element.append(header, this.terminalMount);\n options.mount.appendChild(this.element);\n this.applyVisibility();\n }\n\n async mount(): Promise<void> {\n if (this.surfaceElement) {\n return;\n }\n\n if (this.painter instanceof DomSurfacePainter) {\n const surfaceRoot = document.createElement(\"div\");\n surfaceRoot.className = \"webhost-scene__surface webhost-scene__surface--dom\";\n surfaceRoot.setAttribute(\"aria-hidden\", \"true\");\n this.domSurfaceRoot = surfaceRoot;\n this.painter.attach(surfaceRoot);\n } else {\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"webhost-scene__surface\";\n canvas.setAttribute(\"aria-hidden\", \"true\");\n this.canvas = canvas;\n this.painter.attach(canvas, () => this.draw());\n }\n this.accessibilityTree = new AccessibilityTreeMounter();\n this.terminalMount.replaceChildren(\n this.surfaceElement as HTMLElement,\n this.accessibilityTree.element,\n this.accessibilityTree.announcerElement\n );\n this.installInputHandlers();\n this.installResizeObserver();\n\n this.bridge?.bindOutput({\n presentSurface: (frame, recoveredImagePayloadIds) =>\n this.presentSurface(frame, recoveredImagePayloadIds),\n writeClipboard: (text) => this.writeClipboard(text),\n notifyRuntimeIssue: (issue) => this.notifyRuntimeIssue(issue),\n recordFrameDiagnostic: (diagnostic) => this.recordFrameDiagnostic(diagnostic),\n writeOutput: (text) => this.writeOutput(text),\n writeError: (text) => this.writeOutput(text),\n });\n\n this.applyStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n setVisible(\n visible: boolean\n ): void {\n this.isVisible = visible;\n this.applyVisibility();\n if (visible) {\n this.resizeToMount();\n if (this.synchronizeAccessibilityFocus) {\n this.terminalMount.focus?.({ preventScroll: true });\n }\n }\n this.updateRuntimeSuspension();\n }\n\n /**\n * Reports whether the surrounding document can be seen at all (browser tab\n * visible, iframe on-screen, …). Combined with the scene-level\n * `setVisible`: the app is suspended while either says hidden, unless\n * `suspendWhenHidden` is `false`.\n */\n setDocumentVisible(\n visible: boolean\n ): void {\n this.documentVisible = visible;\n this.updateRuntimeSuspension();\n }\n\n private updateRuntimeSuspension(): void {\n const suspended = this.suspendWhenHidden && (!this.isVisible || !this.documentVisible);\n if (suspended === this.runtimeSuspended) {\n return;\n }\n this.runtimeSuspended = suspended;\n this.onRuntimeSuspensionChange(suspended);\n }\n\n /**\n * Suspension hook for subclasses that own an app execution vehicle (the\n * WASI worker / JSPI executor). The base runtime only presents frames, so\n * it has nothing to suspend.\n */\n protected onRuntimeSuspensionChange(\n _suspended: boolean\n ): void {}\n\n setStyle(\n style: WebHostTerminalStyle\n ): void {\n this.currentStyle = normalizeWebHostTerminalStyle(style);\n this.applyStyle(this.currentStyle);\n this.bridge?.updateRenderStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n resize(\n columns: number,\n rows: number\n ): void {\n this.columns = Math.max(1, Math.round(columns));\n this.rows = Math.max(1, Math.round(rows));\n this.resizeSurface();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n writeOutput(\n text: string\n ): void {\n if (!this.diagnosticText) {\n const diagnosticText = document.createElement(\"pre\");\n diagnosticText.className = \"webhost-scene__diagnostic\";\n this.diagnosticText = diagnosticText;\n this.terminalMount.appendChild(diagnosticText);\n }\n this.diagnosticText.textContent = `${this.diagnosticText.textContent ?? \"\"}${text}`;\n }\n\n notifyRuntimeIssue(\n issue: WebHostRuntimeIssue\n ): void {\n // Into the mount, not only the console: a runtime issue is the app telling\n // the user something went wrong, and a console line is invisible to anyone\n // who is not already looking at devtools.\n this.writeOutput(`${issue.description}\\n`);\n }\n\n private recordFrameDiagnostic(\n diagnostic: WebHostFrameDiagnosticRecord\n ): void {\n this.onFrameDiagnostic?.(diagnostic);\n }\n\n async writeClipboard(\n text: string\n ): Promise<void> {\n const clipboard = globalThis.navigator?.clipboard;\n if (!clipboard?.writeText) {\n return;\n }\n\n try {\n await clipboard.writeText(text);\n } catch {\n // Clipboard permissions are browser/user-gesture dependent; hosts treat\n // rejection as a best-effort no-op rather than surfacing diagnostics.\n }\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.onInput(chunk);\n }\n\n dispose(): void {\n this.detachInputHandlers?.();\n this.resizeObserver?.disconnect();\n this.element.remove();\n }\n\n private presentSurface(\n frame: WebHostSurfaceFrame,\n recoveredImagePayloadIds?: readonly string[]\n ): void {\n const previousFrame = this.currentFrame;\n this.currentFrame = frame;\n this.columns = Math.max(1, Math.round(frame.width));\n this.rows = Math.max(1, Math.round(frame.height));\n const resized = this.resizeSurface();\n this.draw(\n previousFrame && !resized ? frame.damage : undefined,\n recoveredImagePayloadIds\n );\n this.syncAccessibilityTree();\n }\n\n /**\n * The current frame's preferred grid size in cells, when the app published\n * one — the measured pre-minimum content size, for embedders negotiating\n * with an outer layout system (the Android host's preferred columns/rows).\n */\n get preferredGridSize(): { width: number; height: number } | undefined {\n const frame = this.currentFrame;\n if (frame?.preferredGridWidth === undefined || frame.preferredGridHeight === undefined) {\n return undefined;\n }\n return { width: frame.preferredGridWidth, height: frame.preferredGridHeight };\n }\n\n /**\n * The current frame's settled focus presentation, when the app published\n * one. `prefersTextInput` is what the Android host uses to gate its IME;\n * embedders can drive virtual-keyboard or focus affordances from it.\n */\n get focusPresentation(): WebHostFocusPresentation | undefined {\n const presentation = this.currentFrame?.focusPresentation;\n if (!presentation) {\n return undefined;\n }\n return {\n ...presentation,\n semantics: normalizeSemantics(presentation.semantics),\n };\n }\n\n private linkTarget(\n location: CellLocation\n ): string | undefined {\n return linkTargetAt(\n this.currentFrame?.links,\n this.currentFrame?.linkTargets,\n location\n );\n }\n\n private openHyperlink(\n url: string\n ): void {\n if (this.onOpenHyperlink) {\n this.onOpenHyperlink(url);\n return;\n }\n // Defense in depth on top of the app-side OSC-8 destination sanitization:\n // the default handler only opens web schemes.\n if (!/^https?:/i.test(url)) {\n return;\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n }\n\n private applyStyle(\n style: WebHostTerminalStyle\n ): void {\n applyWebHostTerminalStyle(this.element, style);\n this.element.style.padding = \"0.75rem\";\n this.element.style.borderRadius = \"16px\";\n this.element.style.boxShadow = \"0 20px 50px rgba(0, 0, 0, 0.28)\";\n this.element.style.overflow = \"hidden\";\n this.element.style.gap = \"0.5rem\";\n this.element.style.gridTemplateRows = \"auto 1fr\";\n\n this.terminalMount.style.position = \"relative\";\n this.terminalMount.style.overflow = \"hidden\";\n // Keep a captured wheel from rubber-banding/chaining the page; the wheel\n // capture vs. fall-through decision lives in handleWheel.\n this.terminalMount.style.overscrollBehavior = \"contain\";\n this.terminalMount.style.outline = \"none\";\n this.terminalMount.style.background = webTUITerminalBackgroundColor(this.currentStyle);\n this.terminalMount.style.minHeight = `${this.cellHeight * 8}px`;\n\n if (this.canvas) {\n this.canvas.style.display = \"block\";\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n }\n if (this.domSurfaceRoot) {\n this.domSurfaceRoot.style.display = \"block\";\n this.domSurfaceRoot.style.position = \"relative\";\n }\n }\n\n /** The element the active painter presents frames into. */\n private get surfaceElement(): HTMLElement | undefined {\n return this.canvas ?? this.domSurfaceRoot;\n }\n\n private applyVisibility(): void {\n this.element.hidden = !this.isVisible;\n this.element.style.setProperty(\n \"display\",\n this.isVisible ? \"grid\" : \"none\",\n \"important\"\n );\n }\n\n private installResizeObserver(): void {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeToMount();\n });\n this.resizeObserver.observe(this.terminalMount);\n }\n\n private installInputHandlers(): void {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.metaKey || event.isComposing) {\n return;\n }\n const message = this.inputEncoder.encodeKey(event);\n if (!message) {\n return;\n }\n\n this.onInput(message);\n event.preventDefault();\n };\n\n const handlePaste = (event: ClipboardEvent) => {\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) {\n return;\n }\n this.onInput(this.inputEncoder.encodePaste(text));\n event.preventDefault();\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n if (this.allowsNativeTextSelection(event)) {\n // DOM renderer + Alt/Option: leave the event to the browser so the\n // drag becomes a native text selection instead of app pointer input.\n return;\n }\n const location = this.cellLocation(event);\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button);\n this.activePointerButton = button;\n this.hasCapturedPointer = true;\n this.pointerDownLinkTarget = button === \"primary\"\n ? this.linkTarget(location)\n : undefined;\n this.terminalMount.focus?.({ preventScroll: true });\n this.terminalMount.setPointerCapture?.(event.pointerId);\n this.onInput(this.inputEncoder.encodePointerDown(location, button, event));\n event.preventDefault();\n };\n\n const handlePointerUp = (event: PointerEvent) => {\n if (!this.hasCapturedPointer && this.allowsNativeTextSelection(event)) {\n return;\n }\n const location = this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n this.terminalMount.releasePointerCapture?.(event.pointerId);\n this.hasCapturedPointer = false;\n const downLinkTarget = this.pointerDownLinkTarget;\n this.pointerDownLinkTarget = undefined;\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button) ?? this.activePointerButton;\n this.onInput(this.inputEncoder.encodePointerUp(location, button, event));\n // A click — down and up over the same link target — opens the link,\n // mirroring the Android host's tap-to-open. The app still receives the\n // pointer messages above.\n if (downLinkTarget !== undefined && this.linkTarget(location) === downLinkTarget) {\n this.openHyperlink(downLinkTarget);\n }\n event.preventDefault();\n };\n\n const handlePointerMove = (event: PointerEvent) => {\n if (!this.hasCapturedPointer && this.allowsNativeTextSelection(event)) {\n return;\n }\n const location = event.buttons && this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n if (!location) {\n return;\n }\n\n if (!this.hasCapturedPointer) {\n this.terminalMount.style.cursor =\n this.linkTarget(location) !== undefined ? \"pointer\" : \"\";\n }\n this.onInput(this.inputEncoder.encodePointerMove(location, this.activePointerButton, event));\n };\n\n const handleWheel = (event: WheelEvent) => {\n if (this.wheelMode === \"passive\") {\n return;\n }\n\n const location = this.cellLocation(event);\n if (!location) {\n // Pointer is outside the cell grid (sub-cell margin / gutter). Don't\n // capture — let the wheel fall through to the page.\n return;\n }\n\n // In \"chain\" mode, capture only while a scrollable region under the\n // pointer can still move in this direction; otherwise let the wheel fall\n // through so the page (or parent iframe) scrolls — iframe-like behavior.\n // \"capture\" mode always forwards while over the surface (legacy).\n if (this.wheelMode === \"chain\"\n && !wheelTargetCanScroll(this.currentFrame?.scrollRegions, location, event.deltaX, event.deltaY)) {\n return;\n }\n\n this.onInput(this.inputEncoder.encodeWheel(location, event));\n event.preventDefault();\n };\n\n this.terminalMount.addEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.addEventListener(\"paste\", handlePaste);\n this.terminalMount.addEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.addEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.addEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.addEventListener(\"wheel\", handleWheel, { passive: false });\n\n this.detachInputHandlers = () => {\n this.terminalMount.removeEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.removeEventListener(\"paste\", handlePaste);\n this.terminalMount.removeEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.removeEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.removeEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.removeEventListener(\"wheel\", handleWheel);\n };\n }\n\n private resizeToMount(): void {\n this.measureCells();\n const rect = this.terminalMount.getBoundingClientRect?.();\n const width = rect?.width && rect.width > 0 ? rect.width : this.columns * this.cellWidth;\n const height = rect?.height && rect.height > 0 ? rect.height : this.rows * this.cellHeight;\n const nextColumns = Math.max(1, Math.floor(width / this.cellWidth));\n const nextRows = Math.max(1, Math.floor(height / this.cellHeight));\n\n this.columns = nextColumns;\n this.rows = nextRows;\n this.sendResizeIfNeeded();\n this.resizeSurface();\n }\n\n private sendResizeIfNeeded(): void {\n const current = {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n };\n if (this.lastSentResize\n && this.lastSentResize.columns === current.columns\n && this.lastSentResize.rows === current.rows\n && this.lastSentResize.cellWidth === current.cellWidth\n && this.lastSentResize.cellHeight === current.cellHeight\n ) {\n return;\n }\n\n this.lastSentResize = current;\n this.bridge?.resize(current.columns, current.rows, current.cellWidth, current.cellHeight);\n }\n\n private resizeSurface(): boolean {\n const cssWidth = Math.max(1, this.columns * this.cellWidth);\n const cssHeight = Math.max(1, this.rows * this.cellHeight);\n\n if (this.domSurfaceRoot) {\n const last = this.lastDomSurfaceSize;\n if (last && last.width === cssWidth && last.height === cssHeight) {\n return false;\n }\n this.lastDomSurfaceSize = { width: cssWidth, height: cssHeight };\n this.domSurfaceRoot.style.width = `${cssWidth}px`;\n this.domSurfaceRoot.style.height = `${cssHeight}px`;\n return true;\n }\n\n if (!this.canvas) {\n return false;\n }\n\n const scale = globalThis.window?.devicePixelRatio || 1;\n const width = Math.ceil(cssWidth * scale);\n const height = Math.ceil(cssHeight * scale);\n const styleWidth = `${cssWidth}px`;\n const styleHeight = `${cssHeight}px`;\n if (this.canvas.width === width\n && this.canvas.height === height\n && this.canvas.style.width === styleWidth\n && this.canvas.style.height === styleHeight\n ) {\n return false;\n }\n\n this.canvas.width = width;\n this.canvas.height = height;\n this.canvas.style.width = styleWidth;\n this.canvas.style.height = styleHeight;\n return true;\n }\n\n private measureCells(): void {\n const canvas = this.canvas ?? document.createElement(\"canvas\");\n const context = canvas.getContext?.(\"2d\");\n if (!context) {\n this.cellWidth = Math.max(1, Math.round(this.currentStyle.fontSize * 0.62));\n this.cellHeight = Math.max(1, Math.round(this.currentStyle.fontSize * 1.35));\n return;\n }\n\n context.font = fontForStyle(this.currentStyle);\n this.cellWidth = Math.max(1, Math.ceil(context.measureText(\"W\").width));\n this.cellHeight = Math.max(1, Math.ceil(this.currentStyle.fontSize * 1.35));\n }\n\n private draw(\n damage?: WebHostSurfaceDamage,\n recoveredImagePayloadIds?: readonly string[]\n ): void {\n this.painter.paint(\n this.surfaceMetrics(),\n this.currentFrame,\n damage,\n recoveredImagePayloadIds\n );\n }\n\n private syncAccessibilityTree(): void {\n const tree = this.accessibilityTree;\n if (!tree || !this.currentFrame) {\n return;\n }\n\n tree.present(this.currentFrame.accessibilityTree ?? [], {\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n }, this.currentFrame.accessibilityAnnouncements ?? [], {\n synchronizeFocus: this.synchronizeAccessibilityFocus,\n });\n }\n\n private surfaceMetrics(): CanvasSurfaceMetrics {\n return {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n style: this.currentStyle,\n };\n }\n\n private pointerMetrics(): PointerGeometryMetrics {\n return {\n rect: this.surfaceElement?.getBoundingClientRect?.() ?? this.terminalMount.getBoundingClientRect?.(),\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n columns: this.columns,\n rows: this.rows,\n };\n }\n\n /**\n * Whether this pointer event should be left to the browser for native text\n * selection instead of being forwarded to the app. Only the DOM renderer\n * has real text nodes to select, and only while Alt/Option is held — plain\n * pointer input still belongs to the app.\n */\n private allowsNativeTextSelection(\n event: MouseEvent\n ): boolean {\n return this.rendererKind === \"dom\" && event.altKey;\n }\n\n private cellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return cellLocationForEvent(event, this.pointerMetrics());\n }\n\n private rawCellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return rawCellLocationForEvent(event, this.pointerMetrics());\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebHostSceneRuntime.js","names":[],"sources":["../../src/WebHostSceneRuntime.ts"],"sourcesContent":["import {\n applyWebHostTerminalStyle,\n normalizeWebHostTerminalStyle,\n type ResolvedWebHostTerminalStyle,\n type WebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport {\n CanvasSurfacePainter,\n fontForStyle,\n type CanvasSurfaceMetrics,\n} from \"./CanvasSurfacePainter.ts\";\nimport { DomSurfacePainter } from \"./DomSurfacePainter.ts\";\nimport type { WebHostSurfaceRendererKind } from \"./SurfaceRenderer.ts\";\nimport {\n InputEventEncoder,\n type CellLocation,\n type PointerButton,\n} from \"./InputEventEncoder.ts\";\nimport {\n cellLocationForEvent,\n linkTargetAt,\n rawCellLocationForEvent,\n wheelTargetCanScroll,\n type PointerGeometryMetrics,\n} from \"./PointerGeometry.ts\";\nimport { AccessibilityTreeMounter } from \"./AccessibilityTree.ts\";\nimport { normalizeSemantics } from \"./normalizeWireTokens.ts\";\nimport {\n type WebHostFocusPresentation,\n type WebHostFrameDiagnosticRecord,\n type WebHostImagePayloadRequestHandler,\n type WebHostOutputSink,\n type WebHostRuntimeIssue,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostSceneDescriptor } from \"./WebHostSceneManifest.ts\";\n\nexport interface WebHostSceneBridge {\n bindOutput(sink: WebHostOutputSink): void;\n resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;\n updateRenderStyle(style: WebHostTerminalStyle): void;\n sendInput(chunk: Uint8Array): void;\n /** Optional for compatibility with custom bridges predating image recovery. */\n requestImagePayloads?: WebHostImagePayloadRequestHandler;\n /**\n * Declares whether this client scrolls by dragging content directly.\n * Optional for compatibility with custom bridges predating the record; an\n * app that never receives it keeps the desktop paradigm.\n */\n updatePointerCapabilities?(supportsScrollPanning: boolean): void;\n dispose(): void;\n}\n\nexport interface WebHostSceneRuntimeOptions {\n mount: HTMLElement;\n descriptor: WebHostSceneDescriptor;\n style: WebHostTerminalStyle;\n bridge?: WebHostSceneBridge;\n onInput(chunk: Uint8Array): void;\n onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n synchronizeAccessibilityFocus?: boolean;\n /**\n * How the embedded view treats mouse-wheel input.\n * - `\"chain\"` (default): forward the wheel only while a scrollable region\n * under the pointer can still scroll in that direction; otherwise let it\n * fall through so the page (or parent iframe) scrolls — iframe-like nested\n * scrolling. A scene with no `ScrollView` never traps the wheel. Uses the\n * `scrollRegions` the app publishes in its frames.\n * - `\"capture\"`: always forward the wheel to the app while the pointer is over\n * the surface (and `preventDefault` page scroll). Best for full-screen apps\n * where there is no page to scroll past.\n * - `\"passive\"`: never capture; the page always scrolls.\n *\n * Takes precedence over the legacy `captureWheelInput` flag.\n */\n wheelMode?: WheelMode;\n /**\n * Legacy boolean wheel gate. `true` → `\"capture\"`, `false` → `\"passive\"`.\n * Prefer `wheelMode`. Ignored when `wheelMode` is set. When neither is set the\n * mode defaults to `\"chain\"`.\n */\n captureWheelInput?: boolean;\n /**\n * Called when the user clicks a hyperlink cell (a click is a pointer-down\n * and pointer-up over the same link target). When unset, `http(s)` targets\n * open in a new tab via `window.open(url, \"_blank\", \"noopener,noreferrer\")`\n * and other schemes are ignored. Mirrors the Android host's tap-to-open.\n */\n onOpenHyperlink?: (url: string) => void;\n /**\n * Whether to suspend the scene's app while it cannot be seen — when the\n * scene is switched to the background (`setVisible(false)`) or the whole\n * document is hidden (`setDocumentVisible(false)`). Suspension parks the\n * app's run loop and freezes its monotonic clock, so a hidden scene costs\n * no CPU and resumes exactly where it left off. Defaults to `true`; set\n * `false` to let background scenes keep running (pre-suspension behavior).\n */\n suspendWhenHidden?: boolean;\n /**\n * Which surface presenter draws the scene's frames. `\"canvas\"` (default)\n * paints onto a 2D `<canvas>`; `\"dom\"` renders cells as absolutely\n * positioned text elements — native font rendering and, uniquely, real\n * text selection: hold Alt/Option and drag to select instead of sending\n * pointer input to the app. See {@link WebHostSurfaceRendererKind}.\n */\n renderer?: WebHostSurfaceRendererKind;\n}\n\nexport type WheelMode = \"capture\" | \"chain\" | \"passive\";\n\n/**\n * Resolves the legacy `captureWheelInput` flag to a {@link WheelMode}. When the\n * flag is unset the mode defaults to `\"chain\"`, so embeds never trap a visitor\n * who is merely scrolling past the view; `true` maps to `\"capture\"` and `false`\n * to `\"passive\"` to preserve the old boolean behavior.\n */\nfunction legacyWheelMode(captureWheelInput: boolean | undefined): WheelMode {\n if (captureWheelInput === undefined) {\n return \"chain\";\n }\n return captureWheelInput ? \"capture\" : \"passive\";\n}\n\n/**\n * The media query for \"the primary pointing device is coarse\", i.e. a finger.\n * Deliberately `pointer` and not `any-pointer`: a touch-capable laptop has a\n * coarse pointer *available* but is driven by a trackpad, and it should get\n * the desktop paradigm.\n *\n * Returns `undefined` where `matchMedia` is unavailable (SSR, older embedding\n * hosts, some test environments), which callers read as \"desktop\".\n */\nexport function coarsePointerQuery(): MediaQueryList | undefined {\n if (typeof globalThis.matchMedia !== \"function\") {\n return undefined;\n }\n try {\n return globalThis.matchMedia(\"(pointer: coarse)\");\n } catch {\n return undefined;\n }\n}\n\n/** Whether the primary pointing device is a finger rather than a pointer. */\nexport function coarsePrimaryPointer(): boolean {\n return coarsePointerQuery()?.matches ?? false;\n}\n\n/**\n * Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM\n * mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy\n * responsibilities to focused collaborators — {@link CanvasSurfacePainter} for\n * canvas drawing, {@link InputEventEncoder} for wire-message encoding, and the\n * {@link PointerGeometry} helpers for pixel→cell hit-testing and wheel chaining.\n */\nexport class WebHostSceneRuntime {\n readonly descriptor: WebHostSceneDescriptor;\n readonly element: HTMLElement;\n readonly terminalMount: HTMLElement;\n\n private readonly bridge?: WebHostSceneBridge;\n private readonly onInput: (chunk: Uint8Array) => void;\n private readonly onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n private readonly synchronizeAccessibilityFocus: boolean;\n private readonly wheelMode: WheelMode;\n private readonly rendererKind: WebHostSurfaceRendererKind;\n private readonly painter: CanvasSurfacePainter | DomSurfacePainter;\n private readonly inputEncoder = new InputEventEncoder();\n private currentStyle: ResolvedWebHostTerminalStyle;\n private canvas?: HTMLCanvasElement;\n private domSurfaceRoot?: HTMLElement;\n private lastDomSurfaceSize?: { width: number; height: number };\n private accessibilityTree?: AccessibilityTreeMounter;\n private diagnosticText?: HTMLElement;\n private resizeObserver?: ResizeObserver;\n private detachInputHandlers?: () => void;\n private currentFrame?: WebHostSurfaceFrame;\n private columns = 80;\n private rows = 24;\n private cellWidth = 8;\n private cellHeight = 18;\n private activePointerButton: PointerButton = \"primary\";\n private hasCapturedPointer = false;\n private readonly onOpenHyperlink?: (url: string) => void;\n private pointerDownLinkTarget?: string;\n private lastSentResize?: {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n };\n private isVisible = false;\n private documentVisible = true;\n private runtimeSuspended = false;\n private readonly suspendWhenHidden: boolean;\n /**\n * Seeded to `false` rather than left unset because absence of the record\n * already means the desktop paradigm on the Swift side: a mouse-driven\n * client therefore says nothing, and only a client that actually pans by\n * dragging puts a record on the wire.\n */\n private lastSentPointerCapabilities = false;\n private detachPointerParadigmObserver?: () => void;\n\n constructor(options: WebHostSceneRuntimeOptions) {\n this.descriptor = options.descriptor;\n this.currentStyle = normalizeWebHostTerminalStyle(options.style);\n this.bridge = options.bridge;\n this.onInput = options.onInput;\n this.onFrameDiagnostic = options.onFrameDiagnostic;\n this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;\n this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);\n this.rendererKind = options.renderer ?? \"canvas\";\n const onImagePayloadMiss = (\n ids: readonly string[]\n ): readonly string[] | void => {\n return this.bridge?.requestImagePayloads?.(ids);\n };\n this.painter = this.rendererKind === \"dom\"\n ? new DomSurfacePainter({ onImagePayloadMiss })\n : new CanvasSurfacePainter({ onImagePayloadMiss });\n this.onOpenHyperlink = options.onOpenHyperlink;\n this.suspendWhenHidden = options.suspendWhenHidden ?? true;\n this.element = document.createElement(\"section\");\n this.element.className = \"webhost-scene\";\n this.element.dataset.sceneId = options.descriptor.id;\n this.element.hidden = true;\n\n const header = document.createElement(\"div\");\n header.className = \"webhost-scene__header\";\n header.textContent = options.descriptor.title ?? options.descriptor.id;\n\n this.terminalMount = document.createElement(\"div\");\n this.terminalMount.className = \"webhost-scene__terminal\";\n this.terminalMount.tabIndex = 0;\n\n this.element.append(header, this.terminalMount);\n options.mount.appendChild(this.element);\n this.applyVisibility();\n }\n\n async mount(): Promise<void> {\n if (this.surfaceElement) {\n return;\n }\n\n if (this.painter instanceof DomSurfacePainter) {\n const surfaceRoot = document.createElement(\"div\");\n surfaceRoot.className = \"webhost-scene__surface webhost-scene__surface--dom\";\n surfaceRoot.setAttribute(\"aria-hidden\", \"true\");\n this.domSurfaceRoot = surfaceRoot;\n this.painter.attach(surfaceRoot);\n } else {\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"webhost-scene__surface\";\n canvas.setAttribute(\"aria-hidden\", \"true\");\n this.canvas = canvas;\n this.painter.attach(canvas, () => this.draw());\n }\n this.accessibilityTree = new AccessibilityTreeMounter();\n this.terminalMount.replaceChildren(\n this.surfaceElement as HTMLElement,\n this.accessibilityTree.element,\n this.accessibilityTree.announcerElement\n );\n this.installInputHandlers();\n this.installResizeObserver();\n\n this.bridge?.bindOutput({\n presentSurface: (frame, recoveredImagePayloadIds) =>\n this.presentSurface(frame, recoveredImagePayloadIds),\n writeClipboard: (text) => this.writeClipboard(text),\n notifyRuntimeIssue: (issue) => this.notifyRuntimeIssue(issue),\n recordFrameDiagnostic: (diagnostic) => this.recordFrameDiagnostic(diagnostic),\n writeOutput: (text) => this.writeOutput(text),\n writeError: (text) => this.writeOutput(text),\n });\n\n this.applyStyle(this.currentStyle);\n this.installPointerParadigmObserver();\n this.sendPointerCapabilitiesIfChanged(coarsePrimaryPointer());\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n setVisible(\n visible: boolean\n ): void {\n this.isVisible = visible;\n this.applyVisibility();\n if (visible) {\n this.resizeToMount();\n if (this.synchronizeAccessibilityFocus) {\n this.terminalMount.focus?.({ preventScroll: true });\n }\n }\n this.updateRuntimeSuspension();\n }\n\n /**\n * Reports whether the surrounding document can be seen at all (browser tab\n * visible, iframe on-screen, …). Combined with the scene-level\n * `setVisible`: the app is suspended while either says hidden, unless\n * `suspendWhenHidden` is `false`.\n */\n setDocumentVisible(\n visible: boolean\n ): void {\n this.documentVisible = visible;\n this.updateRuntimeSuspension();\n }\n\n private updateRuntimeSuspension(): void {\n const suspended = this.suspendWhenHidden && (!this.isVisible || !this.documentVisible);\n if (suspended === this.runtimeSuspended) {\n return;\n }\n this.runtimeSuspended = suspended;\n this.onRuntimeSuspensionChange(suspended);\n }\n\n /**\n * Suspension hook for subclasses that own an app execution vehicle (the\n * WASI worker / JSPI executor). The base runtime only presents frames, so\n * it has nothing to suspend.\n */\n protected onRuntimeSuspensionChange(\n _suspended: boolean\n ): void {}\n\n setStyle(\n style: WebHostTerminalStyle\n ): void {\n this.currentStyle = normalizeWebHostTerminalStyle(style);\n this.applyStyle(this.currentStyle);\n this.bridge?.updateRenderStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n resize(\n columns: number,\n rows: number\n ): void {\n this.columns = Math.max(1, Math.round(columns));\n this.rows = Math.max(1, Math.round(rows));\n this.resizeSurface();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n writeOutput(\n text: string\n ): void {\n if (!this.diagnosticText) {\n const diagnosticText = document.createElement(\"pre\");\n diagnosticText.className = \"webhost-scene__diagnostic\";\n this.diagnosticText = diagnosticText;\n this.terminalMount.appendChild(diagnosticText);\n }\n this.diagnosticText.textContent = `${this.diagnosticText.textContent ?? \"\"}${text}`;\n }\n\n notifyRuntimeIssue(\n issue: WebHostRuntimeIssue\n ): void {\n // Into the mount, not only the console: a runtime issue is the app telling\n // the user something went wrong, and a console line is invisible to anyone\n // who is not already looking at devtools.\n this.writeOutput(`${issue.description}\\n`);\n }\n\n private recordFrameDiagnostic(\n diagnostic: WebHostFrameDiagnosticRecord\n ): void {\n this.onFrameDiagnostic?.(diagnostic);\n }\n\n async writeClipboard(\n text: string\n ): Promise<void> {\n const clipboard = globalThis.navigator?.clipboard;\n if (!clipboard?.writeText) {\n return;\n }\n\n try {\n await clipboard.writeText(text);\n } catch {\n // Clipboard permissions are browser/user-gesture dependent; hosts treat\n // rejection as a best-effort no-op rather than surfacing diagnostics.\n }\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.onInput(chunk);\n }\n\n dispose(): void {\n this.detachInputHandlers?.();\n this.detachPointerParadigmObserver?.();\n this.resizeObserver?.disconnect();\n this.element.remove();\n }\n\n /**\n * Tells the app whether this client scrolls by dragging content directly.\n *\n * The app cannot see the browsing device, and one page bundle serves both a\n * phone and a desktop, so the paradigm has to be declared from here. It is\n * also not fixed for the session: a tablet can be docked to a mouse, so the\n * media query is watched and every real pointer press refines the answer\n * from `pointerType`. Only changes are sent.\n */\n private sendPointerCapabilitiesIfChanged(\n supportsScrollPanning: boolean\n ): void {\n if (this.lastSentPointerCapabilities === supportsScrollPanning) {\n return;\n }\n this.lastSentPointerCapabilities = supportsScrollPanning;\n this.bridge?.updatePointerCapabilities?.(supportsScrollPanning);\n }\n\n private installPointerParadigmObserver(): void {\n const query = coarsePointerQuery();\n if (!query?.addEventListener) {\n return;\n }\n\n const handleChange = (event: MediaQueryListEvent) => {\n this.sendPointerCapabilitiesIfChanged(event.matches);\n };\n query.addEventListener(\"change\", handleChange);\n this.detachPointerParadigmObserver = () => {\n query.removeEventListener?.(\"change\", handleChange);\n };\n }\n\n private presentSurface(\n frame: WebHostSurfaceFrame,\n recoveredImagePayloadIds?: readonly string[]\n ): void {\n const previousFrame = this.currentFrame;\n this.currentFrame = frame;\n this.columns = Math.max(1, Math.round(frame.width));\n this.rows = Math.max(1, Math.round(frame.height));\n const resized = this.resizeSurface();\n this.draw(\n previousFrame && !resized ? frame.damage : undefined,\n recoveredImagePayloadIds\n );\n this.syncAccessibilityTree();\n }\n\n /**\n * The current frame's preferred grid size in cells, when the app published\n * one — the measured pre-minimum content size, for embedders negotiating\n * with an outer layout system (the Android host's preferred columns/rows).\n */\n get preferredGridSize(): { width: number; height: number } | undefined {\n const frame = this.currentFrame;\n if (frame?.preferredGridWidth === undefined || frame.preferredGridHeight === undefined) {\n return undefined;\n }\n return { width: frame.preferredGridWidth, height: frame.preferredGridHeight };\n }\n\n /**\n * The current frame's settled focus presentation, when the app published\n * one. `prefersTextInput` is what the Android host uses to gate its IME;\n * embedders can drive virtual-keyboard or focus affordances from it.\n */\n get focusPresentation(): WebHostFocusPresentation | undefined {\n const presentation = this.currentFrame?.focusPresentation;\n if (!presentation) {\n return undefined;\n }\n return {\n ...presentation,\n semantics: normalizeSemantics(presentation.semantics),\n };\n }\n\n private linkTarget(\n location: CellLocation\n ): string | undefined {\n return linkTargetAt(\n this.currentFrame?.links,\n this.currentFrame?.linkTargets,\n location\n );\n }\n\n private openHyperlink(\n url: string\n ): void {\n if (this.onOpenHyperlink) {\n this.onOpenHyperlink(url);\n return;\n }\n // Defense in depth on top of the app-side OSC-8 destination sanitization:\n // the default handler only opens web schemes.\n if (!/^https?:/i.test(url)) {\n return;\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n }\n\n private applyStyle(\n style: WebHostTerminalStyle\n ): void {\n applyWebHostTerminalStyle(this.element, style);\n this.element.style.padding = \"0.75rem\";\n this.element.style.borderRadius = \"16px\";\n this.element.style.boxShadow = \"0 20px 50px rgba(0, 0, 0, 0.28)\";\n this.element.style.overflow = \"hidden\";\n this.element.style.gap = \"0.5rem\";\n this.element.style.gridTemplateRows = \"auto 1fr\";\n\n this.terminalMount.style.position = \"relative\";\n this.terminalMount.style.overflow = \"hidden\";\n // Keep a captured wheel from rubber-banding/chaining the page; the wheel\n // capture vs. fall-through decision lives in handleWheel.\n this.terminalMount.style.overscrollBehavior = \"contain\";\n this.terminalMount.style.outline = \"none\";\n this.terminalMount.style.background = webTUITerminalBackgroundColor(this.currentStyle);\n this.terminalMount.style.minHeight = `${this.cellHeight * 8}px`;\n\n if (this.canvas) {\n this.canvas.style.display = \"block\";\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n }\n if (this.domSurfaceRoot) {\n this.domSurfaceRoot.style.display = \"block\";\n this.domSurfaceRoot.style.position = \"relative\";\n }\n }\n\n /** The element the active painter presents frames into. */\n private get surfaceElement(): HTMLElement | undefined {\n return this.canvas ?? this.domSurfaceRoot;\n }\n\n private applyVisibility(): void {\n this.element.hidden = !this.isVisible;\n this.element.style.setProperty(\n \"display\",\n this.isVisible ? \"grid\" : \"none\",\n \"important\"\n );\n }\n\n private installResizeObserver(): void {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeToMount();\n });\n this.resizeObserver.observe(this.terminalMount);\n }\n\n private installInputHandlers(): void {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.metaKey || event.isComposing) {\n return;\n }\n const message = this.inputEncoder.encodeKey(event);\n if (!message) {\n return;\n }\n\n this.onInput(message);\n event.preventDefault();\n };\n\n const handlePaste = (event: ClipboardEvent) => {\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) {\n return;\n }\n this.onInput(this.inputEncoder.encodePaste(text));\n event.preventDefault();\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n // A real press is better evidence than the media query: a hybrid device\n // reports its *primary* pointer there, but this is the pointer actually\n // being used. Refined before the press is forwarded so the app has the\n // paradigm by the time it decides what to do with the gesture.\n if (event.pointerType === \"touch\" || event.pointerType === \"mouse\") {\n this.sendPointerCapabilitiesIfChanged(event.pointerType === \"touch\");\n }\n if (this.allowsNativeTextSelection(event)) {\n // DOM renderer + Alt/Option: leave the event to the browser so the\n // drag becomes a native text selection instead of app pointer input.\n return;\n }\n const location = this.cellLocation(event);\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button);\n this.activePointerButton = button;\n this.hasCapturedPointer = true;\n this.pointerDownLinkTarget = button === \"primary\"\n ? this.linkTarget(location)\n : undefined;\n this.terminalMount.focus?.({ preventScroll: true });\n this.terminalMount.setPointerCapture?.(event.pointerId);\n this.onInput(this.inputEncoder.encodePointerDown(location, button, event));\n event.preventDefault();\n };\n\n const handlePointerUp = (event: PointerEvent) => {\n if (!this.hasCapturedPointer && this.allowsNativeTextSelection(event)) {\n return;\n }\n const location = this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n this.terminalMount.releasePointerCapture?.(event.pointerId);\n this.hasCapturedPointer = false;\n const downLinkTarget = this.pointerDownLinkTarget;\n this.pointerDownLinkTarget = undefined;\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button) ?? this.activePointerButton;\n this.onInput(this.inputEncoder.encodePointerUp(location, button, event));\n // A click — down and up over the same link target — opens the link,\n // mirroring the Android host's tap-to-open. The app still receives the\n // pointer messages above.\n if (downLinkTarget !== undefined && this.linkTarget(location) === downLinkTarget) {\n this.openHyperlink(downLinkTarget);\n }\n event.preventDefault();\n };\n\n const handlePointerMove = (event: PointerEvent) => {\n if (!this.hasCapturedPointer && this.allowsNativeTextSelection(event)) {\n return;\n }\n const location = event.buttons && this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n if (!location) {\n return;\n }\n\n if (!this.hasCapturedPointer) {\n this.terminalMount.style.cursor =\n this.linkTarget(location) !== undefined ? \"pointer\" : \"\";\n }\n this.onInput(this.inputEncoder.encodePointerMove(location, this.activePointerButton, event));\n };\n\n const handleWheel = (event: WheelEvent) => {\n if (this.wheelMode === \"passive\") {\n return;\n }\n\n const location = this.cellLocation(event);\n if (!location) {\n // Pointer is outside the cell grid (sub-cell margin / gutter). Don't\n // capture — let the wheel fall through to the page.\n return;\n }\n\n // In \"chain\" mode, capture only while a scrollable region under the\n // pointer can still move in this direction; otherwise let the wheel fall\n // through so the page (or parent iframe) scrolls — iframe-like behavior.\n // \"capture\" mode always forwards while over the surface (legacy).\n if (this.wheelMode === \"chain\"\n && !wheelTargetCanScroll(this.currentFrame?.scrollRegions, location, event.deltaX, event.deltaY)) {\n return;\n }\n\n this.onInput(this.inputEncoder.encodeWheel(location, event));\n event.preventDefault();\n };\n\n this.terminalMount.addEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.addEventListener(\"paste\", handlePaste);\n this.terminalMount.addEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.addEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.addEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.addEventListener(\"wheel\", handleWheel, { passive: false });\n\n this.detachInputHandlers = () => {\n this.terminalMount.removeEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.removeEventListener(\"paste\", handlePaste);\n this.terminalMount.removeEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.removeEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.removeEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.removeEventListener(\"wheel\", handleWheel);\n };\n }\n\n private resizeToMount(): void {\n this.measureCells();\n const rect = this.terminalMount.getBoundingClientRect?.();\n const width = rect?.width && rect.width > 0 ? rect.width : this.columns * this.cellWidth;\n const height = rect?.height && rect.height > 0 ? rect.height : this.rows * this.cellHeight;\n const nextColumns = Math.max(1, Math.floor(width / this.cellWidth));\n const nextRows = Math.max(1, Math.floor(height / this.cellHeight));\n\n this.columns = nextColumns;\n this.rows = nextRows;\n this.sendResizeIfNeeded();\n this.resizeSurface();\n }\n\n private sendResizeIfNeeded(): void {\n const current = {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n };\n if (this.lastSentResize\n && this.lastSentResize.columns === current.columns\n && this.lastSentResize.rows === current.rows\n && this.lastSentResize.cellWidth === current.cellWidth\n && this.lastSentResize.cellHeight === current.cellHeight\n ) {\n return;\n }\n\n this.lastSentResize = current;\n this.bridge?.resize(current.columns, current.rows, current.cellWidth, current.cellHeight);\n }\n\n private resizeSurface(): boolean {\n const cssWidth = Math.max(1, this.columns * this.cellWidth);\n const cssHeight = Math.max(1, this.rows * this.cellHeight);\n\n if (this.domSurfaceRoot) {\n const last = this.lastDomSurfaceSize;\n if (last && last.width === cssWidth && last.height === cssHeight) {\n return false;\n }\n this.lastDomSurfaceSize = { width: cssWidth, height: cssHeight };\n this.domSurfaceRoot.style.width = `${cssWidth}px`;\n this.domSurfaceRoot.style.height = `${cssHeight}px`;\n return true;\n }\n\n if (!this.canvas) {\n return false;\n }\n\n const scale = globalThis.window?.devicePixelRatio || 1;\n const width = Math.ceil(cssWidth * scale);\n const height = Math.ceil(cssHeight * scale);\n const styleWidth = `${cssWidth}px`;\n const styleHeight = `${cssHeight}px`;\n if (this.canvas.width === width\n && this.canvas.height === height\n && this.canvas.style.width === styleWidth\n && this.canvas.style.height === styleHeight\n ) {\n return false;\n }\n\n this.canvas.width = width;\n this.canvas.height = height;\n this.canvas.style.width = styleWidth;\n this.canvas.style.height = styleHeight;\n return true;\n }\n\n private measureCells(): void {\n const canvas = this.canvas ?? document.createElement(\"canvas\");\n const context = canvas.getContext?.(\"2d\");\n if (!context) {\n this.cellWidth = Math.max(1, Math.round(this.currentStyle.fontSize * 0.62));\n this.cellHeight = Math.max(1, Math.round(this.currentStyle.fontSize * 1.35));\n return;\n }\n\n context.font = fontForStyle(this.currentStyle);\n this.cellWidth = Math.max(1, Math.ceil(context.measureText(\"W\").width));\n this.cellHeight = Math.max(1, Math.ceil(this.currentStyle.fontSize * 1.35));\n }\n\n private draw(\n damage?: WebHostSurfaceDamage,\n recoveredImagePayloadIds?: readonly string[]\n ): void {\n this.painter.paint(\n this.surfaceMetrics(),\n this.currentFrame,\n damage,\n recoveredImagePayloadIds\n );\n }\n\n private syncAccessibilityTree(): void {\n const tree = this.accessibilityTree;\n if (!tree || !this.currentFrame) {\n return;\n }\n\n tree.present(this.currentFrame.accessibilityTree ?? [], {\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n }, this.currentFrame.accessibilityAnnouncements ?? [], {\n synchronizeFocus: this.synchronizeAccessibilityFocus,\n });\n }\n\n private surfaceMetrics(): CanvasSurfaceMetrics {\n return {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n style: this.currentStyle,\n };\n }\n\n private pointerMetrics(): PointerGeometryMetrics {\n return {\n rect: this.surfaceElement?.getBoundingClientRect?.() ?? this.terminalMount.getBoundingClientRect?.(),\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n columns: this.columns,\n rows: this.rows,\n };\n }\n\n /**\n * Whether this pointer event should be left to the browser for native text\n * selection instead of being forwarded to the app. Only the DOM renderer\n * has real text nodes to select, and only while Alt/Option is held — plain\n * pointer input still belongs to the app.\n */\n private allowsNativeTextSelection(\n event: MouseEvent\n ): boolean {\n return this.rendererKind === \"dom\" && event.altKey;\n }\n\n private cellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return cellLocationForEvent(event, this.pointerMetrics());\n }\n\n private rawCellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return rawCellLocationForEvent(event, this.pointerMetrics());\n }\n}\n"],"mappings":"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"}
|
|
@@ -275,10 +275,27 @@ declare class WebHostOutputDecoder {
|
|
|
275
275
|
declare function encodeResizeControlMessage(columns: number, rows: number, cellWidth?: number, cellHeight?: number): Uint8Array;
|
|
276
276
|
declare function encodeRenderStyleControlMessage(style: WebHostTerminalStyle): Uint8Array;
|
|
277
277
|
declare function encodeCapabilitiesControlMessage(): Uint8Array;
|
|
278
|
+
/**
|
|
279
|
+
* The page's pointer-paradigm declaration.
|
|
280
|
+
*
|
|
281
|
+
* `panning=1` tells the app that this client scrolls by dragging content
|
|
282
|
+
* directly, which is how touch devices and coarse-pointer browsers behave and
|
|
283
|
+
* is not how a desktop mouse behaves. The app uses it to decide whether a drag
|
|
284
|
+
* on a scroll view's background pans it, and whether a drag that starts on a
|
|
285
|
+
* control may be taken over by an enclosing scroll view.
|
|
286
|
+
*
|
|
287
|
+
* Payload is `key=value` tokens rather than positional fields so a later
|
|
288
|
+
* pointer fact can join the record without changing its arity; the Swift
|
|
289
|
+
* parser skips keys it does not know. Unlike `caps:`, this record is not a
|
|
290
|
+
* once-per-connection epoch marker — the device can change (a tablet docked to
|
|
291
|
+
* a mouse), so it may be sent again at any time. Servers that predate the
|
|
292
|
+
* record drop it and keep the desktop paradigm.
|
|
293
|
+
*/
|
|
294
|
+
declare function encodePointerCapabilitiesControlMessage(supportsScrollPanning: boolean): Uint8Array;
|
|
278
295
|
declare function encodeResyncControlMessage(request: WebHostResyncRequest): Uint8Array;
|
|
279
296
|
declare function encodeKeyInputMessage(input: WebHostKeyInput): Uint8Array;
|
|
280
297
|
declare function encodePasteInputMessage(text: string): Uint8Array;
|
|
281
298
|
declare function encodeMouseInputMessage(input: WebHostMouseInput): Uint8Array;
|
|
282
299
|
//#endregion
|
|
283
|
-
export { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId };
|
|
300
|
+
export { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostResyncRequest, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId };
|
|
284
301
|
//# sourceMappingURL=WebHostSurfaceTransport.d.ts.map
|
|
@@ -360,6 +360,25 @@ function encodeRenderStyleControlMessage(style) {
|
|
|
360
360
|
function encodeCapabilitiesControlMessage() {
|
|
361
361
|
return textEncoder.encode(`${recordPrefix}caps:{"acceptsDeltaFrames":true,"styleAppend":true}\n`);
|
|
362
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* The page's pointer-paradigm declaration.
|
|
365
|
+
*
|
|
366
|
+
* `panning=1` tells the app that this client scrolls by dragging content
|
|
367
|
+
* directly, which is how touch devices and coarse-pointer browsers behave and
|
|
368
|
+
* is not how a desktop mouse behaves. The app uses it to decide whether a drag
|
|
369
|
+
* on a scroll view's background pans it, and whether a drag that starts on a
|
|
370
|
+
* control may be taken over by an enclosing scroll view.
|
|
371
|
+
*
|
|
372
|
+
* Payload is `key=value` tokens rather than positional fields so a later
|
|
373
|
+
* pointer fact can join the record without changing its arity; the Swift
|
|
374
|
+
* parser skips keys it does not know. Unlike `caps:`, this record is not a
|
|
375
|
+
* once-per-connection epoch marker — the device can change (a tablet docked to
|
|
376
|
+
* a mouse), so it may be sent again at any time. Servers that predate the
|
|
377
|
+
* record drop it and keep the desktop paradigm.
|
|
378
|
+
*/
|
|
379
|
+
function encodePointerCapabilitiesControlMessage(supportsScrollPanning) {
|
|
380
|
+
return textEncoder.encode(`${recordPrefix}pointer:panning=${supportsScrollPanning ? 1 : 0}\n`);
|
|
381
|
+
}
|
|
363
382
|
function encodeResyncControlMessage(request) {
|
|
364
383
|
const payload = request.scope === "keyframe" ? { scope: "keyframe" } : {
|
|
365
384
|
scope: "images",
|
|
@@ -497,6 +516,6 @@ function isWebHostSurfaceScalingMode(value) {
|
|
|
497
516
|
return typeof value === "string";
|
|
498
517
|
}
|
|
499
518
|
//#endregion
|
|
500
|
-
export { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId };
|
|
519
|
+
export { MAX_IMAGE_RECOVERY_ID_BYTES, MAX_OUTSTANDING_IMAGE_RECOVERY_IDS, SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage, isWebHostImageRecoveryId };
|
|
501
520
|
|
|
502
521
|
//# sourceMappingURL=WebHostSurfaceTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebHostSurfaceTransport.js","names":[],"sources":["../../src/WebHostSurfaceTransport.ts"],"sourcesContent":["/**\n * Browser consumer for SwiftTUI's host wire. The normative cross-host\n * contract lives upstream:\n * https://github.com/SwiftTUI/swift-tui/blob/main/docs/HOST-WIRE-CONTRACT.md\n */\nimport {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"./WebHostTerminalStyle.ts\";\n\nexport interface WebHostSurfaceStyle {\n fg?: string;\n bg?: string;\n em?: number;\n underline?: WebHostSurfaceLineStyle;\n strikethrough?: WebHostSurfaceLineStyle;\n opacity?: number;\n}\n\nexport interface WebHostSurfaceLineStyle {\n pattern: \"solid\" | \"dot\" | \"dash\" | \"dashDot\" | \"dashDotDot\" | \"double\" | \"curly\";\n color?: string;\n}\n\nexport type WebHostSurfaceCell = [\n x: number,\n text: string,\n span: number,\n styleIndex: number,\n];\n\nexport type WebHostSurfaceRect = [\n x: number,\n y: number,\n width: number,\n height: number,\n];\n\nexport type WebHostSurfaceSize = [\n width: number,\n height: number,\n];\n\nexport type WebHostAccessibilityPoint = [\n x: number,\n y: number,\n];\n\nexport type WebHostAccessibilityLiveRegion = string;\n\nexport interface WebHostAccessibilityNode {\n id: string;\n parentId?: string;\n rect: WebHostSurfaceRect;\n role: string;\n label?: string;\n hint?: string;\n /** omitted when false */\n hidden?: boolean;\n liveRegion?: WebHostAccessibilityLiveRegion;\n cursorAnchor?: WebHostAccessibilityPoint;\n isFocused?: boolean;\n}\n\n/**\n * One hyperlink run within a row: [x, spanWidth, linkTargetIndex]. The index\n * points into the frame's deduplicated `linkTargets` table.\n */\nexport type WebHostSurfaceLinkRun = [\n x: number,\n span: number,\n targetIndex: number,\n];\n\n/** Hyperlink runs for one row: [rowIndex, runs]. */\nexport type WebHostSurfaceLinkRow = [\n row: number,\n runs: WebHostSurfaceLinkRun[],\n];\n\nexport type WebHostFocusSemantics = string;\n\n/**\n * The settled focus presentation for a committed frame — the same derivation\n * the Android host consumes (`prefersTextInput` gates its IME).\n */\nexport interface WebHostFocusPresentation {\n focusedIdentity?: string;\n semantics: WebHostFocusSemantics;\n prefersTextInput: boolean;\n hasFocusedRegion: boolean;\n}\n\nexport interface WebHostAccessibilityAnnouncement {\n message: string;\n politeness: WebHostAccessibilityLiveRegion;\n}\n\nexport type WebHostSurfaceImageFormat = string;\n\n/**\n * Reports locally unresolved image IDs to a recovery-capable host. The first\n * branch lets bounded hosts return the admitted subset; the explicit legacy\n * void branch preserves contextual typing for concise callbacks that return\n * incidental values such as `array.push(...)`.\n */\nexport type WebHostImagePayloadRequestHandler =\n | ((ids: readonly string[]) => readonly string[])\n | ((ids: readonly string[]) => void);\n\nexport interface WebHostSurfaceImage {\n id: string;\n format: WebHostSurfaceImageFormat;\n bounds: WebHostSurfaceRect;\n visibleBounds: WebHostSurfaceRect;\n scalingMode: string;\n pixelSize?: WebHostSurfaceSize;\n dataBase64?: string;\n}\n\nexport type WebHostSurfaceDamageRange = [\n start: number,\n end: number,\n];\n\nexport type WebHostSurfaceDamageTextRow = [\n row: number,\n ranges: WebHostSurfaceDamageRange[],\n];\n\nexport interface WebHostSurfaceDamage {\n textRows: WebHostSurfaceDamageTextRow[];\n requiresFullTextRepaint: boolean;\n requiresFullGraphicsReplay: boolean;\n}\n\n/**\n * Per-region scroll extent published with each frame so the host can implement\n * scroll-chaining: capture the wheel only while the region under the pointer can\n * still scroll in the wheel's direction, otherwise let it fall through to the\n * page. The host recomputes the per-direction headroom from `offset`/`content`/\n * the viewport `rect`, mirroring SwiftTUI's\n * `min(max(0, offset), max(0, content - viewport))` clamp.\n */\nexport interface WebHostScrollRegion {\n /** identity path — same key space as accessibility node ids */\n id: string;\n /** viewport rect in cells: [x, y, width, height] */\n rect: WebHostSurfaceRect;\n /** current clamped scroll offset in cells: [x, y] */\n offset: WebHostAccessibilityPoint;\n /** total content size in cells: [width, height] */\n content: WebHostSurfaceSize;\n}\n\nexport interface WebHostSurfaceFrame {\n version: 1 | 2;\n epoch?: number;\n gen?: number;\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n rows: WebHostSurfaceCell[][];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport type WebHostSurfaceDeltaRow = [\n row: number,\n cells: WebHostSurfaceCell[],\n];\n\nexport interface WebHostSurfaceDeltaFrame {\n version: 3;\n encoding: \"delta\";\n epoch?: number;\n gen?: number;\n baselineGen?: number;\n sequence?: number;\n width: number;\n height: number;\n /**\n * With `stylesBase` present, only the styles this record *added*: index `i`\n * of this array is table index `stylesBase + i`. Absent `stylesBase`, the\n * complete accumulated table, as deployed decoders expect.\n */\n styles: Array<WebHostSurfaceStyle | null>;\n /**\n * Where `styles` splices onto the retained table. Present only when the\n * client declared `styleAppend`, which is why it is negotiated rather than\n * additive: reading `styles` as a whole table when this is present\n * mis-indexes every style in the record.\n */\n stylesBase?: number;\n deltaRows: WebHostSurfaceDeltaRow[];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport interface WebHostRuntimeIssue {\n severity: \"warning\" | \"error\";\n code: string;\n message: string;\n description: string;\n identity?: string;\n source?: string;\n}\n\nexport interface WebHostFrameDiagnosticRecord {\n format: \"swift-tui-frame-diagnostics-v1\";\n header: string[];\n fields: string[];\n}\n\nexport type WebHostOutputRecord =\n | { type: \"surface\"; frame: WebHostSurfaceFrame }\n | { type: \"clipboard\"; text: string }\n | { type: \"runtimeIssue\"; issue: WebHostRuntimeIssue }\n | { type: \"frameDiagnostic\"; diagnostic: WebHostFrameDiagnosticRecord }\n | { type: \"surfaceDropped\"; reason: \"noBaseline\" | \"staleBaseline\" }\n | { type: \"text\"; text: string };\n\nexport type WebHostResyncRequest =\n | { scope: \"keyframe\" }\n | { scope: \"images\"; ids?: string[] };\n\nexport interface WebHostOutputSink {\n presentSurface(\n frame: WebHostSurfaceFrame,\n recoveredImagePayloadIds?: readonly string[]\n ): void;\n writeClipboard?(text: string): void | Promise<void>;\n notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;\n recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;\n writeOutput?(text: string): void;\n writeError?(text: string): void;\n}\n\nexport interface WebHostKeyInput {\n key:\n | \"return\"\n | \"space\"\n | \"tab\"\n | \"arrowLeft\"\n | \"arrowRight\"\n | \"arrowUp\"\n | \"arrowDown\"\n | \"backspace\"\n | \"escape\"\n | \"home\"\n | \"end\"\n | \"character\";\n character?: string;\n modifiers?: number;\n}\n\nexport interface WebHostMouseInput {\n kind: \"down\" | \"up\" | \"moved\" | \"dragged\" | \"scrolled\";\n x: number;\n y: number;\n button?: \"primary\" | \"middle\" | \"secondary\";\n deltaX?: number;\n deltaY?: number;\n modifiers?: number;\n}\n\nconst recordPrefix = \"\\u001E\";\nconst textEncoder = new TextEncoder();\n\n/** Limits wire-derived recovery state and keeps every single-ID WASI request bounded. */\nexport const MAX_IMAGE_RECOVERY_ID_BYTES = 1_024;\nexport const MAX_OUTSTANDING_IMAGE_RECOVERY_IDS = 1_024;\n\nexport function isWebHostImageRecoveryId(\n id: string\n): boolean {\n return id.length > 0\n && id.length <= MAX_IMAGE_RECOVERY_ID_BYTES\n && textEncoder.encode(id).byteLength <= MAX_IMAGE_RECOVERY_ID_BYTES;\n}\n\n/**\n * The newest `surface` record version this runtime understands. Unknown\n * additive object keys are ignored by design (older runtimes render newer\n * frames), but a frame declaring a NEWER version than this is surfaced as an\n * error-severity runtime issue instead of silently degrading to a text\n * diagnostic — silent version skew is the failure mode this guards against\n * (F57).\n */\nexport const SUPPORTED_SURFACE_VERSION = 3;\n\nexport class WebHostOutputDecoder {\n private readonly textDecoder = new TextDecoder();\n private bufferedText = \"\";\n private lastSurfaceFrame?: WebHostSurfaceFrame;\n private lastEpoch?: number;\n private lastGen?: number;\n private lastPresentedEpoch?: number;\n private keyframeResyncOutstanding = false;\n private keyframeResyncPending = false;\n private readonly imageResyncOutstandingIds = new Set<string>();\n private readonly imageResyncPendingIds = new Set<string>();\n\n feed(\n chunk: Uint8Array\n ): WebHostOutputRecord[] {\n this.bufferedText += this.textDecoder.decode(chunk, { stream: true });\n const records: WebHostOutputRecord[] = [];\n\n while (true) {\n const newlineIndex = this.bufferedText.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n break;\n }\n\n const line = this.bufferedText.slice(0, newlineIndex);\n this.bufferedText = this.bufferedText.slice(newlineIndex + 1);\n records.push(this.decodeLine(line));\n }\n\n if (this.bufferedText.length > 4096 && !this.bufferedText.startsWith(recordPrefix)) {\n records.push({ type: \"text\", text: this.bufferedText });\n this.bufferedText = \"\";\n }\n\n return records;\n }\n\n flush(): WebHostOutputRecord[] {\n if (!this.bufferedText) {\n return [];\n }\n const text = this.bufferedText;\n this.bufferedText = \"\";\n return [this.decodeLine(text)];\n }\n\n takeResyncRequest(\n maximumEncodedBytes?: number\n ): WebHostResyncRequest | undefined {\n if (this.keyframeResyncPending) {\n this.keyframeResyncPending = false;\n return { scope: \"keyframe\" };\n }\n if (this.imageResyncPendingIds.size === 0) {\n return undefined;\n }\n\n const sortedIds = [...this.imageResyncPendingIds].sort();\n const { ids, rejectedIds } = boundedImageResyncIds(\n sortedIds,\n maximumEncodedBytes\n );\n for (const id of rejectedIds) {\n this.imageResyncPendingIds.delete(id);\n this.imageResyncOutstandingIds.delete(id);\n }\n for (const id of ids) {\n this.imageResyncPendingIds.delete(id);\n }\n if (ids.length === 0) {\n return undefined;\n }\n return { scope: \"images\", ids };\n }\n\n /**\n * Adds locally unresolved image IDs to this epoch's recovery set. IDs remain\n * outstanding after delivery, so repeat painter misses cannot create storms;\n * a payload-bearing record or epoch re-anchor releases them. Returns the IDs\n * now tracked (new or already outstanding); callers should suppress repeats\n * only for this admitted subset.\n */\n requestImagePayloads(\n ids: Iterable<string>\n ): readonly string[] {\n const acceptedIds = new Set<string>();\n for (const id of ids) {\n if (!isWebHostImageRecoveryId(id)) {\n continue;\n }\n if (this.imageResyncOutstandingIds.has(id)) {\n acceptedIds.add(id);\n continue;\n }\n if (\n this.imageResyncOutstandingIds.size\n >= MAX_OUTSTANDING_IMAGE_RECOVERY_IDS\n ) {\n continue;\n }\n this.imageResyncOutstandingIds.add(id);\n this.imageResyncPendingIds.add(id);\n acceptedIds.add(id);\n }\n return [...acceptedIds];\n }\n\n /**\n * Advances image recovery immediately before one decoded surface reaches its\n * presenter. Payload arrival and epoch reset therefore follow delivery order\n * rather than `feed`'s parse-ahead order across a multi-record chunk. The\n * return value identifies payloads that answer an outstanding image request,\n * so presenters can open exactly one fresh local decode generation even when\n * content-addressed retransmission uses identical bytes.\n */\n prepareToPresentSurface(\n frame: WebHostSurfaceFrame\n ): readonly string[] {\n const recoveredImagePayloadIds = this.recoveredImagePayloadIds(frame.images);\n this.resetImageResyncForEpoch(frame.epoch);\n this.sweepImageResyncForPresentedImages(frame.images);\n this.clearArrivedImagePayloads(frame.images);\n if (frame.epoch !== undefined) {\n this.lastPresentedEpoch = frame.epoch;\n }\n return recoveredImagePayloadIds;\n }\n\n resyncRequestDeliveryFailed(\n request: WebHostResyncRequest\n ): void {\n if (request.scope === \"keyframe\") {\n if (this.keyframeResyncOutstanding) {\n this.keyframeResyncPending = true;\n }\n return;\n }\n\n for (const id of request.ids ?? []) {\n if (this.imageResyncOutstandingIds.has(id)) {\n this.imageResyncPendingIds.add(id);\n }\n }\n }\n\n private decodeLine(\n line: string\n ): WebHostOutputRecord {\n if (line.startsWith(`${recordPrefix}clipboard:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}clipboard:`.length));\n if (isWebHostClipboardRecord(record)) {\n return { type: \"clipboard\", text: record.text };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}runtimeIssue:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}runtimeIssue:`.length));\n if (isWebHostRuntimeIssue(record)) {\n return { type: \"runtimeIssue\", issue: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}frameDiagnostic:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}frameDiagnostic:`.length));\n if (isWebHostFrameDiagnosticRecord(record)) {\n return { type: \"frameDiagnostic\", diagnostic: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (!line.startsWith(`${recordPrefix}surface:`)) {\n return { type: \"text\", text: `${line}\\n` };\n }\n\n try {\n const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));\n if (declaresNewerSurfaceVersion(frame)) {\n return {\n type: \"runtimeIssue\",\n issue: {\n severity: \"error\",\n code: \"surface.unsupportedVersion\",\n message: `SwiftTUI surface version ${frame.version} is newer than the supported ${SUPPORTED_SURFACE_VERSION}`,\n description: \"The app emitted a surface record with version \"\n + `${frame.version}, but this @swifttui/web runtime understands `\n + `versions up to ${SUPPORTED_SURFACE_VERSION}. Update @swifttui/web `\n + \"to render it.\",\n },\n };\n }\n if (isWebHostSurfaceFrame(frame)) {\n this.lastSurfaceFrame = frame;\n this.lastEpoch = frame.epoch;\n this.lastGen = frame.gen;\n this.keyframeResyncOutstanding = false;\n this.keyframeResyncPending = false;\n return { type: \"surface\", frame };\n }\n if (isWebHostSurfaceDeltaFrame(frame)) {\n const carriesDeliveryStamps = frame.epoch !== undefined\n || frame.gen !== undefined\n || frame.baselineGen !== undefined;\n if (\n !this.lastSurfaceFrame\n || this.lastSurfaceFrame.width !== frame.width\n || this.lastSurfaceFrame.height !== frame.height\n ) {\n if (carriesDeliveryStamps) {\n this.requestKeyframeResync();\n }\n return { type: \"surfaceDropped\", reason: \"noBaseline\" };\n }\n if (\n carriesDeliveryStamps\n && (\n frame.epoch === undefined\n || frame.gen === undefined\n || frame.baselineGen === undefined\n || frame.epoch !== this.lastEpoch\n || frame.baselineGen !== this.lastGen\n )\n ) {\n this.requestKeyframeResync();\n return { type: \"surfaceDropped\", reason: \"staleBaseline\" };\n }\n const materialized = this.materializeDeltaFrame(frame);\n if (materialized) {\n this.lastSurfaceFrame = materialized;\n this.lastEpoch = frame.epoch;\n this.lastGen = frame.gen;\n return { type: \"surface\", frame: materialized };\n }\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n private requestKeyframeResync(): void {\n if (this.keyframeResyncOutstanding) {\n return;\n }\n this.keyframeResyncOutstanding = true;\n this.keyframeResyncPending = true;\n }\n\n private resetImageResyncForEpoch(\n epoch: number | undefined\n ): void {\n if (epoch === undefined || epoch === this.lastPresentedEpoch) {\n return;\n }\n this.imageResyncOutstandingIds.clear();\n this.imageResyncPendingIds.clear();\n }\n\n private clearArrivedImagePayloads(\n images: WebHostSurfaceImage[] | undefined\n ): void {\n for (const image of images ?? []) {\n if (image.dataBase64 === undefined) {\n continue;\n }\n this.imageResyncOutstandingIds.delete(image.id);\n this.imageResyncPendingIds.delete(image.id);\n }\n }\n\n private recoveredImagePayloadIds(\n images: WebHostSurfaceImage[] | undefined\n ): string[] {\n const recoveredIds = new Set<string>();\n for (const image of images ?? []) {\n if (\n image.dataBase64 !== undefined\n && this.imageResyncOutstandingIds.has(image.id)\n ) {\n recoveredIds.add(image.id);\n }\n }\n return [...recoveredIds].sort();\n }\n\n private sweepImageResyncForPresentedImages(\n images: WebHostSurfaceImage[] | undefined\n ): void {\n const presentedIds = new Set<string>();\n for (const image of images ?? []) {\n if (this.imageResyncOutstandingIds.has(image.id)) {\n presentedIds.add(image.id);\n }\n }\n for (const id of this.imageResyncOutstandingIds) {\n if (presentedIds.has(id)) {\n continue;\n }\n this.imageResyncOutstandingIds.delete(id);\n this.imageResyncPendingIds.delete(id);\n }\n }\n\n private materializeDeltaFrame(\n frame: WebHostSurfaceDeltaFrame\n ): WebHostSurfaceFrame | undefined {\n const baseline = this.lastSurfaceFrame;\n if (!baseline) {\n return undefined;\n }\n\n const styles = this.materializeDeltaStyles(frame, baseline);\n if (!styles) {\n return undefined;\n }\n\n const rows = baseline.rows.slice();\n for (const [row, cells] of frame.deltaRows) {\n if (!Number.isSafeInteger(row) || row < 0 || row >= frame.height) {\n return undefined;\n }\n rows[row] = cells;\n }\n\n return {\n version: baseline.version,\n epoch: frame.epoch,\n gen: frame.gen,\n sequence: frame.sequence,\n width: frame.width,\n height: frame.height,\n styles,\n rows,\n images: frame.images,\n damage: frame.damage,\n accessibilityTree: frame.accessibilityTree,\n accessibilityAnnouncements: frame.accessibilityAnnouncements,\n scrollRegions: frame.scrollRegions,\n links: frame.links,\n linkTargets: frame.linkTargets,\n focusPresentation: frame.focusPresentation,\n preferredGridWidth: frame.preferredGridWidth,\n preferredGridHeight: frame.preferredGridHeight,\n };\n }\n\n /**\n * The delta's style table: either the record's own complete table, or the\n * negotiated append spliced onto the baseline's.\n *\n * A `stylesBase` that does not name the end of the retained table is a\n * structural break, not a recoverable one — splicing at the wrong offset\n * silently repaints cells in the wrong style, which is worse than refusing\n * the record and asking for a keyframe.\n */\n private materializeDeltaStyles(\n frame: WebHostSurfaceDeltaFrame,\n baseline: WebHostSurfaceFrame\n ): Array<WebHostSurfaceStyle | null> | undefined {\n if (frame.stylesBase === undefined) {\n return frame.styles;\n }\n if (frame.stylesBase !== baseline.styles.length) {\n return undefined;\n }\n return baseline.styles.concat(frame.styles);\n }\n}\n\nfunction boundedImageResyncIds(\n sortedIds: string[],\n maximumEncodedBytes: number | undefined\n): {\n ids: string[];\n rejectedIds: string[];\n} {\n if (\n maximumEncodedBytes === undefined\n || !Number.isFinite(maximumEncodedBytes)\n ) {\n return { ids: sortedIds, rejectedIds: [] };\n }\n\n const emptyRequestBytes = encodeResyncControlMessage({\n scope: \"images\",\n ids: [],\n }).byteLength;\n const normalizedMaximum = Math.max(0, Math.floor(maximumEncodedBytes));\n const ids: string[] = [];\n const rejectedIds: string[] = [];\n let encodedBytes = emptyRequestBytes;\n for (const id of sortedIds) {\n const separatorBytes = ids.length === 0 ? 0 : 1;\n const idBytes = textEncoder.encode(JSON.stringify(id)).byteLength;\n if (encodedBytes + separatorBytes + idBytes > normalizedMaximum) {\n if (ids.length === 0) {\n rejectedIds.push(id);\n continue;\n }\n break;\n }\n ids.push(id);\n encodedBytes += separatorBytes + idBytes;\n if (encodedBytes >= normalizedMaximum) {\n break;\n }\n }\n return { ids, rejectedIds };\n}\n\nfunction declaresNewerSurfaceVersion(\n value: unknown\n): value is { version: number } {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const version = (value as { version?: unknown }).version;\n return typeof version === \"number\"\n && Number.isSafeInteger(version)\n && version > SUPPORTED_SURFACE_VERSION;\n}\n\nfunction isWebHostClipboardRecord(\n value: unknown\n): value is { text: string } {\n return !!value && typeof value === \"object\" && typeof (value as { text?: unknown }).text === \"string\";\n}\n\nfunction isWebHostRuntimeIssue(\n value: unknown\n): value is WebHostRuntimeIssue {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostRuntimeIssue>;\n return (record.severity === \"warning\" || record.severity === \"error\")\n && typeof record.code === \"string\"\n && typeof record.message === \"string\"\n && typeof record.description === \"string\"\n && (record.identity === undefined || typeof record.identity === \"string\")\n && (record.source === undefined || typeof record.source === \"string\");\n}\n\nfunction isWebHostFrameDiagnosticRecord(\n value: unknown\n): value is WebHostFrameDiagnosticRecord {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostFrameDiagnosticRecord>;\n return record.format === \"swift-tui-frame-diagnostics-v1\"\n && Array.isArray(record.header)\n && record.header.every((field) => typeof field === \"string\")\n && Array.isArray(record.fields)\n && record.fields.every((field) => typeof field === \"string\");\n}\n\nexport function encodeResizeControlMessage(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n): Uint8Array {\n const normalizedColumns = Math.max(1, Math.round(columns));\n const normalizedRows = Math.max(1, Math.round(rows));\n if (cellWidth && cellHeight) {\n return textEncoder.encode(\n `${recordPrefix}resize:${normalizedColumns}:${normalizedRows}:${Math.max(1, Math.round(cellWidth))}:${Math.max(1, Math.round(cellHeight))}\\n`\n );\n }\n\n return textEncoder.encode(`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}\\n`);\n}\n\nexport function encodeRenderStyleControlMessage(\n style: WebHostTerminalStyle\n): Uint8Array {\n const encoded = encodeWebHostTerminalRenderStyleBase64(style);\n return textEncoder.encode(`${recordPrefix}style:${encoded}\\n`);\n}\n\nexport function encodeCapabilitiesControlMessage(): Uint8Array {\n // The client's wire-capability declaration, sent once after the socket\n // opens. The declaration is truthful today: this decoder materializes v3\n // delta frames (`materializeDeltaFrame`). Byte shape and key order are\n // pinned by the cross-repo fixture Fixtures/Transport/web-caps-record.txt\n // — swift-tui's input parser consumes the identical bytes, and the\n // coordination root's transport_fixture_sync gate keeps the copies in\n // lockstep. Servers that predate the record drop it silently, so the\n // session degrades to today's full-frame defaults.\n //\n // Capabilities are named feature bits. The retired `maxWebSurfaceVersion`\n // key declared a decoder version ceiling, which was only ever read as\n // \"accepts delta or not\" and duplicated — more weakly — the version check\n // this decoder already performs on every record (SUPPORTED_SURFACE_VERSION).\n // Servers still expecting it skip unknown keys, so dropping it is safe in\n // both directions.\n //\n // `styleAppend` is declared here because `materializeDeltaStyles` splices a\n // delta's `styles` onto the retained table when `stylesBase` is present.\n // Declaring it is truthful by construction: this decoder and this record are\n // the same release. Measured at Stage SV, the full retransmit it replaces was\n // 69.7% of late-record bytes in a style-churning epoch.\n return textEncoder.encode(\n `${recordPrefix}caps:{\"acceptsDeltaFrames\":true,\"styleAppend\":true}\\n`\n );\n}\n\nexport function encodeResyncControlMessage(\n request: WebHostResyncRequest\n): Uint8Array {\n const payload = request.scope === \"keyframe\"\n ? { scope: \"keyframe\" }\n : {\n scope: \"images\",\n ...(request.ids === undefined ? {} : { ids: request.ids }),\n };\n return textEncoder.encode(\n `${recordPrefix}resync:${JSON.stringify(payload)}\\n`\n );\n}\n\nexport function encodeKeyInputMessage(\n input: WebHostKeyInput\n): Uint8Array {\n const modifiers = Math.max(0, Math.round(input.modifiers ?? 0));\n if (input.key === \"character\") {\n return textEncoder.encode(\n `${recordPrefix}key:character:${encodeURIComponent(input.character ?? \"\")}:${modifiers}\\n`\n );\n }\n return textEncoder.encode(`${recordPrefix}key:${input.key}:${modifiers}\\n`);\n}\n\nexport function encodePasteInputMessage(\n text: string\n): Uint8Array {\n return textEncoder.encode(`${recordPrefix}paste:${encodeURIComponent(text)}\\n`);\n}\n\nexport function encodeMouseInputMessage(\n input: WebHostMouseInput\n): Uint8Array {\n return textEncoder.encode(\n recordPrefix + [\n \"mouse\",\n input.kind,\n formatCellCoordinate(input.x),\n formatCellCoordinate(input.y),\n input.button ?? \"none\",\n Math.round(input.deltaX ?? 0),\n Math.round(input.deltaY ?? 0),\n Math.max(0, Math.round(input.modifiers ?? 0)),\n ].join(\":\") + \"\\n\"\n );\n}\n\nfunction formatCellCoordinate(\n value: number\n): string {\n return Number.isFinite(value) ? String(value) : \"0\";\n}\n\nfunction isWebHostSurfaceFrame(\n value: unknown\n): value is WebHostSurfaceFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceFrame>;\n return (frame.version === 1 || frame.version === 2)\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.rows)\n && frame.rows.every(isWebHostSurfaceRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\nfunction isWebHostSurfaceDeltaFrame(\n value: unknown\n): value is WebHostSurfaceDeltaFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceDeltaFrame>;\n return frame.version === 3\n && frame.encoding === \"delta\"\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.deltaRows)\n && frame.deltaRows.every(isWebHostSurfaceDeltaRow)\n && isOptionalSafeInteger(frame.baselineGen)\n && isOptionalSafeInteger(frame.stylesBase)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\n/**\n * The F19 additive fields shared by the full and delta record shapes. Absent\n * means \"feature not present\" — servers older than the field omit it.\n */\nfunction hasValidAdditiveFrameFields(\n frame: Partial<WebHostSurfaceFrame> | Partial<WebHostSurfaceDeltaFrame>\n): boolean {\n return isOptionalSafeInteger(frame.epoch)\n && isOptionalSafeInteger(frame.gen)\n && (frame.links === undefined || isWebHostSurfaceLinks(frame.links))\n && (frame.linkTargets === undefined || isWebHostSurfaceLinkTargets(frame.linkTargets))\n && (\n frame.focusPresentation === undefined\n || isWebHostFocusPresentation(frame.focusPresentation)\n )\n && (\n frame.preferredGridWidth === undefined\n || (Number.isSafeInteger(frame.preferredGridWidth) && frame.preferredGridWidth >= 0)\n )\n && (\n frame.preferredGridHeight === undefined\n || (Number.isSafeInteger(frame.preferredGridHeight) && frame.preferredGridHeight >= 0)\n );\n}\n\nfunction isOptionalSafeInteger(\n value: unknown\n): boolean {\n return value === undefined || Number.isSafeInteger(value);\n}\n\nfunction isWebHostSurfaceLinks(\n value: unknown\n): value is WebHostSurfaceLinkRow[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceLinkRow);\n}\n\nfunction isWebHostSurfaceLinkRow(\n value: unknown\n): value is WebHostSurfaceLinkRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceLinkRun);\n}\n\nfunction isWebHostSurfaceLinkRun(\n value: unknown\n): value is WebHostSurfaceLinkRun {\n if (!Array.isArray(value) || value.length !== 3) {\n return false;\n }\n const [x, span, targetIndex] = value as number[];\n return Number.isSafeInteger(x)\n && x >= 0\n && Number.isSafeInteger(span)\n && span >= 1\n && Number.isSafeInteger(targetIndex)\n && targetIndex >= 0;\n}\n\nfunction isWebHostSurfaceLinkTargets(\n value: unknown\n): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction isWebHostFocusPresentation(\n value: unknown\n): value is WebHostFocusPresentation {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const presentation = value as Partial<WebHostFocusPresentation>;\n return (\n presentation.focusedIdentity === undefined\n || typeof presentation.focusedIdentity === \"string\"\n )\n && typeof presentation.semantics === \"string\"\n && typeof presentation.prefersTextInput === \"boolean\"\n && typeof presentation.hasFocusedRegion === \"boolean\";\n}\n\nfunction isWebHostSurfaceDeltaRow(\n value: unknown\n): value is WebHostSurfaceDeltaRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && isWebHostSurfaceRow(value[1]);\n}\n\nfunction isWebHostSurfaceRow(\n value: unknown\n): value is WebHostSurfaceCell[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceCell);\n}\n\nfunction isWebHostSurfaceCell(\n value: unknown\n): value is WebHostSurfaceCell {\n return Array.isArray(value)\n && value.length === 4\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && typeof value[1] === \"string\"\n && Number.isSafeInteger(value[2])\n && value[2] >= 1\n && Number.isSafeInteger(value[3])\n && value[3] >= 0;\n}\n\nfunction isWebHostAccessibilityNodes(\n value: unknown\n): value is WebHostAccessibilityNode[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityNode);\n}\n\nfunction isWebHostAccessibilityNode(\n value: unknown\n): value is WebHostAccessibilityNode {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const node = value as Partial<WebHostAccessibilityNode>;\n return typeof node.id === \"string\"\n && (node.parentId === undefined || typeof node.parentId === \"string\")\n && isWebHostSurfaceRect(node.rect)\n && typeof node.role === \"string\"\n && (node.label === undefined || typeof node.label === \"string\")\n && (node.hint === undefined || typeof node.hint === \"string\")\n && (node.hidden === undefined || typeof node.hidden === \"boolean\")\n && (node.liveRegion === undefined || typeof node.liveRegion === \"string\")\n && (node.cursorAnchor === undefined || isWebHostAccessibilityPoint(node.cursorAnchor))\n && (node.isFocused === undefined || typeof node.isFocused === \"boolean\");\n}\n\nfunction isWebHostAccessibilityPoint(\n value: unknown\n): value is WebHostAccessibilityPoint {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostAccessibilityAnnouncements(\n value: unknown\n): value is WebHostAccessibilityAnnouncement[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityAnnouncement);\n}\n\nfunction isWebHostAccessibilityAnnouncement(\n value: unknown\n): value is WebHostAccessibilityAnnouncement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const announcement = value as Partial<WebHostAccessibilityAnnouncement>;\n return typeof announcement.message === \"string\"\n && typeof announcement.politeness === \"string\";\n}\n\nfunction isWebHostSurfaceImages(\n value: unknown\n): value is WebHostSurfaceImage[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceImage);\n}\n\nfunction isWebHostSurfaceImage(\n value: unknown\n): value is WebHostSurfaceImage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const image = value as Partial<WebHostSurfaceImage>;\n return typeof image.id === \"string\"\n && isWebHostSurfaceImageFormat(image.format)\n && isWebHostSurfaceRect(image.bounds)\n && isWebHostSurfaceRect(image.visibleBounds)\n && isWebHostSurfaceScalingMode(image.scalingMode)\n && (image.pixelSize === undefined || isWebHostSurfaceSize(image.pixelSize))\n && (image.dataBase64 === undefined || typeof image.dataBase64 === \"string\");\n}\n\nfunction isWebHostSurfaceDamage(\n value: unknown\n): value is WebHostSurfaceDamage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const damage = value as Partial<WebHostSurfaceDamage>;\n return Array.isArray(damage.textRows)\n && damage.textRows.every(isWebHostSurfaceDamageTextRow)\n && typeof damage.requiresFullTextRepaint === \"boolean\"\n && typeof damage.requiresFullGraphicsReplay === \"boolean\";\n}\n\nfunction isWebHostSurfaceDamageTextRow(\n value: unknown\n): value is WebHostSurfaceDamageTextRow {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceDamageRange);\n}\n\nfunction isWebHostSurfaceDamageRange(\n value: unknown\n): value is WebHostSurfaceDamageRange {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && typeof value[1] === \"number\";\n}\n\nfunction isWebHostSurfaceImageFormat(\n value: unknown\n): value is WebHostSurfaceImageFormat {\n return typeof value === \"string\";\n}\n\nfunction isWebHostScrollRegions(\n value: unknown\n): value is WebHostScrollRegion[] {\n return Array.isArray(value) && value.every(isWebHostScrollRegion);\n}\n\nfunction isWebHostScrollRegion(\n value: unknown\n): value is WebHostScrollRegion {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const region = value as Partial<WebHostScrollRegion>;\n return typeof region.id === \"string\"\n && isWebHostSurfaceRect(region.rect)\n && isWebHostSurfaceSize(region.offset)\n && isWebHostSurfaceSize(region.content);\n}\n\nfunction isWebHostSurfaceRect(\n value: unknown\n): value is WebHostSurfaceRect {\n return Array.isArray(value)\n && value.length === 4\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceSize(\n value: unknown\n): value is WebHostSurfaceSize {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceScalingMode(\n value: unknown\n): value is WebHostSurfaceImage[\"scalingMode\"] {\n return typeof value === \"string\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebHostSurfaceTransport.js","names":[],"sources":["../../src/WebHostSurfaceTransport.ts"],"sourcesContent":["/**\n * Browser consumer for SwiftTUI's host wire. The normative cross-host\n * contract lives upstream:\n * https://github.com/SwiftTUI/swift-tui/blob/main/docs/HOST-WIRE-CONTRACT.md\n */\nimport {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"./WebHostTerminalStyle.ts\";\n\nexport interface WebHostSurfaceStyle {\n fg?: string;\n bg?: string;\n em?: number;\n underline?: WebHostSurfaceLineStyle;\n strikethrough?: WebHostSurfaceLineStyle;\n opacity?: number;\n}\n\nexport interface WebHostSurfaceLineStyle {\n pattern: \"solid\" | \"dot\" | \"dash\" | \"dashDot\" | \"dashDotDot\" | \"double\" | \"curly\";\n color?: string;\n}\n\nexport type WebHostSurfaceCell = [\n x: number,\n text: string,\n span: number,\n styleIndex: number,\n];\n\nexport type WebHostSurfaceRect = [\n x: number,\n y: number,\n width: number,\n height: number,\n];\n\nexport type WebHostSurfaceSize = [\n width: number,\n height: number,\n];\n\nexport type WebHostAccessibilityPoint = [\n x: number,\n y: number,\n];\n\nexport type WebHostAccessibilityLiveRegion = string;\n\nexport interface WebHostAccessibilityNode {\n id: string;\n parentId?: string;\n rect: WebHostSurfaceRect;\n role: string;\n label?: string;\n hint?: string;\n /** omitted when false */\n hidden?: boolean;\n liveRegion?: WebHostAccessibilityLiveRegion;\n cursorAnchor?: WebHostAccessibilityPoint;\n isFocused?: boolean;\n}\n\n/**\n * One hyperlink run within a row: [x, spanWidth, linkTargetIndex]. The index\n * points into the frame's deduplicated `linkTargets` table.\n */\nexport type WebHostSurfaceLinkRun = [\n x: number,\n span: number,\n targetIndex: number,\n];\n\n/** Hyperlink runs for one row: [rowIndex, runs]. */\nexport type WebHostSurfaceLinkRow = [\n row: number,\n runs: WebHostSurfaceLinkRun[],\n];\n\nexport type WebHostFocusSemantics = string;\n\n/**\n * The settled focus presentation for a committed frame — the same derivation\n * the Android host consumes (`prefersTextInput` gates its IME).\n */\nexport interface WebHostFocusPresentation {\n focusedIdentity?: string;\n semantics: WebHostFocusSemantics;\n prefersTextInput: boolean;\n hasFocusedRegion: boolean;\n}\n\nexport interface WebHostAccessibilityAnnouncement {\n message: string;\n politeness: WebHostAccessibilityLiveRegion;\n}\n\nexport type WebHostSurfaceImageFormat = string;\n\n/**\n * Reports locally unresolved image IDs to a recovery-capable host. The first\n * branch lets bounded hosts return the admitted subset; the explicit legacy\n * void branch preserves contextual typing for concise callbacks that return\n * incidental values such as `array.push(...)`.\n */\nexport type WebHostImagePayloadRequestHandler =\n | ((ids: readonly string[]) => readonly string[])\n | ((ids: readonly string[]) => void);\n\nexport interface WebHostSurfaceImage {\n id: string;\n format: WebHostSurfaceImageFormat;\n bounds: WebHostSurfaceRect;\n visibleBounds: WebHostSurfaceRect;\n scalingMode: string;\n pixelSize?: WebHostSurfaceSize;\n dataBase64?: string;\n}\n\nexport type WebHostSurfaceDamageRange = [\n start: number,\n end: number,\n];\n\nexport type WebHostSurfaceDamageTextRow = [\n row: number,\n ranges: WebHostSurfaceDamageRange[],\n];\n\nexport interface WebHostSurfaceDamage {\n textRows: WebHostSurfaceDamageTextRow[];\n requiresFullTextRepaint: boolean;\n requiresFullGraphicsReplay: boolean;\n}\n\n/**\n * Per-region scroll extent published with each frame so the host can implement\n * scroll-chaining: capture the wheel only while the region under the pointer can\n * still scroll in the wheel's direction, otherwise let it fall through to the\n * page. The host recomputes the per-direction headroom from `offset`/`content`/\n * the viewport `rect`, mirroring SwiftTUI's\n * `min(max(0, offset), max(0, content - viewport))` clamp.\n */\nexport interface WebHostScrollRegion {\n /** identity path — same key space as accessibility node ids */\n id: string;\n /** viewport rect in cells: [x, y, width, height] */\n rect: WebHostSurfaceRect;\n /** current clamped scroll offset in cells: [x, y] */\n offset: WebHostAccessibilityPoint;\n /** total content size in cells: [width, height] */\n content: WebHostSurfaceSize;\n}\n\nexport interface WebHostSurfaceFrame {\n version: 1 | 2;\n epoch?: number;\n gen?: number;\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n rows: WebHostSurfaceCell[][];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport type WebHostSurfaceDeltaRow = [\n row: number,\n cells: WebHostSurfaceCell[],\n];\n\nexport interface WebHostSurfaceDeltaFrame {\n version: 3;\n encoding: \"delta\";\n epoch?: number;\n gen?: number;\n baselineGen?: number;\n sequence?: number;\n width: number;\n height: number;\n /**\n * With `stylesBase` present, only the styles this record *added*: index `i`\n * of this array is table index `stylesBase + i`. Absent `stylesBase`, the\n * complete accumulated table, as deployed decoders expect.\n */\n styles: Array<WebHostSurfaceStyle | null>;\n /**\n * Where `styles` splices onto the retained table. Present only when the\n * client declared `styleAppend`, which is why it is negotiated rather than\n * additive: reading `styles` as a whole table when this is present\n * mis-indexes every style in the record.\n */\n stylesBase?: number;\n deltaRows: WebHostSurfaceDeltaRow[];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport interface WebHostRuntimeIssue {\n severity: \"warning\" | \"error\";\n code: string;\n message: string;\n description: string;\n identity?: string;\n source?: string;\n}\n\nexport interface WebHostFrameDiagnosticRecord {\n format: \"swift-tui-frame-diagnostics-v1\";\n header: string[];\n fields: string[];\n}\n\nexport type WebHostOutputRecord =\n | { type: \"surface\"; frame: WebHostSurfaceFrame }\n | { type: \"clipboard\"; text: string }\n | { type: \"runtimeIssue\"; issue: WebHostRuntimeIssue }\n | { type: \"frameDiagnostic\"; diagnostic: WebHostFrameDiagnosticRecord }\n | { type: \"surfaceDropped\"; reason: \"noBaseline\" | \"staleBaseline\" }\n | { type: \"text\"; text: string };\n\nexport type WebHostResyncRequest =\n | { scope: \"keyframe\" }\n | { scope: \"images\"; ids?: string[] };\n\nexport interface WebHostOutputSink {\n presentSurface(\n frame: WebHostSurfaceFrame,\n recoveredImagePayloadIds?: readonly string[]\n ): void;\n writeClipboard?(text: string): void | Promise<void>;\n notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;\n recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;\n writeOutput?(text: string): void;\n writeError?(text: string): void;\n}\n\nexport interface WebHostKeyInput {\n key:\n | \"return\"\n | \"space\"\n | \"tab\"\n | \"arrowLeft\"\n | \"arrowRight\"\n | \"arrowUp\"\n | \"arrowDown\"\n | \"backspace\"\n | \"escape\"\n | \"home\"\n | \"end\"\n | \"character\";\n character?: string;\n modifiers?: number;\n}\n\nexport interface WebHostMouseInput {\n kind: \"down\" | \"up\" | \"moved\" | \"dragged\" | \"scrolled\";\n x: number;\n y: number;\n button?: \"primary\" | \"middle\" | \"secondary\";\n deltaX?: number;\n deltaY?: number;\n modifiers?: number;\n}\n\nconst recordPrefix = \"\\u001E\";\nconst textEncoder = new TextEncoder();\n\n/** Limits wire-derived recovery state and keeps every single-ID WASI request bounded. */\nexport const MAX_IMAGE_RECOVERY_ID_BYTES = 1_024;\nexport const MAX_OUTSTANDING_IMAGE_RECOVERY_IDS = 1_024;\n\nexport function isWebHostImageRecoveryId(\n id: string\n): boolean {\n return id.length > 0\n && id.length <= MAX_IMAGE_RECOVERY_ID_BYTES\n && textEncoder.encode(id).byteLength <= MAX_IMAGE_RECOVERY_ID_BYTES;\n}\n\n/**\n * The newest `surface` record version this runtime understands. Unknown\n * additive object keys are ignored by design (older runtimes render newer\n * frames), but a frame declaring a NEWER version than this is surfaced as an\n * error-severity runtime issue instead of silently degrading to a text\n * diagnostic — silent version skew is the failure mode this guards against\n * (F57).\n */\nexport const SUPPORTED_SURFACE_VERSION = 3;\n\nexport class WebHostOutputDecoder {\n private readonly textDecoder = new TextDecoder();\n private bufferedText = \"\";\n private lastSurfaceFrame?: WebHostSurfaceFrame;\n private lastEpoch?: number;\n private lastGen?: number;\n private lastPresentedEpoch?: number;\n private keyframeResyncOutstanding = false;\n private keyframeResyncPending = false;\n private readonly imageResyncOutstandingIds = new Set<string>();\n private readonly imageResyncPendingIds = new Set<string>();\n\n feed(\n chunk: Uint8Array\n ): WebHostOutputRecord[] {\n this.bufferedText += this.textDecoder.decode(chunk, { stream: true });\n const records: WebHostOutputRecord[] = [];\n\n while (true) {\n const newlineIndex = this.bufferedText.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n break;\n }\n\n const line = this.bufferedText.slice(0, newlineIndex);\n this.bufferedText = this.bufferedText.slice(newlineIndex + 1);\n records.push(this.decodeLine(line));\n }\n\n if (this.bufferedText.length > 4096 && !this.bufferedText.startsWith(recordPrefix)) {\n records.push({ type: \"text\", text: this.bufferedText });\n this.bufferedText = \"\";\n }\n\n return records;\n }\n\n flush(): WebHostOutputRecord[] {\n if (!this.bufferedText) {\n return [];\n }\n const text = this.bufferedText;\n this.bufferedText = \"\";\n return [this.decodeLine(text)];\n }\n\n takeResyncRequest(\n maximumEncodedBytes?: number\n ): WebHostResyncRequest | undefined {\n if (this.keyframeResyncPending) {\n this.keyframeResyncPending = false;\n return { scope: \"keyframe\" };\n }\n if (this.imageResyncPendingIds.size === 0) {\n return undefined;\n }\n\n const sortedIds = [...this.imageResyncPendingIds].sort();\n const { ids, rejectedIds } = boundedImageResyncIds(\n sortedIds,\n maximumEncodedBytes\n );\n for (const id of rejectedIds) {\n this.imageResyncPendingIds.delete(id);\n this.imageResyncOutstandingIds.delete(id);\n }\n for (const id of ids) {\n this.imageResyncPendingIds.delete(id);\n }\n if (ids.length === 0) {\n return undefined;\n }\n return { scope: \"images\", ids };\n }\n\n /**\n * Adds locally unresolved image IDs to this epoch's recovery set. IDs remain\n * outstanding after delivery, so repeat painter misses cannot create storms;\n * a payload-bearing record or epoch re-anchor releases them. Returns the IDs\n * now tracked (new or already outstanding); callers should suppress repeats\n * only for this admitted subset.\n */\n requestImagePayloads(\n ids: Iterable<string>\n ): readonly string[] {\n const acceptedIds = new Set<string>();\n for (const id of ids) {\n if (!isWebHostImageRecoveryId(id)) {\n continue;\n }\n if (this.imageResyncOutstandingIds.has(id)) {\n acceptedIds.add(id);\n continue;\n }\n if (\n this.imageResyncOutstandingIds.size\n >= MAX_OUTSTANDING_IMAGE_RECOVERY_IDS\n ) {\n continue;\n }\n this.imageResyncOutstandingIds.add(id);\n this.imageResyncPendingIds.add(id);\n acceptedIds.add(id);\n }\n return [...acceptedIds];\n }\n\n /**\n * Advances image recovery immediately before one decoded surface reaches its\n * presenter. Payload arrival and epoch reset therefore follow delivery order\n * rather than `feed`'s parse-ahead order across a multi-record chunk. The\n * return value identifies payloads that answer an outstanding image request,\n * so presenters can open exactly one fresh local decode generation even when\n * content-addressed retransmission uses identical bytes.\n */\n prepareToPresentSurface(\n frame: WebHostSurfaceFrame\n ): readonly string[] {\n const recoveredImagePayloadIds = this.recoveredImagePayloadIds(frame.images);\n this.resetImageResyncForEpoch(frame.epoch);\n this.sweepImageResyncForPresentedImages(frame.images);\n this.clearArrivedImagePayloads(frame.images);\n if (frame.epoch !== undefined) {\n this.lastPresentedEpoch = frame.epoch;\n }\n return recoveredImagePayloadIds;\n }\n\n resyncRequestDeliveryFailed(\n request: WebHostResyncRequest\n ): void {\n if (request.scope === \"keyframe\") {\n if (this.keyframeResyncOutstanding) {\n this.keyframeResyncPending = true;\n }\n return;\n }\n\n for (const id of request.ids ?? []) {\n if (this.imageResyncOutstandingIds.has(id)) {\n this.imageResyncPendingIds.add(id);\n }\n }\n }\n\n private decodeLine(\n line: string\n ): WebHostOutputRecord {\n if (line.startsWith(`${recordPrefix}clipboard:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}clipboard:`.length));\n if (isWebHostClipboardRecord(record)) {\n return { type: \"clipboard\", text: record.text };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}runtimeIssue:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}runtimeIssue:`.length));\n if (isWebHostRuntimeIssue(record)) {\n return { type: \"runtimeIssue\", issue: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}frameDiagnostic:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}frameDiagnostic:`.length));\n if (isWebHostFrameDiagnosticRecord(record)) {\n return { type: \"frameDiagnostic\", diagnostic: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (!line.startsWith(`${recordPrefix}surface:`)) {\n return { type: \"text\", text: `${line}\\n` };\n }\n\n try {\n const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));\n if (declaresNewerSurfaceVersion(frame)) {\n return {\n type: \"runtimeIssue\",\n issue: {\n severity: \"error\",\n code: \"surface.unsupportedVersion\",\n message: `SwiftTUI surface version ${frame.version} is newer than the supported ${SUPPORTED_SURFACE_VERSION}`,\n description: \"The app emitted a surface record with version \"\n + `${frame.version}, but this @swifttui/web runtime understands `\n + `versions up to ${SUPPORTED_SURFACE_VERSION}. Update @swifttui/web `\n + \"to render it.\",\n },\n };\n }\n if (isWebHostSurfaceFrame(frame)) {\n this.lastSurfaceFrame = frame;\n this.lastEpoch = frame.epoch;\n this.lastGen = frame.gen;\n this.keyframeResyncOutstanding = false;\n this.keyframeResyncPending = false;\n return { type: \"surface\", frame };\n }\n if (isWebHostSurfaceDeltaFrame(frame)) {\n const carriesDeliveryStamps = frame.epoch !== undefined\n || frame.gen !== undefined\n || frame.baselineGen !== undefined;\n if (\n !this.lastSurfaceFrame\n || this.lastSurfaceFrame.width !== frame.width\n || this.lastSurfaceFrame.height !== frame.height\n ) {\n if (carriesDeliveryStamps) {\n this.requestKeyframeResync();\n }\n return { type: \"surfaceDropped\", reason: \"noBaseline\" };\n }\n if (\n carriesDeliveryStamps\n && (\n frame.epoch === undefined\n || frame.gen === undefined\n || frame.baselineGen === undefined\n || frame.epoch !== this.lastEpoch\n || frame.baselineGen !== this.lastGen\n )\n ) {\n this.requestKeyframeResync();\n return { type: \"surfaceDropped\", reason: \"staleBaseline\" };\n }\n const materialized = this.materializeDeltaFrame(frame);\n if (materialized) {\n this.lastSurfaceFrame = materialized;\n this.lastEpoch = frame.epoch;\n this.lastGen = frame.gen;\n return { type: \"surface\", frame: materialized };\n }\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n private requestKeyframeResync(): void {\n if (this.keyframeResyncOutstanding) {\n return;\n }\n this.keyframeResyncOutstanding = true;\n this.keyframeResyncPending = true;\n }\n\n private resetImageResyncForEpoch(\n epoch: number | undefined\n ): void {\n if (epoch === undefined || epoch === this.lastPresentedEpoch) {\n return;\n }\n this.imageResyncOutstandingIds.clear();\n this.imageResyncPendingIds.clear();\n }\n\n private clearArrivedImagePayloads(\n images: WebHostSurfaceImage[] | undefined\n ): void {\n for (const image of images ?? []) {\n if (image.dataBase64 === undefined) {\n continue;\n }\n this.imageResyncOutstandingIds.delete(image.id);\n this.imageResyncPendingIds.delete(image.id);\n }\n }\n\n private recoveredImagePayloadIds(\n images: WebHostSurfaceImage[] | undefined\n ): string[] {\n const recoveredIds = new Set<string>();\n for (const image of images ?? []) {\n if (\n image.dataBase64 !== undefined\n && this.imageResyncOutstandingIds.has(image.id)\n ) {\n recoveredIds.add(image.id);\n }\n }\n return [...recoveredIds].sort();\n }\n\n private sweepImageResyncForPresentedImages(\n images: WebHostSurfaceImage[] | undefined\n ): void {\n const presentedIds = new Set<string>();\n for (const image of images ?? []) {\n if (this.imageResyncOutstandingIds.has(image.id)) {\n presentedIds.add(image.id);\n }\n }\n for (const id of this.imageResyncOutstandingIds) {\n if (presentedIds.has(id)) {\n continue;\n }\n this.imageResyncOutstandingIds.delete(id);\n this.imageResyncPendingIds.delete(id);\n }\n }\n\n private materializeDeltaFrame(\n frame: WebHostSurfaceDeltaFrame\n ): WebHostSurfaceFrame | undefined {\n const baseline = this.lastSurfaceFrame;\n if (!baseline) {\n return undefined;\n }\n\n const styles = this.materializeDeltaStyles(frame, baseline);\n if (!styles) {\n return undefined;\n }\n\n const rows = baseline.rows.slice();\n for (const [row, cells] of frame.deltaRows) {\n if (!Number.isSafeInteger(row) || row < 0 || row >= frame.height) {\n return undefined;\n }\n rows[row] = cells;\n }\n\n return {\n version: baseline.version,\n epoch: frame.epoch,\n gen: frame.gen,\n sequence: frame.sequence,\n width: frame.width,\n height: frame.height,\n styles,\n rows,\n images: frame.images,\n damage: frame.damage,\n accessibilityTree: frame.accessibilityTree,\n accessibilityAnnouncements: frame.accessibilityAnnouncements,\n scrollRegions: frame.scrollRegions,\n links: frame.links,\n linkTargets: frame.linkTargets,\n focusPresentation: frame.focusPresentation,\n preferredGridWidth: frame.preferredGridWidth,\n preferredGridHeight: frame.preferredGridHeight,\n };\n }\n\n /**\n * The delta's style table: either the record's own complete table, or the\n * negotiated append spliced onto the baseline's.\n *\n * A `stylesBase` that does not name the end of the retained table is a\n * structural break, not a recoverable one — splicing at the wrong offset\n * silently repaints cells in the wrong style, which is worse than refusing\n * the record and asking for a keyframe.\n */\n private materializeDeltaStyles(\n frame: WebHostSurfaceDeltaFrame,\n baseline: WebHostSurfaceFrame\n ): Array<WebHostSurfaceStyle | null> | undefined {\n if (frame.stylesBase === undefined) {\n return frame.styles;\n }\n if (frame.stylesBase !== baseline.styles.length) {\n return undefined;\n }\n return baseline.styles.concat(frame.styles);\n }\n}\n\nfunction boundedImageResyncIds(\n sortedIds: string[],\n maximumEncodedBytes: number | undefined\n): {\n ids: string[];\n rejectedIds: string[];\n} {\n if (\n maximumEncodedBytes === undefined\n || !Number.isFinite(maximumEncodedBytes)\n ) {\n return { ids: sortedIds, rejectedIds: [] };\n }\n\n const emptyRequestBytes = encodeResyncControlMessage({\n scope: \"images\",\n ids: [],\n }).byteLength;\n const normalizedMaximum = Math.max(0, Math.floor(maximumEncodedBytes));\n const ids: string[] = [];\n const rejectedIds: string[] = [];\n let encodedBytes = emptyRequestBytes;\n for (const id of sortedIds) {\n const separatorBytes = ids.length === 0 ? 0 : 1;\n const idBytes = textEncoder.encode(JSON.stringify(id)).byteLength;\n if (encodedBytes + separatorBytes + idBytes > normalizedMaximum) {\n if (ids.length === 0) {\n rejectedIds.push(id);\n continue;\n }\n break;\n }\n ids.push(id);\n encodedBytes += separatorBytes + idBytes;\n if (encodedBytes >= normalizedMaximum) {\n break;\n }\n }\n return { ids, rejectedIds };\n}\n\nfunction declaresNewerSurfaceVersion(\n value: unknown\n): value is { version: number } {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const version = (value as { version?: unknown }).version;\n return typeof version === \"number\"\n && Number.isSafeInteger(version)\n && version > SUPPORTED_SURFACE_VERSION;\n}\n\nfunction isWebHostClipboardRecord(\n value: unknown\n): value is { text: string } {\n return !!value && typeof value === \"object\" && typeof (value as { text?: unknown }).text === \"string\";\n}\n\nfunction isWebHostRuntimeIssue(\n value: unknown\n): value is WebHostRuntimeIssue {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostRuntimeIssue>;\n return (record.severity === \"warning\" || record.severity === \"error\")\n && typeof record.code === \"string\"\n && typeof record.message === \"string\"\n && typeof record.description === \"string\"\n && (record.identity === undefined || typeof record.identity === \"string\")\n && (record.source === undefined || typeof record.source === \"string\");\n}\n\nfunction isWebHostFrameDiagnosticRecord(\n value: unknown\n): value is WebHostFrameDiagnosticRecord {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostFrameDiagnosticRecord>;\n return record.format === \"swift-tui-frame-diagnostics-v1\"\n && Array.isArray(record.header)\n && record.header.every((field) => typeof field === \"string\")\n && Array.isArray(record.fields)\n && record.fields.every((field) => typeof field === \"string\");\n}\n\nexport function encodeResizeControlMessage(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n): Uint8Array {\n const normalizedColumns = Math.max(1, Math.round(columns));\n const normalizedRows = Math.max(1, Math.round(rows));\n if (cellWidth && cellHeight) {\n return textEncoder.encode(\n `${recordPrefix}resize:${normalizedColumns}:${normalizedRows}:${Math.max(1, Math.round(cellWidth))}:${Math.max(1, Math.round(cellHeight))}\\n`\n );\n }\n\n return textEncoder.encode(`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}\\n`);\n}\n\nexport function encodeRenderStyleControlMessage(\n style: WebHostTerminalStyle\n): Uint8Array {\n const encoded = encodeWebHostTerminalRenderStyleBase64(style);\n return textEncoder.encode(`${recordPrefix}style:${encoded}\\n`);\n}\n\nexport function encodeCapabilitiesControlMessage(): Uint8Array {\n // The client's wire-capability declaration, sent once after the socket\n // opens. The declaration is truthful today: this decoder materializes v3\n // delta frames (`materializeDeltaFrame`). Byte shape and key order are\n // pinned by the cross-repo fixture Fixtures/Transport/web-caps-record.txt\n // — swift-tui's input parser consumes the identical bytes, and the\n // coordination root's transport_fixture_sync gate keeps the copies in\n // lockstep. Servers that predate the record drop it silently, so the\n // session degrades to today's full-frame defaults.\n //\n // Capabilities are named feature bits. The retired `maxWebSurfaceVersion`\n // key declared a decoder version ceiling, which was only ever read as\n // \"accepts delta or not\" and duplicated — more weakly — the version check\n // this decoder already performs on every record (SUPPORTED_SURFACE_VERSION).\n // Servers still expecting it skip unknown keys, so dropping it is safe in\n // both directions.\n //\n // `styleAppend` is declared here because `materializeDeltaStyles` splices a\n // delta's `styles` onto the retained table when `stylesBase` is present.\n // Declaring it is truthful by construction: this decoder and this record are\n // the same release. Measured at Stage SV, the full retransmit it replaces was\n // 69.7% of late-record bytes in a style-churning epoch.\n return textEncoder.encode(\n `${recordPrefix}caps:{\"acceptsDeltaFrames\":true,\"styleAppend\":true}\\n`\n );\n}\n\n/**\n * The page's pointer-paradigm declaration.\n *\n * `panning=1` tells the app that this client scrolls by dragging content\n * directly, which is how touch devices and coarse-pointer browsers behave and\n * is not how a desktop mouse behaves. The app uses it to decide whether a drag\n * on a scroll view's background pans it, and whether a drag that starts on a\n * control may be taken over by an enclosing scroll view.\n *\n * Payload is `key=value` tokens rather than positional fields so a later\n * pointer fact can join the record without changing its arity; the Swift\n * parser skips keys it does not know. Unlike `caps:`, this record is not a\n * once-per-connection epoch marker — the device can change (a tablet docked to\n * a mouse), so it may be sent again at any time. Servers that predate the\n * record drop it and keep the desktop paradigm.\n */\nexport function encodePointerCapabilitiesControlMessage(\n supportsScrollPanning: boolean\n): Uint8Array {\n return textEncoder.encode(\n `${recordPrefix}pointer:panning=${supportsScrollPanning ? 1 : 0}\\n`\n );\n}\n\nexport function encodeResyncControlMessage(\n request: WebHostResyncRequest\n): Uint8Array {\n const payload = request.scope === \"keyframe\"\n ? { scope: \"keyframe\" }\n : {\n scope: \"images\",\n ...(request.ids === undefined ? {} : { ids: request.ids }),\n };\n return textEncoder.encode(\n `${recordPrefix}resync:${JSON.stringify(payload)}\\n`\n );\n}\n\nexport function encodeKeyInputMessage(\n input: WebHostKeyInput\n): Uint8Array {\n const modifiers = Math.max(0, Math.round(input.modifiers ?? 0));\n if (input.key === \"character\") {\n return textEncoder.encode(\n `${recordPrefix}key:character:${encodeURIComponent(input.character ?? \"\")}:${modifiers}\\n`\n );\n }\n return textEncoder.encode(`${recordPrefix}key:${input.key}:${modifiers}\\n`);\n}\n\nexport function encodePasteInputMessage(\n text: string\n): Uint8Array {\n return textEncoder.encode(`${recordPrefix}paste:${encodeURIComponent(text)}\\n`);\n}\n\nexport function encodeMouseInputMessage(\n input: WebHostMouseInput\n): Uint8Array {\n return textEncoder.encode(\n recordPrefix + [\n \"mouse\",\n input.kind,\n formatCellCoordinate(input.x),\n formatCellCoordinate(input.y),\n input.button ?? \"none\",\n Math.round(input.deltaX ?? 0),\n Math.round(input.deltaY ?? 0),\n Math.max(0, Math.round(input.modifiers ?? 0)),\n ].join(\":\") + \"\\n\"\n );\n}\n\nfunction formatCellCoordinate(\n value: number\n): string {\n return Number.isFinite(value) ? String(value) : \"0\";\n}\n\nfunction isWebHostSurfaceFrame(\n value: unknown\n): value is WebHostSurfaceFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceFrame>;\n return (frame.version === 1 || frame.version === 2)\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.rows)\n && frame.rows.every(isWebHostSurfaceRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\nfunction isWebHostSurfaceDeltaFrame(\n value: unknown\n): value is WebHostSurfaceDeltaFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceDeltaFrame>;\n return frame.version === 3\n && frame.encoding === \"delta\"\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.deltaRows)\n && frame.deltaRows.every(isWebHostSurfaceDeltaRow)\n && isOptionalSafeInteger(frame.baselineGen)\n && isOptionalSafeInteger(frame.stylesBase)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\n/**\n * The F19 additive fields shared by the full and delta record shapes. Absent\n * means \"feature not present\" — servers older than the field omit it.\n */\nfunction hasValidAdditiveFrameFields(\n frame: Partial<WebHostSurfaceFrame> | Partial<WebHostSurfaceDeltaFrame>\n): boolean {\n return isOptionalSafeInteger(frame.epoch)\n && isOptionalSafeInteger(frame.gen)\n && (frame.links === undefined || isWebHostSurfaceLinks(frame.links))\n && (frame.linkTargets === undefined || isWebHostSurfaceLinkTargets(frame.linkTargets))\n && (\n frame.focusPresentation === undefined\n || isWebHostFocusPresentation(frame.focusPresentation)\n )\n && (\n frame.preferredGridWidth === undefined\n || (Number.isSafeInteger(frame.preferredGridWidth) && frame.preferredGridWidth >= 0)\n )\n && (\n frame.preferredGridHeight === undefined\n || (Number.isSafeInteger(frame.preferredGridHeight) && frame.preferredGridHeight >= 0)\n );\n}\n\nfunction isOptionalSafeInteger(\n value: unknown\n): boolean {\n return value === undefined || Number.isSafeInteger(value);\n}\n\nfunction isWebHostSurfaceLinks(\n value: unknown\n): value is WebHostSurfaceLinkRow[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceLinkRow);\n}\n\nfunction isWebHostSurfaceLinkRow(\n value: unknown\n): value is WebHostSurfaceLinkRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceLinkRun);\n}\n\nfunction isWebHostSurfaceLinkRun(\n value: unknown\n): value is WebHostSurfaceLinkRun {\n if (!Array.isArray(value) || value.length !== 3) {\n return false;\n }\n const [x, span, targetIndex] = value as number[];\n return Number.isSafeInteger(x)\n && x >= 0\n && Number.isSafeInteger(span)\n && span >= 1\n && Number.isSafeInteger(targetIndex)\n && targetIndex >= 0;\n}\n\nfunction isWebHostSurfaceLinkTargets(\n value: unknown\n): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction isWebHostFocusPresentation(\n value: unknown\n): value is WebHostFocusPresentation {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const presentation = value as Partial<WebHostFocusPresentation>;\n return (\n presentation.focusedIdentity === undefined\n || typeof presentation.focusedIdentity === \"string\"\n )\n && typeof presentation.semantics === \"string\"\n && typeof presentation.prefersTextInput === \"boolean\"\n && typeof presentation.hasFocusedRegion === \"boolean\";\n}\n\nfunction isWebHostSurfaceDeltaRow(\n value: unknown\n): value is WebHostSurfaceDeltaRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && isWebHostSurfaceRow(value[1]);\n}\n\nfunction isWebHostSurfaceRow(\n value: unknown\n): value is WebHostSurfaceCell[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceCell);\n}\n\nfunction isWebHostSurfaceCell(\n value: unknown\n): value is WebHostSurfaceCell {\n return Array.isArray(value)\n && value.length === 4\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && typeof value[1] === \"string\"\n && Number.isSafeInteger(value[2])\n && value[2] >= 1\n && Number.isSafeInteger(value[3])\n && value[3] >= 0;\n}\n\nfunction isWebHostAccessibilityNodes(\n value: unknown\n): value is WebHostAccessibilityNode[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityNode);\n}\n\nfunction isWebHostAccessibilityNode(\n value: unknown\n): value is WebHostAccessibilityNode {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const node = value as Partial<WebHostAccessibilityNode>;\n return typeof node.id === \"string\"\n && (node.parentId === undefined || typeof node.parentId === \"string\")\n && isWebHostSurfaceRect(node.rect)\n && typeof node.role === \"string\"\n && (node.label === undefined || typeof node.label === \"string\")\n && (node.hint === undefined || typeof node.hint === \"string\")\n && (node.hidden === undefined || typeof node.hidden === \"boolean\")\n && (node.liveRegion === undefined || typeof node.liveRegion === \"string\")\n && (node.cursorAnchor === undefined || isWebHostAccessibilityPoint(node.cursorAnchor))\n && (node.isFocused === undefined || typeof node.isFocused === \"boolean\");\n}\n\nfunction isWebHostAccessibilityPoint(\n value: unknown\n): value is WebHostAccessibilityPoint {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostAccessibilityAnnouncements(\n value: unknown\n): value is WebHostAccessibilityAnnouncement[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityAnnouncement);\n}\n\nfunction isWebHostAccessibilityAnnouncement(\n value: unknown\n): value is WebHostAccessibilityAnnouncement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const announcement = value as Partial<WebHostAccessibilityAnnouncement>;\n return typeof announcement.message === \"string\"\n && typeof announcement.politeness === \"string\";\n}\n\nfunction isWebHostSurfaceImages(\n value: unknown\n): value is WebHostSurfaceImage[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceImage);\n}\n\nfunction isWebHostSurfaceImage(\n value: unknown\n): value is WebHostSurfaceImage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const image = value as Partial<WebHostSurfaceImage>;\n return typeof image.id === \"string\"\n && isWebHostSurfaceImageFormat(image.format)\n && isWebHostSurfaceRect(image.bounds)\n && isWebHostSurfaceRect(image.visibleBounds)\n && isWebHostSurfaceScalingMode(image.scalingMode)\n && (image.pixelSize === undefined || isWebHostSurfaceSize(image.pixelSize))\n && (image.dataBase64 === undefined || typeof image.dataBase64 === \"string\");\n}\n\nfunction isWebHostSurfaceDamage(\n value: unknown\n): value is WebHostSurfaceDamage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const damage = value as Partial<WebHostSurfaceDamage>;\n return Array.isArray(damage.textRows)\n && damage.textRows.every(isWebHostSurfaceDamageTextRow)\n && typeof damage.requiresFullTextRepaint === \"boolean\"\n && typeof damage.requiresFullGraphicsReplay === \"boolean\";\n}\n\nfunction isWebHostSurfaceDamageTextRow(\n value: unknown\n): value is WebHostSurfaceDamageTextRow {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceDamageRange);\n}\n\nfunction isWebHostSurfaceDamageRange(\n value: unknown\n): value is WebHostSurfaceDamageRange {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && typeof value[1] === \"number\";\n}\n\nfunction isWebHostSurfaceImageFormat(\n value: unknown\n): value is WebHostSurfaceImageFormat {\n return typeof value === \"string\";\n}\n\nfunction isWebHostScrollRegions(\n value: unknown\n): value is WebHostScrollRegion[] {\n return Array.isArray(value) && value.every(isWebHostScrollRegion);\n}\n\nfunction isWebHostScrollRegion(\n value: unknown\n): value is WebHostScrollRegion {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const region = value as Partial<WebHostScrollRegion>;\n return typeof region.id === \"string\"\n && isWebHostSurfaceRect(region.rect)\n && isWebHostSurfaceSize(region.offset)\n && isWebHostSurfaceSize(region.content);\n}\n\nfunction isWebHostSurfaceRect(\n value: unknown\n): value is WebHostSurfaceRect {\n return Array.isArray(value)\n && value.length === 4\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceSize(\n value: unknown\n): value is WebHostSurfaceSize {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceScalingMode(\n value: unknown\n): value is WebHostSurfaceImage[\"scalingMode\"] {\n return typeof value === \"string\";\n}\n"],"mappings":"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"}
|
|
@@ -40,6 +40,7 @@ declare class WebSocketSceneBridge implements WebHostSceneBridge {
|
|
|
40
40
|
bindOutput(sink: WebHostOutputSink): void;
|
|
41
41
|
resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;
|
|
42
42
|
updateRenderStyle(style: WebHostTerminalStyle): void;
|
|
43
|
+
updatePointerCapabilities(supportsScrollPanning: boolean): void;
|
|
43
44
|
sendInput(chunk: Uint8Array): void;
|
|
44
45
|
requestImagePayloads(ids: readonly string[]): readonly string[];
|
|
45
46
|
dispose(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "./WebHostSurfaceTransport.js";
|
|
1
|
+
import { WebHostOutputDecoder, encodeCapabilitiesControlMessage, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "./WebHostSurfaceTransport.js";
|
|
2
2
|
//#region src/WebSocketSceneBridge.ts
|
|
3
3
|
const socketOpenState = 1;
|
|
4
4
|
const textEncoder = new TextEncoder();
|
|
@@ -40,6 +40,9 @@ var WebSocketSceneBridge = class {
|
|
|
40
40
|
updateRenderStyle(style) {
|
|
41
41
|
this.sendInput(encodeRenderStyleControlMessage(style));
|
|
42
42
|
}
|
|
43
|
+
updatePointerCapabilities(supportsScrollPanning) {
|
|
44
|
+
this.sendInput(encodePointerCapabilitiesControlMessage(supportsScrollPanning));
|
|
45
|
+
}
|
|
43
46
|
sendInput(chunk) {
|
|
44
47
|
if (this.disposed) return;
|
|
45
48
|
const copy = new Uint8Array(chunk);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebSocketSceneBridge.js","names":[],"sources":["../../src/WebSocketSceneBridge.ts"],"sourcesContent":["import {\n WebHostOutputDecoder,\n encodeCapabilitiesControlMessage,\n encodeResyncControlMessage,\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n type WebHostOutputRecord,\n type WebHostOutputSink,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostTerminalStyle } from \"./WebHostTerminalStyle.ts\";\nimport type { WebHostSceneBridge } from \"./WebHostSceneRuntime.ts\";\n\nexport interface WebSocketSceneBridgeOptions {\n sceneId: string;\n token: string;\n baseURL?: string | URL;\n webSocketURL?: string | URL;\n webSocketFactory?: WebSocketSceneBridgeFactory;\n}\n\nexport type WebSocketSceneBridgeFactory = (url: string | URL) => WebSocketSceneSocket;\n\nexport interface WebSocketSceneSocket {\n binaryType: BinaryType;\n readonly readyState: number;\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", listener: (event: Event) => void): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n addEventListener(type: \"close\", listener: (event: CloseEvent) => void): void;\n addEventListener(type: \"error\", listener: (event: Event) => void): void;\n removeEventListener(type: \"open\", listener: (event: Event) => void): void;\n removeEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n removeEventListener(type: \"close\", listener: (event: CloseEvent) => void): void;\n removeEventListener(type: \"error\", listener: (event: Event) => void): void;\n}\n\nconst socketOpenState = 1;\nconst textEncoder = new TextEncoder();\n\nexport class WebSocketSceneBridge implements WebHostSceneBridge {\n readonly url: URL;\n\n private readonly socket: WebSocketSceneSocket;\n private readonly decoder = new WebHostOutputDecoder();\n private readonly queuedInput: Uint8Array[] = [];\n private readonly queuedOutput: WebHostOutputRecord[] = [];\n private sink?: WebHostOutputSink;\n private disposed = false;\n\n private readonly handleOpen = () => {\n this.flushQueuedInput();\n };\n\n private readonly handleMessage = (event: MessageEvent) => {\n void this.receive(event.data);\n };\n\n private readonly handleClose = () => {\n for (const record of this.decoder.flush()) {\n this.deliver(record);\n }\n };\n\n private readonly handleError = () => {};\n\n constructor(options: WebSocketSceneBridgeOptions) {\n this.url = webSocketSceneURL(options);\n this.socket = (options.webSocketFactory ?? defaultWebSocketFactory)(this.url);\n this.socket.binaryType = \"arraybuffer\";\n this.socket.addEventListener(\"open\", this.handleOpen);\n this.socket.addEventListener(\"message\", this.handleMessage);\n this.socket.addEventListener(\"close\", this.handleClose);\n this.socket.addEventListener(\"error\", this.handleError);\n // Declare wire capabilities first: queued input flushes in order on\n // open, so the declaration reaches the server ahead of any\n // resize/style/input record.\n this.sendInput(encodeCapabilitiesControlMessage());\n }\n\n bindOutput(\n sink: WebHostOutputSink\n ): void {\n this.sink = sink;\n while (this.queuedOutput.length > 0) {\n this.deliver(this.queuedOutput.shift()!);\n }\n }\n\n resize(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ): void {\n this.sendInput(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));\n }\n\n updateRenderStyle(\n style: WebHostTerminalStyle\n ): void {\n this.sendInput(encodeRenderStyleControlMessage(style));\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n if (this.disposed) {\n return;\n }\n\n const copy = new Uint8Array(chunk);\n this.queuedInput.push(copy);\n if (this.socket.readyState === socketOpenState) {\n this.flushQueuedInput();\n }\n }\n\n requestImagePayloads(\n ids: readonly string[]\n ): readonly string[] {\n if (this.disposed) {\n return [];\n }\n const acceptedIds = this.decoder.requestImagePayloads(ids);\n this.sendPendingResyncRequests();\n return acceptedIds;\n }\n\n dispose(): void {\n if (this.disposed) {\n return;\n }\n this.disposed = true;\n this.socket.removeEventListener(\"open\", this.handleOpen);\n this.socket.removeEventListener(\"message\", this.handleMessage);\n this.socket.removeEventListener(\"close\", this.handleClose);\n this.socket.removeEventListener(\"error\", this.handleError);\n this.queuedInput.length = 0;\n this.queuedOutput.length = 0;\n this.socket.close(1000, \"WebHost scene disposed\");\n }\n\n private async receive(\n message: unknown\n ): Promise<void> {\n if (this.disposed) {\n return;\n }\n\n const bytes = await bytesFromWebSocketMessage(message);\n if (!bytes) {\n return;\n }\n\n for (const record of this.decoder.feed(bytes)) {\n this.deliver(record);\n }\n this.sendPendingResyncRequests();\n }\n\n private deliver(\n record: WebHostOutputRecord\n ): void {\n const sink = this.sink;\n if (!sink) {\n this.queuedOutput.push(record);\n return;\n }\n\n switch (record.type) {\n case \"surface\":\n sink.presentSurface(\n record.frame,\n this.decoder.prepareToPresentSurface(record.frame)\n );\n break;\n case \"clipboard\":\n void sink.writeClipboard?.(record.text);\n break;\n case \"runtimeIssue\":\n sink.notifyRuntimeIssue?.(record.issue);\n break;\n case \"frameDiagnostic\":\n sink.recordFrameDiagnostic?.(record.diagnostic);\n break;\n case \"surfaceDropped\":\n break;\n case \"text\":\n sink.writeOutput?.(record.text);\n break;\n }\n }\n\n private flushQueuedInput(): void {\n if (this.disposed || this.socket.readyState !== socketOpenState) {\n return;\n }\n while (this.queuedInput.length > 0) {\n try {\n this.socket.send(this.queuedInput[0]!);\n this.queuedInput.shift();\n } catch {\n return;\n }\n }\n }\n\n private sendPendingResyncRequests(): void {\n while (true) {\n const request = this.decoder.takeResyncRequest();\n if (!request) {\n return;\n }\n try {\n this.sendInput(encodeResyncControlMessage(request));\n } catch {\n this.decoder.resyncRequestDeliveryFailed(request);\n return;\n }\n }\n }\n}\n\nexport function webSocketSceneURL(\n options: Pick<WebSocketSceneBridgeOptions, \"baseURL\" | \"webSocketURL\" | \"sceneId\" | \"token\">\n): URL {\n if (options.webSocketURL) {\n const explicit = new URL(String(options.webSocketURL), currentPageURL());\n explicit.searchParams.set(\"token\", options.token);\n return explicit;\n }\n\n const url = new URL(String(options.baseURL ?? currentPageURL()), currentPageURL());\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const basePath = url.pathname.endsWith(\"/\") ? url.pathname.slice(0, -1) : url.pathname;\n url.pathname = `${basePath}/ws/scene/${encodeURIComponent(options.sceneId)}`;\n url.search = \"\";\n url.searchParams.set(\"token\", options.token);\n return url;\n}\n\nasync function bytesFromWebSocketMessage(\n message: unknown\n): Promise<Uint8Array | undefined> {\n if (typeof message === \"string\") {\n return textEncoder.encode(message);\n }\n if (message instanceof Uint8Array) {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return new Uint8Array(message);\n }\n if (ArrayBuffer.isView(message)) {\n return new Uint8Array(message.buffer, message.byteOffset, message.byteLength);\n }\n if (typeof Blob !== \"undefined\" && message instanceof Blob) {\n return new Uint8Array(await message.arrayBuffer());\n }\n return undefined;\n}\n\nfunction defaultWebSocketFactory(\n url: string | URL\n): WebSocketSceneSocket {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSocket is not available\");\n }\n return new WebSocket(url) as WebSocketSceneSocket;\n}\n\nfunction currentPageURL(): string {\n return globalThis.location?.href ?? \"http://127.0.0.1/\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebSocketSceneBridge.js","names":[],"sources":["../../src/WebSocketSceneBridge.ts"],"sourcesContent":["import {\n WebHostOutputDecoder,\n encodeCapabilitiesControlMessage,\n encodePointerCapabilitiesControlMessage,\n encodeResyncControlMessage,\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n type WebHostOutputRecord,\n type WebHostOutputSink,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostTerminalStyle } from \"./WebHostTerminalStyle.ts\";\nimport type { WebHostSceneBridge } from \"./WebHostSceneRuntime.ts\";\n\nexport interface WebSocketSceneBridgeOptions {\n sceneId: string;\n token: string;\n baseURL?: string | URL;\n webSocketURL?: string | URL;\n webSocketFactory?: WebSocketSceneBridgeFactory;\n}\n\nexport type WebSocketSceneBridgeFactory = (url: string | URL) => WebSocketSceneSocket;\n\nexport interface WebSocketSceneSocket {\n binaryType: BinaryType;\n readonly readyState: number;\n send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: \"open\", listener: (event: Event) => void): void;\n addEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n addEventListener(type: \"close\", listener: (event: CloseEvent) => void): void;\n addEventListener(type: \"error\", listener: (event: Event) => void): void;\n removeEventListener(type: \"open\", listener: (event: Event) => void): void;\n removeEventListener(type: \"message\", listener: (event: MessageEvent) => void): void;\n removeEventListener(type: \"close\", listener: (event: CloseEvent) => void): void;\n removeEventListener(type: \"error\", listener: (event: Event) => void): void;\n}\n\nconst socketOpenState = 1;\nconst textEncoder = new TextEncoder();\n\nexport class WebSocketSceneBridge implements WebHostSceneBridge {\n readonly url: URL;\n\n private readonly socket: WebSocketSceneSocket;\n private readonly decoder = new WebHostOutputDecoder();\n private readonly queuedInput: Uint8Array[] = [];\n private readonly queuedOutput: WebHostOutputRecord[] = [];\n private sink?: WebHostOutputSink;\n private disposed = false;\n\n private readonly handleOpen = () => {\n this.flushQueuedInput();\n };\n\n private readonly handleMessage = (event: MessageEvent) => {\n void this.receive(event.data);\n };\n\n private readonly handleClose = () => {\n for (const record of this.decoder.flush()) {\n this.deliver(record);\n }\n };\n\n private readonly handleError = () => {};\n\n constructor(options: WebSocketSceneBridgeOptions) {\n this.url = webSocketSceneURL(options);\n this.socket = (options.webSocketFactory ?? defaultWebSocketFactory)(this.url);\n this.socket.binaryType = \"arraybuffer\";\n this.socket.addEventListener(\"open\", this.handleOpen);\n this.socket.addEventListener(\"message\", this.handleMessage);\n this.socket.addEventListener(\"close\", this.handleClose);\n this.socket.addEventListener(\"error\", this.handleError);\n // Declare wire capabilities first: queued input flushes in order on\n // open, so the declaration reaches the server ahead of any\n // resize/style/input record.\n this.sendInput(encodeCapabilitiesControlMessage());\n }\n\n bindOutput(\n sink: WebHostOutputSink\n ): void {\n this.sink = sink;\n while (this.queuedOutput.length > 0) {\n this.deliver(this.queuedOutput.shift()!);\n }\n }\n\n resize(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ): void {\n this.sendInput(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));\n }\n\n updateRenderStyle(\n style: WebHostTerminalStyle\n ): void {\n this.sendInput(encodeRenderStyleControlMessage(style));\n }\n\n updatePointerCapabilities(\n supportsScrollPanning: boolean\n ): void {\n this.sendInput(encodePointerCapabilitiesControlMessage(supportsScrollPanning));\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n if (this.disposed) {\n return;\n }\n\n const copy = new Uint8Array(chunk);\n this.queuedInput.push(copy);\n if (this.socket.readyState === socketOpenState) {\n this.flushQueuedInput();\n }\n }\n\n requestImagePayloads(\n ids: readonly string[]\n ): readonly string[] {\n if (this.disposed) {\n return [];\n }\n const acceptedIds = this.decoder.requestImagePayloads(ids);\n this.sendPendingResyncRequests();\n return acceptedIds;\n }\n\n dispose(): void {\n if (this.disposed) {\n return;\n }\n this.disposed = true;\n this.socket.removeEventListener(\"open\", this.handleOpen);\n this.socket.removeEventListener(\"message\", this.handleMessage);\n this.socket.removeEventListener(\"close\", this.handleClose);\n this.socket.removeEventListener(\"error\", this.handleError);\n this.queuedInput.length = 0;\n this.queuedOutput.length = 0;\n this.socket.close(1000, \"WebHost scene disposed\");\n }\n\n private async receive(\n message: unknown\n ): Promise<void> {\n if (this.disposed) {\n return;\n }\n\n const bytes = await bytesFromWebSocketMessage(message);\n if (!bytes) {\n return;\n }\n\n for (const record of this.decoder.feed(bytes)) {\n this.deliver(record);\n }\n this.sendPendingResyncRequests();\n }\n\n private deliver(\n record: WebHostOutputRecord\n ): void {\n const sink = this.sink;\n if (!sink) {\n this.queuedOutput.push(record);\n return;\n }\n\n switch (record.type) {\n case \"surface\":\n sink.presentSurface(\n record.frame,\n this.decoder.prepareToPresentSurface(record.frame)\n );\n break;\n case \"clipboard\":\n void sink.writeClipboard?.(record.text);\n break;\n case \"runtimeIssue\":\n sink.notifyRuntimeIssue?.(record.issue);\n break;\n case \"frameDiagnostic\":\n sink.recordFrameDiagnostic?.(record.diagnostic);\n break;\n case \"surfaceDropped\":\n break;\n case \"text\":\n sink.writeOutput?.(record.text);\n break;\n }\n }\n\n private flushQueuedInput(): void {\n if (this.disposed || this.socket.readyState !== socketOpenState) {\n return;\n }\n while (this.queuedInput.length > 0) {\n try {\n this.socket.send(this.queuedInput[0]!);\n this.queuedInput.shift();\n } catch {\n return;\n }\n }\n }\n\n private sendPendingResyncRequests(): void {\n while (true) {\n const request = this.decoder.takeResyncRequest();\n if (!request) {\n return;\n }\n try {\n this.sendInput(encodeResyncControlMessage(request));\n } catch {\n this.decoder.resyncRequestDeliveryFailed(request);\n return;\n }\n }\n }\n}\n\nexport function webSocketSceneURL(\n options: Pick<WebSocketSceneBridgeOptions, \"baseURL\" | \"webSocketURL\" | \"sceneId\" | \"token\">\n): URL {\n if (options.webSocketURL) {\n const explicit = new URL(String(options.webSocketURL), currentPageURL());\n explicit.searchParams.set(\"token\", options.token);\n return explicit;\n }\n\n const url = new URL(String(options.baseURL ?? currentPageURL()), currentPageURL());\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const basePath = url.pathname.endsWith(\"/\") ? url.pathname.slice(0, -1) : url.pathname;\n url.pathname = `${basePath}/ws/scene/${encodeURIComponent(options.sceneId)}`;\n url.search = \"\";\n url.searchParams.set(\"token\", options.token);\n return url;\n}\n\nasync function bytesFromWebSocketMessage(\n message: unknown\n): Promise<Uint8Array | undefined> {\n if (typeof message === \"string\") {\n return textEncoder.encode(message);\n }\n if (message instanceof Uint8Array) {\n return message;\n }\n if (message instanceof ArrayBuffer) {\n return new Uint8Array(message);\n }\n if (ArrayBuffer.isView(message)) {\n return new Uint8Array(message.buffer, message.byteOffset, message.byteLength);\n }\n if (typeof Blob !== \"undefined\" && message instanceof Blob) {\n return new Uint8Array(await message.arrayBuffer());\n }\n return undefined;\n}\n\nfunction defaultWebSocketFactory(\n url: string | URL\n): WebSocketSceneSocket {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSocket is not available\");\n }\n return new WebSocket(url) as WebSocketSceneSocket;\n}\n\nfunction currentPageURL(): string {\n return globalThis.location?.href ?? \"http://127.0.0.1/\";\n}\n"],"mappings":";;AAsCA,MAAM,kBAAkB;AACxB,MAAM,cAAc,IAAI,YAAY;AAEpC,IAAa,uBAAb,MAAgE;CAC9D;CAEA;CACA,UAA2B,IAAI,qBAAqB;CACpD,cAA6C,CAAC;CAC9C,eAAuD,CAAC;CACxD;CACA,WAAmB;CAEnB,mBAAoC;EAClC,KAAK,iBAAiB;CACxB;CAEA,iBAAkC,UAAwB;EACxD,KAAU,QAAQ,MAAM,IAAI;CAC9B;CAEA,oBAAqC;EACnC,KAAK,MAAM,UAAU,KAAK,QAAQ,MAAM,GACtC,KAAK,QAAQ,MAAM;CAEvB;CAEA,oBAAqC,CAAC;CAEtC,YAAY,SAAsC;EAChD,KAAK,MAAM,kBAAkB,OAAO;EACpC,KAAK,UAAU,QAAQ,oBAAoB,wBAAA,CAAyB,KAAK,GAAG;EAC5E,KAAK,OAAO,aAAa;EACzB,KAAK,OAAO,iBAAiB,QAAQ,KAAK,UAAU;EACpD,KAAK,OAAO,iBAAiB,WAAW,KAAK,aAAa;EAC1D,KAAK,OAAO,iBAAiB,SAAS,KAAK,WAAW;EACtD,KAAK,OAAO,iBAAiB,SAAS,KAAK,WAAW;EAItD,KAAK,UAAU,iCAAiC,CAAC;CACnD;CAEA,WACE,MACM;EACN,KAAK,OAAO;EACZ,OAAO,KAAK,aAAa,SAAS,GAChC,KAAK,QAAQ,KAAK,aAAa,MAAM,CAAE;CAE3C;CAEA,OACE,SACA,MACA,WACA,YACM;EACN,KAAK,UAAU,2BAA2B,SAAS,MAAM,WAAW,UAAU,CAAC;CACjF;CAEA,kBACE,OACM;EACN,KAAK,UAAU,gCAAgC,KAAK,CAAC;CACvD;CAEA,0BACE,uBACM;EACN,KAAK,UAAU,wCAAwC,qBAAqB,CAAC;CAC/E;CAEA,UACE,OACM;EACN,IAAI,KAAK,UACP;EAGF,MAAM,OAAO,IAAI,WAAW,KAAK;EACjC,KAAK,YAAY,KAAK,IAAI;EAC1B,IAAI,KAAK,OAAO,eAAe,iBAC7B,KAAK,iBAAiB;CAE1B;CAEA,qBACE,KACmB;EACnB,IAAI,KAAK,UACP,OAAO,CAAC;EAEV,MAAM,cAAc,KAAK,QAAQ,qBAAqB,GAAG;EACzD,KAAK,0BAA0B;EAC/B,OAAO;CACT;CAEA,UAAgB;EACd,IAAI,KAAK,UACP;EAEF,KAAK,WAAW;EAChB,KAAK,OAAO,oBAAoB,QAAQ,KAAK,UAAU;EACvD,KAAK,OAAO,oBAAoB,WAAW,KAAK,aAAa;EAC7D,KAAK,OAAO,oBAAoB,SAAS,KAAK,WAAW;EACzD,KAAK,OAAO,oBAAoB,SAAS,KAAK,WAAW;EACzD,KAAK,YAAY,SAAS;EAC1B,KAAK,aAAa,SAAS;EAC3B,KAAK,OAAO,MAAM,KAAM,wBAAwB;CAClD;CAEA,MAAc,QACZ,SACe;EACf,IAAI,KAAK,UACP;EAGF,MAAM,QAAQ,MAAM,0BAA0B,OAAO;EACrD,IAAI,CAAC,OACH;EAGF,KAAK,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,GAC1C,KAAK,QAAQ,MAAM;EAErB,KAAK,0BAA0B;CACjC;CAEA,QACE,QACM;EACN,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,MAAM;GACT,KAAK,aAAa,KAAK,MAAM;GAC7B;EACF;EAEA,QAAQ,OAAO,MAAf;GACA,KAAK;IACH,KAAK,eACH,OAAO,OACP,KAAK,QAAQ,wBAAwB,OAAO,KAAK,CACnD;IACA;GACF,KAAK;IACH,KAAU,iBAAiB,OAAO,IAAI;IACtC;GACF,KAAK;IACH,KAAK,qBAAqB,OAAO,KAAK;IACtC;GACF,KAAK;IACH,KAAK,wBAAwB,OAAO,UAAU;IAC9C;GACF,KAAK,kBACH;GACF,KAAK;IACH,KAAK,cAAc,OAAO,IAAI;IAC9B;EACF;CACF;CAEA,mBAAiC;EAC/B,IAAI,KAAK,YAAY,KAAK,OAAO,eAAe,iBAC9C;EAEF,OAAO,KAAK,YAAY,SAAS,GAC/B,IAAI;GACF,KAAK,OAAO,KAAK,KAAK,YAAY,EAAG;GACrC,KAAK,YAAY,MAAM;EACzB,QAAQ;GACN;EACF;CAEJ;CAEA,4BAA0C;EACxC,OAAO,MAAM;GACX,MAAM,UAAU,KAAK,QAAQ,kBAAkB;GAC/C,IAAI,CAAC,SACH;GAEF,IAAI;IACF,KAAK,UAAU,2BAA2B,OAAO,CAAC;GACpD,QAAQ;IACN,KAAK,QAAQ,4BAA4B,OAAO;IAChD;GACF;EACF;CACF;AACF;AAEA,SAAgB,kBACd,SACK;CACL,IAAI,QAAQ,cAAc;EACxB,MAAM,WAAW,IAAI,IAAI,OAAO,QAAQ,YAAY,GAAG,eAAe,CAAC;EACvE,SAAS,aAAa,IAAI,SAAS,QAAQ,KAAK;EAChD,OAAO;CACT;CAEA,MAAM,MAAM,IAAI,IAAI,OAAO,QAAQ,WAAW,eAAe,CAAC,GAAG,eAAe,CAAC;CACjF,IAAI,WAAW,IAAI,aAAa,WAAW,SAAS;CAEpD,IAAI,WAAW,GADE,IAAI,SAAS,SAAS,GAAG,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI,IAAI,SACnD,YAAY,mBAAmB,QAAQ,OAAO;CACzE,IAAI,SAAS;CACb,IAAI,aAAa,IAAI,SAAS,QAAQ,KAAK;CAC3C,OAAO;AACT;AAEA,eAAe,0BACb,SACiC;CACjC,IAAI,OAAO,YAAY,UACrB,OAAO,YAAY,OAAO,OAAO;CAEnC,IAAI,mBAAmB,YACrB,OAAO;CAET,IAAI,mBAAmB,aACrB,OAAO,IAAI,WAAW,OAAO;CAE/B,IAAI,YAAY,OAAO,OAAO,GAC5B,OAAO,IAAI,WAAW,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;CAE9E,IAAI,OAAO,SAAS,eAAe,mBAAmB,MACpD,OAAO,IAAI,WAAW,MAAM,QAAQ,YAAY,CAAC;AAGrD;AAEA,SAAS,wBACP,KACsB;CACtB,IAAI,OAAO,cAAc,aACvB,MAAM,IAAI,MAAM,4BAA4B;CAE9C,OAAO,IAAI,UAAU,GAAG;AAC1B;AAEA,SAAS,iBAAyB;CAChC,OAAO,WAAW,UAAU,QAAQ;AACtC"}
|
|
@@ -32,6 +32,7 @@ declare class BrowserWASIBridge {
|
|
|
32
32
|
bindOutput(sink: BrowserWASIOutputSink): void;
|
|
33
33
|
resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;
|
|
34
34
|
updateRenderStyle(style: WebHostTerminalStyle): void;
|
|
35
|
+
updatePointerCapabilities(supportsScrollPanning: boolean): void;
|
|
35
36
|
sendInput(chunk: Uint8Array): void;
|
|
36
37
|
requestImagePayloads(ids: readonly string[]): readonly string[];
|
|
37
38
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StdIOPipe } from "./StdIOPipe.js";
|
|
2
2
|
import { resolveWasmEngineCapabilities, stackProfileEnvironmentDefaults } from "./WasmEngineCapabilities.js";
|
|
3
3
|
import { encodeWebHostTerminalRenderStyleBase64 } from "../WebHostTerminalStyle.js";
|
|
4
|
-
import { WebHostOutputDecoder, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "../WebHostSurfaceTransport.js";
|
|
4
|
+
import { WebHostOutputDecoder, encodePointerCapabilitiesControlMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeResyncControlMessage } from "../WebHostSurfaceTransport.js";
|
|
5
5
|
import { sharedInputQueueDefaultCapacity } from "./SharedInputQueue.js";
|
|
6
6
|
//#region src/wasi/BrowserWASIBridge.ts
|
|
7
7
|
const maximumWASIResyncControlBytes = sharedInputQueueDefaultCapacity - 1;
|
|
@@ -80,6 +80,9 @@ var BrowserWASIBridge = class {
|
|
|
80
80
|
this.environment.SWIFTTUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);
|
|
81
81
|
this.stdin.write(encodeRenderStyleControlMessage(style));
|
|
82
82
|
}
|
|
83
|
+
updatePointerCapabilities(supportsScrollPanning) {
|
|
84
|
+
this.stdin.write(encodePointerCapabilitiesControlMessage(supportsScrollPanning));
|
|
85
|
+
}
|
|
83
86
|
sendInput(chunk) {
|
|
84
87
|
this.stdin.write(chunk);
|
|
85
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrowserWASIBridge.js","names":[],"sources":["../../../src/wasi/BrowserWASIBridge.ts"],"sourcesContent":["import { StdIOPipe } from \"./StdIOPipe.ts\";\nimport {\n resolveWasmEngineCapabilities,\n stackProfileEnvironmentDefaults,\n type WasmEngineCapabilities,\n} from \"./WasmEngineCapabilities.ts\";\nimport {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"../WebHostTerminalStyle.ts\";\nimport {\n WebHostOutputDecoder,\n encodeResyncControlMessage,\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n type WebHostOutputSink,\n} from \"../WebHostSurfaceTransport.ts\";\nimport { sharedInputQueueDefaultCapacity } from \"./SharedInputQueue.ts\";\n\nconst maximumWASIResyncControlBytes = sharedInputQueueDefaultCapacity - 1;\n\nexport interface BrowserWASIBridgeOptions {\n sceneId: string;\n columns: number;\n rows: number;\n environment?: Record<string, string>;\n renderStyle?: WebHostTerminalStyle;\n /**\n * Detected engine capabilities driving engine-conditional environment\n * defaults (e.g. disabling the stack-lean resolve profile on V8). Defaults\n * to probing the current engine; injectable for tests and embedders that\n * want to force a profile.\n */\n engineCapabilities?: WasmEngineCapabilities;\n}\n\nexport type BrowserWASIOutputSink = WebHostOutputSink;\n\nexport class BrowserWASIBridge {\n readonly stdin = new StdIOPipe();\n readonly stdout = new StdIOPipe();\n readonly stderr = new StdIOPipe();\n readonly environment: Record<string, string>;\n\n private detachStdout?: () => void;\n private detachStderr?: () => void;\n private decoder = new WebHostOutputDecoder();\n private readonly resizeListeners = new Set<(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void>();\n private latestResize: {\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n };\n\n constructor(options: BrowserWASIBridgeOptions) {\n this.environment = {\n SWIFTTUI_MODE: \"browser\",\n SWIFTTUI_TRANSPORT: \"surface\",\n SWIFTTUI_SURFACE_DELTA: \"1\",\n SWIFTTUI_SCENE: options.sceneId,\n SWIFTTUI_COLUMNS: String(Math.max(1, options.columns)),\n SWIFTTUI_ROWS: String(Math.max(1, options.rows)),\n // Browser default (2026-07, engine-blind): the single-threaded WASI\n // drive surfaces tick invalidations exactly where the async driver's\n // supersession predicate samples, so the default `async` disposal\n // (completed-frame visual-only drops + pre-start cancels) coalesces\n // steady scenes to ~1 wire frame per 3+ generations — the 0.1.9 live\n // regression. `async-no-cancel` keeps scheduler intent-merging but\n // commits every completed frame; measured 0.22 → 0.86 distinct-\n // generation coverage on the deployed Life scene, both execution\n // modes, with per-frame cost unchanged. Live lean sessions measure\n // zero drops/cancels, so the default is safe engine-blind. Callers\n // (and the `?renderMode=` page seam) override via\n // `options.environment`; rollback is this one line.\n SWIFTTUI_RENDER_MODE: \"async-no-cancel\",\n ...stackProfileEnvironmentDefaults(\n options.engineCapabilities ?? resolveWasmEngineCapabilities()\n ),\n ...options.environment,\n ...(options.renderStyle\n ? {\n SWIFTTUI_RENDER_STYLE: encodeWebHostTerminalRenderStyleBase64(\n options.renderStyle\n ),\n }\n : {}),\n };\n this.latestResize = {\n columns: Math.max(1, options.columns),\n rows: Math.max(1, options.rows),\n };\n }\n\n bindOutput(\n sink: BrowserWASIOutputSink\n ): void {\n this.detachStdout?.();\n this.detachStderr?.();\n this.decoder = new WebHostOutputDecoder();\n this.detachStdout = this.stdout.subscribe((chunk) => {\n for (const record of this.decoder.feed(chunk)) {\n switch (record.type) {\n case \"surface\":\n sink.presentSurface(\n record.frame,\n this.decoder.prepareToPresentSurface(record.frame)\n );\n break;\n case \"clipboard\":\n void sink.writeClipboard?.(record.text);\n break;\n case \"runtimeIssue\":\n sink.notifyRuntimeIssue?.(record.issue);\n break;\n case \"frameDiagnostic\":\n sink.recordFrameDiagnostic?.(record.diagnostic);\n break;\n case \"surfaceDropped\":\n break;\n case \"text\":\n sink.writeOutput?.(record.text);\n break;\n }\n }\n this.sendPendingResyncRequests();\n });\n this.detachStderr = this.stderr.subscribe((chunk) => {\n sink.writeError?.(new TextDecoder().decode(chunk));\n });\n }\n\n resize(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ): void {\n const normalizedColumns = Math.max(1, columns);\n const normalizedRows = Math.max(1, rows);\n this.environment.SWIFTTUI_COLUMNS = String(normalizedColumns);\n this.environment.SWIFTTUI_ROWS = String(normalizedRows);\n this.latestResize = {\n columns: normalizedColumns,\n rows: normalizedRows,\n cellWidth,\n cellHeight,\n };\n this.stdin.write(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));\n for (const listener of this.resizeListeners) {\n listener(normalizedColumns, normalizedRows, cellWidth, cellHeight);\n }\n }\n\n updateRenderStyle(\n style: WebHostTerminalStyle\n ): void {\n this.environment.SWIFTTUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);\n this.stdin.write(encodeRenderStyleControlMessage(style));\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.stdin.write(chunk);\n }\n\n requestImagePayloads(\n ids: readonly string[]\n ): readonly string[] {\n const acceptedIds = this.decoder.requestImagePayloads(ids);\n this.sendPendingResyncRequests();\n return acceptedIds;\n }\n\n /**\n * Called by the WASI runtime when its shared stdin queue has enough capacity\n * for the delivery that previously failed.\n */\n notifyInputCapacityAvailable(): void {\n this.sendPendingResyncRequests();\n }\n\n subscribeResize(\n listener: (\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void\n ): () => void {\n this.resizeListeners.add(listener);\n listener(\n this.latestResize.columns,\n this.latestResize.rows,\n this.latestResize.cellWidth,\n this.latestResize.cellHeight\n );\n return () => {\n this.resizeListeners.delete(listener);\n };\n }\n\n private sendPendingResyncRequests(): void {\n while (true) {\n const request = this.decoder.takeResyncRequest(\n maximumWASIResyncControlBytes\n );\n if (!request) {\n return;\n }\n const accepted = this.stdin.write(encodeResyncControlMessage(request));\n if (!accepted) {\n this.decoder.resyncRequestDeliveryFailed(request);\n return;\n }\n }\n }\n\n dispose(): void {\n this.detachStdout?.();\n this.detachStderr?.();\n this.resizeListeners.clear();\n this.stdin.close();\n this.stdout.close();\n this.stderr.close();\n }\n}\n\nexport {\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BrowserWASIBridge.js","names":[],"sources":["../../../src/wasi/BrowserWASIBridge.ts"],"sourcesContent":["import { StdIOPipe } from \"./StdIOPipe.ts\";\nimport {\n resolveWasmEngineCapabilities,\n stackProfileEnvironmentDefaults,\n type WasmEngineCapabilities,\n} from \"./WasmEngineCapabilities.ts\";\nimport {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"../WebHostTerminalStyle.ts\";\nimport {\n WebHostOutputDecoder,\n encodePointerCapabilitiesControlMessage,\n encodeResyncControlMessage,\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n type WebHostOutputSink,\n} from \"../WebHostSurfaceTransport.ts\";\nimport { sharedInputQueueDefaultCapacity } from \"./SharedInputQueue.ts\";\n\nconst maximumWASIResyncControlBytes = sharedInputQueueDefaultCapacity - 1;\n\nexport interface BrowserWASIBridgeOptions {\n sceneId: string;\n columns: number;\n rows: number;\n environment?: Record<string, string>;\n renderStyle?: WebHostTerminalStyle;\n /**\n * Detected engine capabilities driving engine-conditional environment\n * defaults (e.g. disabling the stack-lean resolve profile on V8). Defaults\n * to probing the current engine; injectable for tests and embedders that\n * want to force a profile.\n */\n engineCapabilities?: WasmEngineCapabilities;\n}\n\nexport type BrowserWASIOutputSink = WebHostOutputSink;\n\nexport class BrowserWASIBridge {\n readonly stdin = new StdIOPipe();\n readonly stdout = new StdIOPipe();\n readonly stderr = new StdIOPipe();\n readonly environment: Record<string, string>;\n\n private detachStdout?: () => void;\n private detachStderr?: () => void;\n private decoder = new WebHostOutputDecoder();\n private readonly resizeListeners = new Set<(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void>();\n private latestResize: {\n columns: number;\n rows: number;\n cellWidth?: number;\n cellHeight?: number;\n };\n\n constructor(options: BrowserWASIBridgeOptions) {\n this.environment = {\n SWIFTTUI_MODE: \"browser\",\n SWIFTTUI_TRANSPORT: \"surface\",\n SWIFTTUI_SURFACE_DELTA: \"1\",\n SWIFTTUI_SCENE: options.sceneId,\n SWIFTTUI_COLUMNS: String(Math.max(1, options.columns)),\n SWIFTTUI_ROWS: String(Math.max(1, options.rows)),\n // Browser default (2026-07, engine-blind): the single-threaded WASI\n // drive surfaces tick invalidations exactly where the async driver's\n // supersession predicate samples, so the default `async` disposal\n // (completed-frame visual-only drops + pre-start cancels) coalesces\n // steady scenes to ~1 wire frame per 3+ generations — the 0.1.9 live\n // regression. `async-no-cancel` keeps scheduler intent-merging but\n // commits every completed frame; measured 0.22 → 0.86 distinct-\n // generation coverage on the deployed Life scene, both execution\n // modes, with per-frame cost unchanged. Live lean sessions measure\n // zero drops/cancels, so the default is safe engine-blind. Callers\n // (and the `?renderMode=` page seam) override via\n // `options.environment`; rollback is this one line.\n SWIFTTUI_RENDER_MODE: \"async-no-cancel\",\n ...stackProfileEnvironmentDefaults(\n options.engineCapabilities ?? resolveWasmEngineCapabilities()\n ),\n ...options.environment,\n ...(options.renderStyle\n ? {\n SWIFTTUI_RENDER_STYLE: encodeWebHostTerminalRenderStyleBase64(\n options.renderStyle\n ),\n }\n : {}),\n };\n this.latestResize = {\n columns: Math.max(1, options.columns),\n rows: Math.max(1, options.rows),\n };\n }\n\n bindOutput(\n sink: BrowserWASIOutputSink\n ): void {\n this.detachStdout?.();\n this.detachStderr?.();\n this.decoder = new WebHostOutputDecoder();\n this.detachStdout = this.stdout.subscribe((chunk) => {\n for (const record of this.decoder.feed(chunk)) {\n switch (record.type) {\n case \"surface\":\n sink.presentSurface(\n record.frame,\n this.decoder.prepareToPresentSurface(record.frame)\n );\n break;\n case \"clipboard\":\n void sink.writeClipboard?.(record.text);\n break;\n case \"runtimeIssue\":\n sink.notifyRuntimeIssue?.(record.issue);\n break;\n case \"frameDiagnostic\":\n sink.recordFrameDiagnostic?.(record.diagnostic);\n break;\n case \"surfaceDropped\":\n break;\n case \"text\":\n sink.writeOutput?.(record.text);\n break;\n }\n }\n this.sendPendingResyncRequests();\n });\n this.detachStderr = this.stderr.subscribe((chunk) => {\n sink.writeError?.(new TextDecoder().decode(chunk));\n });\n }\n\n resize(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ): void {\n const normalizedColumns = Math.max(1, columns);\n const normalizedRows = Math.max(1, rows);\n this.environment.SWIFTTUI_COLUMNS = String(normalizedColumns);\n this.environment.SWIFTTUI_ROWS = String(normalizedRows);\n this.latestResize = {\n columns: normalizedColumns,\n rows: normalizedRows,\n cellWidth,\n cellHeight,\n };\n this.stdin.write(encodeResizeControlMessage(columns, rows, cellWidth, cellHeight));\n for (const listener of this.resizeListeners) {\n listener(normalizedColumns, normalizedRows, cellWidth, cellHeight);\n }\n }\n\n updateRenderStyle(\n style: WebHostTerminalStyle\n ): void {\n this.environment.SWIFTTUI_RENDER_STYLE = encodeWebHostTerminalRenderStyleBase64(style);\n this.stdin.write(encodeRenderStyleControlMessage(style));\n }\n\n updatePointerCapabilities(\n supportsScrollPanning: boolean\n ): void {\n this.stdin.write(encodePointerCapabilitiesControlMessage(supportsScrollPanning));\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.stdin.write(chunk);\n }\n\n requestImagePayloads(\n ids: readonly string[]\n ): readonly string[] {\n const acceptedIds = this.decoder.requestImagePayloads(ids);\n this.sendPendingResyncRequests();\n return acceptedIds;\n }\n\n /**\n * Called by the WASI runtime when its shared stdin queue has enough capacity\n * for the delivery that previously failed.\n */\n notifyInputCapacityAvailable(): void {\n this.sendPendingResyncRequests();\n }\n\n subscribeResize(\n listener: (\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n ) => void\n ): () => void {\n this.resizeListeners.add(listener);\n listener(\n this.latestResize.columns,\n this.latestResize.rows,\n this.latestResize.cellWidth,\n this.latestResize.cellHeight\n );\n return () => {\n this.resizeListeners.delete(listener);\n };\n }\n\n private sendPendingResyncRequests(): void {\n while (true) {\n const request = this.decoder.takeResyncRequest(\n maximumWASIResyncControlBytes\n );\n if (!request) {\n return;\n }\n const accepted = this.stdin.write(encodeResyncControlMessage(request));\n if (!accepted) {\n this.decoder.resyncRequestDeliveryFailed(request);\n return;\n }\n }\n }\n\n dispose(): void {\n this.detachStdout?.();\n this.detachStderr?.();\n this.resizeListeners.clear();\n this.stdin.close();\n this.stdout.close();\n this.stderr.close();\n }\n}\n\nexport {\n encodeRenderStyleControlMessage,\n encodeResizeControlMessage,\n};\n"],"mappings":"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"}
|