From 917a8ded92a59a490c41dd32f10ab0d2507c4bbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Jan 2026 17:44:20 +0000 Subject: [PATCH 01/10] Bump qs from 6.14.0 to 6.14.1 Bumps [qs](https://github.com/ljharb/qs) from 6.14.0 to 6.14.1. - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e48e512cce..2e55f3de8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -239,6 +239,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -552,6 +553,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" }, @@ -593,6 +595,7 @@ "url": "https://opencollective.com/csstools" } ], + "peer": true, "engines": { "node": ">=18" } @@ -1894,6 +1897,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.1", "@typescript-eslint/types": "8.50.1", @@ -2338,6 +2342,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2727,6 +2732,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3728,6 +3734,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6745,10 +6752,11 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -7082,6 +7090,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7949,6 +7958,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8132,6 +8142,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -8180,6 +8191,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", From 08c49301b7d5a3cca9fbf2dc468e456e725f6455 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 11:19:23 -0800 Subject: [PATCH 02/10] Fix isNode detection when process module is used in webpack Fixes #5341 --- src/common/Platform.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 4102f20c06..7ea0e9c6d4 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -14,7 +14,9 @@ interface INavigator { declare const navigator: INavigator; declare const process: unknown; -export const isNode = (typeof process !== 'undefined' && 'title' in (process as any)) ? true : false; +// navigator is also checked here because bundling with the process module can cause issues +// otherwise +export const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && typeof navigator === 'undefined') ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From 8712f8067d76d7525373d0431ff030e2e12a676d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:01:17 -0800 Subject: [PATCH 03/10] Fix infinite recursion OOM crash in addon-ligatures 1. processLookahead/BacktrackPosition created separate entry objects for each glyph at each position (O(N^M) with N glyphs, M positions). Now all glyphs at a position share one entry object. 2. cloneEntry, cloneTree, flattenEntry, and mergeTreeEntry could infinitely recurse or duplicate work on shared/cyclic references. Added visited/merged tracking maps to cache results. Note that parsing CommitMono locks up the renderer for some time still so there is still a performance issue here, this fixes the OOM though. This was mostly defensive generated code since the codebase is unfamiliar to me. Fixes #5570 --- .../src/fontLigatures/flatten.ts | 17 ++-- .../src/fontLigatures/merge.ts | 55 +++++++++---- .../src/fontLigatures/processors/helper.ts | 80 ++++++++++++------- 3 files changed, 101 insertions(+), 51 deletions(-) diff --git a/addons/addon-ligatures/src/fontLigatures/flatten.ts b/addons/addon-ligatures/src/fontLigatures/flatten.ts index 843f9acf43..967940f67f 100644 --- a/addons/addon-ligatures/src/fontLigatures/flatten.ts +++ b/addons/addon-ligatures/src/fontLigatures/flatten.ts @@ -1,13 +1,13 @@ import { ILookupTree, IFlattenedLookupTree, ILookupTreeEntry, IFlattenedLookupTreeEntry } from './types'; -export default function flatten(tree: ILookupTree): IFlattenedLookupTree { +export default function flatten(tree: ILookupTree, visited: Map = new Map()): IFlattenedLookupTree { const result: IFlattenedLookupTree = {}; for (const [glyphId, entry] of Object.entries(tree.individual)) { - result[glyphId] = flattenEntry(entry); + result[glyphId] = flattenEntry(entry, visited); } for (const { range, entry } of tree.range) { - const flattened = flattenEntry(entry); + const flattened = flattenEntry(entry, visited); for (let glyphId = range[0]; glyphId < range[1]; glyphId++) { result[glyphId] = flattened; } @@ -16,15 +16,20 @@ export default function flatten(tree: ILookupTree): IFlattenedLookupTree { return result; } -function flattenEntry(entry: ILookupTreeEntry): IFlattenedLookupTreeEntry { +function flattenEntry(entry: ILookupTreeEntry, visited: Map): IFlattenedLookupTreeEntry { + if (visited.has(entry)) { + return visited.get(entry)!; + } + const result: IFlattenedLookupTreeEntry = {}; + visited.set(entry, result); if (entry.forward) { - result.forward = flatten(entry.forward); + result.forward = flatten(entry.forward, visited); } if (entry.reverse) { - result.reverse = flatten(entry.reverse); + result.reverse = flatten(entry.reverse, visited); } if (entry.lookup) { diff --git a/addons/addon-ligatures/src/fontLigatures/merge.ts b/addons/addon-ligatures/src/fontLigatures/merge.ts index 3e12704809..262ea85ae3 100644 --- a/addons/addon-ligatures/src/fontLigatures/merge.ts +++ b/addons/addon-ligatures/src/fontLigatures/merge.ts @@ -14,8 +14,9 @@ export default function mergeTrees(trees: ILookupTree[]): ILookupTree { range: [] }; + const mergedEntries = new WeakMap>(); for (const tree of trees) { - mergeSubtree(result, tree); + mergeSubtree(result, tree, mergedEntries); } return result; @@ -26,15 +27,16 @@ export default function mergeTrees(trees: ILookupTree[]): ILookupTree { * * @param mainTree The tree where the values should be merged * @param mergeTree The tree to be merged into the mainTree + * @param mergedEntries WeakMap to track already merged entry pairs */ -function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { +function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntries: WeakMap>): void { // Need to fix this recursively (and handle lookups) for (const [glyphId, value] of Object.entries(mergeTree.individual)) { // The main tree is guaranteed to have no overlaps between the // individual and range values, so if we match an invididual, there // must not be a range if (mainTree.individual[glyphId]) { - mergeTreeEntry(mainTree.individual[glyphId], value); + mergeTreeEntry(mainTree.individual[glyphId], value, mergedEntries); } else { let matched = false; for (const [index, { range, entry }] of mainTree.range.entries()) { @@ -50,7 +52,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { // If they overlap, we have to split the range and then // merge the overlap mainTree.individual[glyphId] = value; - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries); // When there's an overlap, we also have to fix up the range // that we had already processed @@ -101,7 +103,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { mainTree.individual[overlap.both] = entryToMerge; } - mergeTreeEntry(entryToMerge, cloneEntry(entry)); + mergeTreeEntry(entryToMerge, cloneEntry(entry), mergedEntries); for (const second of overlap.second) { if (Array.isArray(second)) { @@ -124,7 +126,7 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { // If they overlap, we have to split the range and then // merge the overlap mainTree.individual[remainingRange] = cloneEntry(entry); - mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry)); + mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry), mergedEntries); // When there's an overlap, we also have to fix up the range // that we had already processed @@ -158,14 +160,14 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { } // If they overlap, we have to merge the overlap - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries); // Update the remaining ranges remainingRanges.splice(remainingIndex, 1, ...overlap.second); break; } else { if (Number(glyphId) === remainingRange) { - mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry)); + mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries); break; } } @@ -191,8 +193,20 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree): void { * * @param mainTree The entry where the values should be merged * @param mergeTree The entry to merge into the mainTree + * @param mergedEntries WeakMap to track already merged entry pairs */ -function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry): void { +function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry, mergedEntries: WeakMap>): void { + // Check if we've already merged this pair + let mergedSet = mergedEntries.get(mainTree); + if (mergedSet?.has(mergeTree)) { + return; + } + if (!mergedSet) { + mergedSet = new Set(); + mergedEntries.set(mainTree, mergedSet); + } + mergedSet.add(mergeTree); + if ( mergeTree.lookup && ( !mainTree.lookup || @@ -207,7 +221,7 @@ function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry) if (!mainTree.forward) { mainTree.forward = mergeTree.forward; } else { - mergeSubtree(mainTree.forward, mergeTree.forward); + mergeSubtree(mainTree.forward, mergeTree.forward, mergedEntries); } } @@ -215,7 +229,7 @@ function mergeTreeEntry(mainTree: ILookupTreeEntry, mergeTree: ILookupTreeEntry) if (!mainTree.reverse) { mainTree.reverse = mergeTree.reverse; } else { - mergeSubtree(mainTree.reverse, mergeTree.reverse); + mergeSubtree(mainTree.reverse, mergeTree.reverse, mergedEntries); } } } @@ -326,16 +340,22 @@ function rangeOrIndividual(start: number, end: number): number | [number, number * Clones an individual lookup tree entry. * * @param entry Lookup tree entry to clone + * @param visited Map to track already cloned entries (prevents infinite loops) */ -function cloneEntry(entry: ILookupTreeEntry): ILookupTreeEntry { +function cloneEntry(entry: ILookupTreeEntry, visited: Map = new Map()): ILookupTreeEntry { + if (visited.has(entry)) { + return visited.get(entry)!; + } + const result: ILookupTreeEntry = {}; + visited.set(entry, result); if (entry.forward) { - result.forward = cloneTree(entry.forward); + result.forward = cloneTree(entry.forward, visited); } if (entry.reverse) { - result.reverse = cloneTree(entry.reverse); + result.reverse = cloneTree(entry.reverse, visited); } if (entry.lookup) { @@ -355,18 +375,19 @@ function cloneEntry(entry: ILookupTreeEntry): ILookupTreeEntry { * Clones a lookup tree. * * @param tree Lookup tree to clone + * @param visited Map to track already cloned entries (prevents infinite loops) */ -function cloneTree(tree: ILookupTree): ILookupTree { +function cloneTree(tree: ILookupTree, visited: Map = new Map()): ILookupTree { const individual: { [glyphId: string]: ILookupTreeEntry } = {}; for (const [glyphId, entry] of Object.entries(tree.individual)) { - individual[glyphId] = cloneEntry(entry); + individual[glyphId] = cloneEntry(entry, visited); } return { individual, range: tree.range.map(({ range, entry }) => ({ range: range.slice() as [number, number], - entry: cloneEntry(entry) + entry: cloneEntry(entry, visited) })) }; } diff --git a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts index b05e1e2e02..3f06d672f1 100644 --- a/addons/addon-ligatures/src/fontLigatures/processors/helper.ts +++ b/addons/addon-ligatures/src/fontLigatures/processors/helper.ts @@ -43,29 +43,41 @@ export function processLookaheadPosition( currentEntries: IEntryMeta[] ): IEntryMeta[] { const nextEntries: IEntryMeta[] = []; + const processedEntries = new Set(); + for (const currentEntry of currentEntries) { - for (const glyph of glyphs) { - const entry: ILookupTreeEntry = {}; - if (!currentEntry.entry.forward) { - currentEntry.entry.forward = { - individual: {}, - range: [] - }; - } - nextEntries.push({ - entry, - substitutions: currentEntry.substitutions - }); + // Skip if we've already processed this entry object + if (processedEntries.has(currentEntry.entry)) { + continue; + } + processedEntries.add(currentEntry.entry); + if (!currentEntry.entry.forward) { + currentEntry.entry.forward = { + individual: {}, + range: [] + }; + } + + // All glyphs at this position share ONE entry - lookahead just needs to match, + // all paths lead to the same result + const sharedEntry: ILookupTreeEntry = {}; + + for (const glyph of glyphs) { if (Array.isArray(glyph)) { currentEntry.entry.forward.range.push({ - entry, + entry: sharedEntry, range: glyph }); } else { - currentEntry.entry.forward.individual[glyph] = entry; + currentEntry.entry.forward.individual[glyph] = sharedEntry; } } + + nextEntries.push({ + entry: sharedEntry, + substitutions: currentEntry.substitutions + }); } return nextEntries; @@ -76,29 +88,41 @@ export function processBacktrackPosition( currentEntries: IEntryMeta[] ): IEntryMeta[] { const nextEntries: IEntryMeta[] = []; + const processedEntries = new Set(); + for (const currentEntry of currentEntries) { - for (const glyph of glyphs) { - const entry: ILookupTreeEntry = {}; - if (!currentEntry.entry.reverse) { - currentEntry.entry.reverse = { - individual: {}, - range: [] - }; - } - nextEntries.push({ - entry, - substitutions: currentEntry.substitutions - }); + // Skip if we've already processed this entry object + if (processedEntries.has(currentEntry.entry)) { + continue; + } + processedEntries.add(currentEntry.entry); + if (!currentEntry.entry.reverse) { + currentEntry.entry.reverse = { + individual: {}, + range: [] + }; + } + + // All glyphs at this position share ONE entry - backtrack just needs to match, + // all paths lead to the same result + const sharedEntry: ILookupTreeEntry = {}; + + for (const glyph of glyphs) { if (Array.isArray(glyph)) { currentEntry.entry.reverse.range.push({ - entry, + entry: sharedEntry, range: glyph }); } else { - currentEntry.entry.reverse.individual[glyph] = entry; + currentEntry.entry.reverse.individual[glyph] = sharedEntry; } } + + nextEntries.push({ + entry: sharedEntry, + substitutions: currentEntry.substitutions + }); } return nextEntries; From e2f45b5f4ee64bc6e50061d0169329f0f12a79ad Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:10:44 -0800 Subject: [PATCH 04/10] Fix for modern node that includes a navigator.userAgent --- src/common/Platform.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 7ea0e9c6d4..7cfb9f8743 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -14,9 +14,9 @@ interface INavigator { declare const navigator: INavigator; declare const process: unknown; -// navigator is also checked here because bundling with the process module can cause issues -// otherwise -export const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && typeof navigator === 'undefined') ? true : false; +// navigator.userAgent is also checked here because bundling with the process module can cause +// issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is "Node.js/". +export const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && (typeof navigator === 'undefined' || navigator.userAgent.startsWith('Node.js/'))) ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From d7850af9a2db27adda9f1f4bd2973292417739bd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:19:36 -0800 Subject: [PATCH 05/10] Fix lint --- src/common/Platform.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/Platform.ts b/src/common/Platform.ts index 7cfb9f8743..ec34acde23 100644 --- a/src/common/Platform.ts +++ b/src/common/Platform.ts @@ -15,7 +15,8 @@ declare const navigator: INavigator; declare const process: unknown; // navigator.userAgent is also checked here because bundling with the process module can cause -// issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is "Node.js/". +// issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is +// "Node.js/". export const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && (typeof navigator === 'undefined' || navigator.userAgent.startsWith('Node.js/'))) ? true : false; const userAgent = (isNode) ? 'node' : navigator.userAgent; const platform = (isNode) ? 'node' : navigator.platform; From a63980819638d7b6ac80114a0521a262e85c89e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:32:12 -0800 Subject: [PATCH 06/10] Recommend eslint extension in vscode Fixes #5575 --- .vscode/extensions.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..897af65d73 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint" + ] +} \ No newline at end of file From 6211bf078cdd187a95f4900599299d82318fb456 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:35:39 -0800 Subject: [PATCH 07/10] Lint demo code Fixes #5576 --- demo/client/components/window/addonSerializeWindow.ts | 2 -- demo/client/components/window/cellInspectorWindow.ts | 10 +--------- demo/client/components/window/gpuWindow.ts | 6 ++++-- demo/client/components/window/optionsWindow.ts | 8 ++++---- demo/client/components/window/testWindow.ts | 8 +++----- demo/client/components/window/vtWindow.ts | 2 +- demo/client/components/window/webglWindow.ts | 6 ++++-- package.json | 4 ++-- 8 files changed, 19 insertions(+), 27 deletions(-) diff --git a/demo/client/components/window/addonSerializeWindow.ts b/demo/client/components/window/addonSerializeWindow.ts index 688708ab73..ebc3df3641 100644 --- a/demo/client/components/window/addonSerializeWindow.ts +++ b/demo/client/components/window/addonSerializeWindow.ts @@ -3,10 +3,8 @@ * @license MIT */ -import type { Terminal } from '@xterm/xterm'; import { BaseWindow } from './baseWindow'; import type { IControlWindow } from '../controlBar'; -import type { AddonCollection } from '../../types'; export class AddonSerializeWindow extends BaseWindow implements IControlWindow { public readonly id = 'addon-serialize'; diff --git a/demo/client/components/window/cellInspectorWindow.ts b/demo/client/components/window/cellInspectorWindow.ts index 6a6f67d7a7..9098fc5e3e 100644 --- a/demo/client/components/window/cellInspectorWindow.ts +++ b/demo/client/components/window/cellInspectorWindow.ts @@ -5,8 +5,7 @@ import { BaseWindow } from './baseWindow'; import type { IControlWindow } from '../controlBar'; -import type { Terminal, IBufferCell } from '@xterm/xterm'; -import type { AddonCollection } from '../../types'; +import type { IBufferCell } from '@xterm/xterm'; // Underline style values from common/buffer/Constants.ts const enum UnderlineStyle { @@ -40,13 +39,6 @@ export class CellInspectorWindow extends BaseWindow implements IControlWindow { private _bgEl: HTMLElement; private _attrsEl: HTMLElement; - constructor( - terminal: Terminal, - addons: AddonCollection, - ) { - super(terminal, addons); - } - public build(container: HTMLElement): void { this._container = container; diff --git a/demo/client/components/window/gpuWindow.ts b/demo/client/components/window/gpuWindow.ts index 7035321f52..84bccf11d7 100644 --- a/demo/client/components/window/gpuWindow.ts +++ b/demo/client/components/window/gpuWindow.ts @@ -43,7 +43,9 @@ export class GpuWindow extends BaseWindow implements IControlWindow { } private _styleAtlasPage(canvas: HTMLCanvasElement): void { - canvas.style.width = `${canvas.width / window.devicePixelRatio}px`; - canvas.style.height = `${canvas.height / window.devicePixelRatio}px`; + // eslint-disable-next-line no-restricted-syntax + const dpr = window.devicePixelRatio; + canvas.style.width = `${canvas.width / dpr}px`; + canvas.style.height = `${canvas.height / dpr}px`; } } diff --git a/demo/client/components/window/optionsWindow.ts b/demo/client/components/window/optionsWindow.ts index c2f810b8eb..0096d2ceaa 100644 --- a/demo/client/components/window/optionsWindow.ts +++ b/demo/client/components/window/optionsWindow.ts @@ -88,11 +88,11 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { terminal: Terminal, addons: AddonCollection, private readonly _handlers: { - updateTerminalSize: () => void, - updateTerminalContainerBackground: () => void + updateTerminalSize: () => void; + updateTerminalContainerBackground: () => void; }, ) { - super(terminal, addons) + super(terminal, addons); } public build(container: HTMLElement): void { @@ -238,7 +238,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { this._autoResize = value; } - private _getTheme() { + private _getTheme(): ITheme { const input = document.querySelector('#opt-theme'); let theme: ITheme; switch (input.value) { diff --git a/demo/client/components/window/testWindow.ts b/demo/client/components/window/testWindow.ts index 1b2d5721e8..e811ce7866 100644 --- a/demo/client/components/window/testWindow.ts +++ b/demo/client/components/window/testWindow.ts @@ -3,8 +3,6 @@ * @license MIT */ -/// - import { writeUnicodeTable } from '../../unicodeTable'; import type { IControlWindow } from '../controlBar'; import { BaseWindow } from './baseWindow'; @@ -21,8 +19,8 @@ export class TestWindow extends BaseWindow implements IControlWindow { terminal: Terminal, addons: AddonCollection, private readonly _handlers: { - disposeRecreateButtonHandler: () => void, - createNewWindowButtonHandler: () => void, + disposeRecreateButtonHandler: () => void; + createNewWindowButtonHandler: () => void; }, ) { super(terminal, addons); @@ -501,7 +499,7 @@ function customGlyphAlignmentHandler(term: Terminal): void { term.write(' \u{F5F9}\u{F5F9} \n\r'); term.write('\x1b[0m'); term.write('\n\r'); - + term.write('\x1b[0mProgress bar alignment tests:\x1b[33m\n\r'); term.write('\u{EE00}\u{EE01}\u{EE02} \u{EE03}\u{EE04}\u{EE05}'); diff --git a/demo/client/components/window/vtWindow.ts b/demo/client/components/window/vtWindow.ts index 7e6d2c7413..b5d01367ee 100644 --- a/demo/client/components/window/vtWindow.ts +++ b/demo/client/components/window/vtWindow.ts @@ -22,7 +22,7 @@ export class VtWindow extends BaseWindow implements IControlWindow { container.appendChild(vtContainer); const vtFragment = document.createDocumentFragment(); - const buttonSpecs: { [key: string]: { label: string; description: string; paramCount?: number } } = { + const buttonSpecs: { [key: string]: { label: string, description: string, paramCount?: number } } = { 'A': { label: 'CUU ↑', description: 'Cursor Up Ps Times' }, 'B': { label: 'CUD ↓', description: 'Cursor Down Ps Times' }, 'C': { label: 'CUF →', description: 'Cursor Forward Ps Times' }, diff --git a/demo/client/components/window/webglWindow.ts b/demo/client/components/window/webglWindow.ts index b63728eb6d..f4fabf63f2 100644 --- a/demo/client/components/window/webglWindow.ts +++ b/demo/client/components/window/webglWindow.ts @@ -43,7 +43,9 @@ export class WebglWindow extends BaseWindow implements IControlWindow { } private _styleAtlasPage(canvas: HTMLCanvasElement): void { - canvas.style.width = `${canvas.width / window.devicePixelRatio}px`; - canvas.style.height = `${canvas.height / window.devicePixelRatio}px`; + // eslint-disable-next-line no-restricted-syntax + const dpr = window.devicePixelRatio; + canvas.style.width = `${canvas.width / dpr}px`; + canvas.style.height = `${canvas.height / dpr}px`; } } diff --git a/package.json b/package.json index e99b535099..ecb60ef47e 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "esbuild-demo-server-watch": "node bin/esbuild.mjs --demo-server --watch", "test": "npm run test-unit", "posttest": "npm run lint", - "lint": "eslint --max-warnings 0 src/ addons/", - "lint-fix": "eslint --fix src/ addons/", + "lint": "eslint --max-warnings 0 src/ addons/ demo/", + "lint-fix": "eslint --fix src/ addons/ demo/", "lint-api": "eslint --config eslint.config.typings.mjs --max-warnings 0 typings/", "test-unit": "node ./bin/test_unit.js", "test-unit-coverage": "node ./bin/test_unit.js --coverage", From 8193ceae12721f08187dbd5bd33fec4949c23b17 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:40:07 -0800 Subject: [PATCH 08/10] Make everything except unicode APIs stable --- src/browser/public/Terminal.ts | 4 ---- src/headless/public/Terminal.ts | 4 ---- typings/xterm-headless.d.ts | 11 ++++------- typings/xterm.d.ts | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index c872f5e172..157ceb1457 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -103,7 +103,6 @@ export class Terminal extends Disposable implements ITerminalApi { return this._buffer; } public get markers(): ReadonlyArray { - this._checkProposedApi(); return this._core.markers; } public get modes(): IModes { @@ -166,11 +165,9 @@ export class Terminal extends Disposable implements ITerminalApi { return this._core.registerLinkProvider(linkProvider); } public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { - this._checkProposedApi(); return this._core.registerCharacterJoiner(handler); } public deregisterCharacterJoiner(joinerId: number): void { - this._checkProposedApi(); this._core.deregisterCharacterJoiner(joinerId); } public registerMarker(cursorYOffset: number = 0): IMarker { @@ -178,7 +175,6 @@ export class Terminal extends Disposable implements ITerminalApi { return this._core.registerMarker(cursorYOffset); } public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { - this._checkProposedApi(); this._verifyPositiveIntegers(decorationOptions.x ?? 0, decorationOptions.width ?? 0, decorationOptions.height ?? 0); return this._core.registerDecoration(decorationOptions); } diff --git a/src/headless/public/Terminal.ts b/src/headless/public/Terminal.ts index 811345b903..e0a47e1f5d 100644 --- a/src/headless/public/Terminal.ts +++ b/src/headless/public/Terminal.ts @@ -84,7 +84,6 @@ export class Terminal extends Disposable implements ITerminalApi { public get onWriteParsed(): Event { return this._core.onWriteParsed; } public get parser(): IParser { - this._checkProposedApi(); if (!this._parser) { this._parser = new ParserApi(this._core); } @@ -97,14 +96,12 @@ export class Terminal extends Disposable implements ITerminalApi { public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } public get buffer(): IBufferNamespaceApi { - this._checkProposedApi(); if (!this._buffer) { this._buffer = this._register(new BufferNamespaceApi(this._core)); } return this._buffer; } public get markers(): ReadonlyArray { - this._checkProposedApi(); return this._core.markers; } public get modes(): IModes { @@ -146,7 +143,6 @@ export class Terminal extends Disposable implements ITerminalApi { this._core.resize(columns, rows); } public registerMarker(cursorYOffset: number = 0): IMarker | undefined { - this._checkProposedApi(); this._verifyIntegers(cursorYOffset); return this._core.addMarker(cursorYOffset); } diff --git a/typings/xterm-headless.d.ts b/typings/xterm-headless.d.ts index 3c018a3ba3..abc28d0816 100644 --- a/typings/xterm-headless.d.ts +++ b/typings/xterm-headless.d.ts @@ -584,21 +584,18 @@ declare module '@xterm/headless' { readonly cols: number; /** - * (EXPERIMENTAL) The terminal's current buffer, this might be either the - * normal buffer or the alt buffer depending on what's running in the - * terminal. + * Access to the terminal's normal and alt buffer. */ readonly buffer: IBufferNamespace; /** - * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt - * buffer is active this will always return []. + * Get all markers registered against the buffer. If the alt buffer is + * active this will always return []. */ readonly markers: ReadonlyArray; /** - * (EXPERIMENTAL) Get the parser interface to register - * custom escape sequence handlers. + * Get the parser interface to register custom escape sequence handlers. */ readonly parser: IParser; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1084cae809..9e60e74ca9 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -856,8 +856,8 @@ declare module '@xterm/xterm' { readonly buffer: IBufferNamespace; /** - * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt - * buffer is active this will always return []. + * Get all markers registered against the buffer. If the alt buffer is + * active this will always return []. */ readonly markers: ReadonlyArray; @@ -867,8 +867,8 @@ declare module '@xterm/xterm' { readonly parser: IParser; /** - * (EXPERIMENTAL) Get the Unicode handling interface - * to register and switch Unicode version. + * (EXPERIMENTAL) Get the Unicode handling interface to register and switch + * Unicode version. */ readonly unicode: IUnicodeHandling; @@ -1129,9 +1129,9 @@ declare module '@xterm/xterm' { registerLinkProvider(linkProvider: ILinkProvider): IDisposable; /** - * (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of - * characters to be rendered as a single unit. This is useful in particular - * for rendering ligatures and graphemes, among other things. + * Registers a character joiner, allowing custom sequences of characters to + * be rendered as a single unit. This is useful in particular for rendering + * ligatures and graphemes, among other things. * * Each registered character joiner is called with a string of text * representing a portion of a line in the terminal that can be rendered as @@ -1160,8 +1160,8 @@ declare module '@xterm/xterm' { registerCharacterJoiner(handler: (text: string) => [number, number][]): number; /** - * (EXPERIMENTAL) Deregisters the character joiner if one was registered. - * NOTE: character joiners are only used by the webgl renderer. + * Deregisters the character joiner if one was registered. Note that + * character joiners are only used by the webgl renderer. * @param joinerId The character joiner's ID (returned after register) */ deregisterCharacterJoiner(joinerId: number): void; @@ -1174,7 +1174,7 @@ declare module '@xterm/xterm' { registerMarker(cursorYOffset?: number): IMarker; /** - * (EXPERIMENTAL) Adds a decoration to the terminal using + * Registers a decoration to the terminal. * @param decorationOptions, which takes a marker and an optional anchor, * width, height, and x offset from the anchor. Returns the decoration or * undefined if the alt buffer is active or the marker has already been From 0c7b1cb448626009807311adace0b5ce06c52ff2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:52:32 -0800 Subject: [PATCH 09/10] Update demo dims on font family change Fixes #5572 --- demo/client/components/window/optionsWindow.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/demo/client/components/window/optionsWindow.ts b/demo/client/components/window/optionsWindow.ts index c2f810b8eb..9238658192 100644 --- a/demo/client/components/window/optionsWindow.ts +++ b/demo/client/components/window/optionsWindow.ts @@ -226,6 +226,9 @@ export class OptionsWindow extends BaseWindow implements IControlWindow { if (o === 'theme') { this._handlers.updateTerminalContainerBackground(); } + if (o === 'fontFamily') { + this._handlers.updateTerminalSize(); + } }); }); } From 7389a1097df0041583c3e727225b7cbb4a82e1ad Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 3 Jan 2026 14:57:55 -0800 Subject: [PATCH 10/10] Update tests to use unicode for proposed check --- src/headless/public/Terminal.test.ts | 2 +- test/playwright/Terminal.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/headless/public/Terminal.test.ts b/src/headless/public/Terminal.test.ts index 8beffc6922..059b4a5051 100644 --- a/src/headless/public/Terminal.test.ts +++ b/src/headless/public/Terminal.test.ts @@ -22,7 +22,7 @@ describe('Headless API Tests', function (): void { it('Proposed API check', async () => { term = new Terminal({ allowProposedApi: false }); - throws(() => term.markers, (error: any) => error.message === 'You must set the allowProposedApi option to true to use proposed API'); + throws(() => term.unicode, (error: any) => error.message === 'You must set the allowProposedApi option to true to use proposed API'); }); it('write', async () => { diff --git a/test/playwright/Terminal.test.ts b/test/playwright/Terminal.test.ts index f03d687d9e..acd89f7352 100644 --- a/test/playwright/Terminal.test.ts +++ b/test/playwright/Terminal.test.ts @@ -25,7 +25,7 @@ test.describe('API Integration Tests', () => { await openTerminal(ctx, { allowProposedApi: false }, { loadUnicodeGraphemesAddon: false }); await ctx.page.evaluate(` try { - window.term.markers; + window.term.unicode; } catch (e) { window.throwMessage = e.message; }