@simon_he/vue-tui 0.0.5 → 0.0.7

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 (4) hide show
  1. package/dist/index.cjs +2262 -266
  2. package/dist/index.d.ts +180 -106
  3. package/dist/index.js +1966 -217
  4. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { isAbsolutePath, normalizePath, parsePathQuery, resolvePath, resolveUserPath as resolveUserPath$1, resolveUserPath$1 as resolveUserPath, stripTrailingSlash, suggestPaths, suggestPaths$1 } from "./path-suggest-CZQYJIIg.js";
2
- import process from "node:process";
2
+ import process$1 from "node:process";
3
3
  import { appendFileSync, writeFileSync } from "node:fs";
4
4
  import { Buffer } from "node:buffer";
5
5
  import { EventEmitter } from "node:events";
6
- import { computed, createRenderer, defineComponent, effectScope, h, inject, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, provide, reactive, ref, shallowReactive, shallowRef, toRef, vShow, watch, watchEffect } from "vue";
6
+ import { computed, createRenderer, defineComponent, effectScope, h, inject, nextTick, onBeforeUnmount, onMounted, onScopeDispose, onUnmounted, provide, reactive, ref, shallowReactive, shallowRef, toRef, vShow, watch, watchEffect } from "vue";
7
7
 
8
8
  //#region src/core/ansi-palette.ts
9
9
  const ANSI_PALETTE_HEX = Object.freeze({
@@ -380,6 +380,308 @@ function ansiColors(level) {
380
380
  return ANSI256_COLORS;
381
381
  }
382
382
 
383
+ //#endregion
384
+ //#region src/observability/cli-latency.ts
385
+ const EVENT_OP_ID = Symbol("dimcode.cliLatencyOpId");
386
+ const DEFAULT_LOG_PATH = "/tmp/dimcode-cli-latency.jsonl";
387
+ const INVALIDATE_ASSOCIATION_WINDOW_MS = 32;
388
+ const MAX_OP_AGE_MS = 5e3;
389
+ let nextOpId = 1;
390
+ let singleton;
391
+ function now$2() {
392
+ return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
393
+ }
394
+ function diffMs(end, start) {
395
+ if (end == null || start == null) return null;
396
+ return Math.max(0, end - start);
397
+ }
398
+ function parseEnabled$1(value) {
399
+ const raw = String(value ?? "").trim().toLowerCase();
400
+ return raw === "1" || raw === "true" || raw === "yes";
401
+ }
402
+ function isTrackedEventType(type) {
403
+ return type === "keydown" || type === "beforeinput" || type === "input" || type === "paste";
404
+ }
405
+ function defineOpId(target, id) {
406
+ try {
407
+ Object.defineProperty(target, EVENT_OP_ID, {
408
+ configurable: true,
409
+ enumerable: false,
410
+ writable: true,
411
+ value: id
412
+ });
413
+ } catch {}
414
+ }
415
+ function readOpId(target) {
416
+ const value = target === null || target === void 0 ? void 0 : target[EVENT_OP_ID];
417
+ return typeof value === "number" ? value : null;
418
+ }
419
+ function createDisabledProfiler() {
420
+ return null;
421
+ }
422
+ function createProfiler() {
423
+ var _process$once;
424
+ const env = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
425
+ if (!parseEnabled$1(env.DIMCODE_PROFILE_INPUT_LATENCY)) return createDisabledProfiler();
426
+ const logPath = String(env.DIMCODE_PROFILE_INPUT_LATENCY_LOG_PATH ?? DEFAULT_LOG_PATH).trim() || DEFAULT_LOG_PATH;
427
+ const ops = new Map();
428
+ const pendingCommitIds = new Set();
429
+ const pendingWriteIds = new Set();
430
+ let currentDispatchOpId = null;
431
+ let lastDispatchOpId = null;
432
+ let lastDispatchEndedAt = null;
433
+ let currentFlushIds = [];
434
+ let lastRawInputAt = null;
435
+ const emit = (record) => {
436
+ try {
437
+ appendFileSync(logPath, `${JSON.stringify(record)}\n`);
438
+ } catch {}
439
+ };
440
+ const cleanupOp = (id) => {
441
+ ops.delete(id);
442
+ pendingCommitIds.delete(id);
443
+ pendingWriteIds.delete(id);
444
+ if (currentDispatchOpId === id) currentDispatchOpId = null;
445
+ if (lastDispatchOpId === id) {
446
+ lastDispatchOpId = null;
447
+ lastDispatchEndedAt = null;
448
+ }
449
+ if (currentFlushIds.includes(id)) currentFlushIds = currentFlushIds.filter((value) => value !== id);
450
+ };
451
+ const flushOp = (op, outcome) => {
452
+ const totalStart = op.rawInputAt ?? op.stdinDispatchAt ?? op.dispatchStartAt;
453
+ emit({
454
+ ts: op.createdAtIso,
455
+ id: op.id,
456
+ event: {
457
+ type: op.eventType,
458
+ key: op.key,
459
+ code: op.code,
460
+ defaultPrevented: op.defaultPrevented,
461
+ parser: op.parser
462
+ },
463
+ operation: op.operation,
464
+ stages: {
465
+ rawInputAt: op.rawInputAt,
466
+ stdinDispatchAt: op.stdinDispatchAt,
467
+ dispatchStartAt: op.dispatchStartAt,
468
+ dispatchEndAt: op.dispatchEndAt,
469
+ invalidateAt: op.invalidateAt,
470
+ flushStartAt: op.flushStartAt,
471
+ flushEndAt: op.flushEndAt,
472
+ commitAt: op.commitAt,
473
+ stdoutRenderStartAt: op.stdoutRenderStartAt,
474
+ writeStartAt: op.writeStartAt,
475
+ writeEndAt: op.writeEndAt
476
+ },
477
+ timingsMs: {
478
+ inputParseWait: diffMs(op.stdinDispatchAt, op.rawInputAt),
479
+ eventDispatch: diffMs(op.dispatchEndAt, op.dispatchStartAt),
480
+ dispatchToInvalidate: diffMs(op.invalidateAt, op.dispatchEndAt),
481
+ dispatchToFlush: diffMs(op.flushStartAt, op.dispatchEndAt),
482
+ flushToCommitDone: diffMs(op.flushEndAt, op.flushStartAt),
483
+ commitToRender: diffMs(op.stdoutRenderStartAt, op.commitAt),
484
+ renderToWriteStart: diffMs(op.writeStartAt, op.stdoutRenderStartAt),
485
+ write: op.writeDurationMs,
486
+ totalToWrite: diffMs(op.writeEndAt, totalStart)
487
+ },
488
+ scheduler: {
489
+ invalidatePriority: op.invalidatePriority,
490
+ invalidatePlane: op.invalidatePlane,
491
+ commitSync: op.commitSync,
492
+ commitPlaneCount: op.commitPlaneCount,
493
+ commitDirtyRows: op.commitDirtyRows,
494
+ stdoutQueuedDelayMs: op.stdoutQueuedDelayMs,
495
+ writeMode: op.writeMode,
496
+ writeBytes: op.writeBytes
497
+ },
498
+ outcome
499
+ });
500
+ cleanupOp(op.id);
501
+ };
502
+ const expireOldOps = () => {
503
+ const currentTime = now$2();
504
+ for (const op of ops.values()) {
505
+ if (currentTime - op.createdAt < MAX_OP_AGE_MS) continue;
506
+ flushOp(op, op.writeEndAt != null ? "completed" : "timeout");
507
+ }
508
+ };
509
+ const flushPendingOps = (outcome) => {
510
+ for (const op of Array.from(ops.values())) flushOp(op, op.writeEndAt != null ? "completed" : outcome);
511
+ };
512
+ const ensureOp = (event) => {
513
+ const type = String((event === null || event === void 0 ? void 0 : event.type) ?? "");
514
+ if (!isTrackedEventType(type)) return null;
515
+ const existingId = readOpId(event);
516
+ if (existingId != null) return ops.get(existingId) ?? null;
517
+ const id = nextOpId++;
518
+ const op = {
519
+ id,
520
+ createdAt: now$2(),
521
+ createdAtIso: new Date().toISOString(),
522
+ eventType: type,
523
+ key: String((event === null || event === void 0 ? void 0 : event.key) ?? ""),
524
+ code: String((event === null || event === void 0 ? void 0 : event.code) ?? ""),
525
+ operation: null,
526
+ rawInputAt: null,
527
+ stdinDispatchAt: null,
528
+ dispatchStartAt: null,
529
+ dispatchEndAt: null,
530
+ invalidateAt: null,
531
+ invalidatePriority: null,
532
+ invalidatePlane: null,
533
+ flushStartAt: null,
534
+ flushEndAt: null,
535
+ commitAt: null,
536
+ commitSync: null,
537
+ commitPlaneCount: null,
538
+ commitDirtyRows: null,
539
+ stdoutQueuedDelayMs: null,
540
+ stdoutRenderStartAt: null,
541
+ writeStartAt: null,
542
+ writeEndAt: null,
543
+ writeDurationMs: null,
544
+ writeMode: null,
545
+ writeBytes: null,
546
+ defaultPrevented: null,
547
+ parser: null
548
+ };
549
+ ops.set(id, op);
550
+ defineOpId(event, id);
551
+ expireOldOps();
552
+ return op;
553
+ };
554
+ const forIds = (ids, fn) => {
555
+ for (const id of ids) {
556
+ const op = ops.get(id);
557
+ if (!op) continue;
558
+ fn(op);
559
+ }
560
+ };
561
+ const resolveActiveOp = () => {
562
+ if (currentDispatchOpId != null) return ops.get(currentDispatchOpId) ?? null;
563
+ if (lastDispatchOpId != null && lastDispatchEndedAt != null && now$2() - lastDispatchEndedAt <= INVALIDATE_ASSOCIATION_WINDOW_MS) return ops.get(lastDispatchOpId) ?? null;
564
+ return null;
565
+ };
566
+ process$1 === null || process$1 === void 0 || (_process$once = process$1.once) === null || _process$once === void 0 || _process$once.call(process$1, "exit", () => {
567
+ flushPendingOps("process-exit");
568
+ });
569
+ return {
570
+ enabled: true,
571
+ recordRawInput(_info) {
572
+ lastRawInputAt = now$2();
573
+ expireOldOps();
574
+ },
575
+ recordStdinDispatch(event, info) {
576
+ const op = ensureOp(event);
577
+ if (!op) return;
578
+ if (op.rawInputAt == null && lastRawInputAt != null) op.rawInputAt = lastRawInputAt;
579
+ if (op.stdinDispatchAt == null) op.stdinDispatchAt = now$2();
580
+ if (!op.parser && (info === null || info === void 0 ? void 0 : info.parser)) op.parser = String(info.parser);
581
+ },
582
+ recordEventDispatchStart(event) {
583
+ const op = ensureOp(event);
584
+ if (!op) return;
585
+ currentDispatchOpId = op.id;
586
+ if (op.dispatchStartAt == null) op.dispatchStartAt = now$2();
587
+ },
588
+ recordEventDispatchEnd(event, info) {
589
+ const op = ensureOp(event);
590
+ if (!op) return;
591
+ op.defaultPrevented = Boolean(info.defaultPrevented);
592
+ if (op.dispatchEndAt == null) op.dispatchEndAt = now$2();
593
+ currentDispatchOpId = currentDispatchOpId === op.id ? null : currentDispatchOpId;
594
+ lastDispatchOpId = op.id;
595
+ lastDispatchEndedAt = op.dispatchEndAt;
596
+ },
597
+ recordSchedulerInvalidate(info) {
598
+ const op = resolveActiveOp();
599
+ if (!op) return;
600
+ if (op.invalidateAt == null) op.invalidateAt = now$2();
601
+ if (!op.invalidatePriority && (info === null || info === void 0 ? void 0 : info.priority)) op.invalidatePriority = String(info.priority);
602
+ if (!op.invalidatePlane && (info === null || info === void 0 ? void 0 : info.plane)) op.invalidatePlane = String(info.plane);
603
+ pendingCommitIds.add(op.id);
604
+ },
605
+ recordFlushStart(info) {
606
+ currentFlushIds = Array.from(pendingCommitIds);
607
+ if (!currentFlushIds.length) return;
608
+ const flushTime = now$2();
609
+ forIds(currentFlushIds, (op) => {
610
+ if (op.flushStartAt == null) op.flushStartAt = flushTime;
611
+ if (op.commitSync == null && (info === null || info === void 0 ? void 0 : info.sync) != null) op.commitSync = Boolean(info.sync);
612
+ if (op.commitPlaneCount == null && Array.isArray(info === null || info === void 0 ? void 0 : info.activePlanes)) op.commitPlaneCount = info.activePlanes.length;
613
+ });
614
+ },
615
+ recordFlushEnd() {
616
+ if (!currentFlushIds.length) return;
617
+ const flushTime = now$2();
618
+ forIds(currentFlushIds, (op) => {
619
+ if (op.flushEndAt == null) op.flushEndAt = flushTime;
620
+ });
621
+ currentFlushIds = [];
622
+ },
623
+ recordCommit(info) {
624
+ if (!pendingCommitIds.size) return;
625
+ const commitTime = now$2();
626
+ const ids = Array.from(pendingCommitIds);
627
+ pendingCommitIds.clear();
628
+ forIds(ids, (op) => {
629
+ if (op.flushEndAt == null && op.flushStartAt != null) op.flushEndAt = commitTime;
630
+ if (op.commitAt == null) op.commitAt = commitTime;
631
+ op.commitSync = (info === null || info === void 0 ? void 0 : info.sync) == null ? op.commitSync : Boolean(info.sync);
632
+ op.commitPlaneCount = Array.isArray(info === null || info === void 0 ? void 0 : info.planes) ? info.planes.length : op.commitPlaneCount;
633
+ op.commitDirtyRows = Array.isArray(info === null || info === void 0 ? void 0 : info.dirtyRows) ? info.dirtyRows.length : (info === null || info === void 0 ? void 0 : info.dirtyRows) === null ? null : op.commitDirtyRows;
634
+ pendingWriteIds.add(op.id);
635
+ });
636
+ },
637
+ recordStdoutQueued(delayMs) {
638
+ if (!pendingWriteIds.size) return;
639
+ forIds(pendingWriteIds, (op) => {
640
+ if (op.stdoutQueuedDelayMs == null) op.stdoutQueuedDelayMs = Math.max(0, delayMs);
641
+ });
642
+ },
643
+ recordStdoutRenderStart() {
644
+ if (!pendingWriteIds.size) return;
645
+ const renderTime = now$2();
646
+ forIds(pendingWriteIds, (op) => {
647
+ if (op.stdoutRenderStartAt == null) op.stdoutRenderStartAt = renderTime;
648
+ });
649
+ },
650
+ recordStdoutNoOutput() {
651
+ if (!pendingWriteIds.size) return;
652
+ const ids = Array.from(pendingWriteIds);
653
+ pendingWriteIds.clear();
654
+ forIds(ids, (op) => flushOp(op, "no-output"));
655
+ },
656
+ recordStdoutWrite(info) {
657
+ if (!pendingWriteIds.size) return;
658
+ const writeEndTime = now$2();
659
+ const ids = Array.from(pendingWriteIds);
660
+ pendingWriteIds.clear();
661
+ forIds(ids, (op) => {
662
+ op.writeEndAt = writeEndTime;
663
+ op.writeDurationMs = Math.max(0, info.durationMs);
664
+ op.writeStartAt = writeEndTime - op.writeDurationMs;
665
+ op.writeMode = String(info.mode ?? "");
666
+ op.writeBytes = Math.max(0, Math.floor(info.bytes));
667
+ flushOp(op, "completed");
668
+ });
669
+ },
670
+ markOperation(operation) {
671
+ const nextOperation = String(operation ?? "").trim();
672
+ if (!nextOperation) return;
673
+ const op = resolveActiveOp();
674
+ if (!op) return;
675
+ op.operation = nextOperation;
676
+ }
677
+ };
678
+ }
679
+ function getCliLatencyProfiler() {
680
+ if (singleton !== void 0) return singleton;
681
+ singleton = createProfiler();
682
+ return singleton;
683
+ }
684
+
383
685
  //#endregion
384
686
  //#region src/utils/newlines.ts
385
687
  function normalizeNewlines(text) {
@@ -1286,7 +1588,7 @@ function parseMouseSgr(sequence) {
1286
1588
  handled: false,
1287
1589
  event: null
1288
1590
  };
1289
- const env = (process === null || process === void 0 ? void 0 : process.env) ?? {};
1591
+ const env = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
1290
1592
  const invertWheel = String(env.DIMCODE_WHEEL_INVERT ?? env.VUE_TUI_WHEEL_INVERT ?? "") === "1";
1291
1593
  const mouseDebugEnabled = String(env.DIMCODE_MOUSE_DEBUG ?? env.VUE_TUI_MOUSE_DEBUG ?? "") === "1";
1292
1594
  const mouseDebugPath = String(env.DIMCODE_MOUSE_DEBUG_PATH ?? env.VUE_TUI_MOUSE_DEBUG_PATH ?? "/tmp/goatchain-mouse-debug.log");
@@ -1422,6 +1724,7 @@ function parseMouseSequence(sequence) {
1422
1724
  const ESC$1 = "\x1B";
1423
1725
  const BRACKETED_PASTE_START = "\x1B[200~";
1424
1726
  const BRACKETED_PASTE_END = "\x1B[201~";
1727
+ const ESC_CONTINUATION_RESCUE_WINDOW_MS = 64;
1425
1728
  function looksLikeQuotedPathPaste(value) {
1426
1729
  if (!value) return false;
1427
1730
  if (value.includes(ESC$1)) return false;
@@ -1524,6 +1827,11 @@ function extractCompleteSequences(buffer) {
1524
1827
  remainder: ""
1525
1828
  };
1526
1829
  }
1830
+ function looksLikeSplitEscapeContinuation(value) {
1831
+ const first = value[0];
1832
+ if (first !== "[" && first !== "]" && first !== "P" && first !== "_" && first !== "O") return false;
1833
+ return isCompleteSequence(`${ESC$1}${value}`) !== "not-escape";
1834
+ }
1527
1835
  var StdinBuffer = class extends EventEmitter {
1528
1836
  constructor(options = {}) {
1529
1837
  super();
@@ -1531,6 +1839,7 @@ var StdinBuffer = class extends EventEmitter {
1531
1839
  this.timeout = null;
1532
1840
  this.timeoutMs = void 0;
1533
1841
  this.escTimeoutMs = void 0;
1842
+ this.pendingEscContinuationAt = null;
1534
1843
  this.pasteMode = false;
1535
1844
  this.pasteBuffer = "";
1536
1845
  this.timeoutMs = options.timeout ?? 10;
@@ -1547,6 +1856,11 @@ var StdinBuffer = class extends EventEmitter {
1547
1856
  str = `\x1B${String.fromCharCode(byte)}`;
1548
1857
  } else str = data.toString();
1549
1858
  else str = data;
1859
+ if (this.pendingEscContinuationAt != null) {
1860
+ const canRescue = Date.now() - this.pendingEscContinuationAt <= ESC_CONTINUATION_RESCUE_WINDOW_MS;
1861
+ this.pendingEscContinuationAt = null;
1862
+ if (canRescue && looksLikeSplitEscapeContinuation(str)) str = `${ESC$1}${str}`;
1863
+ }
1550
1864
  if (str.length === 0 && this.buffer.length === 0) {
1551
1865
  this.emit("data", "");
1552
1866
  return;
@@ -1600,7 +1914,9 @@ var StdinBuffer = class extends EventEmitter {
1600
1914
  if (this.buffer.length > 0) {
1601
1915
  const timeoutMs = this.buffer === ESC$1 ? this.escTimeoutMs : this.timeoutMs;
1602
1916
  this.timeout = setTimeout(() => {
1917
+ const rescuableLoneEsc = this.buffer === ESC$1;
1603
1918
  const flushed = this.flush();
1919
+ if (rescuableLoneEsc && flushed.length === 1 && flushed[0] === ESC$1) this.pendingEscContinuationAt = Date.now();
1604
1920
  for (const sequence of flushed) this.emit("data", sequence);
1605
1921
  }, timeoutMs);
1606
1922
  }
@@ -1621,6 +1937,7 @@ var StdinBuffer = class extends EventEmitter {
1621
1937
  this.timeout = null;
1622
1938
  }
1623
1939
  this.buffer = "";
1940
+ this.pendingEscContinuationAt = null;
1624
1941
  this.pasteMode = false;
1625
1942
  this.pasteBuffer = "";
1626
1943
  }
@@ -1634,6 +1951,10 @@ var StdinBuffer = class extends EventEmitter {
1634
1951
 
1635
1952
  //#endregion
1636
1953
  //#region src/cli/input.ts
1954
+ const KITTY_KEYBOARD_PROTOCOL_ENABLE = "\x1B[>1u";
1955
+ const KITTY_KEYBOARD_PROTOCOL_DISABLE = "\x1B[<u";
1956
+ const XTERM_MODIFY_OTHER_KEYS_ENABLE = "\x1B[>4;2m";
1957
+ const XTERM_MODIFY_OTHER_KEYS_DISABLE = "\x1B[>4n";
1637
1958
  function isPrintable(ch) {
1638
1959
  if (ch.length === 0) return false;
1639
1960
  if (ch.length === 1) {
@@ -1653,12 +1974,54 @@ function ctrlKeyFromChar(ch) {
1653
1974
  if (code >= 1 && code <= 26) return String.fromCharCode(code + 96);
1654
1975
  return null;
1655
1976
  }
1977
+ function isUnhandledCtrlC(event) {
1978
+ return event.type === "keydown" && (event.key === "c" || event.key === "C") && event.ctrlKey === true && event.metaKey !== true && event.altKey !== true && event.shiftKey !== true;
1979
+ }
1980
+ function parseKeyboardProtocol(value) {
1981
+ const normalized = String(value ?? "").trim().toLowerCase();
1982
+ if (normalized === "auto" || normalized === "kitty" || normalized === "xterm" || normalized === "off") return normalized;
1983
+ return null;
1984
+ }
1985
+ function detectKeyboardProtocol(env) {
1986
+ const termProgram = String(env.TERM_PROGRAM ?? "").trim().toLowerCase();
1987
+ const term = String(env.TERM ?? "").trim().toLowerCase();
1988
+ const hasKittyProtocolTerminal = "GHOSTTY_RESOURCES_DIR" in env || "WEZTERM_PANE" in env || "KITTY_WINDOW_ID" in env || "KITTY_INSTALLATION_DIR" in env || "ALACRITTY_WINDOW_ID" in env || "ALACRITTY_LOG" in env || termProgram.includes("ghostty") || termProgram.includes("wezterm") || termProgram.includes("kitty") || termProgram.includes("alacritty");
1989
+ if (hasKittyProtocolTerminal) return "kitty";
1990
+ const isScreenLike = "TMUX" in env || term.includes("screen") || term.includes("tmux");
1991
+ const hasXtermKeyboardTerminal = termProgram.includes("iterm") || termProgram.includes("apple_terminal") || termProgram.includes("vscode") || term.includes("xterm") && !isScreenLike;
1992
+ return hasXtermKeyboardTerminal ? "xterm" : "off";
1993
+ }
1994
+ function resolveKeyboardProtocol(options) {
1995
+ var _options$env;
1996
+ const configured = parseKeyboardProtocol(options.keyboardProtocol) ?? "auto";
1997
+ if (configured !== "auto") return configured;
1998
+ const envOverride = parseKeyboardProtocol((_options$env = options.env) === null || _options$env === void 0 ? void 0 : _options$env.DIMCODE_KEYBOARD_PROTOCOL);
1999
+ if (envOverride && envOverride !== "auto") return envOverride;
2000
+ return detectKeyboardProtocol(options.env ?? {});
2001
+ }
2002
+ function getKeyboardProtocolSequences(protocol) {
2003
+ if (protocol === "kitty") return {
2004
+ enable: KITTY_KEYBOARD_PROTOCOL_ENABLE,
2005
+ disable: KITTY_KEYBOARD_PROTOCOL_DISABLE
2006
+ };
2007
+ if (protocol === "xterm") return {
2008
+ enable: XTERM_MODIFY_OTHER_KEYS_ENABLE,
2009
+ disable: XTERM_MODIFY_OTHER_KEYS_DISABLE
2010
+ };
2011
+ return null;
2012
+ }
1656
2013
  function createStdinDriver(options) {
1657
- const stdin = options.stdin ?? process.stdin;
1658
- const stdout = options.stdout ?? process.stdout;
2014
+ const stdin = options.stdin ?? process$1.stdin;
2015
+ const stdout = options.stdout ?? process$1.stdout;
1659
2016
  if (!stdin || !stdout) throw new Error("createStdinDriver requires Node process.stdin/stdout");
2017
+ const env = options.env ?? process$1.env ?? {};
1660
2018
  const enableMouse = options.enableMouse ?? true;
1661
2019
  const enableMouseMotion = options.enableMouseMotion ?? false;
2020
+ const keyboardProtocol = resolveKeyboardProtocol({
2021
+ keyboardProtocol: options.keyboardProtocol,
2022
+ env
2023
+ });
2024
+ const keyboardProtocolSequences = getKeyboardProtocolSequences(keyboardProtocol);
1662
2025
  let disposed = false;
1663
2026
  let swallowNextLF = false;
1664
2027
  let lastMouseDown = null;
@@ -1668,55 +2031,61 @@ function createStdinDriver(options) {
1668
2031
  });
1669
2032
  const stdinBuffer = new StdinBuffer({
1670
2033
  timeout: 50,
1671
- escTimeout: enableMouse ? 80 : 20
2034
+ escTimeout: enableMouse ? 8 : 6
1672
2035
  });
2036
+ const latency = getCliLatencyProfiler();
2037
+ const dispatchEvent = (event, parser) => {
2038
+ var _options$onExit;
2039
+ latency === null || latency === void 0 || latency.recordStdinDispatch(event, { parser });
2040
+ const prevented = Boolean(options.dispatch(event));
2041
+ if (isUnhandledCtrlC(event) && !prevented) (_options$onExit = options.onExit) === null || _options$onExit === void 0 || _options$onExit.call(options);
2042
+ return prevented;
2043
+ };
1673
2044
  const handlePlainChar = (ch) => {
1674
2045
  if (swallowNextLF) {
1675
2046
  swallowNextLF = false;
1676
2047
  if (ch === "\n") return;
1677
2048
  }
1678
2049
  if (ch === "") {
1679
- var _options$onExit;
1680
- const prevented = Boolean(options.dispatch(keyEvent("c", "", { ctrlKey: true })));
1681
- if (!prevented) (_options$onExit = options.onExit) === null || _options$onExit === void 0 || _options$onExit.call(options);
2050
+ dispatchEvent(keyEvent("c", "", { ctrlKey: true }), "plain");
1682
2051
  return;
1683
2052
  }
1684
2053
  if (ch === "\r") {
1685
2054
  swallowNextLF = true;
1686
- options.dispatch(keyEvent("Enter", "Enter"));
2055
+ dispatchEvent(keyEvent("Enter", "Enter"), "plain");
1687
2056
  return;
1688
2057
  }
1689
2058
  if (ch === "\n") {
1690
- options.dispatch({
2059
+ dispatchEvent({
1691
2060
  type: "input",
1692
2061
  data: "\n",
1693
2062
  inputType: "insertLineBreak",
1694
2063
  text: "\n"
1695
- });
2064
+ }, "plain");
1696
2065
  return;
1697
2066
  }
1698
2067
  const ctrlKey = ctrlKeyFromChar(ch);
1699
2068
  if (ctrlKey) {
1700
2069
  if (ctrlKey === "i") {
1701
- options.dispatch(keyEvent("Tab", "Tab"));
2070
+ dispatchEvent(keyEvent("Tab", "Tab"), "plain");
1702
2071
  return;
1703
2072
  }
1704
2073
  if (ctrlKey === "h") {
1705
- options.dispatch(keyEvent("Backspace", "Backspace"));
2074
+ dispatchEvent(keyEvent("Backspace", "Backspace"), "plain");
1706
2075
  return;
1707
2076
  }
1708
- options.dispatch(keyEvent(ctrlKey, "", { ctrlKey: true }));
2077
+ dispatchEvent(keyEvent(ctrlKey, "", { ctrlKey: true }), "plain");
1709
2078
  return;
1710
2079
  }
1711
2080
  if (ch === "") {
1712
- options.dispatch(keyEvent("Backspace", "Backspace"));
2081
+ dispatchEvent(keyEvent("Backspace", "Backspace"), "plain");
1713
2082
  return;
1714
2083
  }
1715
2084
  if (ch === " ") {
1716
- options.dispatch(keyEvent("Tab", "Tab"));
2085
+ dispatchEvent(keyEvent("Tab", "Tab"), "plain");
1717
2086
  return;
1718
2087
  }
1719
- if (isPrintable(ch)) options.dispatch(keyEvent(ch));
2088
+ if (isPrintable(ch)) dispatchEvent(keyEvent(ch), "plain");
1720
2089
  };
1721
2090
  const handleMouseEvent = (event) => {
1722
2091
  const ev = event;
@@ -1789,21 +2158,21 @@ function createStdinDriver(options) {
1789
2158
  }
1790
2159
  const kitty = parseKittySequence(sequence);
1791
2160
  if (kitty.handled) {
1792
- if (kitty.event) options.dispatch(kitty.event);
2161
+ if (kitty.event) dispatchEvent(kitty.event, "kitty");
1793
2162
  return;
1794
2163
  }
1795
2164
  const legacy = parseLegacySequence(sequence);
1796
2165
  if (legacy.handled) {
1797
- if (legacy.event) options.dispatch(legacy.event);
2166
+ if (legacy.event) dispatchEvent(legacy.event, "legacy");
1798
2167
  return;
1799
2168
  }
1800
2169
  if (sequence.startsWith("\x1B")) {
1801
2170
  if (sequence.length === 2 && isPrintable(sequence[1])) {
1802
- options.dispatch(keyEvent(sequence[1], "", { altKey: true }));
2171
+ dispatchEvent(keyEvent(sequence[1], "", { altKey: true }), "alt");
1803
2172
  return;
1804
2173
  }
1805
2174
  if (sequence === "\x1B") {
1806
- options.dispatch(keyEvent("Escape", "Escape"));
2175
+ dispatchEvent(keyEvent("Escape", "Escape"), "escape");
1807
2176
  return;
1808
2177
  }
1809
2178
  return;
@@ -1813,10 +2182,10 @@ function createStdinDriver(options) {
1813
2182
  stdinBuffer.on("data", handleSequence);
1814
2183
  stdinBuffer.on("paste", (data) => {
1815
2184
  const pastedText = normalizeNewlines(data);
1816
- if (pastedText) options.dispatch({
2185
+ if (pastedText) dispatchEvent({
1817
2186
  type: "paste",
1818
2187
  text: pastedText
1819
- });
2188
+ }, "paste");
1820
2189
  });
1821
2190
  const decodeBytes = (bytes) => {
1822
2191
  if (bytes.length === 1 && bytes[0] > 127) {
@@ -1829,6 +2198,7 @@ function createStdinDriver(options) {
1829
2198
  };
1830
2199
  const onData = (chunk) => {
1831
2200
  if (disposed) return;
2201
+ latency === null || latency === void 0 || latency.recordRawInput();
1832
2202
  if (typeof chunk === "string") {
1833
2203
  stdinBuffer.process(chunk);
1834
2204
  return;
@@ -1854,6 +2224,7 @@ function createStdinDriver(options) {
1854
2224
  if (stdout.isTTY) {
1855
2225
  stdout.write("\x1B[?2004h");
1856
2226
  stdout.write("\x1B[?1004h");
2227
+ if (keyboardProtocolSequences) stdout.write(keyboardProtocolSequences.enable);
1857
2228
  if (enableMouse) {
1858
2229
  stdout.write("\x1B[?1007h");
1859
2230
  if (enableMouseMotion) stdout.write("\x1B[?1000h\x1B[?1003h\x1B[?1006h");
@@ -1876,6 +2247,7 @@ function createStdinDriver(options) {
1876
2247
  if (stdout.isTTY) {
1877
2248
  stdout.write("\x1B[?2004l");
1878
2249
  stdout.write("\x1B[?1004l");
2250
+ if (keyboardProtocolSequences) stdout.write(keyboardProtocolSequences.disable);
1879
2251
  if (enableMouse) {
1880
2252
  stdout.write("\x1B[?1007l");
1881
2253
  if (enableMouseMotion) stdout.write("\x1B[?1000l\x1B[?1003l\x1B[?1006l");
@@ -2511,12 +2883,59 @@ function createGridBuffer(cols, rows) {
2511
2883
  cursorStyle: DEFAULT_STYLE,
2512
2884
  scrollback: [],
2513
2885
  scrollbackLimit: 1e3,
2514
- rowPool: []
2886
+ rowPool: [],
2887
+ soaFingerprints: null,
2888
+ fingerprintFn: null
2515
2889
  };
2516
2890
  }
2517
2891
  function clamp$7(n, min, max) {
2518
2892
  return Math.max(min, Math.min(max, n));
2519
2893
  }
2894
+ /**
2895
+ * Register a fingerprint function on the buffer. This enables SoA fingerprint
2896
+ * pre-computation: cell writes will update fingerprints inline, and the renderer
2897
+ * can read them via getRowFingerprints() instead of per-cell hash computation.
2898
+ */
2899
+ function setFingerprintFn(buffer, fn) {
2900
+ buffer.fingerprintFn = fn;
2901
+ const len = buffer.rows * buffer.cols;
2902
+ if (!len) {
2903
+ buffer.soaFingerprints = null;
2904
+ return;
2905
+ }
2906
+ buffer.soaFingerprints = new Uint32Array(len);
2907
+ for (let y = 0; y < buffer.rows; y++) {
2908
+ const row = getBufferRow(buffer, y);
2909
+ const physY = physicalRowIndex(buffer, y);
2910
+ const base = physY * buffer.cols;
2911
+ for (let x = 0; x < buffer.cols; x++) {
2912
+ const cell = row[x];
2913
+ buffer.soaFingerprints[base + x] = fn(cell.ch, cell.style);
2914
+ }
2915
+ }
2916
+ }
2917
+ /**
2918
+ * Get pre-computed fingerprints for a logical row as a TypedArray slice.
2919
+ * Returns null if fingerprinting is not enabled on this buffer.
2920
+ */
2921
+ function getRowFingerprints(buffer, y) {
2922
+ if (!buffer.soaFingerprints) return null;
2923
+ const physY = physicalRowIndex(buffer, y);
2924
+ const base = physY * buffer.cols;
2925
+ return buffer.soaFingerprints.subarray(base, base + buffer.cols);
2926
+ }
2927
+ /** Update the SoA fingerprint for a single cell (called inline from putCell/fillRect). */
2928
+ function updateCellFingerprint(buffer, physY, x, ch, style) {
2929
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn) return;
2930
+ buffer.soaFingerprints[physY * buffer.cols + x] = buffer.fingerprintFn(ch, style);
2931
+ }
2932
+ /** Update SoA fingerprints for a range of cells in a physical row. */
2933
+ function updateRangeFingerprint(buffer, physY, x0, x1, ch, style) {
2934
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn) return;
2935
+ const fp = buffer.fingerprintFn(ch, style);
2936
+ const base = physY * buffer.cols;
2937
+ for (let x = x0; x < x1; x++) buffer.soaFingerprints[base + x] = fp;
2938
+ }
2520
2939
  function physicalRowIndex(buffer, y) {
2521
2940
  return (buffer.gridStart + y) % buffer.rows;
2522
2941
  }
@@ -2575,8 +2994,14 @@ function putCell(buffer, x, y, ch, style) {
2575
2994
  markDirty(buffer, y);
2576
2995
  return;
2577
2996
  }
2578
- row[x] = createCell(ch, style);
2997
+ const cell = createCell(ch, style);
2998
+ row[x] = cell;
2579
2999
  if (width === 2 && x + 1 < buffer.cols) row[x + 1] = createContinuationCell(style);
3000
+ if (buffer.soaFingerprints) {
3001
+ const physY = physicalRowIndex(buffer, y);
3002
+ updateCellFingerprint(buffer, physY, x, cell.ch, cell.style);
3003
+ if (width === 2 && x + 1 < buffer.cols) updateCellFingerprint(buffer, physY, x + 1, "", cell.style);
3004
+ }
2580
3005
  markDirty(buffer, y);
2581
3006
  }
2582
3007
  function fillRect(buffer, x, y, w, h$1, ch = " ", style) {
@@ -2596,6 +3021,10 @@ function fillRect(buffer, x, y, w, h$1, ch = " ", style) {
2596
3021
  if ((_row$x = row[x0]) === null || _row$x === void 0 ? void 0 : _row$x.continuation) clearWideIfOverwriting(row, x0);
2597
3022
  row.fill(fillCell, x0, x1);
2598
3023
  if (x1 < buffer.cols && ((_row$x2 = row[x1]) === null || _row$x2 === void 0 ? void 0 : _row$x2.continuation)) row[x1] = blank;
3024
+ if (buffer.soaFingerprints) {
3025
+ const physY = physicalRowIndex(buffer, yy);
3026
+ updateRangeFingerprint(buffer, physY, x0, x1, fillCell.ch, fillCell.style);
3027
+ }
2599
3028
  markDirty(buffer, yy);
2600
3029
  }
2601
3030
  return;
@@ -2654,6 +3083,18 @@ function blankRow(buffer) {
2654
3083
  for (let x = 0; x < buffer.cols; x++) pooled[x] = blank;
2655
3084
  return pooled;
2656
3085
  }
3086
+ function recomputeFingerprintsForRows(buffer, startY, endY) {
3087
+ if (!buffer.soaFingerprints || !buffer.fingerprintFn) return;
3088
+ for (let y = startY; y < endY; y++) {
3089
+ const row = getBufferRow(buffer, y);
3090
+ const physY = physicalRowIndex(buffer, y);
3091
+ const base = physY * buffer.cols;
3092
+ for (let x = 0; x < buffer.cols; x++) {
3093
+ const cell = row[x];
3094
+ buffer.soaFingerprints[base + x] = buffer.fingerprintFn(cell.ch, cell.style);
3095
+ }
3096
+ }
3097
+ }
2657
3098
  function scrollBuffer(buffer, lines) {
2658
3099
  const n = Math.trunc(lines);
2659
3100
  if (n === 0 || buffer.rows === 0) return;
@@ -2682,6 +3123,63 @@ function scrollBuffer(buffer, lines) {
2682
3123
  markAllDirty(buffer);
2683
3124
  buffer.cursorY = clamp$7(buffer.cursorY - n, 0, Math.max(0, buffer.rows - 1));
2684
3125
  }
3126
+ function scrollBufferRegion(buffer, startY, endY, lines) {
3127
+ const n = Math.trunc(lines);
3128
+ if (n === 0 || buffer.rows === 0) return [];
3129
+ const start = clamp$7(Math.floor(startY), 0, buffer.rows);
3130
+ const end = clamp$7(Math.floor(endY), 0, buffer.rows);
3131
+ const height = end - start;
3132
+ if (height <= 0) return [];
3133
+ const absDelta = Math.abs(n);
3134
+ const insertedRows = [];
3135
+ const replaceRegionWithBlankRows = () => {
3136
+ for (let y = start; y < end; y++) {
3137
+ const physY = physicalRowIndex(buffer, y);
3138
+ buffer.rowPool.push(buffer.grid[physY]);
3139
+ buffer.grid[physY] = blankRow(buffer);
3140
+ markDirty(buffer, y);
3141
+ insertedRows.push(y);
3142
+ }
3143
+ };
3144
+ if (absDelta >= height) {
3145
+ replaceRegionWithBlankRows();
3146
+ recomputeFingerprintsForRows(buffer, start, end);
3147
+ if (buffer.cursorY >= start && buffer.cursorY < end) buffer.cursorY = clamp$7(buffer.cursorY - n, start, Math.max(start, end - 1));
3148
+ return insertedRows;
3149
+ }
3150
+ const regionRows = [];
3151
+ for (let y = start; y < end; y++) regionRows.push(getBufferRow(buffer, y));
3152
+ const nextRows = Array.from({ length: height }, () => []);
3153
+ if (n > 0) {
3154
+ const movedCount = height - n;
3155
+ for (let i = 0; i < movedCount; i++) nextRows[i] = regionRows[i + n];
3156
+ for (let i = 0; i < n; i++) {
3157
+ buffer.rowPool.push(regionRows[i]);
3158
+ const y = end - n + i;
3159
+ nextRows[movedCount + i] = blankRow(buffer);
3160
+ markDirty(buffer, y);
3161
+ insertedRows.push(y);
3162
+ }
3163
+ } else {
3164
+ const insertedCount = -n;
3165
+ const movedCount = height - insertedCount;
3166
+ for (let i = 0; i < insertedCount; i++) {
3167
+ buffer.rowPool.push(regionRows[height - insertedCount + i]);
3168
+ const y = start + i;
3169
+ nextRows[i] = blankRow(buffer);
3170
+ markDirty(buffer, y);
3171
+ insertedRows.push(y);
3172
+ }
3173
+ for (let i = 0; i < movedCount; i++) nextRows[insertedCount + i] = regionRows[i];
3174
+ }
3175
+ for (let i = 0; i < height; i++) {
3176
+ const physY = physicalRowIndex(buffer, start + i);
3177
+ buffer.grid[physY] = nextRows[i];
3178
+ }
3179
+ recomputeFingerprintsForRows(buffer, start, end);
3180
+ if (buffer.cursorY >= start && buffer.cursorY < end) buffer.cursorY = clamp$7(buffer.cursorY - n, start, Math.max(start, end - 1));
3181
+ return insertedRows;
3182
+ }
2685
3183
  function resizeBuffer(buffer, cols, rows) {
2686
3184
  const nextCols = Math.max(0, Math.floor(cols));
2687
3185
  const nextRows = Math.max(0, Math.floor(rows));
@@ -2712,6 +3210,20 @@ function resizeBuffer(buffer, cols, rows) {
2712
3210
  buffer.dirtyAll = nextRows > 0;
2713
3211
  buffer.cursorX = clamp$7(buffer.cursorX, 0, Math.max(0, nextCols));
2714
3212
  buffer.cursorY = clamp$7(buffer.cursorY, 0, Math.max(0, nextRows - 1));
3213
+ if (buffer.fingerprintFn) {
3214
+ const len = nextRows * nextCols;
3215
+ if (len > 0) {
3216
+ buffer.soaFingerprints = new Uint32Array(len);
3217
+ for (let y = 0; y < nextRows; y++) {
3218
+ const row = nextGrid[y];
3219
+ const base = y * nextCols;
3220
+ for (let x = 0; x < nextCols; x++) {
3221
+ const cell = row[x];
3222
+ buffer.soaFingerprints[base + x] = buffer.fingerprintFn(cell.ch, cell.style);
3223
+ }
3224
+ }
3225
+ } else buffer.soaFingerprints = null;
3226
+ }
2715
3227
  }
2716
3228
  function snapshotText(buffer) {
2717
3229
  const out = [];
@@ -2874,6 +3386,32 @@ function scrollPlaneCoverage(state, lines) {
2874
3386
  state.coverage.splice(rows + n, -n);
2875
3387
  for (let i = 0; i < -n; i++) state.coverage.unshift(new Uint8Array(cols));
2876
3388
  }
3389
+ function scrollPlaneCoverageRegion(state, startY, endY, lines) {
3390
+ const n = Math.trunc(lines);
3391
+ if (n === 0 || state.coverage.length === 0) return;
3392
+ const start = Math.max(0, Math.min(state.coverage.length, Math.floor(startY)));
3393
+ const end = Math.max(0, Math.min(state.coverage.length, Math.floor(endY)));
3394
+ const height = end - start;
3395
+ if (height <= 0) return;
3396
+ const cols = state.buffer.cols;
3397
+ if (Math.abs(n) >= height) {
3398
+ for (let y = start; y < end; y++) state.coverage[y] = new Uint8Array(cols);
3399
+ return;
3400
+ }
3401
+ const region = state.coverage.slice(start, end);
3402
+ const next = Array.from({ length: height }, () => new Uint8Array(0));
3403
+ if (n > 0) {
3404
+ const movedCount = height - n;
3405
+ for (let i = 0; i < movedCount; i++) next[i] = region[i + n];
3406
+ for (let i = 0; i < n; i++) next[movedCount + i] = new Uint8Array(cols);
3407
+ } else {
3408
+ const insertedCount = -n;
3409
+ const movedCount = height - insertedCount;
3410
+ for (let i = 0; i < insertedCount; i++) next[i] = new Uint8Array(cols);
3411
+ for (let i = 0; i < movedCount; i++) next[insertedCount + i] = region[i];
3412
+ }
3413
+ for (let i = 0; i < height; i++) state.coverage[start + i] = next[i];
3414
+ }
2877
3415
  function resizePlaneState(state, cols, rows) {
2878
3416
  resizeBuffer(state.buffer, cols, rows);
2879
3417
  state.coverage = resizeCoverage(state.coverage, state.buffer.cols, state.buffer.rows);
@@ -2902,6 +3440,14 @@ function resetPlaneRowsForRender(terminal, plane, dirtyRows) {
2902
3440
  const internals = terminal[TERMINAL_PLANE_INTERNALS];
2903
3441
  internals === null || internals === void 0 || internals.resetRowsForRender(plane, dirtyRows);
2904
3442
  }
3443
+ function getPlaneRowCoverageKind(terminal, plane, y) {
3444
+ const internals = terminal[TERMINAL_PLANE_INTERNALS];
3445
+ return (internals === null || internals === void 0 ? void 0 : internals.getRowCoverageKind(plane, y)) ?? 0;
3446
+ }
3447
+ function scrollPlaneRows(terminal, plane, startY, endY, lines) {
3448
+ const internals = terminal[TERMINAL_PLANE_INTERNALS];
3449
+ internals === null || internals === void 0 || internals.scrollRows(plane, startY, endY, lines);
3450
+ }
2905
3451
  function createTerminal(opts) {
2906
3452
  const emitter = new Emitter();
2907
3453
  const compositeBuffer = createGridBuffer(opts.cols, opts.rows);
@@ -2911,6 +3457,7 @@ function createTerminal(opts) {
2911
3457
  let pendingCommit = false;
2912
3458
  let pendingCommitAllPlanes = false;
2913
3459
  let pendingCommitPlanes = null;
3460
+ const pendingPlaneScrollOps = new Map();
2914
3461
  const planeTerminals = new Map();
2915
3462
  let base;
2916
3463
  compositeBuffer.cursorVisible = false;
@@ -3031,8 +3578,145 @@ function createTerminal(opts) {
3031
3578
  scrollBuffer(state.buffer, lines);
3032
3579
  scrollPlaneCoverage(state, lines);
3033
3580
  }
3581
+ function normalizeScrollOperation(startY, endY, delta, totalRows) {
3582
+ const start = Math.max(0, Math.min(totalRows, Math.floor(startY)));
3583
+ const end = Math.max(0, Math.min(totalRows, Math.floor(endY)));
3584
+ const height = end - start;
3585
+ const lines = Math.trunc(delta);
3586
+ if (height <= 0 || lines === 0 || Math.abs(lines) >= height) return null;
3587
+ return {
3588
+ startY: start,
3589
+ endY: end,
3590
+ delta: lines
3591
+ };
3592
+ }
3593
+ function recordPendingScrollOp(plane, op) {
3594
+ const prev = pendingPlaneScrollOps.get(plane);
3595
+ if (prev && prev.startY === op.startY && prev.endY === op.endY) {
3596
+ const nextDelta = prev.delta + op.delta;
3597
+ if (nextDelta === 0) {
3598
+ pendingPlaneScrollOps.delete(plane);
3599
+ return;
3600
+ }
3601
+ const next = normalizeScrollOperation(op.startY, op.endY, nextDelta, compositeBuffer.rows);
3602
+ if (!next) {
3603
+ pendingPlaneScrollOps.delete(plane);
3604
+ return;
3605
+ }
3606
+ pendingPlaneScrollOps.set(plane, next);
3607
+ return;
3608
+ }
3609
+ pendingPlaneScrollOps.set(plane, op);
3610
+ }
3611
+ function takePendingScrollOps(planes) {
3612
+ const planesToTake = (planes === null || planes === void 0 ? void 0 : planes.length) ? planes : TERMINAL_RENDER_PLANES;
3613
+ const out = [];
3614
+ for (const plane of planesToTake) {
3615
+ const op = pendingPlaneScrollOps.get(plane);
3616
+ if (!op) continue;
3617
+ pendingPlaneScrollOps.delete(plane);
3618
+ out.push({
3619
+ plane,
3620
+ ...op
3621
+ });
3622
+ }
3623
+ return out.length ? out : null;
3624
+ }
3625
+ function higherPlanesFor(plane) {
3626
+ const planeIndex = TERMINAL_RENDER_PLANES.indexOf(plane);
3627
+ if (planeIndex < 0) return [];
3628
+ return TERMINAL_RENDER_PLANES.slice(planeIndex + 1);
3629
+ }
3630
+ function higherPlaneCoverageKind(plane, y) {
3631
+ const higherPlanes = higherPlanesFor(plane);
3632
+ if (!higherPlanes.length || y < 0 || y >= compositeBuffer.rows) return 0;
3633
+ let covered = 0;
3634
+ for (let x = 0; x < compositeBuffer.cols; x++) {
3635
+ let cellCovered = false;
3636
+ for (const higherPlane of higherPlanes) {
3637
+ var _state$coverage$y2;
3638
+ const state = planeStates.get(higherPlane);
3639
+ if (state === null || state === void 0 || (_state$coverage$y2 = state.coverage[y]) === null || _state$coverage$y2 === void 0 ? void 0 : _state$coverage$y2[x]) {
3640
+ cellCovered = true;
3641
+ break;
3642
+ }
3643
+ }
3644
+ if (cellCovered) covered++;
3645
+ }
3646
+ if (covered === 0) return 0;
3647
+ if (covered >= compositeBuffer.cols) return 2;
3648
+ return 1;
3649
+ }
3650
+ function prepareCompositeScrollOps(pendingOps) {
3651
+ if (!(pendingOps === null || pendingOps === void 0 ? void 0 : pendingOps.length)) return {
3652
+ scrollOperations: null,
3653
+ extraDirtyRows: []
3654
+ };
3655
+ const scrollOperations = [];
3656
+ const extraDirtyRows = new Set();
3657
+ for (const pending of pendingOps) {
3658
+ const blockedRows = new Set();
3659
+ const partiallyBlockedRows = new Set();
3660
+ for (let y = pending.startY; y < pending.endY; y++) {
3661
+ const coverageKind = higherPlaneCoverageKind(pending.plane, y);
3662
+ if (coverageKind === 0) continue;
3663
+ blockedRows.add(y);
3664
+ if (coverageKind === 1) partiallyBlockedRows.add(y);
3665
+ }
3666
+ if (!blockedRows.size) {
3667
+ scrollOperations.push({
3668
+ startY: pending.startY,
3669
+ endY: pending.endY,
3670
+ delta: pending.delta
3671
+ });
3672
+ continue;
3673
+ }
3674
+ const absDelta = Math.abs(pending.delta);
3675
+ for (let y = pending.startY; y < pending.endY; y++) if (partiallyBlockedRows.has(y)) extraDirtyRows.add(y);
3676
+ let bandStart = -1;
3677
+ const flushBand = (bandEnd) => {
3678
+ if (bandStart < 0 || bandEnd <= bandStart) return;
3679
+ const bandHeight = bandEnd - bandStart;
3680
+ if (absDelta >= bandHeight) {
3681
+ for (let y = bandStart; y < bandEnd; y++) extraDirtyRows.add(y);
3682
+ bandStart = -1;
3683
+ return;
3684
+ }
3685
+ scrollOperations.push({
3686
+ startY: bandStart,
3687
+ endY: bandEnd,
3688
+ delta: pending.delta
3689
+ });
3690
+ if (pending.delta > 0) for (let y = bandEnd - absDelta; y < bandEnd; y++) extraDirtyRows.add(y);
3691
+ else for (let y = bandStart; y < bandStart + absDelta; y++) extraDirtyRows.add(y);
3692
+ bandStart = -1;
3693
+ };
3694
+ for (let y = pending.startY; y < pending.endY; y++) {
3695
+ if (blockedRows.has(y)) {
3696
+ flushBand(y);
3697
+ continue;
3698
+ }
3699
+ if (bandStart < 0) bandStart = y;
3700
+ }
3701
+ flushBand(pending.endY);
3702
+ }
3703
+ return {
3704
+ scrollOperations: scrollOperations.length ? scrollOperations : null,
3705
+ extraDirtyRows: Array.from(extraDirtyRows).sort((a, b) => a - b)
3706
+ };
3707
+ }
3708
+ function scrollRowsForPlane(plane, startY, endY, lines) {
3709
+ const state = getPlaneState(plane);
3710
+ const op = normalizeScrollOperation(startY, endY, lines, state.buffer.rows);
3711
+ if (!op) return;
3712
+ scrollBufferRegion(state.buffer, op.startY, op.endY, op.delta);
3713
+ scrollPlaneCoverageRegion(state, op.startY, op.endY, op.delta);
3714
+ recordPendingScrollOp(plane, op);
3715
+ }
3034
3716
  function composeRows(rows) {
3035
3717
  const blank = createBlankCell();
3718
+ const fpFn = compositeBuffer.fingerprintFn;
3719
+ const fpArr = compositeBuffer.soaFingerprints;
3036
3720
  const rowList = rows ?? Array.from({ length: compositeBuffer.rows }, (_, index) => index);
3037
3721
  for (const y of rowList) {
3038
3722
  if (y < 0 || y >= compositeBuffer.rows) continue;
@@ -3056,6 +3740,14 @@ function createTerminal(opts) {
3056
3740
  if (x === 0 || dst[x - 1].width !== 2 || dst[x - 1].continuation) dst[x] = blank;
3057
3741
  }
3058
3742
  }
3743
+ if (fpFn && fpArr) {
3744
+ const physY = (compositeBuffer.gridStart + y) % compositeBuffer.rows;
3745
+ const base$1 = physY * cols;
3746
+ for (let x = 0; x < cols; x++) {
3747
+ const cell = dst[x];
3748
+ fpArr[base$1 + x] = fpFn(cell.ch, cell.style);
3749
+ }
3750
+ }
3059
3751
  markDirty(compositeBuffer, y);
3060
3752
  }
3061
3753
  }
@@ -3228,6 +3920,14 @@ function createTerminal(opts) {
3228
3920
  const all = state.buffer.scrollback.map((row) => row.map((cell) => cell.continuation ? " " : cell.ch || " ").join(""));
3229
3921
  if (count == null) return all;
3230
3922
  return all.slice(Math.max(0, all.length - Math.max(0, Math.floor(count))));
3923
+ },
3924
+ setFingerprintFn(fn) {
3925
+ assertNotDisposed();
3926
+ base.setFingerprintFn(fn);
3927
+ },
3928
+ getRowFingerprints(y) {
3929
+ assertNotDisposed();
3930
+ return base.getRowFingerprints(y);
3231
3931
  }
3232
3932
  };
3233
3933
  planeTerminals.set(plane, api);
@@ -3302,6 +4002,9 @@ function createTerminal(opts) {
3302
4002
  return pendingDirtyRowsForPlanes((meta === null || meta === void 0 ? void 0 : meta.planes) ?? null);
3303
4003
  }
3304
4004
  const planesToCompose = (meta === null || meta === void 0 || (_meta$planes2 = meta.planes) === null || _meta$planes2 === void 0 ? void 0 : _meta$planes2.length) ? meta.planes : TERMINAL_RENDER_PLANES;
4005
+ const pendingScrollOps = takePendingScrollOps(planesToCompose);
4006
+ const preparedScroll = prepareCompositeScrollOps(pendingScrollOps);
4007
+ const scrollOperations = preparedScroll.scrollOperations;
3305
4008
  let composeAllRows = false;
3306
4009
  const dirtyRows = new Set();
3307
4010
  for (const plane of planesToCompose) {
@@ -3314,13 +4017,17 @@ function createTerminal(opts) {
3314
4017
  }
3315
4018
  for (const y of rows) dirtyRows.add(y);
3316
4019
  }
4020
+ for (const y of preparedScroll.extraDirtyRows) dirtyRows.add(y);
3317
4021
  const normalizedDirtyRows = composeAllRows ? null : Array.from(dirtyRows).sort((a, b) => a - b);
4022
+ if (scrollOperations) for (const op of scrollOperations) scrollBufferRegion(compositeBuffer, op.startY, op.endY, op.delta);
3318
4023
  if (normalizedDirtyRows !== null && normalizedDirtyRows.length === 0) {
3319
4024
  const pendingCompositeRows = collectAndClearDirtyRows(compositeBuffer);
3320
4025
  if (pendingCompositeRows !== null && pendingCompositeRows.length === 0) return pendingCompositeRows;
3321
4026
  emitter.emit("commit", {
3322
4027
  dirtyRows: pendingCompositeRows,
3323
- planes: (meta === null || meta === void 0 ? void 0 : meta.planes) ?? null
4028
+ planes: (meta === null || meta === void 0 ? void 0 : meta.planes) ?? null,
4029
+ sync: meta === null || meta === void 0 ? void 0 : meta.sync,
4030
+ scrollOperations
3324
4031
  });
3325
4032
  return pendingCompositeRows;
3326
4033
  }
@@ -3331,7 +4038,9 @@ function createTerminal(opts) {
3331
4038
  if (committedRows !== null && committedRows.length === 0) return committedRows;
3332
4039
  emitter.emit("commit", {
3333
4040
  dirtyRows: committedRows,
3334
- planes: (meta === null || meta === void 0 ? void 0 : meta.planes) ?? null
4041
+ planes: (meta === null || meta === void 0 ? void 0 : meta.planes) ?? null,
4042
+ sync: meta === null || meta === void 0 ? void 0 : meta.sync,
4043
+ scrollOperations
3335
4044
  });
3336
4045
  return committedRows;
3337
4046
  },
@@ -3386,6 +4095,14 @@ function createTerminal(opts) {
3386
4095
  const all = state.buffer.scrollback.map((row) => row.map((cell) => cell.continuation ? " " : cell.ch || " ").join(""));
3387
4096
  if (count == null) return all;
3388
4097
  return all.slice(Math.max(0, all.length - Math.max(0, Math.floor(count))));
4098
+ },
4099
+ setFingerprintFn(fn) {
4100
+ assertNotDisposed();
4101
+ setFingerprintFn(compositeBuffer, fn);
4102
+ },
4103
+ getRowFingerprints(y) {
4104
+ assertNotDisposed();
4105
+ return getRowFingerprints(compositeBuffer, y);
3389
4106
  }
3390
4107
  };
3391
4108
  base[TERMINAL_PLANE_INTERNALS] = {
@@ -3398,6 +4115,20 @@ function createTerminal(opts) {
3398
4115
  return;
3399
4116
  }
3400
4117
  for (const y of rows) clearPlaneCoverageRow(state, y);
4118
+ },
4119
+ getRowCoverageKind(plane, y) {
4120
+ const state = getPlaneState(plane);
4121
+ if (y < 0 || y >= state.coverage.length) return 0;
4122
+ const row = state.coverage[y];
4123
+ if (!(row === null || row === void 0 ? void 0 : row.length)) return 0;
4124
+ let covered = 0;
4125
+ for (let x = 0; x < row.length; x++) if (row[x]) covered++;
4126
+ if (covered === 0) return 0;
4127
+ if (covered >= row.length) return 2;
4128
+ return 1;
4129
+ },
4130
+ scrollRows(plane, startY, endY, lines) {
4131
+ scrollRowsForPlane(plane, startY, endY, lines);
3401
4132
  }
3402
4133
  };
3403
4134
  return base;
@@ -3542,6 +4273,7 @@ function createCliEventManager(options) {
3542
4273
  let hoverPath = [];
3543
4274
  const record = options === null || options === void 0 ? void 0 : options.record;
3544
4275
  const onFocusChange = options === null || options === void 0 ? void 0 : options.onFocusChange;
4276
+ const latency = getCliLatencyProfiler();
3545
4277
  const MODAL_FOCUS_Z_THRESHOLD = 100;
3546
4278
  const MODAL_ROOT_MIN_AREA = 40;
3547
4279
  function focusLockTarget() {
@@ -3719,7 +4451,7 @@ function createCliEventManager(options) {
3719
4451
  handler(event);
3720
4452
  } catch (err) {
3721
4453
  try {
3722
- const env = process === null || process === void 0 ? void 0 : process.env;
4454
+ const env = process$1 === null || process$1 === void 0 ? void 0 : process$1.env;
3723
4455
  if ((env === null || env === void 0 ? void 0 : env.DIMCODE_DEBUG) === "1") {
3724
4456
  const timestamp = new Date().toISOString().split("T")[1].slice(0, -1);
3725
4457
  appendFileSync("/tmp/goatchain-debug.log", `[${timestamp}] [EVENT-MGR] ERROR in handler ${node.id}.${handlerKey}: ${err}\n`);
@@ -3921,51 +4653,65 @@ function createCliEventManager(options) {
3921
4653
  },
3922
4654
  dispatch(event) {
3923
4655
  record === null || record === void 0 || record(event);
3924
- if (event.type === "keydown" || event.type === "keyup") return dispatchToFocused(event.type, event);
3925
- if (event.type === "pointerdown" || event.type === "pointermove" || event.type === "pointerup" || event.type === "click" || event.type === "dblclick" || event.type === "contextmenu" || event.type === "wheel") {
3926
- if (event.type === "pointerdown") {
3927
- const list = candidatesAt(event.cellX, event.cellY);
3928
- const target = pickTarget(list);
3929
- if (target === null || target === void 0 ? void 0 : target.focusable) setFocus(target.id);
3930
- capturedId = (target === null || target === void 0 ? void 0 : target.id) ?? null;
3931
- updateHover(target, event);
3932
- dispatchPointerEvent("pointerdown", event, target);
3933
- return false;
3934
- }
3935
- if (event.type === "pointermove" && capturedId) {
3936
- const target = nodes.get(capturedId) ?? null;
3937
- if (!target) return false;
3938
- updateHover(target, event);
3939
- const path = ancestorsForTarget(target);
3940
- const ev = buildPointerEvent("pointermove", path, event);
3941
- dispatchWithPhases("pointermove", path, target, ev);
3942
- return ev.defaultPrevented;
3943
- }
3944
- if (event.type === "pointermove") {
3945
- const list = candidatesAt(event.cellX, event.cellY);
3946
- const target = pickTarget(list);
3947
- updateHover(target, event);
3948
- dispatchPointerEvent("pointermove", event, target);
3949
- return false;
4656
+ latency === null || latency === void 0 || latency.recordEventDispatchStart(event);
4657
+ let prevented = false;
4658
+ try {
4659
+ if (event.type === "keydown" || event.type === "keyup") {
4660
+ prevented = dispatchToFocused(event.type, event);
4661
+ return prevented;
3950
4662
  }
3951
- if (event.type === "pointerup" && capturedId) {
3952
- const target = nodes.get(capturedId) ?? null;
3953
- const path = target ? ancestorsForTarget(target) : [];
3954
- const ev = buildPointerEvent("pointerup", path, event);
3955
- dispatchWithPhases("pointerup", path, target, ev);
3956
- capturedId = null;
3957
- return ev.defaultPrevented;
4663
+ if (event.type === "pointerdown" || event.type === "pointermove" || event.type === "pointerup" || event.type === "click" || event.type === "dblclick" || event.type === "contextmenu" || event.type === "wheel") {
4664
+ if (event.type === "pointerdown") {
4665
+ const list = candidatesAt(event.cellX, event.cellY);
4666
+ const target = pickTarget(list);
4667
+ if (target === null || target === void 0 ? void 0 : target.focusable) setFocus(target.id);
4668
+ capturedId = (target === null || target === void 0 ? void 0 : target.id) ?? null;
4669
+ updateHover(target, event);
4670
+ dispatchPointerEvent("pointerdown", event, target);
4671
+ return prevented;
4672
+ }
4673
+ if (event.type === "pointermove" && capturedId) {
4674
+ const target = nodes.get(capturedId) ?? null;
4675
+ if (!target) return prevented;
4676
+ updateHover(target, event);
4677
+ const path = ancestorsForTarget(target);
4678
+ const ev = buildPointerEvent("pointermove", path, event);
4679
+ dispatchWithPhases("pointermove", path, target, ev);
4680
+ prevented = ev.defaultPrevented;
4681
+ return prevented;
4682
+ }
4683
+ if (event.type === "pointermove") {
4684
+ const list = candidatesAt(event.cellX, event.cellY);
4685
+ const target = pickTarget(list);
4686
+ updateHover(target, event);
4687
+ dispatchPointerEvent("pointermove", event, target);
4688
+ return prevented;
4689
+ }
4690
+ if (event.type === "pointerup" && capturedId) {
4691
+ const target = nodes.get(capturedId) ?? null;
4692
+ const path = target ? ancestorsForTarget(target) : [];
4693
+ const ev = buildPointerEvent("pointerup", path, event);
4694
+ dispatchWithPhases("pointerup", path, target, ev);
4695
+ capturedId = null;
4696
+ prevented = ev.defaultPrevented;
4697
+ return prevented;
4698
+ }
4699
+ if (event.type === "pointerup") {
4700
+ dispatchPointerEvent("pointerup", event);
4701
+ capturedId = null;
4702
+ return prevented;
4703
+ }
4704
+ dispatchPointerEvent(event.type, event);
4705
+ return prevented;
3958
4706
  }
3959
- if (event.type === "pointerup") {
3960
- dispatchPointerEvent("pointerup", event);
3961
- capturedId = null;
3962
- return false;
4707
+ if (event.type === "beforeinput" || event.type === "input" || event.type === "compositionstart" || event.type === "compositionupdate" || event.type === "compositionend" || event.type === "paste") {
4708
+ prevented = dispatchToFocusedText(event.type, event);
4709
+ return prevented;
3963
4710
  }
3964
- dispatchPointerEvent(event.type, event);
3965
- return false;
4711
+ return prevented;
4712
+ } finally {
4713
+ latency === null || latency === void 0 || latency.recordEventDispatchEnd(event, { defaultPrevented: prevented });
3966
4714
  }
3967
- if (event.type === "beforeinput" || event.type === "input" || event.type === "compositionstart" || event.type === "compositionupdate" || event.type === "compositionend" || event.type === "paste") return dispatchToFocusedText(event.type, event);
3968
- return false;
3969
4715
  },
3970
4716
  debugNodes() {
3971
4717
  return Array.from(nodes.values()).map((n) => ({
@@ -5057,7 +5803,7 @@ function formatClockTime(ms) {
5057
5803
  }
5058
5804
  function createTuiProfiler(name) {
5059
5805
  var _unref;
5060
- const env = process === null || process === void 0 ? void 0 : process.env;
5806
+ const env = process$1 === null || process$1 === void 0 ? void 0 : process$1.env;
5061
5807
  if (!parseEnabled(env === null || env === void 0 ? void 0 : env.DIMCODE_PROFILE_TUI)) return null;
5062
5808
  const format = parseLogFormat(env === null || env === void 0 ? void 0 : env.DIMCODE_PROFILE_TUI_FORMAT);
5063
5809
  const logDest = parseLogDest(env === null || env === void 0 ? void 0 : env.DIMCODE_PROFILE_TUI_LOG_DEST);
@@ -5080,8 +5826,8 @@ function createTuiProfiler(name) {
5080
5826
  const invalidatePlaneCounts = new Map();
5081
5827
  const renderPlaneCounts = new Map();
5082
5828
  const logEveryMs = Number((env === null || env === void 0 ? void 0 : env.DIMCODE_PROFILE_TUI_LOG_EVERY_MS) ?? 1e3);
5083
- const now$2 = defaultNow;
5084
- let lastLogAt = now$2();
5829
+ const now$3 = defaultNow;
5830
+ let lastLogAt = now$3();
5085
5831
  function incrementPlaneCount(target, key) {
5086
5832
  const normalized = String(key ?? "all").trim() || "all";
5087
5833
  target.set(normalized, (target.get(normalized) ?? 0) + 1);
@@ -5094,7 +5840,7 @@ function createTuiProfiler(name) {
5094
5840
  if (dest === "stdout" || dest === "both") console.log(line);
5095
5841
  }
5096
5842
  function flushLog() {
5097
- const at = now$2();
5843
+ const at = now$3();
5098
5844
  const elapsed = at - lastLogAt;
5099
5845
  if (elapsed <= 0) return;
5100
5846
  lastLogAt = at;
@@ -5165,7 +5911,7 @@ function createTuiProfiler(name) {
5165
5911
  const timer = setInterval(flushLog, Number.isFinite(logEveryMs) ? Math.max(100, logEveryMs) : 1e3);
5166
5912
  (_unref = timer.unref) === null || _unref === void 0 || _unref.call(timer);
5167
5913
  return {
5168
- now: now$2,
5914
+ now: now$3,
5169
5915
  recordInvalidate(info) {
5170
5916
  invalidates++;
5171
5917
  incrementPlaneCount(invalidatePlaneCounts, (info === null || info === void 0 ? void 0 : info.plane) ?? "all");
@@ -5536,7 +6282,7 @@ function write(data) {
5536
6282
  * Check if debug logging is enabled via environment variable
5537
6283
  */
5538
6284
  function isDebugEnabled() {
5539
- const env = process === null || process === void 0 ? void 0 : process.env;
6285
+ const env = process$1 === null || process$1 === void 0 ? void 0 : process$1.env;
5540
6286
  return (env === null || env === void 0 ? void 0 : env.DIMCODE_DEBUG) === "1" || (env === null || env === void 0 ? void 0 : env.DEBUG) === "1";
5541
6287
  }
5542
6288
 
@@ -6012,6 +6758,26 @@ function createRenderManager(terminal) {
6012
6758
  if (y > state.dirtyMaxY) state.dirtyMaxY = y;
6013
6759
  }
6014
6760
  }
6761
+ function markRows(plane, rows) {
6762
+ if (!rows.length) return;
6763
+ const state = getDirtyState(plane);
6764
+ for (let i = 0; i < rows.length; i++) {
6765
+ const y = Math.floor(rows[i] ?? -1);
6766
+ if (y < 0 || y >= terminalRows) continue;
6767
+ if (state.dirtyRowBits[y] === 0) {
6768
+ state.dirtyRowBits[y] = 1;
6769
+ state.dirtyRowCount++;
6770
+ if (y < state.dirtyMinY) state.dirtyMinY = y;
6771
+ if (y > state.dirtyMaxY) state.dirtyMaxY = y;
6772
+ }
6773
+ }
6774
+ }
6775
+ function scrollPlane(plane, startY, endY, delta) {
6776
+ scrollPlaneRows(terminal, plane, startY, endY, delta);
6777
+ }
6778
+ function invalidatePlane(plane) {
6779
+ getDirtyState(plane).allRowsDirty = true;
6780
+ }
6015
6781
  function register(node) {
6016
6782
  const id = `r${nextNodeId++}`;
6017
6783
  const rect = node.rect ?? null;
@@ -6039,8 +6805,13 @@ function createRenderManager(terminal) {
6039
6805
  const nextPlane = next.plane ?? prev.plane;
6040
6806
  const nextRect = next.rect ?? prev.rect;
6041
6807
  const { y0, y1 } = rectToYBounds(nextRect);
6042
- markRect(prev.plane, prev.rect);
6043
- markRect(nextPlane, nextRect);
6808
+ const dirtyRowsHint = next.dirtyRowsHint;
6809
+ const canUseDirtyRowsHint = nextPlane === prev.plane && dirtyRowsHint != null && dirtyRowsHint.length > 0;
6810
+ if (canUseDirtyRowsHint) markRows(nextPlane, dirtyRowsHint);
6811
+ else {
6812
+ markRect(prev.plane, prev.rect);
6813
+ markRect(nextPlane, nextRect);
6814
+ }
6044
6815
  const full = Object.freeze({
6045
6816
  ...prev,
6046
6817
  stack: next.stack ?? prev.stack,
@@ -6102,10 +6873,11 @@ function createRenderManager(terminal) {
6102
6873
  return false;
6103
6874
  }
6104
6875
  function render(options) {
6876
+ var _process;
6105
6877
  const renderStart = profiler === null || profiler === void 0 ? void 0 : profiler.now();
6106
6878
  const activePlanes = (options === null || options === void 0 ? void 0 : options.activePlanes) ?? null;
6107
6879
  const requestedPlanes = activePlanes ?? TERMINAL_RENDER_PLANES;
6108
- const env = process === null || process === void 0 ? void 0 : process.env;
6880
+ const env = (_process = process) === null || _process === void 0 ? void 0 : _process.env;
6109
6881
  if ((env === null || env === void 0 ? void 0 : env.DIMCODE_DEBUG) === "1") renderMgrDebugLog.render("[RENDER-MANAGER] render() called");
6110
6882
  if (!hasPendingDirtyWork(requestedPlanes)) {
6111
6883
  if ((env === null || env === void 0 ? void 0 : env.DIMCODE_DEBUG) === "1") renderMgrDebugLog.render("[RENDER-MANAGER] render() skipped (no dirty rows)");
@@ -6213,6 +6985,8 @@ function createRenderManager(terminal) {
6213
6985
  return {
6214
6986
  rootStack,
6215
6987
  createStack,
6988
+ invalidatePlane,
6989
+ scrollPlane,
6216
6990
  register,
6217
6991
  update,
6218
6992
  unregister,
@@ -6257,6 +7031,7 @@ function createTerminalApp(options) {
6257
7031
  rows: options.rows
6258
7032
  });
6259
7033
  const trace = createTraceStore({ enabled: Boolean(globalThis.__VT_DEBUG_TRACE__) });
7034
+ const latency = getCliLatencyProfiler();
6260
7035
  const profiler = createTuiProfiler("cli-scheduler");
6261
7036
  const events = createCliEventManager({
6262
7037
  record: (event) => {
@@ -6278,7 +7053,12 @@ function createTerminalApp(options) {
6278
7053
  }
6279
7054
  });
6280
7055
  const render = createRenderManager(terminal);
6281
- const offCommit = terminal.on("commit", ({ dirtyRows, planes }) => {
7056
+ const offCommit = terminal.on("commit", ({ dirtyRows, planes, sync }) => {
7057
+ latency === null || latency === void 0 || latency.recordCommit({
7058
+ dirtyRows,
7059
+ planes,
7060
+ sync
7061
+ });
6282
7062
  if (!trace.enabled.value) return;
6283
7063
  trace.push({
6284
7064
  type: "commit",
@@ -6295,10 +7075,10 @@ function createTerminalApp(options) {
6295
7075
  let lastFlushAtMs = 0;
6296
7076
  let scheduledAtMs = 0;
6297
7077
  let timer = null;
7078
+ let pendingInvalidateAfterFlush = false;
6298
7079
  let pendingInvalidateAllPlanes = false;
6299
7080
  const pendingInvalidatePlanes = new Set();
6300
- const env = (process === null || process === void 0 ? void 0 : process.env) ?? {};
6301
- const isGhostty = "GHOSTTY_RESOURCES_DIR" in env;
7081
+ const env = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
6302
7082
  const throttleMs = (() => {
6303
7083
  const raw = env.DIMCODE_TUI_THROTTLE_MS;
6304
7084
  if (!raw) return 0;
@@ -6307,8 +7087,8 @@ function createTerminalApp(options) {
6307
7087
  })();
6308
7088
  const frameThrottleMs = (() => {
6309
7089
  var _process$stdout;
6310
- if (!(process === null || process === void 0 || (_process$stdout = process.stdout) === null || _process$stdout === void 0 ? void 0 : _process$stdout.isTTY)) return 0;
6311
- return isGhostty ? 24 : 16;
7090
+ if (!(process$1 === null || process$1 === void 0 || (_process$stdout = process$1.stdout) === null || _process$stdout === void 0 ? void 0 : _process$stdout.isTTY)) return 0;
7091
+ return 16;
6312
7092
  })();
6313
7093
  function queueInvalidatePlane(plane) {
6314
7094
  if (!plane) {
@@ -6330,16 +7110,31 @@ function createTerminalApp(options) {
6330
7110
  pendingInvalidatePlanes.clear();
6331
7111
  return activePlanes;
6332
7112
  }
6333
- function flush() {
7113
+ function flush(sync) {
6334
7114
  if (disposed) return;
6335
7115
  scheduled = false;
6336
7116
  flushing = true;
6337
7117
  const activePlanes = takeActivePlanes();
6338
- render.render({ activePlanes });
6339
- terminal.commit({ planes: activePlanes });
6340
- flushing = false;
6341
- lastFlushAtMs = Date.now();
6342
- scheduledAtMs = 0;
7118
+ latency === null || latency === void 0 || latency.recordFlushStart({
7119
+ sync,
7120
+ activePlanes
7121
+ });
7122
+ try {
7123
+ render.render({ activePlanes });
7124
+ terminal.commit({
7125
+ planes: activePlanes,
7126
+ sync
7127
+ });
7128
+ } finally {
7129
+ latency === null || latency === void 0 || latency.recordFlushEnd();
7130
+ flushing = false;
7131
+ lastFlushAtMs = Date.now();
7132
+ scheduledAtMs = 0;
7133
+ }
7134
+ if (pendingInvalidateAfterFlush) {
7135
+ pendingInvalidateAfterFlush = false;
7136
+ flush(true);
7137
+ }
6343
7138
  }
6344
7139
  function clearScheduledTimer() {
6345
7140
  if (!timer) return;
@@ -6360,7 +7155,7 @@ function createTerminalApp(options) {
6360
7155
  }
6361
7156
  clearScheduledTimer();
6362
7157
  scheduledAtMs = nowMs;
6363
- if (typeof process.nextTick === "function") process.nextTick(() => {
7158
+ if (typeof process$1.nextTick === "function") process$1.nextTick(() => {
6364
7159
  flush();
6365
7160
  });
6366
7161
  else if (typeof globalThis.setImmediate === "function") globalThis.setImmediate(() => {
@@ -6375,14 +7170,26 @@ function createTerminalApp(options) {
6375
7170
  if (flushing) return;
6376
7171
  clearScheduledTimer();
6377
7172
  scheduled = false;
6378
- flush();
7173
+ flush(true);
6379
7174
  }
6380
7175
  function invalidate(options$1) {
6381
7176
  if (disposed) return;
6382
- if (flushing) return;
6383
7177
  const priority = (options$1 === null || options$1 === void 0 ? void 0 : options$1.priority) ?? "normal";
7178
+ latency === null || latency === void 0 || latency.recordSchedulerInvalidate({
7179
+ priority,
7180
+ plane: (options$1 === null || options$1 === void 0 ? void 0 : options$1.plane) ?? null
7181
+ });
7182
+ if (flushing) {
7183
+ queueInvalidatePlane(options$1 === null || options$1 === void 0 ? void 0 : options$1.plane);
7184
+ pendingInvalidateAfterFlush = true;
7185
+ return;
7186
+ }
6384
7187
  queueInvalidatePlane(options$1 === null || options$1 === void 0 ? void 0 : options$1.plane);
6385
7188
  profiler === null || profiler === void 0 || profiler.recordInvalidate({ plane: (options$1 === null || options$1 === void 0 ? void 0 : options$1.plane) ?? null });
7189
+ if (priority === "high") {
7190
+ flushNow();
7191
+ return;
7192
+ }
6386
7193
  const nowMs = Date.now();
6387
7194
  const throttleDelayMs = throttleMs > 0 ? Math.max(0, lastFlushAtMs + throttleMs - nowMs) : 0;
6388
7195
  const laneDelayMs = priority === "low" ? Math.max(throttleDelayMs, frameThrottleMs || 16) : priority === "normal" ? Math.max(throttleDelayMs, frameThrottleMs) : 0;
@@ -6519,6 +7326,7 @@ function createTerminalApp(options) {
6519
7326
  dispose() {
6520
7327
  if (disposed) return;
6521
7328
  disposed = true;
7329
+ clearScheduledTimer();
6522
7330
  if (mounted) app.unmount();
6523
7331
  offResize === null || offResize === void 0 || offResize();
6524
7332
  offCommit === null || offCommit === void 0 || offCommit();
@@ -6602,7 +7410,7 @@ function parseWriteMode(raw) {
6602
7410
  }
6603
7411
  function createStdoutRenderer(terminal, options) {
6604
7412
  var _ref3;
6605
- const output = (options === null || options === void 0 ? void 0 : options.output) ?? process.stdout;
7413
+ const output = (options === null || options === void 0 ? void 0 : options.output) ?? process$1.stdout;
6606
7414
  if (!output) throw new Error("createStdoutRenderer requires a Node stdout-like output");
6607
7415
  const out = output;
6608
7416
  const clear = (options === null || options === void 0 ? void 0 : options.clear) ?? true;
@@ -6612,22 +7420,23 @@ function createStdoutRenderer(terminal, options) {
6612
7420
  let palette = options === null || options === void 0 ? void 0 : options.palette;
6613
7421
  const trackResize = (options === null || options === void 0 ? void 0 : options.trackResize) ?? true;
6614
7422
  const getImeAnchor = options === null || options === void 0 ? void 0 : options.getImeAnchor;
7423
+ const cliLatency = getCliLatencyProfiler();
6615
7424
  const profiler = createTuiProfiler("stdout-renderer");
6616
7425
  function resolveUseSyncOutput() {
6617
7426
  if ((options === null || options === void 0 ? void 0 : options.useSyncOutput) != null) return options.useSyncOutput;
6618
- const env$1 = (process === null || process === void 0 ? void 0 : process.env) ?? {};
7427
+ const env$1 = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
6619
7428
  const envValue = env$1.DIMCODE_SYNC_OUTPUT;
6620
7429
  if (envValue != null) {
6621
7430
  const v = String(envValue).trim().toLowerCase();
6622
7431
  return v === "1" || v === "true" || v === "yes";
6623
7432
  }
6624
- const term = String(env$1.TERM ?? "").toLowerCase();
6625
- const termProgram = String(env$1.TERM_PROGRAM ?? "").toLowerCase();
7433
+ const term$1 = String(env$1.TERM ?? "").toLowerCase();
7434
+ const termProgram$1 = String(env$1.TERM_PROGRAM ?? "").toLowerCase();
6626
7435
  const termProgramVersion = env$1.TERM_PROGRAM_VERSION;
6627
7436
  const isGhostty$1 = "GHOSTTY_RESOURCES_DIR" in env$1;
6628
- const isProblematicTerminal = isGhostty$1 || termProgram.includes("apple_terminal") || term.includes("screen") && !term.includes("tmux");
7437
+ const isProblematicTerminal = isGhostty$1 || termProgram$1.includes("apple_terminal") || term$1.includes("screen") && !term$1.includes("tmux");
6629
7438
  if (isProblematicTerminal) return false;
6630
- const isGoodTerminal = termProgram.includes("iterm") && termProgramVersion && compareVersion(String(termProgramVersion), "3.5.0") >= 0;
7439
+ const isGoodTerminal = termProgram$1.includes("iterm") && termProgramVersion && compareVersion(String(termProgramVersion), "3.5.0") >= 0;
6631
7440
  if (isGoodTerminal) return true;
6632
7441
  return false;
6633
7442
  }
@@ -6647,8 +7456,8 @@ function createStdoutRenderer(terminal, options) {
6647
7456
  return 0;
6648
7457
  }
6649
7458
  const useSyncOutput = resolveUseSyncOutput();
6650
- const isGhostty = "GHOSTTY_RESOURCES_DIR" in ((process === null || process === void 0 ? void 0 : process.env) ?? {});
6651
- const env = (process === null || process === void 0 ? void 0 : process.env) ?? {};
7459
+ const isGhostty = "GHOSTTY_RESOURCES_DIR" in ((process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {});
7460
+ const env = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
6652
7461
  const forcedWriteMode = parseWriteMode(env.DIMCODE_TUI_WRITE_MODE ?? env.VUE_TUI_WRITE_MODE);
6653
7462
  const forcedChunkSize = Number(env.DIMCODE_TUI_CHUNK_SIZE ?? env.VUE_TUI_CHUNK_SIZE);
6654
7463
  const chunkSize = Number.isFinite(forcedChunkSize) ? Math.max(512, Math.floor(forcedChunkSize)) : 8 * 1024;
@@ -6663,12 +7472,15 @@ function createStdoutRenderer(terminal, options) {
6663
7472
  if (Number.isFinite(n)) return Math.min(1, Math.max(.05, n));
6664
7473
  return .6;
6665
7474
  })();
6666
- const disableCursorPos = isGhostty && (process === null || process === void 0 || (_ref3 = process.env) === null || _ref3 === void 0 ? void 0 : _ref3.DIMCODE_DISABLE_CURSOR_POS) === "1";
6667
- const overlayTerminal = getPlaneTerminal(terminal, "overlay");
7475
+ const disableCursorPos = isGhostty && (process$1 === null || process$1 === void 0 || (_ref3 = process$1.env) === null || _ref3 === void 0 ? void 0 : _ref3.DIMCODE_DISABLE_CURSOR_POS) === "1";
7476
+ const termProgram = String(env.TERM_PROGRAM ?? "").trim().toLowerCase();
7477
+ const term = String(env.TERM ?? "").trim().toLowerCase();
7478
+ const isVscodeTerminal = termProgram === "vscode" || "VSCODE_PID" in env || "VSCODE_IPC_HOOK_CLI" in env;
7479
+ const useConservativeDirtyRowRendering = out.isTTY !== false && !isVscodeTerminal && (isGhostty || "KITTY_WINDOW_ID" in env || "ALACRITTY_WINDOW_ID" in env || "ALACRITTY_LOG" in env || "WEZTERM_PANE" in env || "WEZTERM_EXECUTABLE" in env || termProgram.includes("kitty") || termProgram.includes("alacritty") || termProgram.includes("wezterm") || term.includes("kitty") || term.includes("alacritty"));
6668
7480
  const enableOsc8Links = (() => {
6669
7481
  if (!out.isTTY) return false;
6670
7482
  const raw = String(env.DIMCODE_TUI_LINKS ?? "").trim().toLowerCase();
6671
- if (!raw) return !(String(env.TERM_PROGRAM ?? "").trim().toLowerCase() === "vscode" || "VSCODE_PID" in env || "VSCODE_IPC_HOOK_CLI" in env);
7483
+ if (!raw) return !isVscodeTerminal;
6672
7484
  return !(raw === "0" || raw === "false" || raw === "no");
6673
7485
  })();
6674
7486
  let disposed = false;
@@ -6676,12 +7488,16 @@ function createStdoutRenderer(terminal, options) {
6676
7488
  let pendingRender = false;
6677
7489
  let renderTimer = null;
6678
7490
  let writeEmaMs = 0;
7491
+ let lastCursorX = null;
7492
+ let lastCursorY = null;
6679
7493
  let accumulatedAllRows = false;
6680
7494
  let accumulatedDirtyBits = null;
6681
7495
  let accumulatedDirtyCount = 0;
6682
7496
  let accumulatedDirtyMin = Number.POSITIVE_INFINITY;
6683
7497
  let accumulatedDirtyMax = -1;
6684
- const MIN_FRAME_MS = !out.isTTY ? 0 : isGhostty ? 24 : 16;
7498
+ let accumulatedScrollOperations = null;
7499
+ let accumulatedSkipUnchangedDirtyRows = true;
7500
+ const MIN_FRAME_MS = !out.isTTY ? 0 : 16;
6685
7501
  function clampCellToViewport(cell, size) {
6686
7502
  const maxX = Math.max(0, Math.floor(size.cols) - 1);
6687
7503
  const maxY = Math.max(0, Math.floor(size.rows) - 1);
@@ -6697,25 +7513,43 @@ function createStdoutRenderer(terminal, options) {
6697
7513
  function resolveColorMode() {
6698
7514
  const opt = (options === null || options === void 0 ? void 0 : options.colorMode) ?? "auto";
6699
7515
  if (opt !== "auto") return opt;
6700
- const env$1 = (process === null || process === void 0 ? void 0 : process.env) ?? {};
7516
+ const env$1 = (process$1 === null || process$1 === void 0 ? void 0 : process$1.env) ?? {};
6701
7517
  return detectTerminalColorCapability({
6702
7518
  env: env$1,
6703
7519
  isTTY: Boolean(out.isTTY),
6704
- platform: String((process === null || process === void 0 ? void 0 : process.platform) ?? "")
7520
+ platform: String((process$1 === null || process$1 === void 0 ? void 0 : process$1.platform) ?? "")
6705
7521
  }).mode;
6706
7522
  }
6707
7523
  const colorMode = resolveColorMode();
6708
7524
  const enableDim = colorMode === "truecolor";
7525
+ const COLOR_INDEX = {
7526
+ black: 1,
7527
+ red: 2,
7528
+ green: 3,
7529
+ yellow: 4,
7530
+ blue: 5,
7531
+ magenta: 6,
7532
+ cyan: 7,
7533
+ white: 8,
7534
+ blackBright: 9,
7535
+ redBright: 10,
7536
+ greenBright: 11,
7537
+ yellowBright: 12,
7538
+ blueBright: 13,
7539
+ magentaBright: 14,
7540
+ cyanBright: 15,
7541
+ whiteBright: 16
7542
+ };
6709
7543
  let styleKeyCache$1 = new WeakMap();
6710
7544
  function styleKey$1(style) {
6711
7545
  const cached = styleKeyCache$1.get(style);
6712
- if (cached) return cached;
6713
- const key = `${style.fg ?? ""}|${style.bg ?? defaultBg}|${style.bold ? 1 : 0}|${enableDim && style.dim ? 1 : 0}|${style.italic ? 1 : 0}|${style.underline ? 1 : 0}|${style.inverse ? 1 : 0}|${style.href ?? ""}`;
7546
+ if (cached !== void 0) return cached;
7547
+ const key = (COLOR_INDEX[style.fg] ?? 0) | (COLOR_INDEX[style.bg ?? defaultBg] ?? 0) << 5 | (style.bold ? 1024 : 0) | (enableDim && style.dim ? 2048 : 0) | (style.italic ? 4096 : 0) | (style.underline ? 8192 : 0) | (style.inverse ? 16384 : 0) | (style.href ? 32768 : 0);
6714
7548
  styleKeyCache$1.set(style, key);
6715
7549
  return key;
6716
7550
  }
6717
7551
  function styleKeyFromParts(style) {
6718
- return `${style.fg ?? ""}|${style.bg ?? defaultBg}|${style.bold ? 1 : 0}|${enableDim && style.dim ? 1 : 0}|${style.italic ? 1 : 0}|${style.underline ? 1 : 0}|${style.inverse ? 1 : 0}|${style.href ?? ""}`;
7552
+ return (COLOR_INDEX[style.fg] ?? 0) | (COLOR_INDEX[style.bg ?? defaultBg] ?? 0) << 5 | (style.bold ? 1024 : 0) | (enableDim && style.dim ? 2048 : 0) | (style.italic ? 4096 : 0) | (style.underline ? 8192 : 0) | (style.inverse ? 16384 : 0) | (style.href ? 32768 : 0);
6719
7553
  }
6720
7554
  function openColor(fg) {
6721
7555
  if (!fg) return "";
@@ -6747,7 +7581,45 @@ function createStdoutRenderer(terminal, options) {
6747
7581
  return result;
6748
7582
  }
6749
7583
  let lastRenderedRows = 0;
6750
- let renderedRowCache = [];
7584
+ let fpCols = 0;
7585
+ let fpRows = 0;
7586
+ let currentFP = new Uint32Array(0);
7587
+ let prevFP = new Uint32Array(0);
7588
+ let fpPrevValid = false;
7589
+ let prevOverlayBlockedRows = [];
7590
+ let prevOverlayPartialRows = [];
7591
+ function ensureFingerprints(cols, rows) {
7592
+ if (cols === fpCols && rows === fpRows) return;
7593
+ fpCols = cols;
7594
+ fpRows = rows;
7595
+ const len = cols * rows;
7596
+ currentFP = new Uint32Array(len);
7597
+ prevFP = new Uint32Array(len);
7598
+ fpPrevValid = false;
7599
+ prevOverlayBlockedRows = [];
7600
+ prevOverlayPartialRows = [];
7601
+ }
7602
+ function charHash16(ch) {
7603
+ if (ch.length <= 1) return ch.charCodeAt(0) || 0;
7604
+ let h$1 = 33052;
7605
+ for (let i = 0; i < ch.length; i++) {
7606
+ h$1 ^= ch.charCodeAt(i);
7607
+ h$1 = h$1 * 257 & 65535;
7608
+ }
7609
+ return h$1;
7610
+ }
7611
+ function fingerprintRow(row, y, cols) {
7612
+ const rowFP = terminal.getRowFingerprints(y);
7613
+ if (rowFP && rowFP.length >= cols) {
7614
+ currentFP.set(rowFP.subarray(0, cols), y * fpCols);
7615
+ return;
7616
+ }
7617
+ const base = y * fpCols;
7618
+ for (let x = 0; x < cols; x++) {
7619
+ const cell = row[x];
7620
+ currentFP[base + x] = styleKey$1(cell.style) << 16 | charHash16(cell.ch);
7621
+ }
7622
+ }
6751
7623
  const rowCursorToCol1 = [];
6752
7624
  const rowClearToEol = [];
6753
7625
  const rowTextPartsScratch = [];
@@ -6765,13 +7637,6 @@ function createStdoutRenderer(terminal, options) {
6765
7637
  rowClearToEol[y] = `\u001B[${y + 1};1H\u001B[K`;
6766
7638
  }
6767
7639
  };
6768
- const ensureRenderedRowCache = (rows) => {
6769
- if (renderedRowCache.length > rows) {
6770
- renderedRowCache.length = rows;
6771
- return;
6772
- }
6773
- while (renderedRowCache.length < rows) renderedRowCache.push([]);
6774
- };
6775
7640
  const expandSpanStart = (row, startX) => {
6776
7641
  var _row$x;
6777
7642
  let x = Math.max(0, Math.min(row.length, startX));
@@ -6788,30 +7653,279 @@ function createStdoutRenderer(terminal, options) {
6788
7653
  }
6789
7654
  return x;
6790
7655
  };
6791
- const resolveChangedSpan = (row, prevRow) => {
6792
- if (!prevRow || prevRow.length !== row.length) return row.length ? {
7656
+ const resolveChangedSpan = (row, y, cols) => {
7657
+ if (!fpCols || y >= fpRows || !fpPrevValid) return row.length ? {
7658
+ startX: 0,
7659
+ endXExclusive: row.length
7660
+ } : null;
7661
+ const base = y * fpCols;
7662
+ let startX = -1;
7663
+ let endXExclusive = -1;
7664
+ for (let x = 0; x < cols; x++) {
7665
+ if (currentFP[base + x] === prevFP[base + x]) continue;
7666
+ if (startX === -1) startX = x;
7667
+ endXExclusive = x + 1;
7668
+ }
7669
+ if (startX === -1) return null;
7670
+ const expandedStart = expandSpanStart(row, startX);
7671
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
7672
+ return expandedEnd > expandedStart ? {
7673
+ startX: expandedStart,
7674
+ endXExclusive: expandedEnd
7675
+ } : null;
7676
+ };
7677
+ const resolveChangedSpanAgainstFill = (row, y, cols, fillFingerprint) => {
7678
+ if (!fpCols || y >= fpRows) return row.length ? {
6793
7679
  startX: 0,
6794
7680
  endXExclusive: row.length
6795
7681
  } : null;
7682
+ const base = y * fpCols;
6796
7683
  let startX = -1;
6797
7684
  let endXExclusive = -1;
6798
- for (let x = 0; x < row.length; x++) {
6799
- if (row[x] === prevRow[x]) continue;
7685
+ for (let x = 0; x < cols; x++) {
7686
+ if (currentFP[base + x] === fillFingerprint) continue;
6800
7687
  if (startX === -1) startX = x;
6801
7688
  endXExclusive = x + 1;
6802
7689
  }
6803
- if (startX === -1 || endXExclusive === -1) return null;
6804
- const expandedStart = Math.min(expandSpanStart(row, startX), expandSpanStart(prevRow, startX));
6805
- const expandedEnd = Math.max(expandSpanEnd(row, endXExclusive), expandSpanEnd(prevRow, endXExclusive));
7690
+ if (startX === -1) return null;
7691
+ const expandedStart = expandSpanStart(row, startX);
7692
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
6806
7693
  return expandedEnd > expandedStart ? {
6807
7694
  startX: expandedStart,
6808
7695
  endXExclusive: expandedEnd
6809
7696
  } : null;
6810
7697
  };
6811
- function overlayPlaneHasVisibleContent() {
6812
- const lines = overlayTerminal.snapshot().lines;
6813
- for (const line of lines) for (let i = 0; i < line.length; i++) if (line.charCodeAt(i) !== 32) return true;
7698
+ const resolveChangedSpanAgainstReference = (row, y, cols) => {
7699
+ if (!fpCols || y >= fpRows) return row.length ? {
7700
+ startX: 0,
7701
+ endXExclusive: row.length
7702
+ } : null;
7703
+ const rowFP = terminal.getRowFingerprints(y);
7704
+ const base = y * fpCols;
7705
+ let startX = -1;
7706
+ let endXExclusive = -1;
7707
+ for (let x = 0; x < cols; x++) {
7708
+ const fingerprint = rowFP && rowFP.length >= cols ? rowFP[x] : styleKey$1(row[x].style) << 16 | charHash16(row[x].ch);
7709
+ if (fingerprint === currentFP[base + x]) continue;
7710
+ if (startX === -1) startX = x;
7711
+ endXExclusive = x + 1;
7712
+ }
7713
+ if (startX === -1) return null;
7714
+ const expandedStart = expandSpanStart(row, startX);
7715
+ const expandedEnd = expandSpanEnd(row, endXExclusive);
7716
+ return expandedEnd > expandedStart ? {
7717
+ startX: expandedStart,
7718
+ endXExclusive: expandedEnd
7719
+ } : null;
7720
+ };
7721
+ const rowMatchesPreviousFrame = (y, cols) => {
7722
+ if (!fpCols || y < 0 || y >= fpRows || !fpPrevValid) return false;
7723
+ const rowFP = terminal.getRowFingerprints(y);
7724
+ const row = rowFP && rowFP.length >= cols ? null : terminal.getRow(y);
7725
+ const base = y * fpCols;
7726
+ for (let x = 0; x < cols; x++) {
7727
+ const fingerprint = rowFP && rowFP.length >= cols ? rowFP[x] : styleKey$1(row[x].style) << 16 | charHash16(row[x].ch);
7728
+ if (fingerprint !== prevFP[base + x]) return false;
7729
+ }
7730
+ return true;
7731
+ };
7732
+ const rowHasNonDefaultBlankCell = (source, y, cols) => {
7733
+ if (!fpCols || y < 0 || y >= fpRows) return false;
7734
+ const blankFP = styleKeyFromParts({ bg: defaultBg }) << 16 | charHash16(" ");
7735
+ const base = y * fpCols;
7736
+ const limit = Math.min(cols, fpCols);
7737
+ for (let x = 0; x < limit; x++) if (source[base + x] !== blankFP) return true;
6814
7738
  return false;
7739
+ };
7740
+ function overlayPlaneCoverageRows(totalRows) {
7741
+ const blockedRows = [];
7742
+ const partialRows = [];
7743
+ for (let y = 0; y < totalRows; y++) {
7744
+ const kind = getPlaneRowCoverageKind(terminal, "overlay", y);
7745
+ if (!kind) continue;
7746
+ blockedRows.push(y);
7747
+ if (kind === 1) partialRows.push(y);
7748
+ }
7749
+ return {
7750
+ blockedRows,
7751
+ partialRows
7752
+ };
7753
+ }
7754
+ const enableScrollRegions = (() => {
7755
+ const raw = String(env.DIMCODE_TUI_SCROLL_REGIONS ?? "").trim();
7756
+ if (raw === "0" || raw === "false") return false;
7757
+ if (isGhostty || isVscodeTerminal) return false;
7758
+ return out.isTTY !== false;
7759
+ })();
7760
+ function prepareExplicitScrollOperations(operations, blockedRows) {
7761
+ if (!operations.length || !(blockedRows === null || blockedRows === void 0 ? void 0 : blockedRows.size)) return {
7762
+ operations,
7763
+ hiddenRows: new Set(),
7764
+ trimmed: false,
7765
+ blockedInterior: false
7766
+ };
7767
+ const hiddenRows = new Set();
7768
+ const prepared = [];
7769
+ let trimmed = false;
7770
+ for (const op of operations) {
7771
+ let startY = op.startY;
7772
+ let endY = op.endY;
7773
+ while (startY < endY && blockedRows.has(startY)) {
7774
+ hiddenRows.add(startY);
7775
+ startY++;
7776
+ trimmed = true;
7777
+ }
7778
+ while (endY > startY && blockedRows.has(endY - 1)) {
7779
+ hiddenRows.add(endY - 1);
7780
+ endY--;
7781
+ trimmed = true;
7782
+ }
7783
+ for (let y = startY; y < endY; y++) {
7784
+ if (!blockedRows.has(y)) continue;
7785
+ return {
7786
+ operations: null,
7787
+ hiddenRows,
7788
+ trimmed,
7789
+ blockedInterior: true
7790
+ };
7791
+ }
7792
+ if (endY <= startY || Math.abs(op.delta) >= endY - startY) return {
7793
+ operations: null,
7794
+ hiddenRows,
7795
+ trimmed,
7796
+ blockedInterior: false
7797
+ };
7798
+ prepared.push(startY === op.startY && endY === op.endY ? op : {
7799
+ startY,
7800
+ endY,
7801
+ delta: op.delta
7802
+ });
7803
+ }
7804
+ return {
7805
+ operations: prepared,
7806
+ hiddenRows,
7807
+ trimmed,
7808
+ blockedInterior: false
7809
+ };
7810
+ }
7811
+ /**
7812
+ * Detect if the current frame represents a pure vertical scroll by comparing
7813
+ * current fingerprints against previous fingerprints with various shift offsets.
7814
+ * Returns the shift delta and which rows are newly revealed, or null if no
7815
+ * scroll pattern is detected.
7816
+ */
7817
+ function largestDirtyBand(rows, blockedRows) {
7818
+ if (rows.length === 0) return null;
7819
+ const blocked = (blockedRows === null || blockedRows === void 0 ? void 0 : blockedRows.length) ? new Set(blockedRows) : null;
7820
+ let bestStart = -1;
7821
+ let bestEnd = -1;
7822
+ let bandStart = -1;
7823
+ let prev = -1;
7824
+ for (let i = 0; i < rows.length; i++) {
7825
+ const y = rows[i];
7826
+ if (blocked === null || blocked === void 0 ? void 0 : blocked.has(y)) {
7827
+ if (bandStart !== -1 && prev + 1 - bandStart > bestEnd - bestStart) {
7828
+ bestStart = bandStart;
7829
+ bestEnd = prev + 1;
7830
+ }
7831
+ bandStart = -1;
7832
+ prev = -1;
7833
+ continue;
7834
+ }
7835
+ if (bandStart === -1) {
7836
+ bandStart = y;
7837
+ prev = y;
7838
+ if (bestStart === -1) {
7839
+ bestStart = y;
7840
+ bestEnd = y + 1;
7841
+ }
7842
+ continue;
7843
+ }
7844
+ if (y === prev + 1) {
7845
+ prev = y;
7846
+ continue;
7847
+ }
7848
+ if (prev + 1 - bandStart > bestEnd - bestStart) {
7849
+ bestStart = bandStart;
7850
+ bestEnd = prev + 1;
7851
+ }
7852
+ bandStart = y;
7853
+ prev = y;
7854
+ }
7855
+ if (bandStart !== -1 && prev + 1 - bandStart > bestEnd - bestStart) {
7856
+ bestStart = bandStart;
7857
+ bestEnd = prev + 1;
7858
+ }
7859
+ if (bestStart === -1) return null;
7860
+ const outsideRows = [];
7861
+ for (const y of rows) if ((blocked === null || blocked === void 0 ? void 0 : blocked.has(y)) || y < bestStart || y >= bestEnd) outsideRows.push(y);
7862
+ return {
7863
+ start: bestStart,
7864
+ end: bestEnd,
7865
+ outsideRows
7866
+ };
7867
+ }
7868
+ function detectScrollShift(cols, dirtyRows, blockedRows) {
7869
+ if (!fpPrevValid || !fpCols || cols !== fpCols) return null;
7870
+ const band = largestDirtyBand(dirtyRows, blockedRows);
7871
+ if (!band || band.end - band.start < 3) return null;
7872
+ const maxDelta = Math.min(12, Math.max(5, band.end - band.start - 1));
7873
+ for (let delta = -maxDelta; delta <= maxDelta; delta++) {
7874
+ if (delta === 0) continue;
7875
+ let matches = 0;
7876
+ let checked = 0;
7877
+ let mismatches = 0;
7878
+ for (let y = band.start; y < band.end; y++) {
7879
+ const srcY = y + delta;
7880
+ if (srcY < band.start || srcY >= band.end) continue;
7881
+ const informative = rowHasNonDefaultBlankCell(currentFP, y, cols) || rowHasNonDefaultBlankCell(prevFP, srcY, cols);
7882
+ if (!informative) continue;
7883
+ checked++;
7884
+ const curBase = y * fpCols;
7885
+ const prevBase = srcY * fpCols;
7886
+ let rowMatch = true;
7887
+ for (let x = 0; x < cols; x++) if (currentFP[curBase + x] !== prevFP[prevBase + x]) {
7888
+ rowMatch = false;
7889
+ break;
7890
+ }
7891
+ if (rowMatch) matches++;
7892
+ else mismatches++;
7893
+ }
7894
+ if (checked > 0 && matches >= 3 && mismatches <= Math.ceil(checked * .2)) {
7895
+ const absDelta = Math.abs(delta);
7896
+ let newRowStart;
7897
+ let newRowEnd;
7898
+ if (delta > 0) {
7899
+ newRowStart = Math.max(band.start, band.end - absDelta);
7900
+ newRowEnd = band.end;
7901
+ } else {
7902
+ newRowStart = band.start;
7903
+ newRowEnd = Math.min(band.end, band.start + absDelta);
7904
+ }
7905
+ const extraDirty = [...band.outsideRows];
7906
+ for (let y = band.start; y < band.end; y++) {
7907
+ const srcY = y + delta;
7908
+ if (srcY < band.start || srcY >= band.end) continue;
7909
+ const curBase = y * fpCols;
7910
+ const prevBase = srcY * fpCols;
7911
+ let rowMatch = true;
7912
+ for (let x = 0; x < cols; x++) if (currentFP[curBase + x] !== prevFP[prevBase + x]) {
7913
+ rowMatch = false;
7914
+ break;
7915
+ }
7916
+ if (!rowMatch) extraDirty.push(y);
7917
+ }
7918
+ return {
7919
+ regionStart: band.start,
7920
+ regionEnd: band.end,
7921
+ delta,
7922
+ newRowStart,
7923
+ newRowEnd,
7924
+ extraDirtyRows: extraDirty
7925
+ };
7926
+ }
7927
+ }
7928
+ return null;
6815
7929
  }
6816
7930
  /**
6817
7931
  * Write data in chunks to avoid overwhelming terminal buffers.
@@ -6849,24 +7963,60 @@ function createStdoutRenderer(terminal, options) {
6849
7963
  * Internal function that actually renders the frame to stdout.
6850
7964
  * When dirtyRows is provided, only those rows are repainted.
6851
7965
  */
6852
- function doRender(dirtyRows) {
7966
+ function doRender(dirtyRows, scrollOperations, skipUnchangedDirtyRows = false) {
6853
7967
  if (isDebugEnabled()) getDebugLog().render(`doRender() START: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}`);
6854
7968
  if (disposed) return;
7969
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutRenderStart();
6855
7970
  pendingRender = false;
6856
7971
  accumulatedAllRows = false;
6857
7972
  accumulatedDirtyBits = null;
6858
7973
  accumulatedDirtyCount = 0;
6859
7974
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
6860
7975
  accumulatedDirtyMax = -1;
7976
+ accumulatedScrollOperations = null;
7977
+ accumulatedSkipUnchangedDirtyRows = true;
6861
7978
  lastFrameTime = Date.now();
6862
7979
  const renderStart = performance.now();
6863
7980
  const size = terminal.size();
6864
7981
  ensureRowEscapes(Math.max(size.rows, lastRenderedRows));
6865
- ensureRenderedRowCache(size.rows);
7982
+ ensureFingerprints(size.cols, size.rows);
6866
7983
  const bgSeq = openBg(defaultBg);
6867
7984
  const bgOnlyStyle = { bg: defaultBg };
6868
7985
  const bgKey = styleKeyFromParts({ bg: defaultBg });
7986
+ const blankFP = bgKey << 16 | charHash16(" ");
7987
+ const cellFingerprint = (cell) => styleKey$1(cell.style) << 16 | charHash16(cell.ch);
7988
+ const lastOccupiedColumnInRow = (row, cols) => {
7989
+ const limit = Math.min(row.length, cols);
7990
+ for (let x = limit - 1; x >= 0; x--) if (cellFingerprint(row[x]) !== blankFP) return x;
7991
+ return -1;
7992
+ };
7993
+ const lastOccupiedColumnInReference = (source, y, cols) => {
7994
+ if (!fpCols || y < 0 || y >= fpRows) return -1;
7995
+ const limit = Math.min(fpCols, cols);
7996
+ const base = y * fpCols;
7997
+ for (let x = limit - 1; x >= 0; x--) if (source[base + x] !== blankFP) return x;
7998
+ return -1;
7999
+ };
8000
+ const shouldRewriteRowTail = (row, y, cols, reference) => lastOccupiedColumnInRow(row, cols) < lastOccupiedColumnInReference(reference, y, cols);
6869
8001
  let dirtySorted = true;
8002
+ const normalizedScrollOperations = (() => {
8003
+ if (!(scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length)) return null;
8004
+ const outOps = [];
8005
+ for (const op of scrollOperations) {
8006
+ const startY = Math.max(0, Math.min(size.rows, Math.floor(op.startY)));
8007
+ const endY = Math.max(0, Math.min(size.rows, Math.floor(op.endY)));
8008
+ const delta = Math.trunc(op.delta);
8009
+ if (endY <= startY || delta === 0 || Math.abs(delta) >= endY - startY) continue;
8010
+ outOps.push({
8011
+ startY,
8012
+ endY,
8013
+ delta
8014
+ });
8015
+ }
8016
+ if (!outOps.length) return null;
8017
+ outOps.sort((a, b) => a.startY - b.startY);
8018
+ return outOps;
8019
+ })();
6870
8020
  let rowsToRender = (() => {
6871
8021
  if (!dirtyRows || dirtyRows.length === 0) return null;
6872
8022
  const outRows = [];
@@ -6887,11 +8037,14 @@ function createStdoutRenderer(terminal, options) {
6887
8037
  if (!sorted) outRows.sort((a, b) => a - b);
6888
8038
  return outRows;
6889
8039
  })();
6890
- if (rowsToRender && rowsToRender.length >= size.rows * dirtyFullThreshold) rowsToRender = null;
8040
+ if (fpPrevValid && rowsToRender) currentFP.set(prevFP);
6891
8041
  const frameParts = [];
6892
8042
  frameParts.push(!isGhostty && useSyncOutput ? SYNC_START : "");
6893
8043
  frameParts.push("\x1B[?7l");
6894
8044
  frameParts.push(SGR_RESET, bgSeq);
8045
+ let lastRenderedY = -1;
8046
+ let lastRenderWasFullRow = false;
8047
+ let hasFrameOutput = false;
6895
8048
  let activeStyleKey = null;
6896
8049
  let activeStyle = {
6897
8050
  fg: null,
@@ -6942,11 +8095,10 @@ function createStdoutRenderer(terminal, options) {
6942
8095
  const renderRow$1 = (y, row, startX = 0, endXExclusive = row.length, clearToEol = true) => {
6943
8096
  const spanStart = Math.max(0, Math.min(row.length, Math.floor(startX)));
6944
8097
  const spanEnd = Math.max(spanStart, Math.min(row.length, Math.floor(endXExclusive)));
6945
- if (spanStart >= spanEnd && !clearToEol) {
6946
- renderedRowCache[y] = row.slice();
6947
- return;
6948
- }
6949
- if (!disableCursorPos) frameParts.push(spanStart === 0 ? rowCursorToCol1[y] : `\u001B[${y + 1};${spanStart + 1}H`);
8098
+ if (spanStart >= spanEnd && !clearToEol) return;
8099
+ hasFrameOutput = true;
8100
+ if (!disableCursorPos) if (spanStart === 0 && lastRenderWasFullRow && y === lastRenderedY + 1) frameParts.push("\r\n");
8101
+ else frameParts.push(spanStart === 0 ? rowCursorToCol1[y] : `\u001B[${y + 1};${spanStart + 1}H`);
6950
8102
  let currentKey = null;
6951
8103
  let currentStyle = null;
6952
8104
  const currentTextParts = rowTextPartsScratch;
@@ -6964,7 +8116,7 @@ function createStdoutRenderer(terminal, options) {
6964
8116
  if (needsWideCursorFix(cell, ch)) currentTextParts.push(`\u001B[${y + 1};${x + 1 + (cell.width ?? 1)}H`);
6965
8117
  continue;
6966
8118
  }
6967
- if (key === currentKey) {
8119
+ if (key === currentKey && !(key & 32768 && nextStyle.href !== (currentStyle === null || currentStyle === void 0 ? void 0 : currentStyle.href))) {
6968
8120
  currentTextParts.push(ch);
6969
8121
  if (needsWideCursorFix(cell, ch)) currentTextParts.push(`\u001B[${y + 1};${x + 1 + (cell.width ?? 1)}H`);
6970
8122
  continue;
@@ -6985,29 +8137,237 @@ function createStdoutRenderer(terminal, options) {
6985
8137
  if (activeStyleKey !== bgKey) emitStyle(bgOnlyStyle, bgKey);
6986
8138
  frameParts.push("\x1B[K");
6987
8139
  }
6988
- renderedRowCache[y] = row.slice();
8140
+ lastRenderedY = y;
8141
+ lastRenderWasFullRow = spanStart === 0 && clearToEol;
6989
8142
  };
6990
- if (!rowsToRender) {
8143
+ const overlayCoverage = overlayPlaneCoverageRows(size.rows);
8144
+ const overlayRows = Array.from(new Set([...overlayCoverage.blockedRows, ...prevOverlayBlockedRows])).sort((a, b) => a - b);
8145
+ const overlayRowSet = overlayRows.length ? new Set(overlayRows) : null;
8146
+ const overlayPartialRows = Array.from(new Set([...overlayCoverage.partialRows, ...prevOverlayPartialRows])).sort((a, b) => a - b);
8147
+ const overlayPartialRowSet = overlayPartialRows.length ? new Set(overlayPartialRows) : null;
8148
+ const overlayTouchedRowSet = (() => {
8149
+ if (!overlayRowSet && !overlayPartialRowSet) return null;
8150
+ return new Set([...overlayRowSet ? Array.from(overlayRowSet) : [], ...overlayPartialRowSet ? Array.from(overlayPartialRowSet) : []]);
8151
+ })();
8152
+ let explicitScrollOperations = normalizedScrollOperations;
8153
+ let hiddenExplicitDirtyRows = null;
8154
+ let allowInferredScrollRegions = true;
8155
+ if (explicitScrollOperations && overlayRows.length) {
8156
+ const preparedExplicit = prepareExplicitScrollOperations(explicitScrollOperations, overlayRowSet);
8157
+ hiddenExplicitDirtyRows = preparedExplicit.hiddenRows.size ? preparedExplicit.hiddenRows : null;
8158
+ if (preparedExplicit.operations) {
8159
+ explicitScrollOperations = preparedExplicit.operations;
8160
+ if (preparedExplicit.trimmed && isDebugEnabled()) getDebugLog().render(` Explicit scroll ops clipped around overlay rows: ${normalizedScrollOperations.map((op, index) => `${op.startY}-${op.endY - 1}:${op.delta}->${explicitScrollOperations[index].startY}-${explicitScrollOperations[index].endY - 1}:${explicitScrollOperations[index].delta}`).join(", ")}`);
8161
+ } else {
8162
+ allowInferredScrollRegions = false;
8163
+ const expandedRows = new Set(rowsToRender ?? []);
8164
+ for (const op of explicitScrollOperations) for (let y = op.startY; y < op.endY; y++) expandedRows.add(y);
8165
+ rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
8166
+ dirtySorted = true;
8167
+ explicitScrollOperations = null;
8168
+ hiddenExplicitDirtyRows = null;
8169
+ if (isDebugEnabled()) getDebugLog().render(preparedExplicit.blockedInterior ? " Explicit scroll ops split by interior overlay rows; falling back to region repaint" : " Explicit scroll ops could not be clipped safely; falling back to region repaint");
8170
+ }
8171
+ }
8172
+ const scrollRowsCandidate = rowsToRender;
8173
+ if (explicitScrollOperations && rowsToRender && (!enableScrollRegions || !fpPrevValid)) {
8174
+ const expandedRows = new Set(rowsToRender);
8175
+ for (const op of explicitScrollOperations) for (let y = op.startY; y < op.endY; y++) {
8176
+ if (hiddenExplicitDirtyRows === null || hiddenExplicitDirtyRows === void 0 ? void 0 : hiddenExplicitDirtyRows.has(y)) continue;
8177
+ expandedRows.add(y);
8178
+ }
8179
+ rowsToRender = Array.from(expandedRows).sort((a, b) => a - b);
8180
+ dirtySorted = true;
8181
+ explicitScrollOperations = null;
8182
+ hiddenExplicitDirtyRows = null;
8183
+ if (isDebugEnabled()) getDebugLog().render(enableScrollRegions ? " Explicit scroll ops fell back to region repaint because previous fingerprints are unavailable" : " Explicit scroll ops fell back to region repaint because scroll regions are disabled");
8184
+ }
8185
+ const denseDirtyRows = Boolean(rowsToRender && rowsToRender.length >= size.rows * dirtyFullThreshold);
8186
+ if (denseDirtyRows) rowsToRender = null;
8187
+ let profiledRowCount = rowsToRender ? rowsToRender.length : size.rows;
8188
+ const useScrollRegions = enableScrollRegions && fpPrevValid && scrollRowsCandidate && scrollRowsCandidate.length >= 3 && allowInferredScrollRegions;
8189
+ let scrollHandled = false;
8190
+ if (enableScrollRegions && explicitScrollOperations && scrollRowsCandidate && fpPrevValid) {
8191
+ if (currentFP.length === prevFP.length) currentFP.set(prevFP);
8192
+ const insertedRows = new Set();
8193
+ const explicitRowsToRender = new Set();
8194
+ for (const y of scrollRowsCandidate) {
8195
+ if (hiddenExplicitDirtyRows === null || hiddenExplicitDirtyRows === void 0 ? void 0 : hiddenExplicitDirtyRows.has(y)) continue;
8196
+ explicitRowsToRender.add(y);
8197
+ }
8198
+ if (isDebugEnabled()) getDebugLog().render(` Explicit scroll ops: ${explicitScrollOperations.map((op) => `${op.startY}-${op.endY - 1}:${op.delta}`).join(", ")}`);
8199
+ for (const op of explicitScrollOperations) {
8200
+ hasFrameOutput = true;
8201
+ frameParts.push(`\u001B[${op.startY + 1};${op.endY}r`);
8202
+ if (op.delta > 0) {
8203
+ frameParts.push(`\u001B[${op.endY};1H`);
8204
+ frameParts.push(`\u001B[${op.delta}S`);
8205
+ for (let y = op.startY; y < op.endY - op.delta; y++) {
8206
+ const dstBase = y * fpCols;
8207
+ const srcBase = (y + op.delta) * fpCols;
8208
+ for (let x = 0; x < size.cols; x++) currentFP[dstBase + x] = prevFP[srcBase + x];
8209
+ }
8210
+ for (let y = op.endY - op.delta; y < op.endY; y++) {
8211
+ const base = y * fpCols;
8212
+ insertedRows.add(y);
8213
+ explicitRowsToRender.add(y);
8214
+ for (let x = 0; x < size.cols; x++) currentFP[base + x] = blankFP;
8215
+ }
8216
+ } else {
8217
+ const absDelta = -op.delta;
8218
+ frameParts.push(`\u001B[${op.startY + 1};1H`);
8219
+ frameParts.push(`\u001B[${absDelta}T`);
8220
+ for (let y = op.endY - 1; y >= op.startY + absDelta; y--) {
8221
+ const dstBase = y * fpCols;
8222
+ const srcBase = (y - absDelta) * fpCols;
8223
+ for (let x = 0; x < size.cols; x++) currentFP[dstBase + x] = prevFP[srcBase + x];
8224
+ }
8225
+ for (let y = op.startY; y < op.startY + absDelta; y++) {
8226
+ const base = y * fpCols;
8227
+ insertedRows.add(y);
8228
+ explicitRowsToRender.add(y);
8229
+ for (let x = 0; x < size.cols; x++) currentFP[base + x] = blankFP;
8230
+ }
8231
+ }
8232
+ frameParts.push("\x1B[r");
8233
+ }
8234
+ const explicitDirtyRows = Array.from(explicitRowsToRender).sort((a, b) => a - b);
8235
+ const overlayDirtyRows = overlayTouchedRowSet ? explicitDirtyRows.filter((y) => overlayTouchedRowSet.has(y)) : [];
8236
+ const paintedExplicitRows = [];
8237
+ for (const y of explicitDirtyRows) {
8238
+ const row = terminal.getRow(y);
8239
+ const overlayRow = overlayTouchedRowSet === null || overlayTouchedRowSet === void 0 ? void 0 : overlayTouchedRowSet.has(y);
8240
+ if (overlayRow || useConservativeDirtyRowRendering) {
8241
+ fingerprintRow(row, y, size.cols);
8242
+ renderRow$1(y, row);
8243
+ paintedExplicitRows.push(y);
8244
+ continue;
8245
+ }
8246
+ const span = (() => {
8247
+ if (insertedRows.has(y)) {
8248
+ fingerprintRow(row, y, size.cols);
8249
+ return resolveChangedSpanAgainstFill(row, y, size.cols, blankFP);
8250
+ }
8251
+ return resolveChangedSpanAgainstReference(row, y, size.cols);
8252
+ })();
8253
+ if (!span) {
8254
+ if (!skipUnchangedDirtyRows) fingerprintRow(row, y, size.cols);
8255
+ if (!skipUnchangedDirtyRows) {
8256
+ renderRow$1(y, row);
8257
+ paintedExplicitRows.push(y);
8258
+ }
8259
+ continue;
8260
+ }
8261
+ const rewriteTail = !insertedRows.has(y) && shouldRewriteRowTail(row, y, size.cols, currentFP);
8262
+ fingerprintRow(row, y, size.cols);
8263
+ renderRow$1(y, row, span.startX, rewriteTail ? row.length : span.endXExclusive, rewriteTail || span.endXExclusive >= row.length);
8264
+ paintedExplicitRows.push(y);
8265
+ }
8266
+ if (isDebugEnabled() && overlayDirtyRows.length) getDebugLog().render(` Explicit scroll ops overlay rows: [${overlayDirtyRows.join(", ")}]`);
8267
+ profiledRowCount = explicitDirtyRows.length;
8268
+ scrollHandled = true;
8269
+ } else if (useScrollRegions && scrollRowsCandidate) {
8270
+ for (let y = 0; y < size.rows; y++) fingerprintRow(terminal.getRow(y), y, size.cols);
8271
+ const shift = detectScrollShift(size.cols, scrollRowsCandidate, overlayRows);
8272
+ if (shift) {
8273
+ if (currentFP.length === prevFP.length) currentFP.set(prevFP);
8274
+ hasFrameOutput = true;
8275
+ if (isDebugEnabled()) getDebugLog().render(` Scroll region: delta=${shift.delta}, region=${shift.regionStart}-${shift.regionEnd - 1}, new rows=${shift.newRowStart}-${shift.newRowEnd - 1}`);
8276
+ frameParts.push(`\u001B[${shift.regionStart + 1};${shift.regionEnd}r`);
8277
+ if (shift.delta > 0) {
8278
+ frameParts.push(`\u001B[${shift.regionEnd};1H`);
8279
+ frameParts.push(`\u001B[${shift.delta}S`);
8280
+ } else {
8281
+ frameParts.push(`\u001B[${shift.regionStart + 1};1H`);
8282
+ frameParts.push(`\u001B[${-shift.delta}T`);
8283
+ }
8284
+ frameParts.push("\x1B[r");
8285
+ if (shift.delta > 0) {
8286
+ for (let y = shift.regionStart; y < shift.regionEnd - shift.delta; y++) {
8287
+ const dstBase = y * fpCols;
8288
+ const srcBase = (y + shift.delta) * fpCols;
8289
+ for (let x = 0; x < size.cols; x++) currentFP[dstBase + x] = prevFP[srcBase + x];
8290
+ }
8291
+ for (let y = shift.regionEnd - shift.delta; y < shift.regionEnd; y++) {
8292
+ const base = y * fpCols;
8293
+ for (let x = 0; x < size.cols; x++) currentFP[base + x] = blankFP;
8294
+ }
8295
+ } else {
8296
+ const absDelta = -shift.delta;
8297
+ for (let y = shift.regionEnd - 1; y >= shift.regionStart + absDelta; y--) {
8298
+ const dstBase = y * fpCols;
8299
+ const srcBase = (y - absDelta) * fpCols;
8300
+ for (let x = 0; x < size.cols; x++) currentFP[dstBase + x] = prevFP[srcBase + x];
8301
+ }
8302
+ for (let y = shift.regionStart; y < shift.regionStart + absDelta; y++) {
8303
+ const base = y * fpCols;
8304
+ for (let x = 0; x < size.cols; x++) currentFP[base + x] = blankFP;
8305
+ }
8306
+ }
8307
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++) {
8308
+ const row = terminal.getRow(y);
8309
+ fingerprintRow(row, y, size.cols);
8310
+ renderRow$1(y, row);
8311
+ }
8312
+ const extraDirtyRows = overlayRowSet ? shift.extraDirtyRows.filter((y) => !overlayRowSet.has(y) || Boolean(overlayPartialRowSet === null || overlayPartialRowSet === void 0 ? void 0 : overlayPartialRowSet.has(y))) : shift.extraDirtyRows;
8313
+ for (const y of extraDirtyRows) {
8314
+ const row = terminal.getRow(y);
8315
+ const overlayRow = overlayTouchedRowSet === null || overlayTouchedRowSet === void 0 ? void 0 : overlayTouchedRowSet.has(y);
8316
+ if (overlayRow || useConservativeDirtyRowRendering) {
8317
+ fingerprintRow(row, y, size.cols);
8318
+ renderRow$1(y, row);
8319
+ continue;
8320
+ }
8321
+ const span = resolveChangedSpanAgainstReference(row, y, size.cols);
8322
+ if (!span) {
8323
+ if (!skipUnchangedDirtyRows) {
8324
+ fingerprintRow(row, y, size.cols);
8325
+ renderRow$1(y, row);
8326
+ }
8327
+ continue;
8328
+ }
8329
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, currentFP);
8330
+ renderRow$1(y, row, span.startX, rewriteTail ? row.length : span.endXExclusive, rewriteTail || span.endXExclusive >= row.length);
8331
+ fingerprintRow(row, y, size.cols);
8332
+ }
8333
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++) fingerprintRow(terminal.getRow(y), y, size.cols);
8334
+ const renderedRows = new Set();
8335
+ for (let y = shift.newRowStart; y < shift.newRowEnd; y++) renderedRows.add(y);
8336
+ for (const y of extraDirtyRows) renderedRows.add(y);
8337
+ profiledRowCount = renderedRows.size;
8338
+ scrollHandled = true;
8339
+ }
8340
+ }
8341
+ if (!scrollHandled && denseDirtyRows && scrollRowsCandidate && useScrollRegions) {
8342
+ rowsToRender = scrollRowsCandidate;
8343
+ profiledRowCount = rowsToRender.length;
8344
+ if (isDebugEnabled()) getDebugLog().render(` Dense dirty rows fell back to partial render: ${rowsToRender.length} rows`);
8345
+ }
8346
+ if (scrollHandled) {} else if (!rowsToRender) {
6991
8347
  if (isDebugEnabled()) getDebugLog().render(` Full render: ${size.rows} rows`);
6992
8348
  for (let y = 0; y < size.rows; y++) {
6993
8349
  const row = terminal.getRow(y);
8350
+ fingerprintRow(row, y, size.cols);
6994
8351
  renderRow$1(y, row);
6995
8352
  }
6996
8353
  } else {
6997
- const repaintFullDirtyRows = overlayPlaneHasVisibleContent();
6998
- if (isDebugEnabled()) getDebugLog().render(` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(", ")}]${repaintFullDirtyRows ? " (overlay-full-row)" : ""}`);
8354
+ const overlayDirtyRows = overlayTouchedRowSet ? rowsToRender.filter((y) => overlayTouchedRowSet.has(y)) : [];
8355
+ if (isDebugEnabled()) getDebugLog().render(` Partial render: ${rowsToRender.length} dirty rows: [${rowsToRender.join(", ")}]${overlayDirtyRows.length ? ` (overlay-full-row: [${overlayDirtyRows.join(", ")}])` : ""}`);
6999
8356
  for (const y of rowsToRender) {
7000
8357
  const row = terminal.getRow(y);
7001
- if (repaintFullDirtyRows) {
8358
+ fingerprintRow(row, y, size.cols);
8359
+ const overlayRow = overlayTouchedRowSet === null || overlayTouchedRowSet === void 0 ? void 0 : overlayTouchedRowSet.has(y);
8360
+ if (overlayRow || useConservativeDirtyRowRendering) {
7002
8361
  renderRow$1(y, row);
7003
8362
  continue;
7004
8363
  }
7005
- const span = resolveChangedSpan(row, renderedRowCache[y]);
8364
+ const span = resolveChangedSpan(row, y, size.cols);
7006
8365
  if (!span) {
7007
- renderRow$1(y, row);
8366
+ if (!skipUnchangedDirtyRows) renderRow$1(y, row);
7008
8367
  continue;
7009
8368
  }
7010
- renderRow$1(y, row, span.startX, span.endXExclusive, span.endXExclusive >= row.length);
8369
+ const rewriteTail = shouldRewriteRowTail(row, y, size.cols, prevFP);
8370
+ renderRow$1(y, row, span.startX, rewriteTail ? row.length : span.endXExclusive, rewriteTail || span.endXExclusive >= row.length);
7011
8371
  }
7012
8372
  }
7013
8373
  if (!rowsToRender && lastRenderedRows > size.rows) {
@@ -7016,17 +8376,38 @@ function createStdoutRenderer(terminal, options) {
7016
8376
  frameParts.push(SGR_RESET, bgSeq);
7017
8377
  activeStyleKey = bgKey;
7018
8378
  }
7019
- for (let i = 0; i < extraRows; i++) frameParts.push(rowClearToEol[size.rows + i]);
8379
+ for (let i = 0; i < extraRows; i++) {
8380
+ hasFrameOutput = true;
8381
+ frameParts.push(rowClearToEol[size.rows + i]);
8382
+ }
7020
8383
  }
7021
8384
  if (!rowsToRender) lastRenderedRows = size.rows;
7022
- if (!rowsToRender || renderedRowCache.length !== size.rows) renderedRowCache.length = size.rows;
8385
+ const tmpFP = prevFP;
8386
+ prevFP = currentFP;
8387
+ currentFP = tmpFP;
8388
+ fpPrevValid = true;
8389
+ prevOverlayBlockedRows = overlayCoverage.blockedRows;
8390
+ prevOverlayPartialRows = overlayCoverage.partialRows;
8391
+ let emittedCursorPos = null;
7023
8392
  if (getImeAnchor) {
7024
8393
  const anchor = getImeAnchor();
7025
8394
  if (anchor) {
7026
8395
  const { x, y } = clampCellToViewport(anchor, size);
7027
- frameParts.push(`\u001B[${y + 1};${x + 1}H`);
8396
+ if (hasFrameOutput || x !== lastCursorX || y !== lastCursorY) {
8397
+ hasFrameOutput = true;
8398
+ emittedCursorPos = {
8399
+ x,
8400
+ y
8401
+ };
8402
+ frameParts.push(`\u001B[${y + 1};${x + 1}H`);
8403
+ }
7028
8404
  }
7029
8405
  }
8406
+ if (!hasFrameOutput) {
8407
+ if (isDebugEnabled()) getDebugLog().render(" No-op frame skipped");
8408
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutNoOutput();
8409
+ return;
8410
+ }
7030
8411
  if (enableOsc8Links && activeStyle.href) frameParts.push(OSC8_CLOSE);
7031
8412
  frameParts.push(SGR_RESET);
7032
8413
  frameParts.push("\x1B[?7h");
@@ -7034,9 +8415,9 @@ function createStdoutRenderer(terminal, options) {
7034
8415
  const frame = frameParts.join("");
7035
8416
  if (profiler) profiler.recordRender({
7036
8417
  durationMs: profiler.now() - renderStart,
7037
- rows: rowsToRender ? rowsToRender.length : size.rows,
8418
+ rows: profiledRowCount,
7038
8419
  nodes: 0,
7039
- fullRepaint: !rowsToRender,
8420
+ fullRepaint: !rowsToRender && !scrollHandled,
7040
8421
  sorted: dirtySorted
7041
8422
  });
7042
8423
  if (isDebugEnabled()) {
@@ -7134,6 +8515,13 @@ function createStdoutRenderer(terminal, options) {
7134
8515
  {
7135
8516
  const writeMs = performance.now() - writeStart;
7136
8517
  writeEmaMs = writeEmaMs === 0 ? writeMs : writeEmaMs * .85 + writeMs * .15;
8518
+ if (emittedCursorPos) {
8519
+ lastCursorX = emittedCursorPos.x;
8520
+ lastCursorY = emittedCursorPos.y;
8521
+ } else {
8522
+ lastCursorX = null;
8523
+ lastCursorY = null;
8524
+ }
7137
8525
  recordStdoutFrame({
7138
8526
  at: Date.now(),
7139
8527
  bytes: frame.length,
@@ -7146,6 +8534,11 @@ function createStdoutRenderer(terminal, options) {
7146
8534
  bytes: frame.length,
7147
8535
  mode: writeMode === "sync" ? "sync" : writeMode === "chunked" ? "chunked" : "stream"
7148
8536
  });
8537
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutWrite({
8538
+ durationMs: writeMs,
8539
+ bytes: frame.length,
8540
+ mode: writeMode === "sync" ? "sync" : writeMode === "chunked" ? "chunked" : "stream"
8541
+ });
7149
8542
  }
7150
8543
  if (isDebugEnabled()) {
7151
8544
  const totalTime = (performance.now() - renderStart).toFixed(2);
@@ -7158,9 +8551,20 @@ function createStdoutRenderer(terminal, options) {
7158
8551
  * Prevents excessive renders that cause flickering in Bun binaries.
7159
8552
  * Accumulates dirty rows while waiting for next frame to ensure no updates are lost.
7160
8553
  */
7161
- function render(dirtyRows) {
8554
+ function render(dirtyRows, sync, scrollOperations, skipUnchangedDirtyRows = false) {
7162
8555
  if (disposed) return;
7163
8556
  if (isDebugEnabled()) getDebugLog().render(`render() called: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}, pending=${pendingRender}, elapsed=${Date.now() - lastFrameTime}ms`);
8557
+ const isSmallSyncPatch = Boolean(sync && (dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) && dirtyRows.length <= 12 && !(scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length));
8558
+ if (isSmallSyncPatch) skipUnchangedDirtyRows = false;
8559
+ if ((dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) && skipUnchangedDirtyRows && fpPrevValid && !(scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length) && dirtyRows.length < terminal.size().rows) {
8560
+ const cols = terminal.size().cols;
8561
+ const nextDirtyRows = dirtyRows.filter((y) => !rowMatchesPreviousFrame(y, cols));
8562
+ if (nextDirtyRows.length === 0) {
8563
+ if (isDebugEnabled()) getDebugLog().render(`Skipped unchanged dirty commit: ${dirtyRows.length} rows`);
8564
+ return;
8565
+ }
8566
+ dirtyRows = nextDirtyRows;
8567
+ }
7164
8568
  const ensureDirtyBits = (rowCount) => {
7165
8569
  if (!accumulatedDirtyBits || accumulatedDirtyBits.length !== rowCount) {
7166
8570
  accumulatedDirtyBits = new Uint8Array(rowCount);
@@ -7182,12 +8586,43 @@ function createStdoutRenderer(terminal, options) {
7182
8586
  out$1.length = outLen;
7183
8587
  return outLen ? out$1 : null;
7184
8588
  };
8589
+ const buildAccumulatedScrollOperations = () => {
8590
+ return (accumulatedScrollOperations === null || accumulatedScrollOperations === void 0 ? void 0 : accumulatedScrollOperations.length) ? accumulatedScrollOperations : null;
8591
+ };
8592
+ const mergeScrollOperations = (next) => {
8593
+ if (!(next === null || next === void 0 ? void 0 : next.length) || accumulatedAllRows) return;
8594
+ const merged = accumulatedScrollOperations ? accumulatedScrollOperations.slice() : [];
8595
+ for (const op of next) {
8596
+ const startY = Math.floor(op.startY);
8597
+ const endY = Math.floor(op.endY);
8598
+ const delta = Math.trunc(op.delta);
8599
+ if (endY <= startY || delta === 0) continue;
8600
+ const existingIndex = merged.findIndex((existing) => existing.startY === startY && existing.endY === endY);
8601
+ if (existingIndex >= 0) {
8602
+ const existing = merged[existingIndex];
8603
+ const nextDelta = existing.delta + delta;
8604
+ if (nextDelta === 0) merged.splice(existingIndex, 1);
8605
+ else merged[existingIndex] = {
8606
+ startY,
8607
+ endY,
8608
+ delta: nextDelta
8609
+ };
8610
+ } else merged.push({
8611
+ startY,
8612
+ endY,
8613
+ delta
8614
+ });
8615
+ }
8616
+ accumulatedScrollOperations = merged.length ? merged : null;
8617
+ };
7185
8618
  if (!dirtyRows || dirtyRows.length === 0) {
7186
8619
  accumulatedAllRows = true;
7187
8620
  accumulatedDirtyBits = null;
7188
8621
  accumulatedDirtyCount = 0;
7189
8622
  accumulatedDirtyMin = Number.POSITIVE_INFINITY;
7190
8623
  accumulatedDirtyMax = -1;
8624
+ accumulatedScrollOperations = null;
8625
+ accumulatedSkipUnchangedDirtyRows = false;
7191
8626
  } else if (!accumulatedAllRows) {
7192
8627
  const rowCount = terminal.size().rows;
7193
8628
  const bits = ensureDirtyBits(rowCount);
@@ -7201,13 +8636,35 @@ function createStdoutRenderer(terminal, options) {
7201
8636
  if (y > accumulatedDirtyMax) accumulatedDirtyMax = y;
7202
8637
  }
7203
8638
  }
8639
+ mergeScrollOperations(scrollOperations);
8640
+ accumulatedSkipUnchangedDirtyRows && (accumulatedSkipUnchangedDirtyRows = skipUnchangedDirtyRows);
8641
+ }
8642
+ const now$3 = Date.now();
8643
+ const elapsed = now$3 - lastFrameTime;
8644
+ const queuedDelayMs = Math.max(0, MIN_FRAME_MS - elapsed);
8645
+ if (pendingRender && !sync) {
8646
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(queuedDelayMs);
8647
+ return;
8648
+ }
8649
+ if (sync) {
8650
+ if (pendingRender) {
8651
+ pendingRender = false;
8652
+ if (renderTimer) {
8653
+ clearTimeout(renderTimer);
8654
+ renderTimer = null;
8655
+ }
8656
+ }
8657
+ const rows = buildAccumulatedRows();
8658
+ const pendingScrolls = buildAccumulatedScrollOperations();
8659
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(0);
8660
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
8661
+ return;
7204
8662
  }
7205
- if (pendingRender) return;
7206
- const now$2 = Date.now();
7207
- const elapsed = now$2 - lastFrameTime;
7208
8663
  if (elapsed >= MIN_FRAME_MS) {
7209
8664
  const rows = buildAccumulatedRows();
7210
- doRender(rows);
8665
+ const pendingScrolls = buildAccumulatedScrollOperations();
8666
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(0);
8667
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
7211
8668
  } else {
7212
8669
  pendingRender = true;
7213
8670
  if (renderTimer) clearTimeout(renderTimer);
@@ -7215,19 +8672,24 @@ function createStdoutRenderer(terminal, options) {
7215
8672
  renderTimer = null;
7216
8673
  if (!disposed) {
7217
8674
  const rows = buildAccumulatedRows();
7218
- doRender(rows);
8675
+ const pendingScrolls = buildAccumulatedScrollOperations();
8676
+ doRender(rows, pendingScrolls, accumulatedSkipUnchangedDirtyRows);
7219
8677
  }
7220
- }, MIN_FRAME_MS - elapsed);
8678
+ }, queuedDelayMs);
8679
+ cliLatency === null || cliLatency === void 0 || cliLatency.recordStdoutQueued(queuedDelayMs);
7221
8680
  }
7222
8681
  }
8682
+ terminal.setFingerprintFn((ch, style) => {
8683
+ return styleKey$1(style) << 16 | charHash16(ch);
8684
+ });
7223
8685
  if (altScreen && out.isTTY) out.write("\x1B[?1049h");
7224
8686
  if (hideCursor) out.write("\x1B[?25l");
7225
8687
  if (clear) out.write(`${SGR_RESET}${openBg(defaultBg)}\u001B[2J\u001B[H${SGR_RESET}`);
7226
- const off = terminal.on("commit", ({ dirtyRows }) => {
7227
- if (isDebugEnabled()) getDebugLog().render(`Commit event: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}, rows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.join(",")) ?? "all"}`);
7228
- render(dirtyRows);
8688
+ const off = terminal.on("commit", ({ dirtyRows, scrollOperations, sync }) => {
8689
+ if (isDebugEnabled()) getDebugLog().render(`Commit event: dirtyRows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.length) ?? "null"}, rows=${(dirtyRows === null || dirtyRows === void 0 ? void 0 : dirtyRows.join(",")) ?? "all"}${(scrollOperations === null || scrollOperations === void 0 ? void 0 : scrollOperations.length) ? `, scrollOps=${scrollOperations.map((op) => `${op.startY}-${op.endY - 1}:${op.delta}`).join("|")}` : ""}${sync ? " (sync)" : ""}`);
8690
+ render(dirtyRows, sync, scrollOperations, true);
7229
8691
  });
7230
- const resizeSource = (options === null || options === void 0 ? void 0 : options.output) ?? process.stdout;
8692
+ const resizeSource = (options === null || options === void 0 ? void 0 : options.output) ?? process$1.stdout;
7231
8693
  const canTrackResize = Boolean(trackResize && out.isTTY && typeof (resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.on) === "function");
7232
8694
  const onResize = () => {
7233
8695
  const cols = Number(resizeSource === null || resizeSource === void 0 ? void 0 : resizeSource.columns);
@@ -7255,6 +8717,8 @@ function createStdoutRenderer(terminal, options) {
7255
8717
  rows: size.rows
7256
8718
  });
7257
8719
  out.write(`\u001B[${cy + 1};${cx + 1}H`);
8720
+ lastCursorX = cx;
8721
+ lastCursorY = cy;
7258
8722
  }
7259
8723
  /** Show or hide the terminal cursor */
7260
8724
  function showCursor(visible) {
@@ -7288,7 +8752,10 @@ function createStdoutRenderer(terminal, options) {
7288
8752
  if (next.defaultBg && next.defaultBg !== defaultBg) defaultBg = next.defaultBg;
7289
8753
  if ("palette" in next) palette = next.palette;
7290
8754
  styleKeyCache$1 = new WeakMap();
7291
- renderedRowCache = [];
8755
+ fpRows = 0;
8756
+ fpPrevValid = false;
8757
+ prevOverlayBlockedRows = [];
8758
+ prevOverlayPartialRows = [];
7292
8759
  render();
7293
8760
  };
7294
8761
  return {
@@ -7486,12 +8953,22 @@ const rowCache = new WeakMap();
7486
8953
  function segmentsToKey(segments) {
7487
8954
  return segments.map((s) => `${s.key}:${s.text}:${s.cols}:${s.wide}`).join("|");
7488
8955
  }
8956
+ function isPlainStyle(style) {
8957
+ return !style.fg && !style.bg && !style.bold && !style.dim && !style.italic && !style.underline && !style.inverse && !style.href;
8958
+ }
8959
+ function isTransparentBlankRow(segments) {
8960
+ return segments.length === 1 && isPlainStyle(segments[0].style) && segments[0].text.trim().length === 0;
8961
+ }
7489
8962
  function renderRow(terminal, metrics$1, wideScaleX, y, lineEl) {
7490
8963
  const segments = computeRowSegments(terminal, y);
7491
8964
  const newKey = segmentsToKey(segments);
7492
8965
  const cachedKey = rowCache.get(lineEl);
7493
8966
  if (cachedKey === newKey) return;
7494
8967
  rowCache.set(lineEl, newKey);
8968
+ if (isTransparentBlankRow(segments)) {
8969
+ lineEl.replaceChildren();
8970
+ return;
8971
+ }
7495
8972
  const fragment = document.createDocumentFragment();
7496
8973
  for (const seg of segments) {
7497
8974
  const span = document.createElement("span");
@@ -7512,6 +8989,7 @@ function createDomRenderer(terminal, container) {
7512
8989
  container.style.whiteSpace = "pre";
7513
8990
  container.style.display = "inline-block";
7514
8991
  container.style.overflow = "hidden";
8992
+ container.style.position = "relative";
7515
8993
  container.style.userSelect = "text";
7516
8994
  container.style.outline = "none";
7517
8995
  container.style.contain = "layout style";
@@ -7519,21 +8997,93 @@ function createDomRenderer(terminal, container) {
7519
8997
  installAnsiPaletteCssVars(container);
7520
8998
  let metrics$1 = measureCell(container);
7521
8999
  let wideScaleX = 1;
7522
- let lineEls = [];
9000
+ const planeLayers = new Map();
7523
9001
  let raf = 0;
7524
- const pending = new Set();
9002
+ const pending = new Map();
7525
9003
  let disposed = false;
9004
+ function applyPlaneOffset(plane, offsetPx) {
9005
+ const layer = planeLayers.get(plane);
9006
+ if (!layer) return;
9007
+ const next = Number.isFinite(offsetPx) ? offsetPx : 0;
9008
+ if (layer.offsetPx === next) return;
9009
+ layer.offsetPx = next;
9010
+ layer.contentEl.style.transform = next ? `translateY(${next}px)` : "translateY(0px)";
9011
+ }
9012
+ function applyPlaneViewport(plane, viewport) {
9013
+ const layer = planeLayers.get(plane);
9014
+ if (!layer) return;
9015
+ const totalHeight = terminal.size().rows * metrics$1.cellHeight;
9016
+ if (!viewport) {
9017
+ layer.viewportTopPx = 0;
9018
+ layer.viewportHeightPx = null;
9019
+ layer.el.style.clipPath = "none";
9020
+ return;
9021
+ }
9022
+ const topPx = Math.max(0, Math.min(totalHeight, Math.floor(viewport.topPx)));
9023
+ const heightPx = Math.max(0, Math.min(totalHeight - topPx, Math.floor(viewport.heightPx)));
9024
+ layer.viewportTopPx = topPx;
9025
+ layer.viewportHeightPx = heightPx;
9026
+ if (topPx === 0 && heightPx >= totalHeight) {
9027
+ layer.el.style.clipPath = "none";
9028
+ return;
9029
+ }
9030
+ const bottomPx = Math.max(0, totalHeight - topPx - heightPx);
9031
+ layer.el.style.clipPath = `inset(${topPx}px 0px ${bottomPx}px 0px)`;
9032
+ }
7526
9033
  function rebuildLines() {
7527
9034
  const { rows, cols } = terminal.size();
7528
9035
  container.style.width = `${cols * metrics$1.cellWidth}px`;
7529
9036
  container.style.height = `${rows * metrics$1.cellHeight}px`;
7530
9037
  const fragment = document.createDocumentFragment();
7531
- lineEls = Array.from({ length: rows }, () => {
7532
- const line = document.createElement("div");
7533
- line.style.cssText = `height:${metrics$1.cellHeight}px;width:${cols * metrics$1.cellWidth}px;overflow:hidden;contain:layout style`;
7534
- fragment.appendChild(line);
7535
- return line;
7536
- });
9038
+ for (let i = 0; i < TERMINAL_RENDER_PLANES.length; i++) {
9039
+ const plane = TERMINAL_RENDER_PLANES[i];
9040
+ const planeTerminal = getPlaneTerminal(terminal, plane);
9041
+ let layer = planeLayers.get(plane);
9042
+ if (!layer) {
9043
+ const el = document.createElement("div");
9044
+ const contentEl = document.createElement("div");
9045
+ el.dataset.vtPlane = plane;
9046
+ el.style.pointerEvents = "none";
9047
+ contentEl.style.position = "absolute";
9048
+ contentEl.style.left = "0";
9049
+ contentEl.style.top = "0";
9050
+ layer = {
9051
+ el,
9052
+ contentEl,
9053
+ lines: [],
9054
+ offsetPx: 0,
9055
+ terminal: planeTerminal,
9056
+ viewportTopPx: 0,
9057
+ viewportHeightPx: null
9058
+ };
9059
+ planeLayers.set(plane, layer);
9060
+ }
9061
+ layer.terminal = planeTerminal;
9062
+ layer.el.style.position = "absolute";
9063
+ layer.el.style.left = "0";
9064
+ layer.el.style.top = "0";
9065
+ layer.el.style.width = `${cols * metrics$1.cellWidth}px`;
9066
+ layer.el.style.height = `${rows * metrics$1.cellHeight}px`;
9067
+ layer.el.style.overflow = "hidden";
9068
+ layer.el.style.zIndex = String(i);
9069
+ layer.contentEl.style.width = `${cols * metrics$1.cellWidth}px`;
9070
+ layer.contentEl.style.height = `${rows * metrics$1.cellHeight}px`;
9071
+ applyPlaneOffset(plane, layer.offsetPx);
9072
+ applyPlaneViewport(plane, layer.viewportHeightPx == null ? null : {
9073
+ topPx: layer.viewportTopPx,
9074
+ heightPx: layer.viewportHeightPx
9075
+ });
9076
+ const lineFragment = document.createDocumentFragment();
9077
+ layer.lines = Array.from({ length: rows }, () => {
9078
+ const line = document.createElement("div");
9079
+ line.style.cssText = `height:${metrics$1.cellHeight}px;width:${cols * metrics$1.cellWidth}px;overflow:hidden;contain:layout style`;
9080
+ lineFragment.appendChild(line);
9081
+ return line;
9082
+ });
9083
+ layer.contentEl.replaceChildren(lineFragment);
9084
+ layer.el.replaceChildren(layer.contentEl);
9085
+ fragment.appendChild(layer.el);
9086
+ }
7537
9087
  container.replaceChildren(fragment);
7538
9088
  }
7539
9089
  function refresh() {
@@ -7542,22 +9092,46 @@ function createDomRenderer(terminal, container) {
7542
9092
  wideScaleX = wideW > 0 ? Math.max(1, Math.min(2, metrics$1.cellWidth * 2 / wideW)) : 1;
7543
9093
  rebuildLines();
7544
9094
  const size = terminal.size();
7545
- for (let y = 0; y < size.rows; y++) renderRow(terminal, metrics$1, wideScaleX, y, lineEls[y]);
9095
+ for (const plane of TERMINAL_RENDER_PLANES) {
9096
+ const layer = planeLayers.get(plane);
9097
+ if (!layer) continue;
9098
+ for (let y = 0; y < size.rows; y++) renderRow(layer.terminal, metrics$1, wideScaleX, y, layer.lines[y]);
9099
+ }
7546
9100
  }
7547
9101
  function flushPending() {
7548
9102
  raf = 0;
7549
9103
  if (disposed) return;
7550
- for (const y of pending) {
7551
- const line = lineEls[y];
7552
- if (line) renderRow(terminal, metrics$1, wideScaleX, y, line);
9104
+ for (const plane of TERMINAL_RENDER_PLANES) {
9105
+ const layer = planeLayers.get(plane);
9106
+ const rows = pending.get(plane);
9107
+ if (!layer || !(rows === null || rows === void 0 ? void 0 : rows.size)) continue;
9108
+ for (const y of rows) {
9109
+ const line = layer.lines[y];
9110
+ if (line) renderRow(layer.terminal, metrics$1, wideScaleX, y, line);
9111
+ }
9112
+ rows.clear();
7553
9113
  }
7554
- pending.clear();
7555
9114
  }
7556
- const offCommit = terminal.on("commit", ({ dirtyRows }) => {
9115
+ const offCommit = terminal.on("commit", ({ dirtyRows, planes }) => {
9116
+ const activePlanes = (planes === null || planes === void 0 ? void 0 : planes.length) ? planes : TERMINAL_RENDER_PLANES;
7557
9117
  if (dirtyRows === null) {
7558
9118
  const size = terminal.size();
7559
- for (let y = 0; y < size.rows; y++) pending.add(y);
7560
- } else for (const y of dirtyRows) pending.add(y);
9119
+ for (const plane of activePlanes) {
9120
+ let rows = pending.get(plane);
9121
+ if (!rows) {
9122
+ rows = new Set();
9123
+ pending.set(plane, rows);
9124
+ }
9125
+ for (let y = 0; y < size.rows; y++) rows.add(y);
9126
+ }
9127
+ } else for (const plane of activePlanes) {
9128
+ let rows = pending.get(plane);
9129
+ if (!rows) {
9130
+ rows = new Set();
9131
+ pending.set(plane, rows);
9132
+ }
9133
+ for (const y of dirtyRows) rows.add(y);
9134
+ }
7561
9135
  if (!raf) {
7562
9136
  raf = -1;
7563
9137
  const id = requestAnimationFrame(() => {
@@ -7583,7 +9157,14 @@ function createDomRenderer(terminal, container) {
7583
9157
  offResize();
7584
9158
  if (raf > 0) cancelAnimationFrame(raf);
7585
9159
  pending.clear();
9160
+ planeLayers.clear();
7586
9161
  container.replaceChildren();
9162
+ },
9163
+ setPlaneOffset(plane, offsetPx) {
9164
+ applyPlaneOffset(plane, offsetPx);
9165
+ },
9166
+ setPlaneViewport(plane, viewport) {
9167
+ applyPlaneViewport(plane, viewport);
7587
9168
  }
7588
9169
  };
7589
9170
  }
@@ -7592,7 +9173,7 @@ function createDomRenderer(terminal, container) {
7592
9173
  //#region src/runtime/index.ts
7593
9174
  function isNodeLike() {
7594
9175
  var _process$versions;
7595
- return typeof (process === null || process === void 0 || (_process$versions = process.versions) === null || _process$versions === void 0 ? void 0 : _process$versions.node) === "string";
9176
+ return typeof (process$1 === null || process$1 === void 0 || (_process$versions = process$1.versions) === null || _process$versions === void 0 ? void 0 : _process$versions.node) === "string";
7596
9177
  }
7597
9178
  function detectEnv() {
7598
9179
  const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
@@ -7732,26 +9313,39 @@ function useTerminal() {
7732
9313
  //#endregion
7733
9314
  //#region src/vue/composables/use-render-node.ts
7734
9315
  const pendingInvalidateByScheduler = new WeakMap();
7735
- function requestBatchedInvalidate(scheduler, plane) {
9316
+ function mergePriority(prev, next) {
9317
+ if (prev === "high" || next === "high") return "high";
9318
+ if (prev === "normal" || next === "normal") return "normal";
9319
+ return "low";
9320
+ }
9321
+ function requestBatchedInvalidate(scheduler, plane, priority) {
7736
9322
  let state = pendingInvalidateByScheduler.get(scheduler);
7737
9323
  if (!state) {
7738
9324
  state = {
7739
9325
  queued: false,
7740
- plane: null
9326
+ plane: null,
9327
+ priority: "low"
7741
9328
  };
7742
9329
  pendingInvalidateByScheduler.set(scheduler, state);
7743
9330
  }
7744
9331
  if (state.queued) {
7745
9332
  state.plane = state.plane == null || state.plane === plane ? plane : null;
9333
+ state.priority = mergePriority(state.priority, priority);
7746
9334
  return;
7747
9335
  }
7748
9336
  state.plane = plane;
9337
+ state.priority = priority;
7749
9338
  state.queued = true;
7750
9339
  queueMicrotask(() => {
7751
9340
  state.queued = false;
7752
9341
  const queuedPlane = state.plane;
9342
+ const queuedPriority = state.priority;
7753
9343
  state.plane = null;
7754
- scheduler.invalidate(queuedPlane ? { plane: queuedPlane } : void 0);
9344
+ state.priority = "low";
9345
+ scheduler.invalidate(queuedPlane || queuedPriority !== "normal" ? {
9346
+ plane: queuedPlane ?? void 0,
9347
+ priority: queuedPriority
9348
+ } : void 0);
7755
9349
  });
7756
9350
  }
7757
9351
  function useRenderNode(getOptions) {
@@ -7759,11 +9353,13 @@ function useRenderNode(getOptions) {
7759
9353
  const parentStack = useRenderStack();
7760
9354
  const plane = inject(RenderPlaneContextKey, ref("default"));
7761
9355
  const id = ref(null);
9356
+ const lastPlane = ref(plane.value);
7762
9357
  const options = computed(() => getOptions());
7763
9358
  const stop = watchEffect(() => {
7764
9359
  const opt = options.value;
7765
9360
  opt.deps;
7766
9361
  const stack = opt.stack ?? parentStack.value;
9362
+ lastPlane.value = plane.value;
7767
9363
  if (!stack) return;
7768
9364
  if (!id.value) {
7769
9365
  const node = render.register({
@@ -7774,23 +9370,24 @@ function useRenderNode(getOptions) {
7774
9370
  paint: opt.paint
7775
9371
  });
7776
9372
  id.value = node.id;
7777
- requestBatchedInvalidate(scheduler, plane.value);
9373
+ requestBatchedInvalidate(scheduler, plane.value, opt.priority ?? "normal");
7778
9374
  return;
7779
9375
  }
7780
9376
  render.update(id.value, {
7781
9377
  stack,
7782
9378
  zIndex: opt.zIndex ?? 0,
7783
9379
  rect: opt.rect ?? null,
9380
+ dirtyRowsHint: opt.dirtyRowsHint,
7784
9381
  plane: plane.value,
7785
9382
  paint: opt.paint
7786
9383
  });
7787
- requestBatchedInvalidate(scheduler, plane.value);
9384
+ requestBatchedInvalidate(scheduler, plane.value, opt.priority ?? "normal");
7788
9385
  });
7789
9386
  onBeforeUnmount(() => {
7790
9387
  stop();
7791
9388
  if (id.value) {
7792
9389
  render.unregister(id.value);
7793
- requestBatchedInvalidate(scheduler, plane.value);
9390
+ requestBatchedInvalidate(scheduler, lastPlane.value, "normal");
7794
9391
  }
7795
9392
  });
7796
9393
  return { id };
@@ -8976,11 +10573,14 @@ function mentionIndexAt$1(value, mentionToken, index) {
8976
10573
  return countMentionTokens$1(value, mentionToken, index);
8977
10574
  }
8978
10575
  function createPromptMentionPlugin(options = {}) {
10576
+ const latency = getCliLatencyProfiler();
8979
10577
  return {
8980
10578
  name: "promptMention",
8981
10579
  install: (ctx) => {
8982
10580
  const getProps = () => ctx.getProps();
8983
10581
  const promptOverlayStack = ctx.render.createStack(ctx.render.rootStack, 1e4);
10582
+ let lastTopClampedRect = null;
10583
+ let lastPromptRect = null;
8984
10584
  const derivedStyleCache = new WeakMap();
8985
10585
  const reactiveProps = reactive({
8986
10586
  promptSuggestions: getProps().promptSuggestions,
@@ -9028,6 +10628,20 @@ function createPromptMentionPlugin(options = {}) {
9028
10628
  multilineToken: "",
9029
10629
  mentionToken: ctx.mentionToken
9030
10630
  });
10631
+ watch(() => {
10632
+ if (!ctx.visible.value || !promptMention.promptVisible.value) return null;
10633
+ const rect = promptMention.promptRect.value;
10634
+ return [
10635
+ Math.floor(rect.x),
10636
+ Math.floor(rect.y),
10637
+ Math.floor(rect.w),
10638
+ Math.floor(rect.h)
10639
+ ].join(":");
10640
+ }, (nextRectKey, prevRectKey) => {
10641
+ if (!prevRectKey || nextRectKey === prevRectKey) return;
10642
+ ctx.render.invalidatePlane("default");
10643
+ ctx.scheduler.invalidate({ priority: "high" });
10644
+ });
9031
10645
  ctx.registerChipStyleProvider({
9032
10646
  getStyle: (baseStyle, chip) => {
9033
10647
  if (chip.kind !== "mention") return null;
@@ -9105,6 +10719,12 @@ function createPromptMentionPlugin(options = {}) {
9105
10719
  query: ctx0.query
9106
10720
  });
9107
10721
  if (handled !== false) {
10722
+ const value = ctx.getValue();
10723
+ const before$1 = value.slice(0, ctx0.tokenStart);
10724
+ const after$1 = value.slice(ctx0.tokenEnd);
10725
+ const nextValue$1 = `${before$1}${after$1}`;
10726
+ ctx.pushUndoSnapshot(nextValue$1);
10727
+ ctx.applyEdit(nextValue$1, ctx0.tokenStart);
9108
10728
  promptMention.promptSuppressedKey.value = ctx0.key;
9109
10729
  ctx.scheduler.invalidate();
9110
10730
  return;
@@ -9141,8 +10761,17 @@ function createPromptMentionPlugin(options = {}) {
9141
10761
  }
9142
10762
  function handlePromptKeydown(e) {
9143
10763
  if (!promptMention.promptVisible.value) return false;
10764
+ const markPromptOperation = (suffix) => {
10765
+ const ctx0 = promptMention.activeContext.value;
10766
+ const mentionTrigger = getProps().mentionTrigger || "@";
10767
+ const skillTrigger = getProps().skillTrigger || "";
10768
+ const tokenText = String((ctx0 === null || ctx0 === void 0 ? void 0 : ctx0.tokenText) ?? "");
10769
+ const scope = tokenText.startsWith(mentionTrigger) ? "prompt.mention" : skillTrigger && tokenText.startsWith(skillTrigger) ? "prompt.skill" : "prompt.command";
10770
+ latency === null || latency === void 0 || latency.markOperation(`${scope}.${suffix}`);
10771
+ };
9144
10772
  const len = promptMention.promptMatches.value.length;
9145
10773
  if (e.key === "ArrowDown") {
10774
+ markPromptOperation("navigate.next");
9146
10775
  e.preventDefault();
9147
10776
  if (len <= 0) promptMention.promptActive.value = 0;
9148
10777
  else if (promptMention.promptActive.value >= len - 1) promptMention.promptActive.value = 0;
@@ -9151,6 +10780,7 @@ function createPromptMentionPlugin(options = {}) {
9151
10780
  return true;
9152
10781
  }
9153
10782
  if (e.key === "ArrowUp") {
10783
+ markPromptOperation("navigate.prev");
9154
10784
  e.preventDefault();
9155
10785
  if (len <= 0) promptMention.promptActive.value = 0;
9156
10786
  else if (promptMention.promptActive.value <= 0) promptMention.promptActive.value = len - 1;
@@ -9172,11 +10802,13 @@ function createPromptMentionPlugin(options = {}) {
9172
10802
  const typed = String((ctx0 === null || ctx0 === void 0 ? void 0 : ctx0.tokenText) ?? "");
9173
10803
  const candidate = String((match === null || match === void 0 || (_match$item = match.item) === null || _match$item === void 0 ? void 0 : _match$item.value) ?? "");
9174
10804
  if (!typed || !candidate.startsWith(typed)) return false;
10805
+ markPromptOperation("accept");
9175
10806
  e.preventDefault();
9176
10807
  acceptPrompt(promptMention.promptActive.value);
9177
10808
  return true;
9178
10809
  }
9179
10810
  if (e.key === "Tab" && (isMention || isSkill)) {
10811
+ markPromptOperation("accept");
9180
10812
  e.preventDefault();
9181
10813
  if (len === 0) return true;
9182
10814
  const list = promptMention.promptMatches.value;
@@ -9186,12 +10818,14 @@ function createPromptMentionPlugin(options = {}) {
9186
10818
  return true;
9187
10819
  }
9188
10820
  if (len === 0) return false;
10821
+ markPromptOperation("accept");
9189
10822
  e.preventDefault();
9190
10823
  acceptPrompt(promptMention.promptActive.value);
9191
10824
  return true;
9192
10825
  }
9193
10826
  if (e.key === "Escape") {
9194
10827
  var _promptMention$active;
10828
+ markPromptOperation("dismiss");
9195
10829
  e.preventDefault();
9196
10830
  promptMention.promptSuppressedKey.value = ((_promptMention$active = promptMention.activeContext.value) === null || _promptMention$active === void 0 ? void 0 : _promptMention$active.key) ?? null;
9197
10831
  ctx.scheduler.invalidate();
@@ -9203,6 +10837,20 @@ function createPromptMentionPlugin(options = {}) {
9203
10837
  useRenderNode(() => ({
9204
10838
  zIndex: getProps().zIndex + 5,
9205
10839
  stack: promptOverlayStack,
10840
+ dirtyRowsHint: (() => {
10841
+ const currentRect = ctx.visible.value && promptMention.promptVisible.value ? {
10842
+ y: Math.floor(promptMention.promptRect.value.y),
10843
+ h: Math.max(0, Math.floor(promptMention.promptRect.value.h))
10844
+ } : null;
10845
+ const rects = [lastPromptRect, currentRect].filter(Boolean);
10846
+ if (!rects.length) return void 0;
10847
+ const maxRows = ctx.terminal.size().rows;
10848
+ const start = Math.max(0, Math.min(...rects.map((rect) => rect.y)) - 1);
10849
+ const end = Math.min(maxRows, Math.max(...rects.map((rect) => rect.y + rect.h)) + 1);
10850
+ if (end <= start) return void 0;
10851
+ return Array.from({ length: end - start }, (_, index) => start + index);
10852
+ })(),
10853
+ priority: "high",
9206
10854
  rect: ctx.visible.value && promptMention.promptVisible.value ? promptMention.promptRect.value : {
9207
10855
  x: 0,
9208
10856
  y: 0,
@@ -9229,6 +10877,10 @@ function createPromptMentionPlugin(options = {}) {
9229
10877
  if (!ctx.visible.value || !promptMention.promptVisible.value) return;
9230
10878
  const r = promptMention.promptRect.value;
9231
10879
  if (r.w < 3 || r.h < 3) return;
10880
+ lastPromptRect = {
10881
+ y: Math.floor(r.y),
10882
+ h: Math.max(0, Math.floor(r.h))
10883
+ };
9232
10884
  const base = getProps().style ?? ctx.defaultStyle.value;
9233
10885
  const selectedOverride = { bg: "blue" };
9234
10886
  const styleKey$1 = base && typeof base === "object" ? base : null;
@@ -9278,6 +10930,19 @@ function createPromptMentionPlugin(options = {}) {
9278
10930
  const contentW = Math.max(0, innerW - sidePad * 2);
9279
10931
  const contentX = x0 + 1 + sidePad;
9280
10932
  const borderStyle = derived.borderStyle;
10933
+ if (lastTopClampedRect && y0 > 0) {
10934
+ ctx.terminal.write(spaces(lastTopClampedRect.w), {
10935
+ x: lastTopClampedRect.x,
10936
+ y: 0,
10937
+ style: getProps().style ?? ctx.defaultStyle.value
10938
+ });
10939
+ lastTopClampedRect = null;
10940
+ }
10941
+ if (y0 === 0) lastTopClampedRect = {
10942
+ x: x0,
10943
+ w
10944
+ };
10945
+ else lastTopClampedRect = null;
9281
10946
  ctx.terminal.write(`┌${repeatChar("─", innerW)}┐`, {
9282
10947
  x: x0,
9283
10948
  y: y0,
@@ -11543,6 +13208,8 @@ const TerminalProvider = defineComponent({
11543
13208
  let rafToken = 0;
11544
13209
  let pendingInvalidate = false;
11545
13210
  let timer = null;
13211
+ let holdNormalInvalidates = false;
13212
+ let holdReleaseToken = 0;
11546
13213
  let pendingInvalidateAllPlanes = false;
11547
13214
  const pendingInvalidatePlanes = new Set();
11548
13215
  let imeComposing = false;
@@ -11571,11 +13238,14 @@ const TerminalProvider = defineComponent({
11571
13238
  pendingInvalidatePlanes.clear();
11572
13239
  return activePlanes;
11573
13240
  }
11574
- function flush() {
13241
+ function flush(sync = false) {
11575
13242
  if (unmounting) return;
11576
13243
  const activePlanes = takeActivePlanes();
11577
13244
  render.render({ activePlanes });
11578
- terminal.commit({ planes: activePlanes });
13245
+ terminal.commit({
13246
+ planes: activePlanes,
13247
+ sync
13248
+ });
11579
13249
  updateImePositionAfterFlush === null || updateImePositionAfterFlush === void 0 || updateImePositionAfterFlush();
11580
13250
  }
11581
13251
  function clearTimer() {
@@ -11583,6 +13253,16 @@ const TerminalProvider = defineComponent({
11583
13253
  clearTimeout(timer);
11584
13254
  timer = null;
11585
13255
  }
13256
+ function scheduleNormalInvalidateRelease() {
13257
+ const token = ++holdReleaseToken;
13258
+ nextTick(() => {
13259
+ if (token !== holdReleaseToken) return;
13260
+ holdNormalInvalidates = false;
13261
+ if (unmounting || !pendingInvalidate) return;
13262
+ pendingInvalidate = false;
13263
+ invalidate({ priority: "normal" });
13264
+ });
13265
+ }
11586
13266
  function flushNow() {
11587
13267
  if (unmounting) return;
11588
13268
  pendingInvalidate = false;
@@ -11592,7 +13272,9 @@ const TerminalProvider = defineComponent({
11592
13272
  raf = 0;
11593
13273
  }
11594
13274
  clearTimer();
11595
- flush();
13275
+ holdNormalInvalidates = true;
13276
+ scheduleNormalInvalidateRelease();
13277
+ flush(true);
11596
13278
  }
11597
13279
  function invalidate(options) {
11598
13280
  if (unmounting) return;
@@ -11603,6 +13285,10 @@ const TerminalProvider = defineComponent({
11603
13285
  flushNow();
11604
13286
  return;
11605
13287
  }
13288
+ if (holdNormalInvalidates) {
13289
+ pendingInvalidate = true;
13290
+ return;
13291
+ }
11606
13292
  if (priority === "low") {
11607
13293
  if (timer || raf) return;
11608
13294
  profiler === null || profiler === void 0 || profiler.recordInvalidate({ plane: (options === null || options === void 0 ? void 0 : options.plane) ?? null });
@@ -12145,6 +13831,8 @@ const TerminalProvider = defineComponent({
12145
13831
  onBeforeUnmount(() => {
12146
13832
  unmounting = true;
12147
13833
  offCommit === null || offCommit === void 0 || offCommit();
13834
+ clearTimer();
13835
+ holdReleaseToken++;
12148
13836
  if (raf > 0) cancelAnimationFrame(raf);
12149
13837
  raf = 0;
12150
13838
  scope.stop();
@@ -13073,7 +14761,10 @@ const TInput = defineComponent({
13073
14761
  emit("update:modelValue", nextValue);
13074
14762
  emit("input", nextValue);
13075
14763
  if (commit) emit("change", nextValue);
13076
- scheduler.invalidate();
14764
+ scheduler.flushNow();
14765
+ nextTick(() => {
14766
+ scheduler.flushNow();
14767
+ });
13077
14768
  }
13078
14769
  function applyMove(nextCursor, extend) {
13079
14770
  const prev = cursor.value;
@@ -13090,7 +14781,7 @@ const TInput = defineComponent({
13090
14781
  } else desiredCol.value = indexToLineCellColInline(value, props.multilineTexts, props.mentions, cursor.value).col;
13091
14782
  ensureCursorVisible();
13092
14783
  syncImeAnchorNow();
13093
- scheduler.invalidate();
14784
+ scheduler.flushNow();
13094
14785
  }
13095
14786
  watch([
13096
14787
  () => absRect.value.w,
@@ -13209,7 +14900,11 @@ const TInput = defineComponent({
13209
14900
  terminal,
13210
14901
  scheduler,
13211
14902
  defaultStyle,
13212
- render,
14903
+ render: {
14904
+ rootStack: render.rootStack,
14905
+ createStack: render.createStack,
14906
+ invalidatePlane: render.invalidatePlane
14907
+ },
13213
14908
  visible,
13214
14909
  rawAbsRect,
13215
14910
  eventZ,
@@ -13306,7 +15001,7 @@ const TInput = defineComponent({
13306
15001
  emitMentions(nextMentions);
13307
15002
  ensureCursorVisible();
13308
15003
  syncImeAnchorNow();
13309
- scheduler.invalidate();
15004
+ scheduler.flushNow();
13310
15005
  }
13311
15006
  function removePendingPasteImage(placeholderPath) {
13312
15007
  const current = getCurrentMentions();
@@ -13966,10 +15661,12 @@ const TInput = defineComponent({
13966
15661
  clearAll();
13967
15662
  return;
13968
15663
  }
13969
- focused.value = false;
13970
- anchor.value = null;
13971
- emit("blur");
13972
- scheduler.invalidate();
15664
+ const manager = events.value;
15665
+ if (manager && manager.getFocused()) {
15666
+ manager.focus(null);
15667
+ return;
15668
+ }
15669
+ applyBlur();
13973
15670
  return;
13974
15671
  }
13975
15672
  if (isPrintableKey(e)) {
@@ -14207,6 +15904,7 @@ const TInput = defineComponent({
14207
15904
  var _chipStyleProvider$va;
14208
15905
  return {
14209
15906
  zIndex: props.zIndex,
15907
+ priority: pendingValue.value != null ? "high" : "normal",
14210
15908
  rect: visible.value ? absRect.value : {
14211
15909
  x: 0,
14212
15910
  y: 0,
@@ -14239,6 +15937,7 @@ const TInput = defineComponent({
14239
15937
  blinkOn.value,
14240
15938
  defaultStyle.value,
14241
15939
  ((_chipStyleProvider$va = chipStyleProvider.value) === null || _chipStyleProvider$va === void 0 ? void 0 : _chipStyleProvider$va.version.value) ?? 0,
15940
+ pendingValue.value,
14242
15941
  props.skillHighlightStyle,
14243
15942
  props.skillTrigger
14244
15943
  ],
@@ -14857,25 +16556,29 @@ const TJsonEditor = defineComponent({
14857
16556
  //#endregion
14858
16557
  //#region src/vue/utils/wheel-scroll.ts
14859
16558
  const LINE_UNIT_THRESHOLD = 3;
14860
- const PIXELS_PER_LINE = 100;
16559
+ const PIXELS_PER_LINE = .75;
14861
16560
  const ACCEL_WINDOW_MS = 120;
14862
- const MAX_ACCEL = 2.5;
16561
+ const MAX_ACCEL = 26;
14863
16562
  const EDGE_BOUNCE_MS = 120;
14864
16563
  const MIN_TICK_INTERVAL_MS = 6;
14865
16564
  function clamp$3(n, min, max) {
14866
16565
  return Math.max(min, Math.min(max, n));
14867
16566
  }
14868
- function isLineUnitDelta(deltaY) {
16567
+ function isLineUnitDelta(deltaY, mode = "auto") {
16568
+ if (mode === "line") return true;
16569
+ if (mode === "pixel") return false;
14869
16570
  const abs = Math.abs(deltaY);
14870
16571
  return abs > 0 && abs <= LINE_UNIT_THRESHOLD && Number.isInteger(deltaY);
14871
16572
  }
14872
- function normalizeDelta(deltaY) {
16573
+ function normalizeDelta(deltaY, mode = "auto") {
16574
+ if (mode === "line") return deltaY;
16575
+ if (mode === "pixel") return deltaY / PIXELS_PER_LINE;
14873
16576
  const abs = Math.abs(deltaY);
14874
16577
  if (abs <= LINE_UNIT_THRESHOLD && Number.isInteger(deltaY)) return deltaY;
14875
16578
  return deltaY / PIXELS_PER_LINE;
14876
16579
  }
14877
- function accelFactor(now$2, lastAt) {
14878
- const dt = now$2 - lastAt;
16580
+ function accelFactor(now$3, lastAt) {
16581
+ const dt = now$3 - lastAt;
14879
16582
  if (!Number.isFinite(dt) || dt <= 0 || dt > ACCEL_WINDOW_MS) return 1;
14880
16583
  const t = 1 - dt / ACCEL_WINDOW_MS;
14881
16584
  return 1 + t * (MAX_ACCEL - 1);
@@ -14885,15 +16588,17 @@ function createWheelScrollState() {
14885
16588
  accumulator: 0,
14886
16589
  lastAt: 0,
14887
16590
  lastEdgeDir: 0,
14888
- lastEdgeAt: 0
16591
+ lastEdgeAt: 0,
16592
+ lastLineUnitDir: 0
14889
16593
  };
14890
16594
  }
14891
- function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$2 = Date.now()) {
16595
+ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$3 = Date.now(), deltaMode = "auto", options = {}) {
14892
16596
  if (!Number.isFinite(deltaY) || deltaY === 0 || maxTop <= 0) {
14893
16597
  if (maxTop <= 0) {
14894
16598
  state.accumulator = 0;
14895
16599
  state.lastEdgeDir = 0;
14896
16600
  state.lastEdgeAt = 0;
16601
+ state.lastLineUnitDir = 0;
14897
16602
  }
14898
16603
  return {
14899
16604
  nextTop: scrollTop,
@@ -14901,16 +16606,18 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$2 = Date.now())
14901
16606
  lines: 0
14902
16607
  };
14903
16608
  }
14904
- const dt = state.lastAt ? now$2 - state.lastAt : Infinity;
14905
- const lineUnits = isLineUnitDelta(deltaY);
14906
- if (lineUnits && dt !== Infinity && dt >= 0 && dt < MIN_TICK_INTERVAL_MS) return {
16609
+ const dt = state.lastAt ? now$3 - state.lastAt : Infinity;
16610
+ const lineUnits = isLineUnitDelta(deltaY, deltaMode);
16611
+ const inputDir = Math.sign(normalizeDelta(deltaY, deltaMode));
16612
+ if (lineUnits && dt !== Infinity && dt >= 0 && dt < MIN_TICK_INTERVAL_MS && inputDir === state.lastLineUnitDir) return {
14907
16613
  nextTop: scrollTop,
14908
16614
  dir: 0,
14909
16615
  lines: 0
14910
16616
  };
14911
- const accel = lineUnits ? 1 : state.lastAt ? accelFactor(now$2, state.lastAt) : 1;
14912
- state.lastAt = now$2;
14913
- state.accumulator += normalizeDelta(deltaY) * accel;
16617
+ state.lastLineUnitDir = lineUnits ? inputDir : 0;
16618
+ const accel = lineUnits ? 1 : options.disableAcceleration ? 1 : state.lastAt ? accelFactor(now$3, state.lastAt) : 1;
16619
+ state.lastAt = now$3;
16620
+ state.accumulator += normalizeDelta(deltaY, deltaMode) * accel;
14914
16621
  const lines = Math.trunc(state.accumulator);
14915
16622
  if (lines === 0) return {
14916
16623
  nextTop: scrollTop,
@@ -14923,7 +16630,7 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$2 = Date.now())
14923
16630
  const atBottom = scrollTop >= maxTop;
14924
16631
  if (dir < 0 && atTop || dir > 0 && atBottom) {
14925
16632
  state.lastEdgeDir = dir;
14926
- state.lastEdgeAt = now$2;
16633
+ state.lastEdgeAt = now$3;
14927
16634
  state.accumulator = 0;
14928
16635
  return {
14929
16636
  nextTop: scrollTop,
@@ -14931,7 +16638,7 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$2 = Date.now())
14931
16638
  lines: 0
14932
16639
  };
14933
16640
  }
14934
- if (state.lastEdgeDir !== 0 && now$2 - state.lastEdgeAt < EDGE_BOUNCE_MS && dir === -state.lastEdgeDir && (atTop || atBottom)) return {
16641
+ if (state.lastEdgeDir !== 0 && now$3 - state.lastEdgeAt < EDGE_BOUNCE_MS && dir === -state.lastEdgeDir && (atTop || atBottom)) return {
14935
16642
  nextTop: scrollTop,
14936
16643
  dir: 0,
14937
16644
  lines: 0
@@ -14960,6 +16667,26 @@ function applyWheelScroll(state, deltaY, scrollTop, maxTop, now$2 = Date.now())
14960
16667
  function clamp$2(n, min, max) {
14961
16668
  return Math.max(min, Math.min(max, n));
14962
16669
  }
16670
+ function getWheelScrollInput$1(e) {
16671
+ const deltaY = Number(e.deltaY ?? 0);
16672
+ const deltaMode = typeof e.deltaMode === "number" ? e.deltaMode : void 0;
16673
+ if (Number.isInteger(deltaY) && deltaY !== 0 && Math.abs(deltaY) >= 100 && Math.abs(deltaY) % 100 === 0 && (deltaMode == null || deltaMode === 0)) return {
16674
+ deltaY: deltaY / 100,
16675
+ mode: "line"
16676
+ };
16677
+ if (deltaMode === 1) return {
16678
+ deltaY,
16679
+ mode: "line"
16680
+ };
16681
+ if (deltaMode === 0) return {
16682
+ deltaY,
16683
+ mode: "pixel"
16684
+ };
16685
+ return {
16686
+ deltaY,
16687
+ mode: "auto"
16688
+ };
16689
+ }
14963
16690
  const TList = defineComponent({
14964
16691
  name: "TList",
14965
16692
  props: {
@@ -15152,11 +16879,12 @@ const TList = defineComponent({
15152
16879
  if (idx >= 0 && idx < props.items.length) commit(idx);
15153
16880
  },
15154
16881
  wheel: (e) => {
15155
- const delta = e.deltaY ?? 0;
16882
+ const { deltaY, mode } = getWheelScrollInput$1(e);
16883
+ const delta = deltaY;
15156
16884
  if (!delta) return;
15157
16885
  const h$1 = Math.max(0, props.h);
15158
16886
  const maxTop = Math.max(0, props.items.length - h$1);
15159
- const { nextTop, dir } = applyWheelScroll(wheelState, delta, scrollTop.value, maxTop);
16887
+ const { nextTop, dir } = applyWheelScroll(wheelState, delta, scrollTop.value, maxTop, Date.now(), mode);
15160
16888
  if (!dir || nextTop === scrollTop.value) return;
15161
16889
  scrollTop.value = nextTop;
15162
16890
  const visibleStart = nextTop;
@@ -15493,6 +17221,26 @@ const TMultilineModal = defineComponent({
15493
17221
  function clamp$1(n, min, max) {
15494
17222
  return Math.max(min, Math.min(max, n));
15495
17223
  }
17224
+ function getWheelScrollInput(e) {
17225
+ const deltaY = Number(e.deltaY ?? 0);
17226
+ const deltaMode = typeof e.deltaMode === "number" ? e.deltaMode : void 0;
17227
+ if (Number.isInteger(deltaY) && deltaY !== 0 && Math.abs(deltaY) >= 100 && Math.abs(deltaY) % 100 === 0 && (deltaMode == null || deltaMode === 0)) return {
17228
+ deltaY: deltaY / 100,
17229
+ mode: "line"
17230
+ };
17231
+ if (deltaMode === 1) return {
17232
+ deltaY,
17233
+ mode: "line"
17234
+ };
17235
+ if (deltaMode === 0) return {
17236
+ deltaY,
17237
+ mode: "pixel"
17238
+ };
17239
+ return {
17240
+ deltaY,
17241
+ mode: "auto"
17242
+ };
17243
+ }
15496
17244
  function isCtrlLike(e) {
15497
17245
  return Boolean(e.ctrlKey || e.metaKey);
15498
17246
  }
@@ -15910,11 +17658,12 @@ const TPathPicker = defineComponent({
15910
17658
  await trySelect(s.absPath);
15911
17659
  },
15912
17660
  wheel: (e) => {
15913
- const delta = e.deltaY ?? 0;
17661
+ const { deltaY, mode } = getWheelScrollInput(e);
17662
+ const delta = deltaY;
15914
17663
  if (!delta) return;
15915
17664
  const h$1 = Math.max(0, listH.value);
15916
17665
  const maxTop = Math.max(0, suggestions.value.length - h$1);
15917
- const { nextTop, dir } = applyWheelScroll(wheelState, delta, scrollTop.value, maxTop);
17666
+ const { nextTop, dir } = applyWheelScroll(wheelState, delta, scrollTop.value, maxTop, Date.now(), mode);
15918
17667
  if (!dir || nextTop === scrollTop.value) return;
15919
17668
  scrollTop.value = nextTop;
15920
17669
  const visibleStart = nextTop;
@@ -16986,4 +18735,4 @@ const TRouterView = defineComponent({
16986
18735
  });
16987
18736
 
16988
18737
  //#endregion
16989
- export { ANSI16_COLORS, ANSI256_COLORS, ANSI8_COLORS, SGR_RESET, TAnchor, TBox, TDebugOverlay, TDialog, TERMINAL_RENDER_PLANES, TFlow, TInput, TInputBox, TInputPluginsContextKey, TJsonEditor, TList, TMultilineModal, TPathPicker, TRenderLayer, TRenderPlane, TRouterView, TSelect, TText, TTransition, TView, TerminalProvider, ansiColors, ansiStyles, applyWheelScroll, charCellWidth, clearTextCaches, createCliEventManager, createDefaultTInputHostAdapter, createDomRenderer, createEventManager, createNodeMentionPathProvider, createNodePathPickerProvider, createPromptMentionPlugin, createRuntime, createStdinDriver, createStdoutRenderer, createTInputHostPlugin, createTerminal, createTerminalApp, createTerminalRouter, createTextRestrictionPlugin, createTraceStore, createWheelScrollState, defaultTInputHostPlugin, detectTerminalColorCapability, formatInlineCellLine, getStdoutRendererMetrics, lintJsonText, normalizeNewlines, padEndByCells, parseAnsiSgr, readEventLog, resolveUserPath$1 as resolveUserPath, rgbToAnsi256, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, suggestPaths, textCellWidth, truecolorBgOpen, truecolorFgOpen, useLayout, useRenderNode, useRoute, useRouter, useTerminal, useTerminalNode, useTerminalRuntime, useVisibility, wrapByCells, writeEventLog, writeSnapshot };
18738
+ export { ANSI16_COLORS, ANSI256_COLORS, ANSI8_COLORS, SGR_RESET, TAnchor, TBox, TDebugOverlay, TDialog, TERMINAL_RENDER_PLANES, TFlow, TInput, TInputBox, TInputPluginsContextKey, TJsonEditor, TList, TMultilineModal, TPathPicker, TRenderLayer, TRenderPlane, TRouterView, TSelect, TText, TTransition, TView, TerminalProvider, ansiColors, ansiStyles, applyWheelScroll, charCellWidth, clearTextCaches, createCliEventManager, createDefaultTInputHostAdapter, createDomRenderer, createEventManager, createNodeMentionPathProvider, createNodePathPickerProvider, createPromptMentionPlugin, createRuntime, createStdinDriver, createStdoutRenderer, createTInputHostPlugin, createTerminal, createTerminalApp, createTerminalRouter, createTextRestrictionPlugin, createTraceStore, createWheelScrollState, defaultTInputHostPlugin, detectTerminalColorCapability, formatInlineCellLine, getCliLatencyProfiler, getStdoutRendererMetrics, lintJsonText, normalizeNewlines, padEndByCells, parseAnsiSgr, readEventLog, resolveUserPath$1 as resolveUserPath, rgbToAnsi256, sanitizeInlineText, sanitizeTextBlock, sliceByCells, sliceByCellsRange, spaces, suggestPaths, textCellWidth, truecolorBgOpen, truecolorFgOpen, useLayout, useRenderNode, useRoute, useRouter, useTerminal, useTerminalNode, useTerminalRuntime, useVisibility, wrapByCells, writeEventLog, writeSnapshot };