@swifttui/web 0.7.1 → 0.8.0

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 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":"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"}
@@ -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":";;;;;;AAoBA,MAAM,gCAAgC,kCAAkC;AAmBxE,IAAa,oBAAb,MAA+B;CAC7B,QAAiB,IAAI,UAAU;CAC/B,SAAkB,IAAI,UAAU;CAChC,SAAkB,IAAI,UAAU;CAChC;CAEA;CACA;CACA,UAAkB,IAAI,qBAAqB;CAC3C,kCAAmC,IAAI,IAK5B;CACX;CAOA,YAAY,SAAmC;EAC7C,KAAK,cAAc;GACjB,eAAe;GACf,oBAAoB;GACpB,wBAAwB;GACxB,gBAAgB,QAAQ;GACxB,kBAAkB,OAAO,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;GACrD,eAAe,OAAO,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC;GAa/C,sBAAsB;GACtB,GAAG,gCACD,QAAQ,sBAAsB,8BAA8B,CAC9D;GACA,GAAG,QAAQ;GACX,GAAI,QAAQ,cACR,EACE,uBAAuB,uCACrB,QAAQ,WACV,EACF,IACA,CAAC;EACP;EACA,KAAK,eAAe;GAClB,SAAS,KAAK,IAAI,GAAG,QAAQ,OAAO;GACpC,MAAM,KAAK,IAAI,GAAG,QAAQ,IAAI;EAChC;CACF;CAEA,WACE,MACM;EACN,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,UAAU,IAAI,qBAAqB;EACxC,KAAK,eAAe,KAAK,OAAO,WAAW,UAAU;GACnD,KAAK,MAAM,UAAU,KAAK,QAAQ,KAAK,KAAK,GAC1C,QAAQ,OAAO,MAAf;IACA,KAAK;KACH,KAAK,eACH,OAAO,OACP,KAAK,QAAQ,wBAAwB,OAAO,KAAK,CACnD;KACA;IACF,KAAK;KACH,KAAU,iBAAiB,OAAO,IAAI;KACtC;IACF,KAAK;KACH,KAAK,qBAAqB,OAAO,KAAK;KACtC;IACF,KAAK;KACH,KAAK,wBAAwB,OAAO,UAAU;KAC9C;IACF,KAAK,kBACH;IACF,KAAK;KACH,KAAK,cAAc,OAAO,IAAI;KAC9B;GACF;GAEF,KAAK,0BAA0B;EACjC,CAAC;EACD,KAAK,eAAe,KAAK,OAAO,WAAW,UAAU;GACnD,KAAK,aAAa,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;EACnD,CAAC;CACH;CAEA,OACE,SACA,MACA,WACA,YACM;EACN,MAAM,oBAAoB,KAAK,IAAI,GAAG,OAAO;EAC7C,MAAM,iBAAiB,KAAK,IAAI,GAAG,IAAI;EACvC,KAAK,YAAY,mBAAmB,OAAO,iBAAiB;EAC5D,KAAK,YAAY,gBAAgB,OAAO,cAAc;EACtD,KAAK,eAAe;GAClB,SAAS;GACT,MAAM;GACN;GACA;EACF;EACA,KAAK,MAAM,MAAM,2BAA2B,SAAS,MAAM,WAAW,UAAU,CAAC;EACjF,KAAK,MAAM,YAAY,KAAK,iBAC1B,SAAS,mBAAmB,gBAAgB,WAAW,UAAU;CAErE;CAEA,kBACE,OACM;EACN,KAAK,YAAY,wBAAwB,uCAAuC,KAAK;EACrF,KAAK,MAAM,MAAM,gCAAgC,KAAK,CAAC;CACzD;CAEA,0BACE,uBACM;EACN,KAAK,MAAM,MAAM,wCAAwC,qBAAqB,CAAC;CACjF;CAEA,UACE,OACM;EACN,KAAK,MAAM,MAAM,KAAK;CACxB;CAEA,qBACE,KACmB;EACnB,MAAM,cAAc,KAAK,QAAQ,qBAAqB,GAAG;EACzD,KAAK,0BAA0B;EAC/B,OAAO;CACT;;;;;CAMA,+BAAqC;EACnC,KAAK,0BAA0B;CACjC;CAEA,gBACE,UAMY;EACZ,KAAK,gBAAgB,IAAI,QAAQ;EACjC,SACE,KAAK,aAAa,SAClB,KAAK,aAAa,MAClB,KAAK,aAAa,WAClB,KAAK,aAAa,UACpB;EACA,aAAa;GACX,KAAK,gBAAgB,OAAO,QAAQ;EACtC;CACF;CAEA,4BAA0C;EACxC,OAAO,MAAM;GACX,MAAM,UAAU,KAAK,QAAQ,kBAC3B,6BACF;GACA,IAAI,CAAC,SACH;GAGF,IAAI,CADa,KAAK,MAAM,MAAM,2BAA2B,OAAO,CACxD,GAAG;IACb,KAAK,QAAQ,4BAA4B,OAAO;IAChD;GACF;EACF;CACF;CAEA,UAAgB;EACd,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,gBAAgB,MAAM;EAC3B,KAAK,MAAM,MAAM;EACjB,KAAK,OAAO,MAAM;EAClB,KAAK,OAAO,MAAM;CACpB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swifttui/web",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",