@swifttui/web 0.4.1 → 0.4.3

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.
Files changed (35) hide show
  1. package/dist/index.d.ts +3 -3
  2. package/dist/index.js +2 -2
  3. package/dist/src/CanvasSurfacePainter.d.ts +16 -0
  4. package/dist/src/CanvasSurfacePainter.js +160 -21
  5. package/dist/src/CanvasSurfacePainter.js.map +1 -1
  6. package/dist/src/DomSurfacePainter.d.ts +17 -3
  7. package/dist/src/DomSurfacePainter.js +50 -5
  8. package/dist/src/DomSurfacePainter.js.map +1 -1
  9. package/dist/src/SurfacePainterConformanceControl.js +13 -0
  10. package/dist/src/SurfacePainterConformanceControl.js.map +1 -0
  11. package/dist/src/SurfaceRenderer.d.ts +3 -2
  12. package/dist/src/SurfaceRenderer.js.map +1 -1
  13. package/dist/src/WebHostSceneRuntime.d.ts +3 -1
  14. package/dist/src/WebHostSceneRuntime.js +10 -7
  15. package/dist/src/WebHostSceneRuntime.js.map +1 -1
  16. package/dist/src/WebHostSurfaceTransport.d.ts +61 -4
  17. package/dist/src/WebHostSurfaceTransport.js +150 -12
  18. package/dist/src/WebHostSurfaceTransport.js.map +1 -1
  19. package/dist/src/WebSocketSceneBridge.d.ts +2 -1
  20. package/dist/src/WebSocketSceneBridge.js +26 -11
  21. package/dist/src/WebSocketSceneBridge.js.map +1 -1
  22. package/dist/src/normalizeWireTokens.d.ts +5 -0
  23. package/dist/src/wasi/BrowserWASIBridge.d.ts +8 -0
  24. package/dist/src/wasi/BrowserWASIBridge.js +29 -7
  25. package/dist/src/wasi/BrowserWASIBridge.js.map +1 -1
  26. package/dist/src/wasi/SharedInputQueue.d.ts +61 -1
  27. package/dist/src/wasi/SharedInputQueue.js +108 -0
  28. package/dist/src/wasi/SharedInputQueue.js.map +1 -1
  29. package/dist/src/wasi/WasmSceneRuntime.js +38 -8
  30. package/dist/src/wasi/WasmSceneRuntime.js.map +1 -1
  31. package/dist/src/wasi/WasmSceneWorker.js +1 -1
  32. package/dist/testing.d.ts +2 -1
  33. package/dist/wasi.d.ts +2 -2
  34. package/dist/wasi.js +1 -1
  35. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DomSurfacePainter.js","names":[],"sources":["../../src/DomSurfacePainter.ts"],"sourcesContent":["import { fontForStyle } from \"./CanvasSurfacePainter.ts\";\nimport {\n resolvedSurfaceBackground,\n resolvedSurfaceForeground,\n type SurfaceMetrics,\n type WebHostSurfacePainter,\n} from \"./SurfaceRenderer.ts\";\nimport { webTUITerminalBackgroundColor } from \"./WebHostTerminalStyle.ts\";\nimport {\n isSupportedImageFormat,\n normalizeScalingMode,\n} from \"./normalizeWireTokens.ts\";\nimport type {\n WebHostSurfaceDamage,\n WebHostSurfaceFrame,\n WebHostSurfaceImage,\n WebHostSurfaceLineStyle,\n WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\n\ninterface RenderedImage {\n container: HTMLElement;\n image: HTMLElement;\n source: string;\n}\n\n/**\n * Draws SwiftTUI surface frames as a DOM element tree instead of canvas\n * pixels: one absolutely positioned row container per grid row, one `<span>`\n * per styled cell run, and `<img>` elements for surface images.\n *\n * Rendering cells as real text buys what canvas cannot offer — the browser's\n * own font shaping and fallback (emoji, CJK), crisp text at any page zoom, an\n * inspectable tree, and native text selection — at the cost of pixel-exact\n * box-drawing seams, which render as font glyphs here rather than the canvas\n * painter's hand-drawn strokes.\n *\n * Damage handling mirrors the canvas painter at row granularity: a frame\n * carrying scoped damage rebuilds only the touched rows' elements; geometry\n * or style changes force a full rebuild. Grid alignment across a run is kept\n * exact by stretching each glyph advance to the cell width via\n * `letter-spacing`, measured once per font/size pair.\n */\nexport class DomSurfacePainter implements WebHostSurfacePainter {\n private root?: HTMLElement;\n private rowsLayer?: HTMLElement;\n private imagesLayer?: HTMLElement;\n private rowElements: HTMLElement[] = [];\n private renderedImages = new Map<string, RenderedImage>();\n private appliedMetricsKey?: string;\n private renderedGridKey?: string;\n private hasRenderedFrame = false;\n private letterSpacing?: { key: string; value: string };\n\n /**\n * Binds the container the painter renders into. The runtime owns the\n * container's size; the painter owns everything inside it.\n */\n attach(\n root: HTMLElement\n ): void {\n this.root = root;\n\n const rowsLayer = createElement(\"div\");\n rowsLayer.className = \"webhost-scene__surface-rows\";\n fillContainer(rowsLayer.style);\n\n const imagesLayer = createElement(\"div\");\n imagesLayer.className = \"webhost-scene__surface-images\";\n fillContainer(imagesLayer.style);\n imagesLayer.style.pointerEvents = \"none\";\n\n this.rowsLayer = rowsLayer;\n this.imagesLayer = imagesLayer;\n root.replaceChildren(rowsLayer, imagesLayer);\n this.rowElements = [];\n this.renderedImages = new Map();\n this.appliedMetricsKey = undefined;\n this.renderedGridKey = undefined;\n this.hasRenderedFrame = false;\n }\n\n paint(\n metrics: SurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage\n ): void {\n const root = this.root;\n const rowsLayer = this.rowsLayer;\n if (!root || !rowsLayer) {\n return;\n }\n\n const metricsKey = metricsKeyFor(metrics);\n const metricsChanged = metricsKey !== this.appliedMetricsKey;\n if (metricsChanged) {\n this.applyRootStyle(root, metrics);\n this.appliedMetricsKey = metricsKey;\n }\n\n if (!frame) {\n this.rowElements = [];\n rowsLayer.replaceChildren();\n this.reconcileImages([], metrics);\n this.renderedGridKey = undefined;\n this.hasRenderedFrame = false;\n return;\n }\n\n const gridKey = `${frame.width}x${frame.height}x${frame.rows.length}`;\n const fullRepaint = metricsChanged\n || !this.hasRenderedFrame\n || gridKey !== this.renderedGridKey\n || !damage\n || damage.requiresFullTextRepaint\n || damage.requiresFullGraphicsReplay;\n this.renderedGridKey = gridKey;\n\n if (fullRepaint) {\n for (let y = this.rowElements.length; y > frame.rows.length; y -= 1) {\n this.rowElements[y - 1]?.remove();\n }\n this.rowElements.length = Math.min(this.rowElements.length, frame.rows.length);\n for (let y = 0; y < frame.rows.length; y += 1) {\n this.rebuildRow(y, frame, metrics);\n }\n } else {\n for (const [row] of damage.textRows) {\n if (row < 0 || row >= frame.rows.length) {\n continue;\n }\n this.rebuildRow(row, frame, metrics);\n }\n }\n\n this.reconcileImages(frame.images ?? [], metrics);\n this.hasRenderedFrame = true;\n }\n\n private rebuildRow(\n y: number,\n frame: WebHostSurfaceFrame,\n metrics: SurfaceMetrics\n ): void {\n const rowElement = this.ensureRowElement(y, metrics);\n const children: HTMLElement[] = [];\n for (const [x, text, span, styleIndex] of frame.rows[y] ?? []) {\n const cellElement = buildCellElement(\n x,\n text,\n span,\n frame.styles[styleIndex] ?? undefined,\n metrics\n );\n if (cellElement) {\n children.push(cellElement);\n }\n }\n rowElement.replaceChildren(...children);\n }\n\n private ensureRowElement(\n y: number,\n metrics: SurfaceMetrics\n ): HTMLElement {\n let rowElement = this.rowElements[y];\n if (!rowElement) {\n rowElement = createElement(\"div\");\n rowElement.className = \"webhost-scene__surface-row\";\n rowElement.style.position = \"absolute\";\n rowElement.style.left = \"0\";\n this.rowElements[y] = rowElement;\n this.rowsLayer?.appendChild(rowElement);\n }\n rowElement.style.top = `${y * metrics.cellHeight}px`;\n rowElement.style.height = `${metrics.cellHeight}px`;\n rowElement.style.width = `${metrics.columns * metrics.cellWidth}px`;\n return rowElement;\n }\n\n private applyRootStyle(\n root: HTMLElement,\n metrics: SurfaceMetrics\n ): void {\n const style = root.style;\n style.position = \"relative\";\n style.overflow = \"hidden\";\n style.background = webTUITerminalBackgroundColor(metrics.style);\n style.font = fontForStyle(metrics.style);\n // Set after `font`: the shorthand resets line-height, and the grid needs\n // every row to be exactly one cell tall.\n style.lineHeight = `${metrics.cellHeight}px`;\n style.letterSpacing = this.letterSpacingFor(metrics);\n // Ligature-capable monospace fonts would merge runs like \"->\" into one\n // glyph and break the column grid.\n style.fontVariantLigatures = \"none\";\n style.userSelect = \"text\";\n }\n\n /**\n * The per-glyph advance correction that stretches the font's natural\n * monospace advance to exactly `cellWidth`, so long runs stay on the cell\n * grid instead of drifting by the sub-pixel remainder of the runtime's\n * ceil'd cell measurement.\n */\n private letterSpacingFor(\n metrics: SurfaceMetrics\n ): string {\n const font = fontForStyle(metrics.style);\n const key = `${font}|${metrics.cellWidth}`;\n if (this.letterSpacing?.key === key) {\n return this.letterSpacing.value;\n }\n\n let value = \"0px\";\n const canvas = createElement(\"canvas\") as HTMLCanvasElement;\n const context = canvas.getContext?.(\"2d\");\n if (context) {\n context.font = font;\n const advance = context.measureText(\"W\").width;\n const correction = metrics.cellWidth - advance;\n if (advance > 0 && Math.abs(correction) >= 0.01) {\n value = `${Math.round(correction * 1000) / 1000}px`;\n }\n }\n\n this.letterSpacing = { key, value };\n return value;\n }\n\n private reconcileImages(\n images: WebHostSurfaceImage[],\n metrics: SurfaceMetrics\n ): void {\n const layer = this.imagesLayer;\n if (!layer) {\n return;\n }\n\n const next = new Map<string, RenderedImage>();\n for (const rawImage of images) {\n if (!isSupportedImageFormat(rawImage.format)) {\n continue;\n }\n const image = {\n ...rawImage,\n scalingMode: normalizeScalingMode(rawImage.scalingMode),\n };\n const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;\n const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;\n const existing = this.renderedImages.get(image.id);\n if (\n (!existing && !image.dataBase64)\n || boundsWidth <= 0\n || boundsHeight <= 0\n || clipWidth <= 0\n || clipHeight <= 0\n ) {\n continue;\n }\n\n const entry = existing ?? makeImageEntry();\n entry.container.style.left = `${clipX * metrics.cellWidth}px`;\n entry.container.style.top = `${clipY * metrics.cellHeight}px`;\n entry.container.style.width = `${clipWidth * metrics.cellWidth}px`;\n entry.container.style.height = `${clipHeight * metrics.cellHeight}px`;\n entry.image.style.left = `${(boundsX - clipX) * metrics.cellWidth}px`;\n entry.image.style.top = `${(boundsY - clipY) * metrics.cellHeight}px`;\n entry.image.style.width = `${boundsWidth * metrics.cellWidth}px`;\n entry.image.style.height = `${boundsHeight * metrics.cellHeight}px`;\n\n if (image.dataBase64) {\n const source = `data:image/${image.format};base64,${image.dataBase64}`;\n if (entry.source !== source) {\n entry.image.setAttribute(\"src\", source);\n entry.source = source;\n }\n }\n if (!existing) {\n layer.appendChild(entry.container);\n }\n next.set(image.id, entry);\n }\n\n for (const [id, entry] of this.renderedImages) {\n if (!next.has(id)) {\n entry.container.remove();\n }\n }\n this.renderedImages = next;\n }\n}\n\n/**\n * A change key over everything the rendered tree bakes into element styles —\n * grid geometry, font, and theme colors. A key change invalidates every\n * rendered row, so the next paint restyles the root and rebuilds in full.\n */\nfunction metricsKeyFor(\n metrics: SurfaceMetrics\n): string {\n return [\n metrics.columns,\n metrics.rows,\n metrics.cellWidth,\n metrics.cellHeight,\n fontForStyle(metrics.style),\n metrics.style.theme.foreground,\n metrics.style.theme.background,\n metrics.style.theme.windowBackground,\n metrics.style.backgroundOpacity,\n ].join(\"|\");\n}\n\nfunction buildCellElement(\n x: number,\n text: string,\n span: number,\n style: WebHostSurfaceStyle | undefined,\n metrics: SurfaceMetrics\n): HTMLElement | undefined {\n const background = resolvedSurfaceBackground(style, metrics.style);\n const hasDecoration = Boolean(style?.underline || style?.strikethrough);\n if (!background && !hasDecoration && text.trim() === \"\") {\n return undefined;\n }\n\n const element = createElement(\"span\");\n element.textContent = text;\n const elementStyle = element.style;\n elementStyle.position = \"absolute\";\n elementStyle.left = `${x * metrics.cellWidth}px`;\n elementStyle.top = \"0\";\n elementStyle.width = `${Math.max(1, span) * metrics.cellWidth}px`;\n elementStyle.height = \"100%\";\n elementStyle.whiteSpace = \"pre\";\n elementStyle.color = resolvedSurfaceForeground(style, metrics.style);\n if (background) {\n elementStyle.backgroundColor = background;\n }\n\n const emphasis = style?.em ?? 0;\n if (emphasis & 1) {\n elementStyle.fontWeight = \"700\";\n }\n if (emphasis & 2) {\n elementStyle.fontStyle = \"italic\";\n }\n\n const opacity = style?.opacity ?? 1;\n if (opacity !== 1) {\n elementStyle.opacity = String(opacity);\n }\n\n applyTextDecoration(elementStyle, style);\n return element;\n}\n\nfunction applyTextDecoration(\n elementStyle: CSSStyleDeclaration,\n style: WebHostSurfaceStyle | undefined\n): void {\n const lines: string[] = [];\n if (style?.underline) {\n lines.push(\"underline\");\n }\n if (style?.strikethrough) {\n lines.push(\"line-through\");\n }\n if (lines.length === 0) {\n return;\n }\n\n elementStyle.textDecorationLine = lines.join(\" \");\n const pattern = style?.underline?.pattern ?? style?.strikethrough?.pattern;\n elementStyle.textDecorationStyle = decorationStyleFor(pattern);\n // CSS shares one decoration color across both lines; the underline's color\n // wins when the app styles them differently.\n const color = style?.underline?.color ?? style?.strikethrough?.color;\n if (color) {\n elementStyle.textDecorationColor = color;\n }\n}\n\nfunction decorationStyleFor(\n pattern: WebHostSurfaceLineStyle[\"pattern\"] | undefined\n): string {\n switch (pattern) {\n case \"dot\":\n return \"dotted\";\n case \"dash\":\n case \"dashDot\":\n case \"dashDotDot\":\n return \"dashed\";\n case \"double\":\n return \"double\";\n case \"curly\":\n return \"wavy\";\n default:\n return \"solid\";\n }\n}\n\nfunction fillContainer(\n style: CSSStyleDeclaration\n): void {\n style.position = \"absolute\";\n style.left = \"0\";\n style.top = \"0\";\n style.width = \"100%\";\n style.height = \"100%\";\n}\n\nfunction makeImageEntry(): RenderedImage {\n const container = createElement(\"div\");\n container.className = \"webhost-scene__surface-image\";\n container.style.position = \"absolute\";\n container.style.overflow = \"hidden\";\n\n const image = createElement(\"img\");\n image.style.position = \"absolute\";\n image.setAttribute(\"alt\", \"\");\n image.setAttribute(\"draggable\", \"false\");\n container.appendChild(image);\n return { container, image, source: \"\" };\n}\n\nfunction createElement(\n tagName: string\n): HTMLElement {\n if (typeof document === \"undefined\") {\n throw new Error(\"document is not available\");\n }\n return document.createElement(tagName);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DomSurfacePainter.js","names":[],"sources":["../../src/DomSurfacePainter.ts"],"sourcesContent":["import { fontForStyle } from \"./CanvasSurfacePainter.ts\";\nimport {\n resolvedSurfaceBackground,\n resolvedSurfaceForeground,\n type SurfaceMetrics,\n type WebHostSurfacePainter,\n} from \"./SurfaceRenderer.ts\";\nimport { webTUITerminalBackgroundColor } from \"./WebHostTerminalStyle.ts\";\nimport {\n isSupportedImageFormat,\n normalizeScalingMode,\n} from \"./normalizeWireTokens.ts\";\nimport {\n isWebHostImageRecoveryId,\n type WebHostImagePayloadRequestHandler,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n type WebHostSurfaceImage,\n type WebHostSurfaceLineStyle,\n type WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\nimport {\n registerDomSurfacePainterConformanceControl,\n} from \"./SurfacePainterConformanceControl.ts\";\n\ninterface RenderedImage {\n container: HTMLElement;\n image: HTMLElement;\n source: string;\n}\n\nexport interface DomSurfacePainterOptions {\n /**\n * Reports supported, positive-area image IDs absent from the retained DOM\n * cache. Returning the admitted subset lets bounded transports keep overflow\n * IDs eligible on the next presentation frame; `void` preserves legacy\n * all-accepted behavior for custom hosts.\n */\n onImagePayloadMiss?: WebHostImagePayloadRequestHandler;\n}\n\n/**\n * Draws SwiftTUI surface frames as a DOM element tree instead of canvas\n * pixels: one absolutely positioned row container per grid row, one `<span>`\n * per styled cell run, and `<img>` elements for surface images.\n *\n * Rendering cells as real text buys what canvas cannot offer — the browser's\n * own font shaping and fallback (emoji, CJK), crisp text at any page zoom, an\n * inspectable tree, and native text selection — at the cost of pixel-exact\n * box-drawing seams, which render as font glyphs here rather than the canvas\n * painter's hand-drawn strokes.\n *\n * Damage handling mirrors the canvas painter at row granularity: a frame\n * carrying scoped damage rebuilds only the touched rows' elements; geometry\n * or style changes force a full rebuild. Grid alignment across a run is kept\n * exact by stretching each glyph advance to the cell width via\n * `letter-spacing`, measured once per font/size pair.\n */\nexport class DomSurfacePainter implements WebHostSurfacePainter {\n private readonly onImagePayloadMiss: WebHostImagePayloadRequestHandler;\n private root?: HTMLElement;\n private rowsLayer?: HTMLElement;\n private imagesLayer?: HTMLElement;\n private rowElements: HTMLElement[] = [];\n private renderedImages = new Map<string, RenderedImage>();\n private appliedMetricsKey?: string;\n private renderedGridKey?: string;\n private hasRenderedFrame = false;\n private letterSpacing?: { key: string; value: string };\n private reportedMissingImageIds = new Set<string>();\n private lastImageRecoveryFrame?: WebHostSurfaceFrame;\n private lastEpoch?: number;\n\n constructor(options: DomSurfacePainterOptions = {}) {\n this.onImagePayloadMiss = options.onImagePayloadMiss ?? (() => {});\n registerDomSurfacePainterConformanceControl(this, {\n evictImages: (ids) => {\n for (const id of ids) {\n this.renderedImages.get(id)?.container.remove();\n this.renderedImages.delete(id);\n this.reportedMissingImageIds.delete(id);\n }\n },\n visibleImageIDs: () => [...this.renderedImages.keys()].sort(),\n });\n }\n\n /**\n * Binds the container the painter renders into. The runtime owns the\n * container's size; the painter owns everything inside it.\n */\n attach(\n root: HTMLElement\n ): void {\n this.root = root;\n\n const rowsLayer = createElement(\"div\");\n rowsLayer.className = \"webhost-scene__surface-rows\";\n fillContainer(rowsLayer.style);\n\n const imagesLayer = createElement(\"div\");\n imagesLayer.className = \"webhost-scene__surface-images\";\n fillContainer(imagesLayer.style);\n imagesLayer.style.pointerEvents = \"none\";\n\n this.rowsLayer = rowsLayer;\n this.imagesLayer = imagesLayer;\n root.replaceChildren(rowsLayer, imagesLayer);\n this.rowElements = [];\n this.renderedImages = new Map();\n this.appliedMetricsKey = undefined;\n this.renderedGridKey = undefined;\n this.hasRenderedFrame = false;\n this.reportedMissingImageIds.clear();\n this.lastImageRecoveryFrame = undefined;\n this.lastEpoch = undefined;\n }\n\n paint(\n metrics: SurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage,\n _recoveredImagePayloadIds?: readonly string[]\n ): void {\n const root = this.root;\n const rowsLayer = this.rowsLayer;\n if (!root || !rowsLayer) {\n return;\n }\n\n if (frame?.epoch !== undefined && frame.epoch !== this.lastEpoch) {\n this.lastEpoch = frame.epoch;\n this.reportedMissingImageIds.clear();\n }\n\n const metricsKey = metricsKeyFor(metrics);\n const metricsChanged = metricsKey !== this.appliedMetricsKey;\n if (metricsChanged) {\n this.applyRootStyle(root, metrics);\n this.appliedMetricsKey = metricsKey;\n }\n\n if (!frame) {\n this.rowElements = [];\n rowsLayer.replaceChildren();\n this.lastImageRecoveryFrame = undefined;\n this.reconcileImages([], metrics, false);\n this.renderedGridKey = undefined;\n this.hasRenderedFrame = false;\n return;\n }\n\n const gridKey = `${frame.width}x${frame.height}x${frame.rows.length}`;\n const fullRepaint = metricsChanged\n || !this.hasRenderedFrame\n || gridKey !== this.renderedGridKey\n || !damage\n || damage.requiresFullTextRepaint\n || damage.requiresFullGraphicsReplay;\n this.renderedGridKey = gridKey;\n\n if (fullRepaint) {\n for (let y = this.rowElements.length; y > frame.rows.length; y -= 1) {\n this.rowElements[y - 1]?.remove();\n }\n this.rowElements.length = Math.min(this.rowElements.length, frame.rows.length);\n for (let y = 0; y < frame.rows.length; y += 1) {\n this.rebuildRow(y, frame, metrics);\n }\n } else {\n for (const [row] of damage.textRows) {\n if (row < 0 || row >= frame.rows.length) {\n continue;\n }\n this.rebuildRow(row, frame, metrics);\n }\n }\n\n const allowRecoveryRequests = frame !== this.lastImageRecoveryFrame;\n this.lastImageRecoveryFrame = frame;\n this.reconcileImages(frame.images ?? [], metrics, allowRecoveryRequests);\n this.hasRenderedFrame = true;\n }\n\n private rebuildRow(\n y: number,\n frame: WebHostSurfaceFrame,\n metrics: SurfaceMetrics\n ): void {\n const rowElement = this.ensureRowElement(y, metrics);\n const children: HTMLElement[] = [];\n for (const [x, text, span, styleIndex] of frame.rows[y] ?? []) {\n const cellElement = buildCellElement(\n x,\n text,\n span,\n frame.styles[styleIndex] ?? undefined,\n metrics\n );\n if (cellElement) {\n children.push(cellElement);\n }\n }\n rowElement.replaceChildren(...children);\n }\n\n private ensureRowElement(\n y: number,\n metrics: SurfaceMetrics\n ): HTMLElement {\n let rowElement = this.rowElements[y];\n if (!rowElement) {\n rowElement = createElement(\"div\");\n rowElement.className = \"webhost-scene__surface-row\";\n rowElement.style.position = \"absolute\";\n rowElement.style.left = \"0\";\n this.rowElements[y] = rowElement;\n this.rowsLayer?.appendChild(rowElement);\n }\n rowElement.style.top = `${y * metrics.cellHeight}px`;\n rowElement.style.height = `${metrics.cellHeight}px`;\n rowElement.style.width = `${metrics.columns * metrics.cellWidth}px`;\n return rowElement;\n }\n\n private applyRootStyle(\n root: HTMLElement,\n metrics: SurfaceMetrics\n ): void {\n const style = root.style;\n style.position = \"relative\";\n style.overflow = \"hidden\";\n style.background = webTUITerminalBackgroundColor(metrics.style);\n style.font = fontForStyle(metrics.style);\n // Set after `font`: the shorthand resets line-height, and the grid needs\n // every row to be exactly one cell tall.\n style.lineHeight = `${metrics.cellHeight}px`;\n style.letterSpacing = this.letterSpacingFor(metrics);\n // Ligature-capable monospace fonts would merge runs like \"->\" into one\n // glyph and break the column grid.\n style.fontVariantLigatures = \"none\";\n style.userSelect = \"text\";\n }\n\n /**\n * The per-glyph advance correction that stretches the font's natural\n * monospace advance to exactly `cellWidth`, so long runs stay on the cell\n * grid instead of drifting by the sub-pixel remainder of the runtime's\n * ceil'd cell measurement.\n */\n private letterSpacingFor(\n metrics: SurfaceMetrics\n ): string {\n const font = fontForStyle(metrics.style);\n const key = `${font}|${metrics.cellWidth}`;\n if (this.letterSpacing?.key === key) {\n return this.letterSpacing.value;\n }\n\n let value = \"0px\";\n const canvas = createElement(\"canvas\") as HTMLCanvasElement;\n const context = canvas.getContext?.(\"2d\");\n if (context) {\n context.font = font;\n const advance = context.measureText(\"W\").width;\n const correction = metrics.cellWidth - advance;\n if (advance > 0 && Math.abs(correction) >= 0.01) {\n value = `${Math.round(correction * 1000) / 1000}px`;\n }\n }\n\n this.letterSpacing = { key, value };\n return value;\n }\n\n private reconcileImages(\n images: WebHostSurfaceImage[],\n metrics: SurfaceMetrics,\n allowRecoveryRequests: boolean\n ): void {\n const layer = this.imagesLayer;\n if (!layer) {\n return;\n }\n\n const next = new Map<string, RenderedImage>();\n const currentMissingImageIds = new Set<string>();\n const newlyMissingImageIds = new Set<string>();\n for (const rawImage of images) {\n if (!isSupportedImageFormat(rawImage.format)) {\n continue;\n }\n const image = {\n ...rawImage,\n scalingMode: normalizeScalingMode(rawImage.scalingMode),\n };\n const [boundsX, boundsY, boundsWidth, boundsHeight] = image.bounds;\n const [clipX, clipY, clipWidth, clipHeight] = image.visibleBounds;\n const existing = this.renderedImages.get(image.id);\n if (\n boundsWidth <= 0\n || boundsHeight <= 0\n || clipWidth <= 0\n || clipHeight <= 0\n ) {\n continue;\n }\n if (!existing && image.dataBase64 === undefined) {\n if (!isWebHostImageRecoveryId(image.id)) {\n continue;\n }\n currentMissingImageIds.add(image.id);\n if (!this.reportedMissingImageIds.has(image.id)) {\n newlyMissingImageIds.add(image.id);\n }\n continue;\n }\n\n const entry = existing ?? makeImageEntry();\n entry.container.style.left = `${clipX * metrics.cellWidth}px`;\n entry.container.style.top = `${clipY * metrics.cellHeight}px`;\n entry.container.style.width = `${clipWidth * metrics.cellWidth}px`;\n entry.container.style.height = `${clipHeight * metrics.cellHeight}px`;\n entry.image.style.left = `${(boundsX - clipX) * metrics.cellWidth}px`;\n entry.image.style.top = `${(boundsY - clipY) * metrics.cellHeight}px`;\n entry.image.style.width = `${boundsWidth * metrics.cellWidth}px`;\n entry.image.style.height = `${boundsHeight * metrics.cellHeight}px`;\n\n if (image.dataBase64) {\n const source = `data:image/${image.format};base64,${image.dataBase64}`;\n if (entry.source !== source) {\n entry.image.setAttribute(\"src\", source);\n entry.source = source;\n }\n }\n if (!existing) {\n layer.appendChild(entry.container);\n }\n next.set(image.id, entry);\n }\n\n for (const [id, entry] of this.renderedImages) {\n if (!next.has(id)) {\n entry.container.remove();\n }\n }\n this.renderedImages = next;\n for (const id of this.reportedMissingImageIds) {\n if (!currentMissingImageIds.has(id)) {\n this.reportedMissingImageIds.delete(id);\n }\n }\n if (allowRecoveryRequests && newlyMissingImageIds.size > 0) {\n const candidateIds = [...newlyMissingImageIds].sort();\n const admittedIds = this.onImagePayloadMiss(candidateIds);\n const acceptedIds = Array.isArray(admittedIds)\n ? admittedIds\n : candidateIds;\n const candidates = new Set(candidateIds);\n for (const id of acceptedIds) {\n if (candidates.has(id)) {\n this.reportedMissingImageIds.add(id);\n }\n }\n }\n }\n}\n\n/**\n * A change key over everything the rendered tree bakes into element styles —\n * grid geometry, font, and theme colors. A key change invalidates every\n * rendered row, so the next paint restyles the root and rebuilds in full.\n */\nfunction metricsKeyFor(\n metrics: SurfaceMetrics\n): string {\n return [\n metrics.columns,\n metrics.rows,\n metrics.cellWidth,\n metrics.cellHeight,\n fontForStyle(metrics.style),\n metrics.style.theme.foreground,\n metrics.style.theme.background,\n metrics.style.theme.windowBackground,\n metrics.style.backgroundOpacity,\n ].join(\"|\");\n}\n\nfunction buildCellElement(\n x: number,\n text: string,\n span: number,\n style: WebHostSurfaceStyle | undefined,\n metrics: SurfaceMetrics\n): HTMLElement | undefined {\n const background = resolvedSurfaceBackground(style, metrics.style);\n const hasDecoration = Boolean(style?.underline || style?.strikethrough);\n if (!background && !hasDecoration && text.trim() === \"\") {\n return undefined;\n }\n\n const element = createElement(\"span\");\n element.textContent = text;\n const elementStyle = element.style;\n elementStyle.position = \"absolute\";\n elementStyle.left = `${x * metrics.cellWidth}px`;\n elementStyle.top = \"0\";\n elementStyle.width = `${Math.max(1, span) * metrics.cellWidth}px`;\n elementStyle.height = \"100%\";\n elementStyle.whiteSpace = \"pre\";\n elementStyle.color = resolvedSurfaceForeground(style, metrics.style);\n if (background) {\n elementStyle.backgroundColor = background;\n }\n\n const emphasis = style?.em ?? 0;\n if (emphasis & 1) {\n elementStyle.fontWeight = \"700\";\n }\n if (emphasis & 2) {\n elementStyle.fontStyle = \"italic\";\n }\n\n const opacity = style?.opacity ?? 1;\n if (opacity !== 1) {\n elementStyle.opacity = String(opacity);\n }\n\n applyTextDecoration(elementStyle, style);\n return element;\n}\n\nfunction applyTextDecoration(\n elementStyle: CSSStyleDeclaration,\n style: WebHostSurfaceStyle | undefined\n): void {\n const lines: string[] = [];\n if (style?.underline) {\n lines.push(\"underline\");\n }\n if (style?.strikethrough) {\n lines.push(\"line-through\");\n }\n if (lines.length === 0) {\n return;\n }\n\n elementStyle.textDecorationLine = lines.join(\" \");\n const pattern = style?.underline?.pattern ?? style?.strikethrough?.pattern;\n elementStyle.textDecorationStyle = decorationStyleFor(pattern);\n // CSS shares one decoration color across both lines; the underline's color\n // wins when the app styles them differently.\n const color = style?.underline?.color ?? style?.strikethrough?.color;\n if (color) {\n elementStyle.textDecorationColor = color;\n }\n}\n\nfunction decorationStyleFor(\n pattern: WebHostSurfaceLineStyle[\"pattern\"] | undefined\n): string {\n switch (pattern) {\n case \"dot\":\n return \"dotted\";\n case \"dash\":\n case \"dashDot\":\n case \"dashDotDot\":\n return \"dashed\";\n case \"double\":\n return \"double\";\n case \"curly\":\n return \"wavy\";\n default:\n return \"solid\";\n }\n}\n\nfunction fillContainer(\n style: CSSStyleDeclaration\n): void {\n style.position = \"absolute\";\n style.left = \"0\";\n style.top = \"0\";\n style.width = \"100%\";\n style.height = \"100%\";\n}\n\nfunction makeImageEntry(): RenderedImage {\n const container = createElement(\"div\");\n container.className = \"webhost-scene__surface-image\";\n container.style.position = \"absolute\";\n container.style.overflow = \"hidden\";\n\n const image = createElement(\"img\");\n image.style.position = \"absolute\";\n image.setAttribute(\"alt\", \"\");\n image.setAttribute(\"draggable\", \"false\");\n container.appendChild(image);\n return { container, image, source: \"\" };\n}\n\nfunction createElement(\n tagName: string\n): HTMLElement {\n if (typeof document === \"undefined\") {\n throw new Error(\"document is not available\");\n }\n return document.createElement(tagName);\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ //#region src/SurfacePainterConformanceControl.ts
2
+ const canvasControls = /* @__PURE__ */ new WeakMap();
3
+ const domControls = /* @__PURE__ */ new WeakMap();
4
+ function registerCanvasSurfacePainterConformanceControl(painter, control) {
5
+ canvasControls.set(painter, control);
6
+ }
7
+ function registerDomSurfacePainterConformanceControl(painter, control) {
8
+ domControls.set(painter, control);
9
+ }
10
+ //#endregion
11
+ export { registerCanvasSurfacePainterConformanceControl, registerDomSurfacePainterConformanceControl };
12
+
13
+ //# sourceMappingURL=SurfacePainterConformanceControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SurfacePainterConformanceControl.js","names":[],"sources":["../../src/SurfacePainterConformanceControl.ts"],"sourcesContent":["/**\n * Internal test-only seams shared by the S5 runner and the real painters.\n * This module is intentionally absent from every package entry point.\n */\nimport type { WebHostSurfaceImage } from \"./WebHostSurfaceTransport.ts\";\n\nexport interface CanvasSurfacePainterConformanceControl {\n evictImages(ids: readonly string[]): void;\n visibleImageIDs(images: readonly WebHostSurfaceImage[]): string[];\n}\n\nexport interface DomSurfacePainterConformanceControl {\n evictImages(ids: readonly string[]): void;\n visibleImageIDs(): string[];\n}\n\nconst canvasControls = new WeakMap<object, CanvasSurfacePainterConformanceControl>();\nconst domControls = new WeakMap<object, DomSurfacePainterConformanceControl>();\n\nexport function registerCanvasSurfacePainterConformanceControl(\n painter: object,\n control: CanvasSurfacePainterConformanceControl\n): void {\n canvasControls.set(painter, control);\n}\n\nexport function canvasSurfacePainterConformanceControl(\n painter: object\n): CanvasSurfacePainterConformanceControl {\n const control = canvasControls.get(painter);\n if (!control) {\n throw new Error(\"Canvas conformance control is not registered\");\n }\n return control;\n}\n\nexport function registerDomSurfacePainterConformanceControl(\n painter: object,\n control: DomSurfacePainterConformanceControl\n): void {\n domControls.set(painter, control);\n}\n\nexport function domSurfacePainterConformanceControl(\n painter: object\n): DomSurfacePainterConformanceControl {\n const control = domControls.get(painter);\n if (!control) {\n throw new Error(\"DOM conformance control is not registered\");\n }\n return control;\n}\n"],"mappings":";AAgBA,MAAM,iCAAiB,IAAI,QAAwD;AACnF,MAAM,8BAAc,IAAI,QAAqD;AAE7E,SAAgB,+CACd,SACA,SACM;CACN,eAAe,IAAI,SAAS,OAAO;AACrC;AAYA,SAAgB,4CACd,SACA,SACM;CACN,YAAY,IAAI,SAAS,OAAO;AAClC"}
@@ -30,10 +30,11 @@ interface SurfaceMetrics {
30
30
  /**
31
31
  * The paint seam shared by the canvas and DOM painters. The runtime calls
32
32
  * `paint` with the latest metrics snapshot, the current frame (or `undefined`
33
- * before the first frame), and optional damage scoping the repaint.
33
+ * before the first frame), optional damage scoping the repaint, and the image
34
+ * payload IDs that answered an outstanding recovery request in this frame.
34
35
  */
35
36
  interface WebHostSurfacePainter {
36
- paint(metrics: SurfaceMetrics, frame: WebHostSurfaceFrame | undefined, damage?: WebHostSurfaceDamage): void;
37
+ paint(metrics: SurfaceMetrics, frame: WebHostSurfaceFrame | undefined, damage?: WebHostSurfaceDamage, recoveredImagePayloadIds?: readonly string[]): void;
37
38
  }
38
39
  /**
39
40
  * The effective text color for a cell, folding the reverse-video emphasis bit
@@ -1 +1 @@
1
- {"version":3,"file":"SurfaceRenderer.js","names":[],"sources":["../../src/SurfaceRenderer.ts"],"sourcesContent":["import type { ResolvedWebHostTerminalStyle } from \"./WebHostTerminalStyle.ts\";\nimport type {\n WebHostSurfaceDamage,\n WebHostSurfaceFrame,\n WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\n\n/**\n * Which presenter draws surface frames into the scene mount.\n *\n * - `\"canvas\"` (default): paints cells onto a 2D `<canvas>` — pixel-exact\n * box-drawing seams and decoration patterns, one DOM node total.\n * - `\"dom\"`: renders cells as absolutely positioned text elements — native\n * font rendering (fallback glyphs, subpixel AA, crisp zoom), an\n * inspectable element tree, and real selectable text (hold Alt/Option and\n * drag). Box drawing and decoration patterns render via font glyphs and\n * CSS `text-decoration`, so hairline seams may differ from the canvas\n * painter.\n */\nexport type WebHostSurfaceRendererKind = \"canvas\" | \"dom\";\n\n/**\n * A read-only snapshot of the cell grid geometry and active style a surface\n * painter needs for a single paint pass. The runtime owns this state and\n * mutates it as the surface resizes or restyles; passing a fresh snapshot per\n * `paint` keeps painters stateless about geometry and avoids stale reads.\n */\nexport interface SurfaceMetrics {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n style: ResolvedWebHostTerminalStyle;\n}\n\n/**\n * The paint seam shared by the canvas and DOM painters. The runtime calls\n * `paint` with the latest metrics snapshot, the current frame (or `undefined`\n * before the first frame), and optional damage scoping the repaint.\n */\nexport interface WebHostSurfacePainter {\n paint(\n metrics: SurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage\n ): void;\n}\n\n/**\n * The effective text color for a cell, folding the reverse-video emphasis bit\n * (`em & 16`) over the host terminal theme's defaults.\n */\nexport function resolvedSurfaceForeground(\n style: WebHostSurfaceStyle | null | undefined,\n terminalStyle: ResolvedWebHostTerminalStyle\n): string {\n if ((style?.em ?? 0) & 16) {\n return style?.bg ?? terminalStyle.theme.background;\n }\n return style?.fg ?? terminalStyle.theme.foreground;\n}\n\n/**\n * The effective background fill for a cell (or `undefined` for the terminal's\n * base background), folding the reverse-video emphasis bit (`em & 16`).\n */\nexport function resolvedSurfaceBackground(\n style: WebHostSurfaceStyle | null | undefined,\n terminalStyle: ResolvedWebHostTerminalStyle\n): string | undefined {\n if ((style?.em ?? 0) & 16) {\n return style?.fg ?? terminalStyle.theme.foreground;\n }\n return style?.bg;\n}\n"],"mappings":";;;;;AAoDA,SAAgB,0BACd,OACA,eACQ;CACR,KAAK,OAAO,MAAM,KAAK,IACrB,OAAO,OAAO,MAAM,cAAc,MAAM;CAE1C,OAAO,OAAO,MAAM,cAAc,MAAM;AAC1C;;;;;AAMA,SAAgB,0BACd,OACA,eACoB;CACpB,KAAK,OAAO,MAAM,KAAK,IACrB,OAAO,OAAO,MAAM,cAAc,MAAM;CAE1C,OAAO,OAAO;AAChB"}
1
+ {"version":3,"file":"SurfaceRenderer.js","names":[],"sources":["../../src/SurfaceRenderer.ts"],"sourcesContent":["import type { ResolvedWebHostTerminalStyle } from \"./WebHostTerminalStyle.ts\";\nimport type {\n WebHostSurfaceDamage,\n WebHostSurfaceFrame,\n WebHostSurfaceStyle,\n} from \"./WebHostSurfaceTransport.ts\";\n\n/**\n * Which presenter draws surface frames into the scene mount.\n *\n * - `\"canvas\"` (default): paints cells onto a 2D `<canvas>` — pixel-exact\n * box-drawing seams and decoration patterns, one DOM node total.\n * - `\"dom\"`: renders cells as absolutely positioned text elements — native\n * font rendering (fallback glyphs, subpixel AA, crisp zoom), an\n * inspectable element tree, and real selectable text (hold Alt/Option and\n * drag). Box drawing and decoration patterns render via font glyphs and\n * CSS `text-decoration`, so hairline seams may differ from the canvas\n * painter.\n */\nexport type WebHostSurfaceRendererKind = \"canvas\" | \"dom\";\n\n/**\n * A read-only snapshot of the cell grid geometry and active style a surface\n * painter needs for a single paint pass. The runtime owns this state and\n * mutates it as the surface resizes or restyles; passing a fresh snapshot per\n * `paint` keeps painters stateless about geometry and avoids stale reads.\n */\nexport interface SurfaceMetrics {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n style: ResolvedWebHostTerminalStyle;\n}\n\n/**\n * The paint seam shared by the canvas and DOM painters. The runtime calls\n * `paint` with the latest metrics snapshot, the current frame (or `undefined`\n * before the first frame), optional damage scoping the repaint, and the image\n * payload IDs that answered an outstanding recovery request in this frame.\n */\nexport interface WebHostSurfacePainter {\n paint(\n metrics: SurfaceMetrics,\n frame: WebHostSurfaceFrame | undefined,\n damage?: WebHostSurfaceDamage,\n recoveredImagePayloadIds?: readonly string[]\n ): void;\n}\n\n/**\n * The effective text color for a cell, folding the reverse-video emphasis bit\n * (`em & 16`) over the host terminal theme's defaults.\n */\nexport function resolvedSurfaceForeground(\n style: WebHostSurfaceStyle | null | undefined,\n terminalStyle: ResolvedWebHostTerminalStyle\n): string {\n if ((style?.em ?? 0) & 16) {\n return style?.bg ?? terminalStyle.theme.background;\n }\n return style?.fg ?? terminalStyle.theme.foreground;\n}\n\n/**\n * The effective background fill for a cell (or `undefined` for the terminal's\n * base background), folding the reverse-video emphasis bit (`em & 16`).\n */\nexport function resolvedSurfaceBackground(\n style: WebHostSurfaceStyle | null | undefined,\n terminalStyle: ResolvedWebHostTerminalStyle\n): string | undefined {\n if ((style?.em ?? 0) & 16) {\n return style?.fg ?? terminalStyle.theme.foreground;\n }\n return style?.bg;\n}\n"],"mappings":";;;;;AAsDA,SAAgB,0BACd,OACA,eACQ;CACR,KAAK,OAAO,MAAM,KAAK,IACrB,OAAO,OAAO,MAAM,cAAc,MAAM;CAE1C,OAAO,OAAO,MAAM,cAAc,MAAM;AAC1C;;;;;AAMA,SAAgB,0BACd,OACA,eACoB;CACpB,KAAK,OAAO,MAAM,KAAK,IACrB,OAAO,OAAO,MAAM,cAAc,MAAM;CAE1C,OAAO,OAAO;AAChB"}
@@ -1,5 +1,5 @@
1
1
  import { WebHostTerminalStyle } from "./WebHostTerminalStyle.js";
2
- import { WebHostFocusPresentation, WebHostFrameDiagnosticRecord, WebHostOutputSink, WebHostRuntimeIssue } from "./WebHostSurfaceTransport.js";
2
+ import { WebHostFocusPresentation, WebHostFrameDiagnosticRecord, WebHostImagePayloadRequestHandler, WebHostOutputSink, WebHostRuntimeIssue } from "./WebHostSurfaceTransport.js";
3
3
  import { WebHostSurfaceRendererKind } from "./SurfaceRenderer.js";
4
4
  import { WebHostSceneDescriptor } from "./WebHostSceneManifest.js";
5
5
  //#region src/WebHostSceneRuntime.d.ts
@@ -8,6 +8,8 @@ interface WebHostSceneBridge {
8
8
  resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;
9
9
  updateRenderStyle(style: WebHostTerminalStyle): void;
10
10
  sendInput(chunk: Uint8Array): void;
11
+ /** Optional for compatibility with custom bridges predating image recovery. */
12
+ requestImagePayloads?: WebHostImagePayloadRequestHandler;
11
13
  dispose(): void;
12
14
  }
13
15
  interface WebHostSceneRuntimeOptions {
@@ -66,7 +66,10 @@ var WebHostSceneRuntime = class {
66
66
  this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;
67
67
  this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);
68
68
  this.rendererKind = options.renderer ?? "canvas";
69
- this.painter = this.rendererKind === "dom" ? new DomSurfacePainter() : new CanvasSurfacePainter();
69
+ const onImagePayloadMiss = (ids) => {
70
+ return this.bridge?.requestImagePayloads?.(ids);
71
+ };
72
+ this.painter = this.rendererKind === "dom" ? new DomSurfacePainter({ onImagePayloadMiss }) : new CanvasSurfacePainter({ onImagePayloadMiss });
70
73
  this.onOpenHyperlink = options.onOpenHyperlink;
71
74
  this.suspendWhenHidden = options.suspendWhenHidden ?? true;
72
75
  this.element = document.createElement("section");
@@ -103,7 +106,7 @@ var WebHostSceneRuntime = class {
103
106
  this.installInputHandlers();
104
107
  this.installResizeObserver();
105
108
  this.bridge?.bindOutput({
106
- presentSurface: (frame) => this.presentSurface(frame),
109
+ presentSurface: (frame, recoveredImagePayloadIds) => this.presentSurface(frame, recoveredImagePayloadIds),
107
110
  writeClipboard: (text) => this.writeClipboard(text),
108
111
  notifyRuntimeIssue: (issue) => this.notifyRuntimeIssue(issue),
109
112
  recordFrameDiagnostic: (diagnostic) => this.recordFrameDiagnostic(diagnostic),
@@ -173,7 +176,7 @@ var WebHostSceneRuntime = class {
173
176
  this.diagnosticText.textContent = `${this.diagnosticText.textContent ?? ""}${text}`;
174
177
  }
175
178
  notifyRuntimeIssue(issue) {
176
- console.log(issue.description);
179
+ this.writeOutput(`${issue.description}\n`);
177
180
  }
178
181
  recordFrameDiagnostic(diagnostic) {
179
182
  this.onFrameDiagnostic?.(diagnostic);
@@ -193,13 +196,13 @@ var WebHostSceneRuntime = class {
193
196
  this.resizeObserver?.disconnect();
194
197
  this.element.remove();
195
198
  }
196
- presentSurface(frame) {
199
+ presentSurface(frame, recoveredImagePayloadIds) {
197
200
  const previousFrame = this.currentFrame;
198
201
  this.currentFrame = frame;
199
202
  this.columns = Math.max(1, Math.round(frame.width));
200
203
  this.rows = Math.max(1, Math.round(frame.height));
201
204
  const resized = this.resizeSurface();
202
- this.draw(previousFrame && !resized ? frame.damage : void 0);
205
+ this.draw(previousFrame && !resized ? frame.damage : void 0, recoveredImagePayloadIds);
203
206
  this.syncAccessibilityTree();
204
207
  }
205
208
  /**
@@ -409,8 +412,8 @@ var WebHostSceneRuntime = class {
409
412
  this.cellWidth = Math.max(1, Math.ceil(context.measureText("W").width));
410
413
  this.cellHeight = Math.max(1, Math.ceil(this.currentStyle.fontSize * 1.35));
411
414
  }
412
- draw(damage) {
413
- this.painter.paint(this.surfaceMetrics(), this.currentFrame, damage);
415
+ draw(damage, recoveredImagePayloadIds) {
416
+ this.painter.paint(this.surfaceMetrics(), this.currentFrame, damage, recoveredImagePayloadIds);
414
417
  }
415
418
  syncAccessibilityTree() {
416
419
  const tree = this.accessibilityTree;
@@ -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 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 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 this.painter = this.rendererKind === \"dom\"\n ? new DomSurfacePainter()\n : new CanvasSurfacePainter();\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) => this.presentSurface(frame),\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 console.log(issue.description);\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 ): 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(previousFrame && !resized ? frame.damage : undefined);\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 ): void {\n this.painter.paint(this.surfaceMetrics(), this.currentFrame, damage);\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":";;;;;;;;;;;;;;AA6GA,SAAS,gBAAgB,mBAAmD;CAC1E,IAAI,sBAAsB,KAAA,GACxB,OAAO;CAET,OAAO,oBAAoB,YAAY;AACzC;;;;;;;;AASA,IAAa,sBAAb,MAAiC;CAC/B;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,eAAgC,IAAI,kBAAkB;CACtD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,UAAkB;CAClB,OAAe;CACf,YAAoB;CACpB,aAAqB;CACrB,sBAA6C;CAC7C,qBAA6B;CAC7B;CACA;CACA;CAMA,YAAoB;CACpB,kBAA0B;CAC1B,mBAA2B;CAC3B;CAEA,YAAY,SAAqC;EAC/C,KAAK,aAAa,QAAQ;EAC1B,KAAK,eAAe,8BAA8B,QAAQ,KAAK;EAC/D,KAAK,SAAS,QAAQ;EACtB,KAAK,UAAU,QAAQ;EACvB,KAAK,oBAAoB,QAAQ;EACjC,KAAK,gCAAgC,QAAQ,iCAAiC;EAC9E,KAAK,YAAY,QAAQ,aAAa,gBAAgB,QAAQ,iBAAiB;EAC/E,KAAK,eAAe,QAAQ,YAAY;EACxC,KAAK,UAAU,KAAK,iBAAiB,QACjC,IAAI,kBAAkB,IACtB,IAAI,qBAAqB;EAC7B,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,oBAAoB,QAAQ,qBAAqB;EACtD,KAAK,UAAU,SAAS,cAAc,SAAS;EAC/C,KAAK,QAAQ,YAAY;EACzB,KAAK,QAAQ,QAAQ,UAAU,QAAQ,WAAW;EAClD,KAAK,QAAQ,SAAS;EAEtB,MAAM,SAAS,SAAS,cAAc,KAAK;EAC3C,OAAO,YAAY;EACnB,OAAO,cAAc,QAAQ,WAAW,SAAS,QAAQ,WAAW;EAEpE,KAAK,gBAAgB,SAAS,cAAc,KAAK;EACjD,KAAK,cAAc,YAAY;EAC/B,KAAK,cAAc,WAAW;EAE9B,KAAK,QAAQ,OAAO,QAAQ,KAAK,aAAa;EAC9C,QAAQ,MAAM,YAAY,KAAK,OAAO;EACtC,KAAK,gBAAgB;CACvB;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAK,gBACP;EAGF,IAAI,KAAK,mBAAmB,mBAAmB;GAC7C,MAAM,cAAc,SAAS,cAAc,KAAK;GAChD,YAAY,YAAY;GACxB,YAAY,aAAa,eAAe,MAAM;GAC9C,KAAK,iBAAiB;GACtB,KAAK,QAAQ,OAAO,WAAW;EACjC,OAAO;GACL,MAAM,SAAS,SAAS,cAAc,QAAQ;GAC9C,OAAO,YAAY;GACnB,OAAO,aAAa,eAAe,MAAM;GACzC,KAAK,SAAS;GACd,KAAK,QAAQ,OAAO,cAAc,KAAK,KAAK,CAAC;EAC/C;EACA,KAAK,oBAAoB,IAAI,yBAAyB;EACtD,KAAK,cAAc,gBACjB,KAAK,gBACL,KAAK,kBAAkB,SACvB,KAAK,kBAAkB,gBACzB;EACA,KAAK,qBAAqB;EAC1B,KAAK,sBAAsB;EAE3B,KAAK,QAAQ,WAAW;GACtB,iBAAiB,UAAU,KAAK,eAAe,KAAK;GACpD,iBAAiB,SAAS,KAAK,eAAe,IAAI;GAClD,qBAAqB,UAAU,KAAK,mBAAmB,KAAK;GAC5D,wBAAwB,eAAe,KAAK,sBAAsB,UAAU;GAC5E,cAAc,SAAS,KAAK,YAAY,IAAI;GAC5C,aAAa,SAAS,KAAK,YAAY,IAAI;EAC7C,CAAC;EAED,KAAK,WAAW,KAAK,YAAY;EACjC,KAAK,aAAa;EAClB,KAAK,cAAc;EACnB,KAAK,KAAK;EACV,KAAK,sBAAsB;CAC7B;CAEA,WACE,SACM;EACN,KAAK,YAAY;EACjB,KAAK,gBAAgB;EACrB,IAAI,SAAS;GACX,KAAK,cAAc;GACnB,IAAI,KAAK,+BACP,KAAK,cAAc,QAAQ,EAAE,eAAe,KAAK,CAAC;EAEtD;EACA,KAAK,wBAAwB;CAC/B;;;;;;;CAQA,mBACE,SACM;EACN,KAAK,kBAAkB;EACvB,KAAK,wBAAwB;CAC/B;CAEA,0BAAwC;EACtC,MAAM,YAAY,KAAK,sBAAsB,CAAC,KAAK,aAAa,CAAC,KAAK;EACtE,IAAI,cAAc,KAAK,kBACrB;EAEF,KAAK,mBAAmB;EACxB,KAAK,0BAA0B,SAAS;CAC1C;;;;;;CAOA,0BACE,YACM,CAAC;CAET,SACE,OACM;EACN,KAAK,eAAe,8BAA8B,KAAK;EACvD,KAAK,WAAW,KAAK,YAAY;EACjC,KAAK,QAAQ,kBAAkB,KAAK,YAAY;EAChD,KAAK,aAAa;EAClB,KAAK,cAAc;EACnB,KAAK,KAAK;EACV,KAAK,sBAAsB;CAC7B;CAEA,OACE,SACA,MACM;EACN,KAAK,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;EAC9C,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,CAAC;EACxC,KAAK,cAAc;EACnB,KAAK,KAAK;EACV,KAAK,sBAAsB;CAC7B;CAEA,YACE,MACM;EACN,IAAI,CAAC,KAAK,gBAAgB;GACxB,MAAM,iBAAiB,SAAS,cAAc,KAAK;GACnD,eAAe,YAAY;GAC3B,KAAK,iBAAiB;GACtB,KAAK,cAAc,YAAY,cAAc;EAC/C;EACA,KAAK,eAAe,cAAc,GAAG,KAAK,eAAe,eAAe,KAAK;CAC/E;CAEA,mBACE,OACM;EACN,QAAQ,IAAI,MAAM,WAAW;CAC/B;CAEA,sBACE,YACM;EACN,KAAK,oBAAoB,UAAU;CACrC;CAEA,MAAM,eACJ,MACe;EACf,MAAM,YAAY,WAAW,WAAW;EACxC,IAAI,CAAC,WAAW,WACd;EAGF,IAAI;GACF,MAAM,UAAU,UAAU,IAAI;EAChC,QAAQ,CAGR;CACF;CAEA,UACE,OACM;EACN,KAAK,QAAQ,KAAK;CACpB;CAEA,UAAgB;EACd,KAAK,sBAAsB;EAC3B,KAAK,gBAAgB,WAAW;EAChC,KAAK,QAAQ,OAAO;CACtB;CAEA,eACE,OACM;EACN,MAAM,gBAAgB,KAAK;EAC3B,KAAK,eAAe;EACpB,KAAK,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,KAAK,CAAC;EAClD,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,MAAM,CAAC;EAChD,MAAM,UAAU,KAAK,cAAc;EACnC,KAAK,KAAK,iBAAiB,CAAC,UAAU,MAAM,SAAS,KAAA,CAAS;EAC9D,KAAK,sBAAsB;CAC7B;;;;;;CAOA,IAAI,oBAAmE;EACrE,MAAM,QAAQ,KAAK;EACnB,IAAI,OAAO,uBAAuB,KAAA,KAAa,MAAM,wBAAwB,KAAA,GAC3E;EAEF,OAAO;GAAE,OAAO,MAAM;GAAoB,QAAQ,MAAM;EAAoB;CAC9E;;;;;;CAOA,IAAI,oBAA0D;EAC5D,MAAM,eAAe,KAAK,cAAc;EACxC,IAAI,CAAC,cACH;EAEF,OAAO;GACL,GAAG;GACH,WAAW,mBAAmB,aAAa,SAAS;EACtD;CACF;CAEA,WACE,UACoB;EACpB,OAAO,aACL,KAAK,cAAc,OACnB,KAAK,cAAc,aACnB,QACF;CACF;CAEA,cACE,KACM;EACN,IAAI,KAAK,iBAAiB;GACxB,KAAK,gBAAgB,GAAG;GACxB;EACF;EAGA,IAAI,CAAC,YAAY,KAAK,GAAG,GACvB;EAEF,OAAO,KAAK,KAAK,UAAU,qBAAqB;CAClD;CAEA,WACE,OACM;EACN,0BAA0B,KAAK,SAAS,KAAK;EAC7C,KAAK,QAAQ,MAAM,UAAU;EAC7B,KAAK,QAAQ,MAAM,eAAe;EAClC,KAAK,QAAQ,MAAM,YAAY;EAC/B,KAAK,QAAQ,MAAM,WAAW;EAC9B,KAAK,QAAQ,MAAM,MAAM;EACzB,KAAK,QAAQ,MAAM,mBAAmB;EAEtC,KAAK,cAAc,MAAM,WAAW;EACpC,KAAK,cAAc,MAAM,WAAW;EAGpC,KAAK,cAAc,MAAM,qBAAqB;EAC9C,KAAK,cAAc,MAAM,UAAU;EACnC,KAAK,cAAc,MAAM,aAAa,8BAA8B,KAAK,YAAY;EACrF,KAAK,cAAc,MAAM,YAAY,GAAG,KAAK,aAAa,EAAE;EAE5D,IAAI,KAAK,QAAQ;GACf,KAAK,OAAO,MAAM,UAAU;GAC5B,KAAK,OAAO,MAAM,QAAQ;GAC1B,KAAK,OAAO,MAAM,SAAS;EAC7B;EACA,IAAI,KAAK,gBAAgB;GACvB,KAAK,eAAe,MAAM,UAAU;GACpC,KAAK,eAAe,MAAM,WAAW;EACvC;CACF;;CAGA,IAAY,iBAA0C;EACpD,OAAO,KAAK,UAAU,KAAK;CAC7B;CAEA,kBAAgC;EAC9B,KAAK,QAAQ,SAAS,CAAC,KAAK;EAC5B,KAAK,QAAQ,MAAM,YACjB,WACA,KAAK,YAAY,SAAS,QAC1B,WACF;CACF;CAEA,wBAAsC;EACpC,IAAI,OAAO,mBAAmB,aAC5B;EAGF,KAAK,iBAAiB,IAAI,qBAAqB;GAC7C,KAAK,cAAc;EACrB,CAAC;EACD,KAAK,eAAe,QAAQ,KAAK,aAAa;CAChD;CAEA,uBAAqC;EACnC,MAAM,iBAAiB,UAAyB;GAC9C,IAAI,MAAM,WAAW,MAAM,aACzB;GAEF,MAAM,UAAU,KAAK,aAAa,UAAU,KAAK;GACjD,IAAI,CAAC,SACH;GAGF,KAAK,QAAQ,OAAO;GACpB,MAAM,eAAe;EACvB;EAEA,MAAM,eAAe,UAA0B;GAC7C,MAAM,OAAO,MAAM,eAAe,QAAQ,YAAY,KAAK;GAC3D,IAAI,CAAC,MACH;GAEF,KAAK,QAAQ,KAAK,aAAa,YAAY,IAAI,CAAC;GAChD,MAAM,eAAe;EACvB;EAEA,MAAM,qBAAqB,UAAwB;GACjD,IAAI,KAAK,0BAA0B,KAAK,GAGtC;GAEF,MAAM,WAAW,KAAK,aAAa,KAAK;GACxC,IAAI,CAAC,UACH;GAGF,MAAM,SAAS,KAAK,aAAa,cAAc,MAAM,MAAM;GAC3D,KAAK,sBAAsB;GAC3B,KAAK,qBAAqB;GAC1B,KAAK,wBAAwB,WAAW,YACpC,KAAK,WAAW,QAAQ,IACxB,KAAA;GACJ,KAAK,cAAc,QAAQ,EAAE,eAAe,KAAK,CAAC;GAClD,KAAK,cAAc,oBAAoB,MAAM,SAAS;GACtD,KAAK,QAAQ,KAAK,aAAa,kBAAkB,UAAU,QAAQ,KAAK,CAAC;GACzE,MAAM,eAAe;EACvB;EAEA,MAAM,mBAAmB,UAAwB;GAC/C,IAAI,CAAC,KAAK,sBAAsB,KAAK,0BAA0B,KAAK,GAClE;GAEF,MAAM,WAAW,KAAK,qBAClB,KAAK,gBAAgB,KAAK,IAC1B,KAAK,aAAa,KAAK;GAC3B,KAAK,cAAc,wBAAwB,MAAM,SAAS;GAC1D,KAAK,qBAAqB;GAC1B,MAAM,iBAAiB,KAAK;GAC5B,KAAK,wBAAwB,KAAA;GAC7B,IAAI,CAAC,UACH;GAGF,MAAM,SAAS,KAAK,aAAa,cAAc,MAAM,MAAM,KAAK,KAAK;GACrE,KAAK,QAAQ,KAAK,aAAa,gBAAgB,UAAU,QAAQ,KAAK,CAAC;GAIvE,IAAI,mBAAmB,KAAA,KAAa,KAAK,WAAW,QAAQ,MAAM,gBAChE,KAAK,cAAc,cAAc;GAEnC,MAAM,eAAe;EACvB;EAEA,MAAM,qBAAqB,UAAwB;GACjD,IAAI,CAAC,KAAK,sBAAsB,KAAK,0BAA0B,KAAK,GAClE;GAEF,MAAM,WAAW,MAAM,WAAW,KAAK,qBACnC,KAAK,gBAAgB,KAAK,IAC1B,KAAK,aAAa,KAAK;GAC3B,IAAI,CAAC,UACH;GAGF,IAAI,CAAC,KAAK,oBACR,KAAK,cAAc,MAAM,SACvB,KAAK,WAAW,QAAQ,MAAM,KAAA,IAAY,YAAY;GAE1D,KAAK,QAAQ,KAAK,aAAa,kBAAkB,UAAU,KAAK,qBAAqB,KAAK,CAAC;EAC7F;EAEA,MAAM,eAAe,UAAsB;GACzC,IAAI,KAAK,cAAc,WACrB;GAGF,MAAM,WAAW,KAAK,aAAa,KAAK;GACxC,IAAI,CAAC,UAGH;GAOF,IAAI,KAAK,cAAc,WAClB,CAAC,qBAAqB,KAAK,cAAc,eAAe,UAAU,MAAM,QAAQ,MAAM,MAAM,GAC/F;GAGF,KAAK,QAAQ,KAAK,aAAa,YAAY,UAAU,KAAK,CAAC;GAC3D,MAAM,eAAe;EACvB;EAEA,KAAK,cAAc,iBAAiB,WAAW,aAAa;EAC5D,KAAK,cAAc,iBAAiB,SAAS,WAAW;EACxD,KAAK,cAAc,iBAAiB,eAAe,iBAAiB;EACpE,KAAK,cAAc,iBAAiB,aAAa,eAAe;EAChE,KAAK,cAAc,iBAAiB,eAAe,iBAAiB;EACpE,KAAK,cAAc,iBAAiB,SAAS,aAAa,EAAE,SAAS,MAAM,CAAC;EAE5E,KAAK,4BAA4B;GAC/B,KAAK,cAAc,oBAAoB,WAAW,aAAa;GAC/D,KAAK,cAAc,oBAAoB,SAAS,WAAW;GAC3D,KAAK,cAAc,oBAAoB,eAAe,iBAAiB;GACvE,KAAK,cAAc,oBAAoB,aAAa,eAAe;GACnE,KAAK,cAAc,oBAAoB,eAAe,iBAAiB;GACvE,KAAK,cAAc,oBAAoB,SAAS,WAAW;EAC7D;CACF;CAEA,gBAA8B;EAC5B,KAAK,aAAa;EAClB,MAAM,OAAO,KAAK,cAAc,wBAAwB;EACxD,MAAM,QAAQ,MAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,QAAQ,KAAK,UAAU,KAAK;EAC/E,MAAM,SAAS,MAAM,UAAU,KAAK,SAAS,IAAI,KAAK,SAAS,KAAK,OAAO,KAAK;EAChF,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,KAAK,SAAS,CAAC;EAClE,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,KAAK,UAAU,CAAC;EAEjE,KAAK,UAAU;EACf,KAAK,OAAO;EACZ,KAAK,mBAAmB;EACxB,KAAK,cAAc;CACrB;CAEA,qBAAmC;EACjC,MAAM,UAAU;GACd,SAAS,KAAK;GACd,MAAM,KAAK;GACX,WAAW,KAAK;GAChB,YAAY,KAAK;EACnB;EACA,IAAI,KAAK,kBACJ,KAAK,eAAe,YAAY,QAAQ,WACxC,KAAK,eAAe,SAAS,QAAQ,QACrC,KAAK,eAAe,cAAc,QAAQ,aAC1C,KAAK,eAAe,eAAe,QAAQ,YAE9C;EAGF,KAAK,iBAAiB;EACtB,KAAK,QAAQ,OAAO,QAAQ,SAAS,QAAQ,MAAM,QAAQ,WAAW,QAAQ,UAAU;CAC1F;CAEA,gBAAiC;EAC/B,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,UAAU,KAAK,SAAS;EAC1D,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,UAAU;EAEzD,IAAI,KAAK,gBAAgB;GACvB,MAAM,OAAO,KAAK;GAClB,IAAI,QAAQ,KAAK,UAAU,YAAY,KAAK,WAAW,WACrD,OAAO;GAET,KAAK,qBAAqB;IAAE,OAAO;IAAU,QAAQ;GAAU;GAC/D,KAAK,eAAe,MAAM,QAAQ,GAAG,SAAS;GAC9C,KAAK,eAAe,MAAM,SAAS,GAAG,UAAU;GAChD,OAAO;EACT;EAEA,IAAI,CAAC,KAAK,QACR,OAAO;EAGT,MAAM,QAAQ,WAAW,QAAQ,oBAAoB;EACrD,MAAM,QAAQ,KAAK,KAAK,WAAW,KAAK;EACxC,MAAM,SAAS,KAAK,KAAK,YAAY,KAAK;EAC1C,MAAM,aAAa,GAAG,SAAS;EAC/B,MAAM,cAAc,GAAG,UAAU;EACjC,IAAI,KAAK,OAAO,UAAU,SACrB,KAAK,OAAO,WAAW,UACvB,KAAK,OAAO,MAAM,UAAU,cAC5B,KAAK,OAAO,MAAM,WAAW,aAEhC,OAAO;EAGT,KAAK,OAAO,QAAQ;EACpB,KAAK,OAAO,SAAS;EACrB,KAAK,OAAO,MAAM,QAAQ;EAC1B,KAAK,OAAO,MAAM,SAAS;EAC3B,OAAO;CACT;CAEA,eAA6B;EAE3B,MAAM,WADS,KAAK,UAAU,SAAS,cAAc,QAAQ,EAAA,CACtC,aAAa,IAAI;EACxC,IAAI,CAAC,SAAS;GACZ,KAAK,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa,WAAW,GAAI,CAAC;GAC1E,KAAK,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,aAAa,WAAW,IAAI,CAAC;GAC3E;EACF;EAEA,QAAQ,OAAO,aAAa,KAAK,YAAY;EAC7C,KAAK,YAAY,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC;EACtE,KAAK,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,aAAa,WAAW,IAAI,CAAC;CAC5E;CAEA,KACE,QACM;EACN,KAAK,QAAQ,MAAM,KAAK,eAAe,GAAG,KAAK,cAAc,MAAM;CACrE;CAEA,wBAAsC;EACpC,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,QAAQ,CAAC,KAAK,cACjB;EAGF,KAAK,QAAQ,KAAK,aAAa,qBAAqB,CAAC,GAAG;GACtD,WAAW,KAAK;GAChB,YAAY,KAAK;EACnB,GAAG,KAAK,aAAa,8BAA8B,CAAC,GAAG,EACrD,kBAAkB,KAAK,8BACzB,CAAC;CACH;CAEA,iBAA+C;EAC7C,OAAO;GACL,SAAS,KAAK;GACd,MAAM,KAAK;GACX,WAAW,KAAK;GAChB,YAAY,KAAK;GACjB,OAAO,KAAK;EACd;CACF;CAEA,iBAAiD;EAC/C,OAAO;GACL,MAAM,KAAK,gBAAgB,wBAAwB,KAAK,KAAK,cAAc,wBAAwB;GACnG,WAAW,KAAK;GAChB,YAAY,KAAK;GACjB,SAAS,KAAK;GACd,MAAM,KAAK;EACb;CACF;;;;;;;CAQA,0BACE,OACS;EACT,OAAO,KAAK,iBAAiB,SAAS,MAAM;CAC9C;CAEA,aACE,OAC0B;EAC1B,OAAO,qBAAqB,OAAO,KAAK,eAAe,CAAC;CAC1D;CAEA,gBACE,OAC0B;EAC1B,OAAO,wBAAwB,OAAO,KAAK,eAAe,CAAC;CAC7D;AACF"}
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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"}
@@ -53,6 +53,13 @@ interface WebHostAccessibilityAnnouncement {
53
53
  politeness: WebHostAccessibilityLiveRegion;
54
54
  }
55
55
  type WebHostSurfaceImageFormat = string;
56
+ /**
57
+ * Reports locally unresolved image IDs to a recovery-capable host. The first
58
+ * branch lets bounded hosts return the admitted subset; the explicit legacy
59
+ * void branch preserves contextual typing for concise callbacks that return
60
+ * incidental values such as `array.push(...)`.
61
+ */
62
+ type WebHostImagePayloadRequestHandler = ((ids: readonly string[]) => readonly string[]) | ((ids: readonly string[]) => void);
56
63
  interface WebHostSurfaceImage {
57
64
  id: string;
58
65
  format: WebHostSurfaceImageFormat;
@@ -117,7 +124,19 @@ interface WebHostSurfaceDeltaFrame {
117
124
  sequence?: number;
118
125
  width: number;
119
126
  height: number;
127
+ /**
128
+ * With `stylesBase` present, only the styles this record *added*: index `i`
129
+ * of this array is table index `stylesBase + i`. Absent `stylesBase`, the
130
+ * complete accumulated table, as deployed decoders expect.
131
+ */
120
132
  styles: Array<WebHostSurfaceStyle | null>;
133
+ /**
134
+ * Where `styles` splices onto the retained table. Present only when the
135
+ * client declared `styleAppend`, which is why it is negotiated rather than
136
+ * additive: reading `styles` as a whole table when this is present
137
+ * mis-indexes every style in the record.
138
+ */
139
+ stylesBase?: number;
121
140
  deltaRows: WebHostSurfaceDeltaRow[];
122
141
  images?: WebHostSurfaceImage[];
123
142
  damage?: WebHostSurfaceDamage;
@@ -169,7 +188,7 @@ type WebHostResyncRequest = {
169
188
  ids?: string[];
170
189
  };
171
190
  interface WebHostOutputSink {
172
- presentSurface(frame: WebHostSurfaceFrame): void;
191
+ presentSurface(frame: WebHostSurfaceFrame, recoveredImagePayloadIds?: readonly string[]): void;
173
192
  writeClipboard?(text: string): void | Promise<void>;
174
193
  notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;
175
194
  recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;
@@ -190,6 +209,10 @@ interface WebHostMouseInput {
190
209
  deltaY?: number;
191
210
  modifiers?: number;
192
211
  }
212
+ /** Limits wire-derived recovery state and keeps every single-ID WASI request bounded. */
213
+ declare const MAX_IMAGE_RECOVERY_ID_BYTES = 1024;
214
+ declare const MAX_OUTSTANDING_IMAGE_RECOVERY_IDS = 1024;
215
+ declare function isWebHostImageRecoveryId(id: string): boolean;
193
216
  /**
194
217
  * The newest `surface` record version this runtime understands. Unknown
195
218
  * additive object keys are ignored by design (older runtimes render newer
@@ -205,15 +228,49 @@ declare class WebHostOutputDecoder {
205
228
  private lastSurfaceFrame?;
206
229
  private lastEpoch?;
207
230
  private lastGen?;
208
- private pendingResyncRequest?;
231
+ private lastPresentedEpoch?;
209
232
  private keyframeResyncOutstanding;
233
+ private keyframeResyncPending;
234
+ private readonly imageResyncOutstandingIds;
235
+ private readonly imageResyncPendingIds;
210
236
  feed(chunk: Uint8Array): WebHostOutputRecord[];
211
237
  flush(): WebHostOutputRecord[];
212
- takeResyncRequest(): WebHostResyncRequest | undefined;
238
+ takeResyncRequest(maximumEncodedBytes?: number): WebHostResyncRequest | undefined;
239
+ /**
240
+ * Adds locally unresolved image IDs to this epoch's recovery set. IDs remain
241
+ * outstanding after delivery, so repeat painter misses cannot create storms;
242
+ * a payload-bearing record or epoch re-anchor releases them. Returns the IDs
243
+ * now tracked (new or already outstanding); callers should suppress repeats
244
+ * only for this admitted subset.
245
+ */
246
+ requestImagePayloads(ids: Iterable<string>): readonly string[];
247
+ /**
248
+ * Advances image recovery immediately before one decoded surface reaches its
249
+ * presenter. Payload arrival and epoch reset therefore follow delivery order
250
+ * rather than `feed`'s parse-ahead order across a multi-record chunk. The
251
+ * return value identifies payloads that answer an outstanding image request,
252
+ * so presenters can open exactly one fresh local decode generation even when
253
+ * content-addressed retransmission uses identical bytes.
254
+ */
255
+ prepareToPresentSurface(frame: WebHostSurfaceFrame): readonly string[];
213
256
  resyncRequestDeliveryFailed(request: WebHostResyncRequest): void;
214
257
  private decodeLine;
215
258
  private requestKeyframeResync;
259
+ private resetImageResyncForEpoch;
260
+ private clearArrivedImagePayloads;
261
+ private recoveredImagePayloadIds;
262
+ private sweepImageResyncForPresentedImages;
216
263
  private materializeDeltaFrame;
264
+ /**
265
+ * The delta's style table: either the record's own complete table, or the
266
+ * negotiated append spliced onto the baseline's.
267
+ *
268
+ * A `stylesBase` that does not name the end of the retained table is a
269
+ * structural break, not a recoverable one — splicing at the wrong offset
270
+ * silently repaints cells in the wrong style, which is worse than refusing
271
+ * the record and asking for a keyframe.
272
+ */
273
+ private materializeDeltaStyles;
217
274
  }
218
275
  declare function encodeResizeControlMessage(columns: number, rows: number, cellWidth?: number, cellHeight?: number): Uint8Array;
219
276
  declare function encodeRenderStyleControlMessage(style: WebHostTerminalStyle): Uint8Array;
@@ -223,5 +280,5 @@ declare function encodeKeyInputMessage(input: WebHostKeyInput): Uint8Array;
223
280
  declare function encodePasteInputMessage(text: string): Uint8Array;
224
281
  declare function encodeMouseInputMessage(input: WebHostMouseInput): Uint8Array;
225
282
  //#endregion
226
- export { SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, 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 };
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 };
227
284
  //# sourceMappingURL=WebHostSurfaceTransport.d.ts.map