@cplieger/web-terminal-engine 6.0.4 → 6.1.2

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/README.md CHANGED
@@ -123,22 +123,22 @@ Two engines share nothing: each holds its own DOM handles, timers, listeners, so
123
123
 
124
124
  `engine.dispose()` disposes the five parts in reverse build order: the connection closes its socket with code 1000 (no `onClose` fires), stops its heartbeat, history and reconnect timers, drops every session's queued bytes and detaches your callbacks; the mouse controller detaches its four listeners and cancels its pending motion frame; the renderer cancels its pending frame and blink interval, removes its `visibilitychange` listener, empties `output`, removes its overlays and the `--char-w`, `cursor-blink-off` and `term-reverse-video` marks from `termWrap`, and drops every element reference; the scroll controller removes its `scroll` listener; the mode state resets to `POWER_ON_MODES`. A callback may call `dispose()` on the engine that invoked it; the connection stops after the callback returns. Every method stays callable afterwards and answers an empty value rather than throwing:
125
125
 
126
- | Part | Method | After `dispose()` |
127
- | ------------ | ------------------------------------------------------------------ | ------------------------------------------------------- |
128
- | `renderer` | `boundStore()` | a new, empty `LineStore` on every call |
129
- | `renderer` | `getHighestIndex()`, `getReplayBoundary()` | `-1` |
130
- | `renderer` | `lastBrowseActivityMs()`, `browseCacheSize()`, `pendingRowCount()` | `0` |
131
- | `renderer` | `replayMaxForResume()` | `5000` |
132
- | `renderer` | `captureViewMemory()`, `pendingRestoreAbs()` | `null` |
133
- | `renderer` | `computeSize()`, `gridSize()` | `{ cols: 0, rows: 0 }` |
134
- | `renderer` | `cellSize()` | `{ width: 0, height: 0 }` |
135
- | `renderer` | `getCursorPx()` | `{ left: 0, top: 0, cellH: 0 }` |
136
- | `scroll` | `isUserScrolledUp()` | `false` |
137
- | `scroll` | `currentScrollTop()` | `0` |
138
- | `connection` | `sendBinary()`, `sendEphemeral()`, `requestHistory()` | `false` |
139
- | `connection` | `historyBudget()`, `serverEpochOf()` | `0` |
140
- | `connection` | `currentSessionId()` | `""` |
141
- | `modes` | the nine readers, `snapshot()` | the `POWER_ON_MODES` values; `applySnapshot` is a no-op |
126
+ | Part | Method | After `dispose()` |
127
+ | --- | --- | --- |
128
+ | `renderer` | `boundStore()` | a new, empty `LineStore` on every call |
129
+ | `renderer` | `getHighestIndex()`, `getReplayBoundary()` | `-1` |
130
+ | `renderer` | `lastBrowseActivityMs()`, `browseCacheSize()`, `pendingRowCount()` | `0` |
131
+ | `renderer` | `replayMaxForResume()` | `5000` |
132
+ | `renderer` | `captureViewMemory()`, `pendingRestoreAbs()` | `null` |
133
+ | `renderer` | `computeSize()`, `gridSize()` | `{ cols: 0, rows: 0 }` |
134
+ | `renderer` | `cellSize()` | `{ width: 0, height: 0 }` |
135
+ | `renderer` | `getCursorPx()` | `{ left: 0, top: 0, cellH: 0 }` |
136
+ | `scroll` | `isUserScrolledUp()` | `false` |
137
+ | `scroll` | `currentScrollTop()` | `0` |
138
+ | `connection` | `sendBinary()`, `sendEphemeral()`, `requestHistory()` | `false` |
139
+ | `connection` | `historyBudget()`, `serverEpochOf()` | `0` |
140
+ | `connection` | `currentSessionId()` | `""` |
141
+ | `modes` | the nine readers, `snapshot()` | the `POWER_ON_MODES` values; `applySnapshot` is a no-op |
142
142
 
143
143
  `createTerminalEngine` and each factory throw when a listener or timer cannot be acquired, and a throw leaves nothing behind: the parts built before it are disposed in reverse order, so there is no partially built engine to dispose.
144
144
 
package/jsr.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-engine",
3
- "version": "6.0.4",
3
+ "version": "6.1.2",
4
4
  "license": "MPL-2.0",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/web-terminal-engine",
3
- "version": "6.0.4",
3
+ "version": "6.1.2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "typecheck": "tsc -p tsconfig.json",
@@ -25,7 +25,7 @@
25
25
  "@vitest/browser-playwright": "5.0.3",
26
26
  "@vitest/coverage-v8": "5.0.3",
27
27
  "esbuild": "0.28.2",
28
- "eslint": "10.11.0",
28
+ "eslint": "10.12.0",
29
29
  "fast-check": "4.10.2",
30
30
  "knip": "6.39.0",
31
31
  "playwright": "1.63.0",
package/src/render.ts CHANGED
@@ -322,7 +322,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
322
322
  // read-anchor binary searches keep their monotonic-`data-abs` invariant; an
323
323
  // INTERIOR marker cannot be data-abs-less the way the top trim marker is. A
324
324
  // marker is a PROJECTION of the store's gap geometry, re-derived whenever
325
- // either edge moves and removed when the gap closes (docs/paged-scrollback.md §5.4).
325
+ // either edge moves and removed when the gap closes.
326
326
  const gapMarkerEls = new Map<number, HTMLDivElement>();
327
327
 
328
328
  let requestHistoryFn: ((fromAbs: number, maxLines: number) => boolean) | null =
@@ -396,7 +396,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
396
396
  let fullResetThisPass = false;
397
397
  // The ED3 base observed since the last flush, or -1; consumed by the pass that
398
398
  // applies it. Renderer-local because the renderer calls every path that
399
- // discards a REGION rather than trimming the cap (docs/scroll-position-fidelity.md §5).
399
+ // discards a REGION rather than trimming the cap.
400
400
  let discardedBelowPending = -1;
401
401
  let discardedBelowThisPass = -1;
402
402
  // Whether THIS pass removed rows. Announcing a shrink for a pass that did not
@@ -558,7 +558,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
558
558
  scheduleFlush();
559
559
  }
560
560
 
561
- // --- demand-paged scrollback: the consumer's seams (docs/paged-scrollback.md) ---
561
+ // --- demand-paged scrollback: the consumer's seams ---
562
562
 
563
563
  // The viewport index is supplied here rather than by the caller because the
564
564
  // renderer is the only layer that knows it, and the store's eviction needs it
@@ -590,7 +590,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
590
590
  // uses the viewport to decide which rows survive, and the live transient
591
591
  // would evict the rows an armed restore is about to bring back; the restore
592
592
  // names the position the user is REGAINING, so it wins, and it also
593
- // overrides the live follow flag (docs/scroll-position-fidelity.md §7.2).
593
+ // overrides the live follow flag.
594
594
  const pending = pendingRestoreAbs();
595
595
  const pendingRestoreArmed = pending !== null;
596
596
  store.applyResumeAck({
@@ -892,7 +892,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
892
892
  if (msg.scrollbackCleared) {
893
893
  // ED3. Recorded at the one place that sees the frame before the store
894
894
  // consumes it, so restoreReadAnchor can tell a region DISCARD from a cap
895
- // trim; the two need opposite recoveries (docs/scroll-position-fidelity.md §5).
895
+ // trim; the two need opposite recoveries.
896
896
  // Max, because several frames can land between flushes.
897
897
  discardedBelowPending = Math.max(discardedBelowPending, msg.base);
898
898
  }
@@ -983,7 +983,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
983
983
  // The ONE row-selection primitive every "where is the reader" question
984
984
  // resolves through (the read anchor, the paging trigger, the per-view memory):
985
985
  // two definitions would drift exactly during a rebuild, when the answer
986
- // matters most (docs/scroll-position-fidelity.md §7.2). Binary search, since
986
+ // matters most. Binary search, since
987
987
  // the children are in document order with monotonic offsetTop.
988
988
  function rowAtViewportTop(): HTMLElement | null {
989
989
  if (!output) {
@@ -1022,7 +1022,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1022
1022
  const el = rowAtViewportTop();
1023
1023
  if (el === null) {
1024
1024
  // Stand down rather than fall back to the tail: a tail proxy is what
1025
- // turned a large content shrink into a tail-drag (docs/scroll-position-fidelity.md §1.2).
1025
+ // turned a large content shrink into a tail-drag.
1026
1026
  return null;
1027
1027
  }
1028
1028
  return { el, abs: rowAbs(el), screenTop: el.offsetTop - scroll.currentScrollTop() };
@@ -1076,10 +1076,10 @@ export function createRenderer(opts: RendererOptions): Renderer {
1076
1076
  // A region DISCARD (ED3), not a cap trim: nothing surviving is guaranteed
1077
1077
  // ADJACENT to what the reader saw. An inline TUI that reprints on resize
1078
1078
  // brings the same text back at new indices, and holding the survivor at
1079
- // the old screen position is the "random jump on resize" symptom
1080
- // (docs/scroll-position-fidelity.md §1.2, §5). The test is the ANCHOR's
1081
- // index only: requiring the SURVIVOR above the base too never held, since
1082
- // the reprint re-delivers lines below the base in the same frame.
1079
+ // the old screen position is the "random jump on resize" symptom. The test
1080
+ // is the ANCHOR's index only: requiring the SURVIVOR above the base too
1081
+ // never held, since the reprint re-delivers lines below the base in the
1082
+ // same frame.
1083
1083
  if (discardedBelowThisPass >= 0 && anchor.abs < discardedBelowThisPass) {
1084
1084
  return;
1085
1085
  }
@@ -1149,8 +1149,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1149
1149
  // The post-flush trigger. The reader's position relative to the store's gaps
1150
1150
  // can change without any scroll event — a tail trim moves the frontier up
1151
1151
  // under a stationary reader, and a byte-short page leaves a fresh sub-gap
1152
- // beside them — so the flush is the other place the trigger must run
1153
- // (docs/paged-scrollback.md §5.4).
1152
+ // beside them — so the flush is the other place the trigger must run.
1154
1153
  maybeFetchHistory();
1155
1154
  reportUnnamedDrainStall();
1156
1155
  }
@@ -1317,7 +1316,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1317
1316
  }
1318
1317
  }
1319
1318
 
1320
- // --- Per-view scroll memory (docs/scroll-position-fidelity.md §3) ---
1319
+ // --- Per-view scroll memory ---
1321
1320
  // A reading position is a LINE, not a pixel offset: a replayed `scrollTop` is
1322
1321
  // silently CLAMPED while a rebuild has built only the live window plus one
1323
1322
  // frame's budget, and it stops meaning the same line once the content grows in
@@ -1410,7 +1409,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1410
1409
 
1411
1410
  // The viewport's absolute index for the paging layer's questions about NOW;
1412
1411
  // `pendingRestoreAbs` answers the resume transition, which asks which rows
1413
- // must SURVIVE a switch (docs/scroll-position-fidelity.md §7.2).
1412
+ // must SURVIVE a switch.
1414
1413
  function viewportAbs(): number {
1415
1414
  if (!store) {
1416
1415
  return 0;
@@ -1443,7 +1442,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1443
1442
  function maybeFetchHistory(): void {
1444
1443
  // The pending-demand timer fires from a CLOCK, so this alt guard is the
1445
1444
  // load-bearing one: without it a vim session would fetch pages nobody can
1446
- // see, and each denial would re-arm the timer (docs/paged-scrollback.md §5.5).
1445
+ // see, and each denial would re-arm the timer.
1447
1446
  if (!store || store.isAlt()) {
1448
1447
  return;
1449
1448
  }
@@ -1526,7 +1525,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1526
1525
  // places a row before it. Its frontier is deliberately NOT sourced from gap
1527
1526
  // geometry: the lower edge is a policy question (what is still worth
1528
1527
  // requesting), so an exhausted frontier still renders a marker rather than
1529
- // vanishing when the gap closes (docs/paged-scrollback.md §5.4).
1528
+ // vanishing when the gap closes.
1530
1529
  function updateTrimMarker(): void {
1531
1530
  if (!output) {
1532
1531
  return;
@@ -1557,7 +1556,7 @@ export function createRenderer(opts: RendererOptions): Renderer {
1557
1556
  // Three honest statements about the history above what is held: nothing when
1558
1557
  // index 0 is held; "trimmed" when the client evicted the rows itself with no
1559
1558
  // paging, or when paging proved nothing below survives; "not loaded" when
1560
- // paging is declared and neither proof is in hand (docs/paged-scrollback.md §5.4).
1559
+ // paging is declared and neither proof is in hand.
1561
1560
  function topMarkerLabel(): string | null {
1562
1561
  if (!store) {
1563
1562
  return null;