jeopi-tui 16.4.2 → 16.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.
- package/CHANGELOG.md +10 -0
- package/package.json +3 -3
- package/src/components/markdown.ts +140 -27
- package/src/terminal.ts +21 -1
- package/src/tui.ts +19 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.4.3] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed ordinary navigation keys (arrows, page up/down, etc.) paying a full frame of input-render-grace latency meant only for the Ctrl+C/Esc double-press gesture window: the grace period now arms only when the incoming key is Ctrl+C or Esc, so idle-state keyboard navigation repaints immediately again. Ported from oh-my-pi (upstream `1822603b2`, TUI portion only — the accompanying Model Hub/model-browser keyboard-navigation changes are coupled to jeopi's not-yet-ported Model Hub feature).
|
|
10
|
+
- Fixed forced renders (tool finalization, `resetDisplay`, image reconciliation) landing during a resize drag preempting the alternate-screen viewport fast path: each one left the borrowed alt screen, erased native scrollback (ED3), and visibly replayed the whole transcript on the normal screen mid-drag — then the settle replayed it again. Forced intent now folds into the single authoritative settle paint. Ported from oh-my-pi (upstream `485d207a7`).
|
|
11
|
+
- Hid empty HTML comment separators in Markdown-rendered TUI output instead of showing `<!-- -->` literally. Ported from oh-my-pi (upstream `aeed4d10d`).
|
|
12
|
+
- Fixed unmanaged macOS stderr writes (libmalloc/framework diagnostics) corrupting the viewport: `ProcessTerminal` now suppresses fd 2 via the jeopi-utils stderr guard while it owns the terminal and restores it in `stop()` and the emergency-restore path. Ported from oh-my-pi (upstream `4eaca82fa` by @Kormákur).
|
|
13
|
+
- Fixed completed rows in transient `diff`/`patch`/`udiff` fences entering native terminal scrollback without semantic syntax colors: newline-complete rows are now highlighted incrementally while the final partial row remains lightweight; closed fences and blank completed rows preserve their final layout and styling. Ported from oh-my-pi (upstream `e41b32c87`, `936e83e3d`, `cf6d25f1b`).
|
|
14
|
+
|
|
5
15
|
## [16.2.25] - 2026-07-05
|
|
6
16
|
|
|
7
17
|
### Added
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-tui",
|
|
4
|
-
"version": "16.4.
|
|
4
|
+
"version": "16.4.3",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"jeopi-natives": "16.4.
|
|
41
|
-
"jeopi-utils": "16.4.
|
|
40
|
+
"jeopi-natives": "16.4.3",
|
|
41
|
+
"jeopi-utils": "16.4.3",
|
|
42
42
|
"lru-cache": "11.5.1",
|
|
43
43
|
"marked": "^18.0.5"
|
|
44
44
|
},
|
|
@@ -86,6 +86,7 @@ function createHtmlNormalizationState(): HtmlNormalizationState {
|
|
|
86
86
|
return { lists: [], openItems: [], itemHasContent: [] };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const HTML_COMMENT_REGEX = /<!--[\s\S]*?-->/g;
|
|
89
90
|
const HTML_TAG_REGEX = /<\/?(?:br|p|ol|ul|li|span|text|code|hr|blockquote)\b(?:\s[^>]*)?\s*\/?>/gi;
|
|
90
91
|
// Block-level HTML that needs structural (not just textual) rendering: standalone
|
|
91
92
|
// `<hr>` becomes a rule and balanced `<blockquote>…</blockquote>` renders with
|
|
@@ -136,11 +137,12 @@ function normalizeHtmlForTerminal(
|
|
|
136
137
|
let output = "";
|
|
137
138
|
let lastIndex = 0;
|
|
138
139
|
let inCode = false;
|
|
140
|
+
const withoutComments = raw.replace(HTML_COMMENT_REGEX, "");
|
|
139
141
|
|
|
140
|
-
for (const match of
|
|
142
|
+
for (const match of withoutComments.matchAll(HTML_TAG_REGEX)) {
|
|
141
143
|
const tag = match[0];
|
|
142
144
|
const index = match.index ?? 0;
|
|
143
|
-
const textBeforeTag = normalizeHtmlEntitiesForTerminal(
|
|
145
|
+
const textBeforeTag = normalizeHtmlEntitiesForTerminal(withoutComments.slice(lastIndex, index));
|
|
144
146
|
const name = htmlTagName(tag);
|
|
145
147
|
// Most tags handled here are block-level. Inline contexts — span, text, and
|
|
146
148
|
// the content inside a `<code>` run — keep their surrounding whitespace
|
|
@@ -238,7 +240,7 @@ function normalizeHtmlForTerminal(
|
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
const remainingText = normalizeHtmlEntitiesForTerminal(
|
|
243
|
+
const remainingText = normalizeHtmlEntitiesForTerminal(withoutComments.slice(lastIndex));
|
|
242
244
|
markCurrentHtmlItemContent(state, remainingText);
|
|
243
245
|
return output + (inCode && codeHook ? codeHook(remainingText) : remainingText);
|
|
244
246
|
}
|
|
@@ -789,6 +791,12 @@ interface StreamPrefixLineCache extends RenderSignature {
|
|
|
789
791
|
lines: readonly string[];
|
|
790
792
|
}
|
|
791
793
|
|
|
794
|
+
interface StreamingDiffLineCache extends RenderSignature {
|
|
795
|
+
lang: string | undefined;
|
|
796
|
+
text: string;
|
|
797
|
+
lines: readonly string[];
|
|
798
|
+
}
|
|
799
|
+
|
|
792
800
|
export class Markdown implements Component {
|
|
793
801
|
#text: string;
|
|
794
802
|
#paddingX: number; // Left/right padding
|
|
@@ -817,6 +825,8 @@ export class Markdown implements Component {
|
|
|
817
825
|
#streamPrefixText?: string;
|
|
818
826
|
#streamPrefixTokens?: Token[];
|
|
819
827
|
#streamPrefixLineCache?: StreamPrefixLineCache;
|
|
828
|
+
#streamingDiffLineCache?: StreamingDiffLineCache;
|
|
829
|
+
#activeRenderSignature?: RenderSignature;
|
|
820
830
|
|
|
821
831
|
#ignoreTight = false;
|
|
822
832
|
|
|
@@ -1001,9 +1011,15 @@ export class Markdown implements Component {
|
|
|
1001
1011
|
|
|
1002
1012
|
// Parse markdown to HTML-like tokens
|
|
1003
1013
|
const tokens = this.#lexTokens(normalizedText);
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1014
|
+
let contentLines: string[];
|
|
1015
|
+
this.#activeRenderSignature = signature;
|
|
1016
|
+
try {
|
|
1017
|
+
contentLines = this.transientRenderCache
|
|
1018
|
+
? this.#renderStreamingContentLines(tokens, normalizedText, signature, contentWidth)
|
|
1019
|
+
: this.#renderContentLines(tokens, 0, tokens.length, contentWidth, signature);
|
|
1020
|
+
} finally {
|
|
1021
|
+
this.#activeRenderSignature = undefined;
|
|
1022
|
+
}
|
|
1007
1023
|
const emptyLines = this.#renderEmptyPaddingLines(signature);
|
|
1008
1024
|
|
|
1009
1025
|
// Combine top padding, content, and bottom padding
|
|
@@ -1177,6 +1193,120 @@ export class Markdown implements Component {
|
|
|
1177
1193
|
return contentLines;
|
|
1178
1194
|
}
|
|
1179
1195
|
|
|
1196
|
+
#renderCodeBodyLines(token: Token, codeIndent: string): string[] {
|
|
1197
|
+
const bodyLines: string[] = [];
|
|
1198
|
+
const tokenText = "text" in token && typeof token.text === "string" ? token.text : "";
|
|
1199
|
+
const lang = "lang" in token && typeof token.lang === "string" ? token.lang : undefined;
|
|
1200
|
+
const normalizedLang = lang?.toLowerCase();
|
|
1201
|
+
const canStreamDiff =
|
|
1202
|
+
this.transientRenderCache &&
|
|
1203
|
+
this.#theme.highlightCode &&
|
|
1204
|
+
(normalizedLang === "diff" || normalizedLang === "patch" || normalizedLang === "udiff");
|
|
1205
|
+
|
|
1206
|
+
if (this.#theme.highlightCode && !this.transientRenderCache) {
|
|
1207
|
+
for (const highlightedLine of this.#theme.highlightCode(tokenText, lang)) {
|
|
1208
|
+
bodyLines.push(`${codeIndent}${highlightedLine}`);
|
|
1209
|
+
}
|
|
1210
|
+
return bodyLines;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
if (canStreamDiff) {
|
|
1214
|
+
const closedFence = this.#codeTokenHasClosingFence(token);
|
|
1215
|
+
const lineEnd = tokenText.lastIndexOf("\n");
|
|
1216
|
+
if (closedFence || lineEnd >= 0) {
|
|
1217
|
+
const completedText = closedFence ? tokenText : tokenText.slice(0, lineEnd);
|
|
1218
|
+
for (const highlightedLine of this.#highlightStreamingDiffLines(completedText, lang)) {
|
|
1219
|
+
bodyLines.push(`${codeIndent}${highlightedLine}`);
|
|
1220
|
+
}
|
|
1221
|
+
if (!closedFence) {
|
|
1222
|
+
for (const codeLine of tokenText.slice(lineEnd + 1).split("\n")) {
|
|
1223
|
+
bodyLines.push(`${codeIndent}${this.#theme.codeBlock(codeLine)}`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return bodyLines;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
for (const codeLine of tokenText.split("\n")) {
|
|
1231
|
+
bodyLines.push(`${codeIndent}${this.#theme.codeBlock(codeLine)}`);
|
|
1232
|
+
}
|
|
1233
|
+
return bodyLines;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
#codeTokenHasClosingFence(token: Token): boolean {
|
|
1237
|
+
const raw = "raw" in token && typeof token.raw === "string" ? token.raw : "";
|
|
1238
|
+
const firstLineEnd = raw.indexOf("\n");
|
|
1239
|
+
if (firstLineEnd < 0) return false;
|
|
1240
|
+
const openingLine = raw.slice(0, firstLineEnd);
|
|
1241
|
+
const openingTrimmed = openingLine.trimStart();
|
|
1242
|
+
const openingIndent = openingLine.length - openingTrimmed.length;
|
|
1243
|
+
if (openingIndent > 3) return false;
|
|
1244
|
+
const fenceChar = openingTrimmed.charAt(0);
|
|
1245
|
+
if (fenceChar !== "`" && fenceChar !== "~") return false;
|
|
1246
|
+
let fenceLength = 0;
|
|
1247
|
+
while (openingTrimmed.charAt(fenceLength) === fenceChar) fenceLength++;
|
|
1248
|
+
if (fenceLength < 3) return false;
|
|
1249
|
+
|
|
1250
|
+
let lineStart = firstLineEnd + 1;
|
|
1251
|
+
while (lineStart <= raw.length) {
|
|
1252
|
+
const lineEnd = raw.indexOf("\n", lineStart);
|
|
1253
|
+
const line = lineEnd >= 0 ? raw.slice(lineStart, lineEnd) : raw.slice(lineStart);
|
|
1254
|
+
const trimmed = line.trimStart();
|
|
1255
|
+
const indent = line.length - trimmed.length;
|
|
1256
|
+
let closingLength = 0;
|
|
1257
|
+
while (trimmed.charAt(closingLength) === fenceChar) closingLength++;
|
|
1258
|
+
if (indent <= 3 && closingLength >= fenceLength && trimmed.slice(closingLength).trim().length === 0) {
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1261
|
+
if (lineEnd < 0) break;
|
|
1262
|
+
lineStart = lineEnd + 1;
|
|
1263
|
+
}
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
#highlightStreamingDiffLines(completedText: string, lang: string | undefined): readonly string[] {
|
|
1268
|
+
const highlightCode = this.#theme.highlightCode;
|
|
1269
|
+
if (!highlightCode) return [];
|
|
1270
|
+
const signature = this.#activeRenderSignature;
|
|
1271
|
+
const cache = this.#streamingDiffLineCache;
|
|
1272
|
+
if (
|
|
1273
|
+
signature &&
|
|
1274
|
+
cache &&
|
|
1275
|
+
completedText.startsWith(cache.text) &&
|
|
1276
|
+
(cache.text.length === completedText.length || completedText.charCodeAt(cache.text.length) === 0x0a) &&
|
|
1277
|
+
cache.lang === lang &&
|
|
1278
|
+
cache.width === signature.width &&
|
|
1279
|
+
cache.paddingX === signature.paddingX &&
|
|
1280
|
+
cache.paddingY === signature.paddingY &&
|
|
1281
|
+
cache.codeBlockIndent === signature.codeBlockIndent &&
|
|
1282
|
+
cache.themeId === signature.themeId &&
|
|
1283
|
+
cache.defaultTextStyleId === signature.defaultTextStyleId &&
|
|
1284
|
+
cache.imageProtocol === signature.imageProtocol &&
|
|
1285
|
+
cache.hyperlinks === signature.hyperlinks &&
|
|
1286
|
+
cache.textSizing === signature.textSizing &&
|
|
1287
|
+
cache.bgColorProbe === signature.bgColorProbe &&
|
|
1288
|
+
cache.headingProbe === signature.headingProbe
|
|
1289
|
+
) {
|
|
1290
|
+
if (completedText.length === cache.text.length) return cache.lines;
|
|
1291
|
+
const lines = cache.lines.slice();
|
|
1292
|
+
const addedText = completedText.slice(cache.text.length === 0 ? 0 : cache.text.length + 1);
|
|
1293
|
+
for (const codeLine of addedText.split("\n")) {
|
|
1294
|
+
lines.push(...highlightCode(codeLine, lang));
|
|
1295
|
+
}
|
|
1296
|
+
this.#streamingDiffLineCache = { ...signature, lang, text: completedText, lines };
|
|
1297
|
+
return lines;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const lines: string[] = [];
|
|
1301
|
+
for (const codeLine of completedText.split("\n")) {
|
|
1302
|
+
lines.push(...highlightCode(codeLine, lang));
|
|
1303
|
+
}
|
|
1304
|
+
if (signature) {
|
|
1305
|
+
this.#streamingDiffLineCache = { ...signature, lang, text: completedText, lines };
|
|
1306
|
+
}
|
|
1307
|
+
return lines;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1180
1310
|
#renderEmptyPaddingLines(signature: RenderSignature): string[] {
|
|
1181
1311
|
const emptyLine = padding(signature.width);
|
|
1182
1312
|
const emptyLines: string[] = [];
|
|
@@ -1354,17 +1484,8 @@ export class Markdown implements Component {
|
|
|
1354
1484
|
|
|
1355
1485
|
const codeIndent = padding(this.#codeBlockIndent);
|
|
1356
1486
|
lines.push(this.#theme.codeBlockBorder(`\`\`\`${token.lang || ""}`));
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
for (const hlLine of highlightedLines) {
|
|
1360
|
-
lines.push(`${codeIndent}${hlLine}`);
|
|
1361
|
-
}
|
|
1362
|
-
} else {
|
|
1363
|
-
// Split code by newlines and style each line
|
|
1364
|
-
const codeLines = token.text.split("\n");
|
|
1365
|
-
for (const codeLine of codeLines) {
|
|
1366
|
-
lines.push(`${codeIndent}${this.#theme.codeBlock(codeLine)}`);
|
|
1367
|
-
}
|
|
1487
|
+
for (const bodyLine of this.#renderCodeBodyLines(token, codeIndent)) {
|
|
1488
|
+
lines.push(bodyLine);
|
|
1368
1489
|
}
|
|
1369
1490
|
lines.push(this.#theme.codeBlockBorder("```"));
|
|
1370
1491
|
if (nextTokenType && nextTokenType !== "space") {
|
|
@@ -1744,16 +1865,8 @@ export class Markdown implements Component {
|
|
|
1744
1865
|
// Code block in list item
|
|
1745
1866
|
const codeIndent = padding(this.#codeBlockIndent);
|
|
1746
1867
|
lines.push({ text: this.#theme.codeBlockBorder(`\`\`\`${token.lang || ""}`), nested: false });
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
for (const hlLine of highlightedLines) {
|
|
1750
|
-
lines.push({ text: `${codeIndent}${hlLine}`, nested: false });
|
|
1751
|
-
}
|
|
1752
|
-
} else {
|
|
1753
|
-
const codeLines = token.text.split("\n");
|
|
1754
|
-
for (const codeLine of codeLines) {
|
|
1755
|
-
lines.push({ text: `${codeIndent}${this.#theme.codeBlock(codeLine)}`, nested: false });
|
|
1756
|
-
}
|
|
1868
|
+
for (const bodyLine of this.#renderCodeBodyLines(token, codeIndent)) {
|
|
1869
|
+
lines.push({ text: bodyLine, nested: false });
|
|
1757
1870
|
}
|
|
1758
1871
|
lines.push({ text: this.#theme.codeBlockBorder("```"), nested: false });
|
|
1759
1872
|
} else if (isMathToken(token)) {
|
package/src/terminal.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
$env,
|
|
5
|
+
isBunTestRuntime,
|
|
6
|
+
isTerminalHeadless,
|
|
7
|
+
logger,
|
|
8
|
+
restoreTerminalStderr,
|
|
9
|
+
suppressTerminalStderr,
|
|
10
|
+
} from "jeopi-utils";
|
|
4
11
|
import { setKittyProtocolActive } from "./keys";
|
|
5
12
|
import { StdinBuffer } from "./stdin-buffer";
|
|
6
13
|
import {
|
|
@@ -261,6 +268,9 @@ function createConsoleCodepageGuard(): (() => void) | null {
|
|
|
261
268
|
*/
|
|
262
269
|
export function emergencyTerminalRestore(): void {
|
|
263
270
|
try {
|
|
271
|
+
// Crash paths must surface subsequent stderr (fatal reports) on the
|
|
272
|
+
// real terminal; no-op when the stderr guard is inactive.
|
|
273
|
+
restoreTerminalStderr();
|
|
264
274
|
const terminal = activeTerminal;
|
|
265
275
|
if (terminal) {
|
|
266
276
|
terminal.stop();
|
|
@@ -523,6 +533,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
523
533
|
activeTerminal = this;
|
|
524
534
|
terminalEverStarted = true;
|
|
525
535
|
|
|
536
|
+
// Keep unmanaged fd-2 writes (macOS libmalloc/framework diagnostics) off
|
|
537
|
+
// the viewport while we own the terminal; released in stop(). See
|
|
538
|
+
// stderr-guard in jeopi-utils (mirrors openai/codex#24459).
|
|
539
|
+
suppressTerminalStderr();
|
|
540
|
+
|
|
526
541
|
// Save previous state and enable raw mode
|
|
527
542
|
this.#wasRaw = process.stdin.isRaw || false;
|
|
528
543
|
if (process.stdin.setRawMode) {
|
|
@@ -1235,6 +1250,11 @@ export class ProcessTerminal implements Terminal {
|
|
|
1235
1250
|
activeTerminal = null;
|
|
1236
1251
|
}
|
|
1237
1252
|
|
|
1253
|
+
// Release terminal ownership of fd 2 first so external programs,
|
|
1254
|
+
// suspend, and shutdown see the real stderr even if a later teardown
|
|
1255
|
+
// step throws.
|
|
1256
|
+
restoreTerminalStderr();
|
|
1257
|
+
|
|
1238
1258
|
if (this.#clearProgressTimer()) {
|
|
1239
1259
|
this.#safeWrite(TERMINAL_PROGRESS_CLEAR_SEQUENCE);
|
|
1240
1260
|
}
|
package/src/tui.ts
CHANGED
|
@@ -2155,12 +2155,12 @@ export class TUI extends Container {
|
|
|
2155
2155
|
}
|
|
2156
2156
|
|
|
2157
2157
|
#handleInput(data: string): void {
|
|
2158
|
-
//
|
|
2159
|
-
//
|
|
2160
|
-
//
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2158
|
+
// Ctrl+C/Esc use app-level double-press windows. Give those gestures one
|
|
2159
|
+
// frame to drain queued input before an ordinary repaint; delaying every
|
|
2160
|
+
// key would make idle navigation pay a full frame of latency.
|
|
2161
|
+
if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
|
|
2162
|
+
this.#inputRenderGraceUntilMs = this.#renderScheduler.now() + TUI.#INPUT_RENDER_GRACE_MS;
|
|
2163
|
+
}
|
|
2164
2164
|
if (this.#inputListeners.size > 0) {
|
|
2165
2165
|
let current = data;
|
|
2166
2166
|
for (const listener of this.#inputListeners) {
|
|
@@ -2612,18 +2612,19 @@ export class TUI extends Container {
|
|
|
2612
2612
|
// alternate screen to repaint the whole transcript on the normal
|
|
2613
2613
|
// screen — then the next SIGWINCH re-enters the alt screen and paints
|
|
2614
2614
|
// only the tail, so the block flashes in for one frame and vanishes.
|
|
2615
|
-
// A
|
|
2616
|
-
//
|
|
2617
|
-
//
|
|
2618
|
-
//
|
|
2619
|
-
//
|
|
2620
|
-
//
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
)
|
|
2615
|
+
// A FORCED render mid-drag (tool finalization, resetDisplay, image
|
|
2616
|
+
// reconciliation) also stays on the fast path: preempting would leave
|
|
2617
|
+
// the borrowed alternate screen and run the geometry-rebuild full paint
|
|
2618
|
+
// on the normal screen — ED3 plus an O(history) replay that visibly
|
|
2619
|
+
// scrolls the whole transcript through the viewport, once per forced
|
|
2620
|
+
// render and once more at settle. The forced intent is not lost: the
|
|
2621
|
+
// fast path consumes neither #forceViewportRepaintOnNextRender nor
|
|
2622
|
+
// #clearScrollbackOnNextRender, and the settle's authoritative
|
|
2623
|
+
// requestRender(true) honors both — same fold-into-the-settle contract
|
|
2624
|
+
// as the multiplexer resize debounce. A visible overlay composites over
|
|
2625
|
+
// the transcript and needs the whole window, so it falls through
|
|
2626
|
+
// (overlay resizes are not on the drag-cost hot path).
|
|
2627
|
+
if (this.#resizeViewportActive && this.#hasEverRendered && this.#getTopmostVisibleOverlay() === undefined) {
|
|
2627
2628
|
this.#componentRenderTargets.clear();
|
|
2628
2629
|
this.#renderResizeViewport(width, height);
|
|
2629
2630
|
return;
|