@cplieger/web-terminal-engine 5.0.5 → 5.0.9
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 +1 -1
- package/package.json +6 -4
- package/src/connection.ts +15 -5
- package/src/render.ts +4 -10
- package/src/store.ts +23 -0
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-engine",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc -p tsconfig.json",
|
|
@@ -17,15 +17,17 @@
|
|
|
17
17
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
18
18
|
"@types/pngjs": "6.0.5",
|
|
19
19
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
20
|
+
"@vitest/browser": "4.1.11",
|
|
21
|
+
"@vitest/browser-playwright": "4.1.11",
|
|
20
22
|
"@vitest/coverage-v8": "4.1.11",
|
|
21
23
|
"esbuild": "0.28.2",
|
|
22
|
-
"eslint": "10.
|
|
24
|
+
"eslint": "10.9.1",
|
|
23
25
|
"fast-check": "4.9.0",
|
|
24
|
-
"
|
|
26
|
+
"playwright": "1.62.1",
|
|
25
27
|
"pngjs": "7.0.0",
|
|
26
28
|
"prettier": "3.9.6",
|
|
27
29
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
28
|
-
"typescript-eslint": "8.
|
|
30
|
+
"typescript-eslint": "8.68.0",
|
|
29
31
|
"vitest": "4.1.11"
|
|
30
32
|
},
|
|
31
33
|
"overrides": {
|
package/src/connection.ts
CHANGED
|
@@ -700,10 +700,6 @@ export function sendResize(): void {
|
|
|
700
700
|
sendControl({ type: "resize", cols, rows });
|
|
701
701
|
}
|
|
702
702
|
|
|
703
|
-
// resetLedger drops a session's reliable-input accounting. Called when the
|
|
704
|
-
// server's boot epoch changes (restart) or when the server no longer recognizes
|
|
705
|
-
// the session -- both invalidate the local bytesSent/bytesAcked/outbox state.
|
|
706
|
-
// The three entry points below differ only in whether the UI hears about it.
|
|
707
703
|
// resetLedger drops a session's reliable-input accounting. Called whenever the
|
|
708
704
|
// server no longer holds the ledger these counters were keyed to (a restart, an
|
|
709
705
|
// idle-GC'd resume key, a session the server does not recognize): its
|
|
@@ -782,7 +778,21 @@ function retransmitOutbox(sock: WebSocket, upgraded: boolean, st: ResumeState):
|
|
|
782
778
|
}
|
|
783
779
|
|
|
784
780
|
function scheduleReconnect(): void {
|
|
785
|
-
|
|
781
|
+
// Nothing to schedule when the state already owns a path back to a socket: a
|
|
782
|
+
// backoff timer is pending ("reconnecting"), or a socket exists that will
|
|
783
|
+
// drive its own retry from its close handler or its connect timeout
|
|
784
|
+
// ("connecting"/"connected"). Both callers set "disconnected" before calling
|
|
785
|
+
// cb.onClose(), so a consumer that reconnects from its own close hook lands
|
|
786
|
+
// here at "connecting" — and scheduling over it would OVERWRITE connState,
|
|
787
|
+
// hiding that socket from connect()'s double-call guard, which then never
|
|
788
|
+
// aborts it: the timer's connect() adds a second live socket with the first
|
|
789
|
+
// one's listeners still bound, i.e. the duplicate server connection and
|
|
790
|
+
// double delivery documented inside connect().
|
|
791
|
+
if (
|
|
792
|
+
connState.status === "reconnecting" ||
|
|
793
|
+
connState.status === "connecting" ||
|
|
794
|
+
connState.status === "connected"
|
|
795
|
+
) {
|
|
786
796
|
return;
|
|
787
797
|
}
|
|
788
798
|
const step = nextBackoffDelay(reconnectDelay);
|
package/src/render.ts
CHANGED
|
@@ -1355,9 +1355,7 @@ function flushRender(): void {
|
|
|
1355
1355
|
const anchor = captureReadAnchor();
|
|
1356
1356
|
try {
|
|
1357
1357
|
flushRenderInner();
|
|
1358
|
-
//
|
|
1359
|
-
// later transient error its full retry budget again. Any backlog reschedule
|
|
1360
|
-
// was already issued from flushRenderInner's own end-of-body.
|
|
1358
|
+
// Clean pass: give a later transient error its full retry budget again.
|
|
1361
1359
|
renderNoProgressStreak = 0;
|
|
1362
1360
|
} catch (err) {
|
|
1363
1361
|
console.error("vterm: render error", err);
|
|
@@ -1525,7 +1523,6 @@ function flushRenderInner(): void {
|
|
|
1525
1523
|
}
|
|
1526
1524
|
}
|
|
1527
1525
|
|
|
1528
|
-
// Refresh cursor state from the window.
|
|
1529
1526
|
const win = store.getWindow();
|
|
1530
1527
|
const newCursorAbs = win.base + win.cursorRow;
|
|
1531
1528
|
cursorAbs = newCursorAbs;
|
|
@@ -1538,11 +1535,9 @@ function flushRenderInner(): void {
|
|
|
1538
1535
|
// resulting mode is unchanged.
|
|
1539
1536
|
syncCursorBlink();
|
|
1540
1537
|
|
|
1541
|
-
//
|
|
1542
|
-
//
|
|
1543
|
-
//
|
|
1544
|
-
// branch below rebuilds EVERYTHING via queueRowsViewportFirst, so any line
|
|
1545
|
-
// dirtied during the alt session is repainted at exit.
|
|
1538
|
+
// Skips the dirtyLines queueing below — safe: main-buffer rows never
|
|
1539
|
+
// repaint while alt is active, and alt-exit rebuilds via
|
|
1540
|
+
// queueRowsViewportFirst, repainting any line dirtied during the alt session.
|
|
1546
1541
|
if (store.isAlt()) {
|
|
1547
1542
|
const altRows = store.getAltRows();
|
|
1548
1543
|
renderAlt(altRows);
|
|
@@ -1570,7 +1565,6 @@ function flushRenderInner(): void {
|
|
|
1570
1565
|
queueRowsViewportFirst();
|
|
1571
1566
|
}
|
|
1572
1567
|
|
|
1573
|
-
// Queue this frame's changed rows for building.
|
|
1574
1568
|
for (const abs of ch.dirtyLines) {
|
|
1575
1569
|
renderQueue.add(abs);
|
|
1576
1570
|
}
|
package/src/store.ts
CHANGED
|
@@ -1826,6 +1826,17 @@ export class LineStore {
|
|
|
1826
1826
|
if (this.highest < 0) {
|
|
1827
1827
|
return;
|
|
1828
1828
|
}
|
|
1829
|
+
// No window established yet — the descriptor is still the initial base
|
|
1830
|
+
// 0/height 0, whose bottom row is -1, below every index a line can hold.
|
|
1831
|
+
// This whole pass rests on "the window's bottom row is the most recent line
|
|
1832
|
+
// in the terminal", which says nothing at all when there is no window: a
|
|
1833
|
+
// resume replays history BEFORE the screen frame describing the window, so
|
|
1834
|
+
// taking -1 as the tail bound strands that replay and forgets it, silently
|
|
1835
|
+
// (this path is not a trim, so no marker explains it). Same test `inWindow`
|
|
1836
|
+
// and applyScroll's alt gate use to ask whether a window exists at all.
|
|
1837
|
+
if (this.win.height <= 0) {
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1829
1840
|
const windowBottom = this.win.base + this.win.height - 1;
|
|
1830
1841
|
if (this.highest <= windowBottom) {
|
|
1831
1842
|
return;
|
|
@@ -2008,6 +2019,18 @@ export class LineStore {
|
|
|
2008
2019
|
}
|
|
2009
2020
|
|
|
2010
2021
|
private updateWindow(msg: ScreenMessage): void {
|
|
2022
|
+
// A base that is not a non-negative integer is not a screen position — the
|
|
2023
|
+
// same validity test markUnconfirmedFrom applies to this very value — so it
|
|
2024
|
+
// must not redefine the window's shape, for the same reason a rows-less
|
|
2025
|
+
// frame must not: `base + height - 1` lands below every retained index, and
|
|
2026
|
+
// truncateBelowWindow then hands the WHOLE store over as stranded past the
|
|
2027
|
+
// live window. That is the silent wipe applyScreen's rows-less guard
|
|
2028
|
+
// records, reached with rows present, and just as silent (this path is not a
|
|
2029
|
+
// trim, so no marker explains the missing transcript). The frame's
|
|
2030
|
+
// addressable rows still apply through applyLine's guard 1.
|
|
2031
|
+
if (!Number.isInteger(msg.base) || msg.base < 0) {
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2011
2034
|
const next: WindowState = {
|
|
2012
2035
|
base: msg.base,
|
|
2013
2036
|
height: msg.rows.length,
|