mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 09:27:29 +00:00
* fix(mobile): draw Claude Code's ⏵⏵ mode marker on Android terminals Android ships no font with U+23F4-23F7, so the terminal drew boxes. Bundle a 664-byte Noto Sans Symbols 2 subset, inline it in both terminal hosts, append it to the Android font stack only, and load it with the Nerd font before the WebGL atlas first paints. * fix(mobile): spell the terminal font probe glyphs as escapes * refactor(mobile): tidy terminal symbols font wiring
142 lines
5.6 KiB
JavaScript
142 lines
5.6 KiB
JavaScript
import { readFile, writeFile } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { createRequire } from 'node:module'
|
|
import * as esbuild from 'esbuild'
|
|
import {
|
|
terminalNerdFontCss,
|
|
terminalSymbolsFontCss
|
|
} from '../src/terminal/terminal-font-face-css.mjs'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const scriptDir = import.meta.dirname
|
|
const mobileRoot = path.resolve(scriptDir, '..')
|
|
const outputPath = path.join(mobileRoot, 'src', 'terminal', 'terminal-webview-engine.generated.ts')
|
|
// Emit stylesheet separately: native WebView needs both strings; page needs CSS without pulling in ~612 KiB engine string forbidden by script-src self.
|
|
const cssOutputPath = path.join(
|
|
mobileRoot,
|
|
'src',
|
|
'terminal',
|
|
'terminal-webview-engine-css.generated.ts'
|
|
)
|
|
const target = 'chrome74'
|
|
|
|
const packages = ['@xterm/xterm', '@xterm/addon-unicode11', '@xterm/addon-webgl']
|
|
|
|
async function readPackageVersion(packageName) {
|
|
// Use / in package specifier; path.join emits unresolvable backslashes on Windows.
|
|
const packageJsonPath = require.resolve(`${packageName}/package.json`)
|
|
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
return `${packageName}@${packageJson.version}`
|
|
}
|
|
|
|
function htmlText(value, closingTag) {
|
|
return value.replace(new RegExp(`</${closingTag}`, 'gi'), `<\\/${closingTag}`)
|
|
}
|
|
|
|
async function buildEngineJs() {
|
|
const result = await esbuild.build({
|
|
stdin: {
|
|
contents: `
|
|
import { Terminal } from '@xterm/xterm'
|
|
import { Unicode11Addon } from '@xterm/addon-unicode11'
|
|
import { WebglAddon } from '@xterm/addon-webgl'
|
|
|
|
// Why: xterm reaches for these runtime APIs on the terminal-bringup path,
|
|
// and esbuild lowers syntax but not runtime APIs. Guarded shims let the
|
|
// chrome74-syntax bundle actually run on old WebViews (the #7030 goal)
|
|
// instead of throwing at construction and only surfacing the error overlay.
|
|
|
|
// WeakRef (Chrome 84+): lazily constructed in window-tracking paths.
|
|
// Strong retention is fine for a single-document terminal WebView.
|
|
if (typeof window.WeakRef === 'undefined') {
|
|
window.WeakRef = function WeakRefShim(target) { this.__target = target }
|
|
window.WeakRef.prototype.deref = function () { return this.__target }
|
|
}
|
|
|
|
// structuredClone (Chrome 98+): xterm clones its plain-data DEC-mode default
|
|
// objects at Terminal construction; a JSON round-trip clones those correctly
|
|
// (any undefined-valued keys drop, but every reader treats absent == undefined).
|
|
if (typeof window.structuredClone === 'undefined') {
|
|
window.structuredClone = function (value) { return JSON.parse(JSON.stringify(value)) }
|
|
}
|
|
|
|
// Element.prototype.replaceChildren (Chrome 86+): used on the row/selection
|
|
// render path; polyfill via remove-all + append (Chrome 54+, under the floor).
|
|
if (typeof Element !== 'undefined' && !Element.prototype.replaceChildren) {
|
|
Element.prototype.replaceChildren = function () {
|
|
while (this.firstChild) this.removeChild(this.firstChild)
|
|
this.append.apply(this, arguments)
|
|
}
|
|
}
|
|
|
|
window.Terminal = Terminal
|
|
window.Unicode11Addon = { Unicode11Addon }
|
|
window.WebglAddon = { WebglAddon }
|
|
`,
|
|
resolveDir: mobileRoot,
|
|
sourcefile: 'terminal-webview-engine-entry.js'
|
|
},
|
|
bundle: true,
|
|
format: 'iife',
|
|
// esbuild 0.25 syntax folding drops xterm's local DECRQM enum declaration.
|
|
minifyWhitespace: true,
|
|
minifyIdentifiers: true,
|
|
minifySyntax: false,
|
|
platform: 'browser',
|
|
target,
|
|
legalComments: 'none',
|
|
write: false,
|
|
logLevel: 'silent'
|
|
})
|
|
|
|
return result.outputFiles[0].text
|
|
}
|
|
|
|
async function main() {
|
|
const [engineJs, rawEngineCss, nerdFont, symbolsFont, ...versions] = await Promise.all([
|
|
buildEngineJs(),
|
|
readFile(require.resolve('@xterm/xterm/css/xterm.css'), 'utf8'),
|
|
readFile(path.join(mobileRoot, 'assets', 'fonts', 'SymbolsNerdFontMono-Regular.woff2')),
|
|
readFile(path.join(mobileRoot, 'assets', 'fonts', 'NotoSansSymbols2-TerminalSubset.woff2')),
|
|
...packages.map(readPackageVersion)
|
|
])
|
|
// Percent-encode xmlns scheme colons inside data URLs: no fetch occurs, but literal http(s) trips document URL gate; SVG parsing decodes them.
|
|
const engineCss = rawEngineCss
|
|
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
.replace(/http:\/\/www\.w3\.org\/2000\/svg/g, 'http%3A//www.w3.org/2000/svg')
|
|
|
|
const nerdFontCss = terminalNerdFontCss(`data:font/woff2;base64,${nerdFont.toString('base64')}`)
|
|
// Inline in both hosts: under 1 KiB, and the page bundle's font allowance admits only the Nerd font file.
|
|
const symbolsFontCss = terminalSymbolsFontCss(
|
|
`data:font/woff2;base64,${symbolsFont.toString('base64')}`
|
|
)
|
|
|
|
const header = [
|
|
'// Generated by scripts/build-terminal-webview-engine.mjs.',
|
|
`// Packages: ${versions.join(', ')}.`,
|
|
`// Target: ${target}. Do not edit by hand; regenerate via pnpm postinstall.`
|
|
]
|
|
await Promise.all([
|
|
writeFile(
|
|
outputPath,
|
|
[
|
|
...header,
|
|
`export const XTERM_ENGINE_JS = ${JSON.stringify(htmlText(engineJs, 'script'))}`,
|
|
''
|
|
].join('\n')
|
|
),
|
|
writeFile(
|
|
cssOutputPath,
|
|
[
|
|
...header,
|
|
`export const XTERM_ENGINE_CSS = ${JSON.stringify(htmlText(engineCss, 'style'))}`,
|
|
`export const TERMINAL_NERD_FONT_CSS = ${JSON.stringify(htmlText(nerdFontCss, 'style'))}`,
|
|
`export const TERMINAL_SYMBOLS_FONT_CSS = ${JSON.stringify(htmlText(symbolsFontCss, 'style'))}`,
|
|
''
|
|
].join('\n')
|
|
)
|
|
])
|
|
}
|
|
|
|
await main()
|