vterm.js 0.6.1 → 0.7.1

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
@@ -35,6 +35,8 @@ Part of the [vterm](https://github.com/beorn/vterm) monorepo.
35
35
 
36
36
  ## Install
37
37
 
38
+ > Install from npm; a git install resolves the TypeScript source and runs only under Bun.
39
+
38
40
  ```bash
39
41
  npm install vterm.js
40
42
  ```
@@ -435,6 +437,67 @@ The **reactive** counterpart is `signals.damage$` (see [Signals](#signals--the-r
435
437
 
436
438
  **Use vterm.js** when you need everything — 100% coverage of the [terminfo.dev](https://terminfo.dev) feature matrix.
437
439
 
440
+ ## vterm.js and xterm.js
441
+
442
+ [xterm.js](https://www.npmjs.com/package/@xterm/xterm) is the standard for putting a terminal on a web page — DOM, canvas, and WebGL renderers, a large addon ecosystem, and the terminal inside VS Code. When the job is rendering a terminal to a human, reach for xterm.js. We do: [Termless](https://termless.dev)'s own `@termless/web-player`, which plays back recorded terminal sessions in the browser, is built directly on `@xterm/xterm`.
443
+
444
+ vterm.js is for the opposite case: nothing ever gets painted. It's headless-first — the terminal as a **readable data structure**, not a rendered surface. Zero dependencies, pure TypeScript, full VT/ECMA-48/xterm standards coverage, a binary snapshot codec for exact state capture, a reactive read plane (`screen.signals`), absolute-row scrollback addressing, and semantic-zone detection (OSC 133 / OSC 633 prompt/command/output). That combination is aimed at testing, session recording, audit trails, and AI-agent infrastructure — consumers that need to know exactly what a terminal contains without ever drawing it.
445
+
446
+ Because vterm.js makes no display claims, it's held to a stricter standard instead: engine conformance, graded publicly and continuously in [Termless](https://termless.dev)'s differential conformance corpus against xterm.js, Ghostty, and neovim's libvterm. Current per-engine results and every known gap live in the ratchet ledger (`known-gaps.json`) — it's two-way: an unlisted failure fails the build, and a listed gap that starts passing fails too. Full results are public at [termless.dev/advanced/conformance-corpus](https://termless.dev/advanced/conformance-corpus).
447
+
448
+ ## Deliberate divergences
449
+
450
+ A handful of behaviours where vterm.js knowingly differs from neovim's
451
+ libvterm, the reference implementation the conformance corpus is mined from.
452
+ Each was ruled deliberately rather than left as a gap, so **100% conformance
453
+ for vterm.js means 126/128 plus these documented divergences** — not 128.
454
+
455
+ Each entry names the _override word_ that would reverse the ruling. If you need
456
+ the other behaviour, open an issue with that word and the reason; it is the
457
+ argument, not the preference, that would move it.
458
+
459
+ ### Cursor follows reflow when a resize shrinks below it
460
+
461
+ Shrinking the terminal past the cursor's column carries the cursor with its
462
+ line through the rewrap. libvterm, xterm.js and vt100.js instead clamp it to
463
+ the last column of the row it was on.
464
+
465
+ Following the line is what a reflowing terminal does — the cursor stays with
466
+ the text a user was typing, rather than jumping to wherever that text used to
467
+ end. Clamping is the pre-reflow behaviour the other three inherit from a
468
+ shared ancestry, and three engines agreeing about their own history is not an
469
+ argument about which is correct. Both ends of our own record/replay path read
470
+ vterm.js, so there is no internal disagreement to resolve.
471
+
472
+ **Override word: `clamp`.**
473
+
474
+ ### RIS clears the scrollback
475
+
476
+ A full reset (`ESC c`) discards scrollback along with the screen. libvterm
477
+ preserves it, so history banked before a reset can still be scrolled back to —
478
+ and can even be popped back onto the screen by a later resize.
479
+
480
+ Every other engine in the corpus — xterm.js, Ghostty and vt100.js — clears on
481
+ RIS, as does xterm itself. A reset that leaves history behind surprises the
482
+ common case, where `reset` is reached for precisely to get a clean terminal.
483
+
484
+ **Override word: `match-ris`.**
485
+
486
+ ### Related: the deferred-wrap cursor
487
+
488
+ Not an excluded case, but the same family and worth knowing if you read the
489
+ cursor. When a glyph fills the last column with autowrap on, the terminal owes
490
+ a wrap it has not performed. vterm.js represents that as the cursor sitting at
491
+ `col == cols` — one past the last column — as xterm.js and vt100.js do.
492
+ libvterm and Ghostty instead keep the cursor _on_ the last column and carry a
493
+ separate pending-wrap flag. The corpus normalizes this rather than excluding
494
+ it, so those cases pass; consumers reading `cursor.col` should simply expect
495
+ the `col == cols` form.
496
+
497
+ These entries mirror the corpus-side table in the suite's own README and the
498
+ machine-readable reasons in `known-gaps.json`. A new ruling lands in all three
499
+ at once.
500
+
438
501
  ## See also
439
502
 
440
503
  - [vt100.js](../vt100/) — VT100-era emulator (smaller, focused)
package/dist/index.d.mts CHANGED
@@ -77,6 +77,8 @@ interface ScreenCell {
77
77
  hidden: boolean;
78
78
  blink: boolean;
79
79
  wide: boolean;
80
+ /** DECSCA: a selective erase (DECSED/DECSEL) leaves this cell alone. */
81
+ protected: boolean;
80
82
  url: string | null;
81
83
  }
82
84
  interface ScreenOptions {
@@ -118,6 +120,8 @@ interface ScreenAttrsSnapshot {
118
120
  inverse: boolean;
119
121
  hidden: boolean;
120
122
  blink: boolean;
123
+ /** DECSCA — cells written under it resist selective erase. */
124
+ protected: boolean;
121
125
  url: string | null;
122
126
  }
123
127
  interface ScreenColorStateSnapshot {
@@ -164,6 +168,26 @@ interface Snapshot {
164
168
  * `restore()` treats missing as all-false (hard-wrapped).
165
169
  */
166
170
  scrollbackSoftWrapped: boolean[];
171
+ /**
172
+ * DEC line attributes (DECDWL/DECDHL), SPARSE: only rows that are not normal
173
+ * appear, as `[rowIndex, attr]` pairs — 1 double-width, 2 double-height top,
174
+ * 3 double-height bottom.
175
+ *
176
+ * Sparse and header-resident on purpose. Double-width lines are a banner
177
+ * feature that almost no session emits, so a dense array per grid would be
178
+ * thousands of zeros; and the binary grid sections have no skippable framing,
179
+ * so a new array THERE would desynchronise an older reader rather than be
180
+ * ignored by it.
181
+ *
182
+ * Absent in snapshots taken before this field existed — `restore()` treats
183
+ * missing as all-normal. **Readers older than this field render DWL/DHL lines
184
+ * single-width**: a visual downgrade, never corruption.
185
+ */
186
+ lineAttrs?: {
187
+ main: [number, number][];
188
+ alt: [number, number][];
189
+ scrollback: [number, number][];
190
+ };
167
191
  cursor: {
168
192
  x: number;
169
193
  y: number;
@@ -180,6 +204,10 @@ interface Snapshot {
180
204
  originMode: boolean;
181
205
  autoWrap: boolean;
182
206
  charsetG0: boolean;
207
+ /** True when DEC Special Graphics is designated as G1. Absent in older snapshots: ASCII. */
208
+ charsetG1?: boolean;
209
+ /** True when SO has invoked G1 into GL. Absent in older snapshots: SI/G0. */
210
+ activeG1?: boolean;
183
211
  };
184
212
  attrs: ScreenAttrsSnapshot;
185
213
  modes: {
@@ -193,6 +221,8 @@ interface Snapshot {
193
221
  focusTracking: boolean;
194
222
  origin: boolean;
195
223
  insert: boolean;
224
+ /** LNM — LF also returns the carriage. Absent in older snapshots. */
225
+ newLine: boolean;
196
226
  reverseVideo: boolean;
197
227
  syncOutput: boolean;
198
228
  kittyKeyboardFlags: number;
@@ -242,6 +272,10 @@ interface Snapshot {
242
272
  };
243
273
  unicode: {
244
274
  charsetG0: boolean;
275
+ /** True when DEC Special Graphics is designated as G1. Absent in older snapshots: ASCII. */
276
+ charsetG1?: boolean;
277
+ /** True when SO has invoked G1 into GL. Absent in older snapshots: SI/G0. */
278
+ activeG1?: boolean;
245
279
  lastChar: string;
246
280
  pendingRegionalIndicator: string | null;
247
281
  afterZWJ: boolean;
@@ -402,6 +436,8 @@ interface TerminalModes {
402
436
  focusTracking: boolean;
403
437
  originMode: boolean;
404
438
  insertMode: boolean;
439
+ /** LNM — LF also returns the carriage (ECMA-48 mode 20). */
440
+ newLineMode: boolean;
405
441
  reverseVideo: boolean;
406
442
  syncOutput: boolean;
407
443
  leftRightMargin: boolean;