@cplieger/web-terminal-engine 3.0.0 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-engine",
3
- "version": "3.0.0",
3
+ "version": "3.0.6",
4
4
  "exports": {
5
5
  ".": "./src/index.ts"
6
6
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-engine",
3
- "version": "3.0.0",
3
+ "version": "3.0.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "typecheck": "tsc -p tsconfig.json",
@@ -21,11 +21,11 @@
21
21
  "esbuild": "0.28.1",
22
22
  "eslint": "10.7.0",
23
23
  "fast-check": "4.9.0",
24
- "happy-dom": "20.10.6",
24
+ "happy-dom": "20.11.0",
25
25
  "pngjs": "7.0.0",
26
- "prettier": "3.9.5",
26
+ "prettier": "3.9.6",
27
27
  "typescript": "npm:@typescript/typescript6@6.0.2",
28
- "typescript-eslint": "8.64.0",
28
+ "typescript-eslint": "8.65.0",
29
29
  "vitest": "4.1.10"
30
30
  },
31
31
  "description": "Browser terminal engine: VT500 screen buffer, DOM renderer, and binary wire protocol",
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ export {
15
15
  WIRE_INCOMPATIBLE_CLOSE_CODE,
16
16
  WIRE_PROTOCOL_VERSION,
17
17
  } from "./wire-compatibility.js";
18
- export type { WireIncompatibility } from "./wire-compatibility.js";
18
+ export type { WireCompatibility, WireIncompatibility } from "./wire-compatibility.js";
19
19
  export { controlFrame, CONTROL_FRAME_PREFIX } from "./wire.js";
20
20
  export { wsURL } from "./wsurl.js";
21
21
  export { WS_PATH, SESSIONS_PATH, SESSION_EVENTS_PATH } from "./routes.js";
package/src/render.ts CHANGED
@@ -125,13 +125,17 @@ const rowEls = new Map<number, HTMLDivElement>();
125
125
  let trimMarkerEl: HTMLDivElement | null = null;
126
126
 
127
127
  // Rows awaiting a DOM (re)build, processed in budgeted batches across frames.
128
- // A session restore (kiro-cli's /chat) or a `cat bigfile` dumps thousands of
129
- // lines in one wire frame; building them all in a single rAF janks or, on a
130
- // constrained device, hangs the tab. The store still ingests the whole burst
131
- // at once (it is cheap, pure data); the renderer drains this queue at most
132
- // MAX_ROWS_PER_FRAME per frame and reschedules until it is empty, so each
133
- // frame stays short and the terminal fills smoothly. The cursor row is always
134
- // built regardless of the budget so the caret never lags.
128
+ // A session restore (kiro-cli's /chat), a post-resize transcript reprint, or a
129
+ // `cat bigfile` dumps thousands of lines in one wire frame; building them all
130
+ // in a single rAF janks or, on a constrained device, hangs the tab. The store
131
+ // still ingests the whole burst at once (it is cheap, pure data); the renderer
132
+ // drains this queue at most MAX_ROWS_PER_FRAME per frame and reschedules until
133
+ // it is empty, so each frame stays short. The drain is VIEWPORT-FIRST each
134
+ // frame (see flushRenderInner): live-window rows build before any scrollback
135
+ // backlog, and scrollback drains newest->oldest so the backlog fills upward,
136
+ // above the bottom-pinned viewport — offscreen — instead of visibly streaming
137
+ // through it. The cursor row is always built regardless of the budget so the
138
+ // caret never lags.
135
139
  const renderQueue = new Set<number>();
136
140
  const MAX_ROWS_PER_FRAME = 300;
137
141
 
@@ -779,12 +783,31 @@ function flushRenderInner(): void {
779
783
  renderQueue.delete(newCursorAbs);
780
784
  }
781
785
 
782
- // Drain up to MAX_ROWS_PER_FRAME queued rows this frame; the rest carry over
783
- // to the next frame (scheduled below) so one big burst never blocks paint.
784
- // flushDrainedThisPass doubles as the per-frame budget counter and the
785
- // forward-progress signal the error-path reschedule reads (it was reset to 0
786
- // at entry and nothing between there and here touches it).
786
+ // Drain up to MAX_ROWS_PER_FRAME queued rows this frame, viewport-first;
787
+ // the rest carry over to the next frame (scheduled below) so one big burst
788
+ // never blocks paint. Live-window rows build first (ascending), then the
789
+ // scrollback backlog newest->oldest, so the backlog fills upward above the
790
+ // bottom-pinned viewport — offscreen. A Set's insertion order did the
791
+ // opposite under load: a multi-thousand-row backlog (a resume replay,
792
+ // kiro-cli's post-resize transcript reprint, `cat bigfile`) queued ahead of
793
+ // freshly-dirtied window rows, so the visible screen either churned through
794
+ // history or froze (only the force-built cursor row moving) for seconds on
795
+ // a slow device while the backlog drained. flushDrainedThisPass doubles as
796
+ // the per-frame budget counter and the forward-progress signal the
797
+ // error-path reschedule reads (it was reset to 0 at entry and nothing
798
+ // between there and here touches it).
799
+ const inWindow: number[] = [];
800
+ const belowWindow: number[] = [];
787
801
  for (const abs of renderQueue) {
802
+ if (abs >= win.base) {
803
+ inWindow.push(abs);
804
+ } else {
805
+ belowWindow.push(abs);
806
+ }
807
+ }
808
+ inWindow.sort((a, b) => a - b);
809
+ belowWindow.sort((a, b) => b - a);
810
+ for (const abs of [...inWindow, ...belowWindow]) {
788
811
  if (flushDrainedThisPass >= MAX_ROWS_PER_FRAME) {
789
812
  break;
790
813
  }
@@ -12,8 +12,15 @@ export const MIN_SUPPORTED_SERVER_WIRE_VERSION = 3;
12
12
  /** Definitive private WebSocket close code for an incompatible declared peer. */
13
13
  export const WIRE_INCOMPATIBLE_CLOSE_CODE = 4002;
14
14
 
15
+ /** Shape of the machine-readable compatibility metadata object. */
16
+ export interface WireCompatibility {
17
+ readonly protocolVersion: number;
18
+ readonly minimumServerProtocolVersion: number;
19
+ readonly incompatibleCloseCode: number;
20
+ }
21
+
15
22
  /** Machine-readable compatibility metadata shipped in every TS release. */
16
- export const WIRE_COMPATIBILITY = Object.freeze({
23
+ export const WIRE_COMPATIBILITY: WireCompatibility = Object.freeze({
17
24
  protocolVersion: WIRE_PROTOCOL_VERSION,
18
25
  minimumServerProtocolVersion: MIN_SUPPORTED_SERVER_WIRE_VERSION,
19
26
  incompatibleCloseCode: WIRE_INCOMPATIBLE_CLOSE_CODE,