@orkestrel/console 0.0.12 → 0.0.14

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.
@@ -1,5 +1,5 @@
1
+ import { isArray, isError, isFunction, isInstance, isObject } from "@orkestrel/contract";
1
2
  import { Emitter } from "@orkestrel/emitter";
2
- import { isArray } from "@orkestrel/contract";
3
3
  //#region src/core/constants.ts
4
4
  /**
5
5
  * Maps each {@link Color} to its SGR foreground parameter — the 8 base colors at 30–37 and their
@@ -102,11 +102,11 @@ var ATTRIBUTES = Object.freeze([
102
102
  /** Holds the SGR RESET parameter (0) — terminates a styled run, clearing all colors and attributes. */
103
103
  var RESET_CODE = 0;
104
104
  /**
105
- * Holds the ESC control character (`U+001B`) that begins every ANSI escape sequence. Built
105
+ * Holds the escape control character (`U+001B`) that begins every ANSI escape sequence. Built
106
106
  * with `String.fromCharCode` so no raw control character appears in source.
107
107
  */
108
108
  var ESC = String.fromCharCode(27);
109
- /** Holds the BEL control character (`U+0007`) that can terminate an OSC sequence. */
109
+ /** Holds the bell control character (`U+0007`) that can terminate an OSC sequence. */
110
110
  var BEL = String.fromCharCode(7);
111
111
  /** Holds the Control Sequence Introducer (`ESC[`) that opens every SGR sequence. */
112
112
  var CSI = `[`;
@@ -171,9 +171,12 @@ var LEVEL_COLORS = Object.freeze({
171
171
  error: "red"
172
172
  });
173
173
  /**
174
- * Sets the default bounded-retention cap for a {@link import('./types.js').LoggerInterface} — at
175
- * most this many recent records are kept (oldest dropped first). Retention is always bounded — the
176
- * oldest record is dropped after the cap is reached; a consumer overrides it through `options.limit`.
174
+ * Sets the default bounded-retention cap for a {@link import('./types.js').LoggerInterface} —
175
+ * `1000`, so at most that many recent records are kept and retention is always bounded.
176
+ *
177
+ * @remarks
178
+ * The oldest record is dropped after the cap is reached; a consumer overrides the cap through
179
+ * `options.limit`.
177
180
  */
178
181
  var DEFAULT_LOG_LIMIT = 1e3;
179
182
  /** Sets the default {@link LogLevel} threshold a logger gates at when none is supplied — `info`. */
@@ -191,9 +194,12 @@ var LOG_LEVELS = Object.freeze([
191
194
  ]);
192
195
  /**
193
196
  * Holds the complete {@link BorderChars} junction set for each {@link BorderStyle} — the standard
194
- * Unicode box-drawing glyphs at the four line weights. The renderers ({@link
195
- * import('./helpers.js').renderBox} / {@link import('./helpers.js').renderTable}) look the
196
- * style up here, so no glyph literal lives in a renderer. Deeply frozen.
197
+ * Unicode box-drawing glyphs at each line weight, deeply frozen.
198
+ *
199
+ * @remarks
200
+ * The renderers ({@link import('./helpers.js').renderBox} /
201
+ * {@link import('./helpers.js').renderTable}) look the style up here, so no glyph literal lives in
202
+ * a renderer.
197
203
  *
198
204
  * @remarks
199
205
  * `round` shares `single`'s edges and tees — only its corners differ (the rounded `╭╮╰╯`).
@@ -290,7 +296,7 @@ var STATUS_LEVELS = Object.freeze([
290
296
  * Sets the default visible column width for the width-aware renderers — the separator rule and a
291
297
  * {@link import('./helpers.js').renderBox} with no explicit `width`, and the reporter's
292
298
  * `section` rule. A sane terminal default (80 columns); a caller overrides it per-call or through
293
- * {@link import('./types.js').ReporterOptions}`.width`.
299
+ * {@link import('./types.js').ReporterOptions.width}.
294
300
  */
295
301
  var DEFAULT_WIDTH = 80;
296
302
  /** Sets the default horizontal padding inside a box's edges ({@link import('./helpers.js').renderBox}) — one cell. */
@@ -307,9 +313,9 @@ var SEPARATOR_FILL = "─";
307
313
  */
308
314
  var SEPARATOR_TITLE_GAP = " ";
309
315
  /**
310
- * Sets the number of milliseconds at or above which {@link import('./helpers.js').formatDuration}
311
- * (and so `Reporter.timing`) switches from a `…ms` rendering to a `…s` (seconds, 2 d.p.)
312
- * rendering — exactly one second.
316
+ * Sets the millisecond threshold at or above which {@link import('./helpers.js').formatDuration}
317
+ * (and so `Reporter.timing`) switches from a `…ms` rendering to a `…s` (seconds, 2 d.p.) rendering
318
+ * — `1000`, exactly one second.
313
319
  */
314
320
  var SECOND_MS = 1e3;
315
321
  /**
@@ -326,17 +332,20 @@ var CAPTURE_LEVELS = Object.freeze([
326
332
  "debug"
327
333
  ]);
328
334
  /**
329
- * Sets the default bounded-buffer cap for a {@link import('./types.js').CaptureInterface} — at most this
330
- * many recent {@link CapturedMessage}s are retained per buffer (the total buffer and each by-level
331
- * bucket; oldest dropped first). Capture retention is always bounded so a long-running capture can
332
- * never grow without bound (the same retention precedent as {@link DEFAULT_LOG_LIMIT}); a consumer
333
- * overrides it through `options.limit`.
335
+ * Sets the default bounded-buffer cap for a {@link import('./types.js').CaptureInterface} — `1000`,
336
+ * so at most that many recent {@link CapturedMessage}s are retained per buffer (the total buffer
337
+ * and each by-level bucket; oldest dropped first) and retention is always bounded.
338
+ *
339
+ * @remarks
340
+ * A long-running capture can never grow without bound (the same retention precedent as
341
+ * {@link DEFAULT_LOG_LIMIT}); a consumer overrides the cap through `options.limit`.
334
342
  */
335
343
  var DEFAULT_CAPTURE_LIMIT = 1e3;
336
344
  /**
337
345
  * Maps each {@link CaptureLevel} to its {@link LogLevel} for the optional sink forward — the projection the
338
346
  * Capture routes through when writing an intercepted call to a {@link
339
- * import('./types.js').SinkInterface} (`sink.write(text, CAPTURE_LEVEL_MAP[level])`). `warn` /
347
+ * import('./types.js').SinkInterface}. `sink.write(text, CAPTURE_LEVEL_MAP[level])` is the call
348
+ * this map backs. `warn` /
340
349
  * `error` / `debug` / `info` map to their matching {@link LogLevel}; `log` maps to `info` (a plain
341
350
  * console log is informational — the default stream), so a stream-aware sink routes `warn` / `error`
342
351
  * captures to the right stream. The source of truth for the capture-to-log projection.
@@ -349,11 +358,14 @@ var CAPTURE_LEVEL_MAP = Object.freeze({
349
358
  debug: "debug"
350
359
  });
351
360
  /**
352
- * Holds the default spinner frame cycle a {@link import('./types.js').SpinnerInterface} advances through —
353
- * the ten braille-pattern glyphs (U+2800 block) that read as a smoothly rotating dot, the universal
354
- * terminal-spinner convention. Frozen; a consumer swaps the whole cycle through `options.frames`.
361
+ * Holds the default spinner frame cycle a {@link import('./types.js').SpinnerInterface} advances
362
+ * through — the braille-pattern glyphs (`⠋⠙⠹…`, the U+2800 block) that read as a smoothly rotating
363
+ * dot.
355
364
  *
356
365
  * @remarks
366
+ * The cycle is the universal terminal-spinner convention. Frozen; a consumer swaps the whole
367
+ * cycle through `options.frames`.
368
+ *
357
369
  * Braille glyphs are single visible cells, so every frame occupies one column — the spinner glyph
358
370
  * never shifts the message beside it as it advances. The source of truth for the default frame axis.
359
371
  */
@@ -370,35 +382,43 @@ var SPINNER_FRAMES = Object.freeze([
370
382
  "⠏"
371
383
  ]);
372
384
  /**
373
- * Sets the default timer period in milliseconds between a {@link import('./types.js').SpinnerInterface}'s
374
- * frames — the `setInterval` interval `start()` arms. Eighty milliseconds (≈12.5 frames/second) is
375
- * the conventional spinner cadence: fast enough to read as motion, slow enough not to thrash a
376
- * terminal. A consumer overrides it through `options.interval`.
385
+ * Sets the default timer period between a {@link import('./types.js').SpinnerInterface}'s frames —
386
+ * the `setInterval` interval `start()` arms, `80` ms (≈12.5 frames/second).
387
+ *
388
+ * @remarks
389
+ * That is the conventional spinner cadence: fast enough to read as motion, slow enough not to
390
+ * thrash a terminal. A consumer overrides it through `options.interval`.
377
391
  */
378
392
  var DEFAULT_SPINNER_INTERVAL = 80;
379
393
  /**
380
- * Holds the default filled-cell glyph {@link import('./helpers.js').renderBar} draws the completed run of a
381
- * progress bar with — the full block `█` (U+2588). A single visible cell; a consumer overrides it
382
- * through {@link import('./types.js').BarOptions}`.fill`.
394
+ * Holds the default filled-cell glyph {@link import('./helpers.js').renderBar} draws the completed
395
+ * run of a progress bar with — the full block `█` (U+2588). A single visible cell; a consumer
396
+ * overrides it through {@link import('./types.js').BarOptions.fill}.
383
397
  */
384
398
  var BAR_FILL = "█";
385
399
  /**
386
- * Holds the default empty-cell glyph {@link import('./helpers.js').renderBar} draws the remaining run of a
387
- * progress bar with — the light-shade block `░` (U+2591). A single visible cell; a consumer overrides
388
- * it through {@link import('./types.js').BarOptions}`.empty`.
400
+ * Holds the default empty-cell glyph {@link import('./helpers.js').renderBar} draws the remaining
401
+ * run of a progress bar with — the light-shade block `░` (U+2591). A single visible cell; a
402
+ * consumer overrides it through {@link import('./types.js').BarOptions.empty}.
389
403
  */
390
404
  var BAR_EMPTY = "░";
391
405
  /**
392
- * Sets the default visible cell count of a progress-bar track — the glyph run {@link
393
- * import('./helpers.js').renderBar} fills (and a {@link import('./types.js').ProgressInterface} sizes
394
- * its bar to). Thirty cells is a compact, terminal-friendly default; a consumer overrides it through
395
- * `options.width`. Distinct from {@link DEFAULT_WIDTH} (the renderers' 80-column line width) — a bar
396
- * track is one inline element, not a full-width rule.
406
+ * Sets the default visible cell count of a progress-bar track — the glyph run
407
+ * {@link import('./helpers.js').renderBar} fills, and the width a
408
+ * {@link import('./types.js').ProgressInterface} sizes its bar to. `30` cells.
409
+ *
410
+ * @remarks
411
+ * Thirty cells is a compact, terminal-friendly default; a consumer overrides it through
412
+ * `options.width`. It is distinct from {@link DEFAULT_WIDTH} (the renderers' 80-column line
413
+ * width) — a bar track is one inline element, not a full-width rule.
397
414
  */
398
415
  var DEFAULT_BAR_WIDTH = 30;
399
416
  /**
400
- * Holds the default {@link Theme} — every role bound to its default {@link Style}, deeply frozen.
401
- * The base {@link import('./factories.js').createTheme} merges over, and the theme every
417
+ * Holds the default {@link Theme} — every role bound to its default {@link Style}, assembled from
418
+ * {@link LEVEL_COLORS}, {@link STATUS_ICONS}, and {@link STATUS_COLORS} and deeply frozen.
419
+ *
420
+ * @remarks
421
+ * It is the base {@link import('./factories.js').createTheme} merges over, and the theme every
402
422
  * entity uses when none is supplied.
403
423
  *
404
424
  * @remarks
@@ -467,12 +487,12 @@ var DEFAULT_THEME = Object.freeze({
467
487
  //#endregion
468
488
  //#region src/core/errors.ts
469
489
  /**
470
- * Represents an error thrown by the console layer.
490
+ * Carries a {@link ConsoleErrorCode} and an optional `context` bag the error the console layer
491
+ * throws for an internal invariant violated at a defensive guard.
471
492
  *
472
493
  * @remarks
473
- * Carries a {@link ConsoleErrorCode} and an optional `context` bag. Thrown for: an
474
- * internal invariant violated at a defensive, structurally-unreachable guard
475
- * (`INVARIANT`).
494
+ * `INVARIANT` is the code for a guard that is structurally unreachable, so a `catch` branches on
495
+ * `error.code` rather than parsing the message.
476
496
  */
477
497
  var ConsoleError = class extends Error {
478
498
  code;
@@ -485,7 +505,7 @@ var ConsoleError = class extends Error {
485
505
  }
486
506
  };
487
507
  /**
488
- * Narrows an unknown caught value to a {@link ConsoleError}.
508
+ * Narrows an unknown caught value to a {@link ConsoleError} — the guard a `catch` branches on.
489
509
  *
490
510
  * @param value - The value to test (typically a `catch` binding)
491
511
  * @returns True if `value` is a {@link ConsoleError}; false otherwise
@@ -500,7 +520,7 @@ var ConsoleError = class extends Error {
500
520
  * ```
501
521
  */
502
522
  function isConsoleError(value) {
503
- return value instanceof ConsoleError;
523
+ return isInstance(value, ConsoleError);
504
524
  }
505
525
  //#endregion
506
526
  //#region src/core/helpers.ts
@@ -526,7 +546,8 @@ function strip(text) {
526
546
  }
527
547
  /**
528
548
  * Removes every non-printing C0 control character from `text` except `\t` / `\n` / `\r`
529
- * (meaningful whitespace), plus DEL — returning the sanitized string.
549
+ * (meaningful whitespace), plus DEL — a separate pass from {@link strip}, so `width` stays
550
+ * untouched.
530
551
  *
531
552
  * @remarks
532
553
  * Deliberately separate from {@link strip} (ANSI-escape removal only, so `width` /
@@ -569,7 +590,8 @@ function width(text) {
569
590
  return [...strip(text)].length;
570
591
  }
571
592
  /**
572
- * Snapshots and deeply freezes one {@link Style} value.
593
+ * Snapshots and deeply freezes one {@link Style} value, including an independent frozen copy of
594
+ * its `attributes`.
573
595
  *
574
596
  * @param style - The caller-owned style to snapshot
575
597
  * @returns A frozen style record with an independently frozen attributes list
@@ -740,9 +762,9 @@ function formatDuration(ms) {
740
762
  return ms < 1e3 ? `${ms}ms` : `${(ms / SECOND_MS).toFixed(2)}s`;
741
763
  }
742
764
  /**
743
- * Colors `text` through `styler`, or returns it verbatim when `styler` is `undefined` the
744
- * single optional-styling primitive every renderer applies to its border / title / connector
745
- * glyphs (the one styler seam, shared, never re-hand-rolled per renderer).
765
+ * Colors `text` through `styler` and an optional by-value {@link Style}, or returns it verbatim
766
+ * when `styler` is `undefined` — the single optional-styling primitive every renderer applies to
767
+ * its border / title / connector glyphs.
746
768
  *
747
769
  * @remarks
748
770
  * The renderers all take an optional `styler`: present ⇒ glyphs are colored, absent ⇒ plain.
@@ -927,8 +949,8 @@ function renderTable(options) {
927
949
  ].join("\n");
928
950
  }
929
951
  /**
930
- * Renders a nested {@link TreeNode} tree with box-drawing connectors. Pure: same
931
- * {@link TreeOptions} → same string.
952
+ * Renders a nested {@link TreeNode} tree whose connectors derive from the chosen `border` set.
953
+ * Pure: same {@link TreeOptions} → same string.
932
954
  *
933
955
  * @remarks
934
956
  * The `root` label is the unindented first line; its descendants are drawn beneath it with
@@ -957,10 +979,14 @@ function renderTree(options) {
957
979
  })].join("\n");
958
980
  }
959
981
  /**
960
- * Renders the connector-prefixed lines for a {@link TreeNode} list — the recursive core
961
- * behind {@link renderTree}. Each child is drawn as `prefix` + its connector (`├─ ` for
962
- * any but the last, `└─ ` for the last) + its label, with its own descendants recursed
963
- * beneath under the carried guide (`│ ` under a non-last node, ` ` under the last).
982
+ * Renders the connector-prefixed lines for a {@link TreeNode} list — the recursive core behind
983
+ * {@link renderTree}, whose third options argument requires `border` and groups the optional
984
+ * `styler` and `style`.
985
+ *
986
+ * @remarks
987
+ * Each child is drawn as `prefix` + its connector (`├─ ` for any but the last, `└─ ` for the
988
+ * last) + its label, with its own descendants recursed beneath under the carried guide (`│ `
989
+ * under a non-last node, ` ` under the last).
964
990
  *
965
991
  * @remarks
966
992
  * A centralized, exported recursion branch so it is directly testable and
@@ -1023,12 +1049,12 @@ function renderTreeChildren(nodes, prefix, options) {
1023
1049
  * ```
1024
1050
  */
1025
1051
  function stringifyValue(value) {
1026
- if (value instanceof Error) return `${value.name}: ${value.message}`;
1027
- if (value === null || typeof value !== "object") return String(value);
1052
+ if (isError(value)) return `${value.name}: ${value.message}`;
1053
+ if (!isObject(value)) return String(value);
1028
1054
  const seen = /* @__PURE__ */ new WeakSet();
1029
1055
  try {
1030
1056
  return JSON.stringify(value, (_key, nested) => {
1031
- if (nested !== null && typeof nested === "object") {
1057
+ if (isObject(nested)) {
1032
1058
  if (seen.has(nested)) return "[Circular]";
1033
1059
  seen.add(nested);
1034
1060
  }
@@ -1062,12 +1088,15 @@ function formatArgs(args) {
1062
1088
  }
1063
1089
  /**
1064
1090
  * Renders a determinate progress bar string — a filled / empty glyph track followed by the percentage
1065
- * and the `(current/total)` count (`█████░░░░░ 50% (5/10)`). Pure: same {@link BarOptions} →
1066
- * same string. The animation-layer sibling of the `render*` renderers (box / table / tree /
1067
- * separator), shared so a {@link import('./types.js').ProgressInterface} and any direct caller draw
1068
- * the one bar — never a second, hand-rolled one.
1091
+ * and the `(current/total)` count (`█████░░░░░ 50% (5/10)`). Pure and width-aware: same
1092
+ * {@link BarOptions} → same string.
1069
1093
  *
1070
1094
  * @remarks
1095
+ * It is the animation-layer sibling of the `render*` renderers (box / table / tree / separator),
1096
+ * shared so a {@link import('./types.js').ProgressInterface} and any direct caller draw the one
1097
+ * bar — never a second,
1098
+ * hand-rolled one.
1099
+ *
1071
1100
  * - **Fill fraction, clamped.** The filled cell count is `round((current / total) · width)` with
1072
1101
  * `current` clamped to `[0, total]`, so an overrun never over-fills and a negative never under-fills.
1073
1102
  * A `total <= 0` renders a full track (there is nothing to fill toward — the work is trivially done).
@@ -1102,12 +1131,13 @@ function renderBar(options) {
1102
1131
  //#region src/core/renderers/ANSIRenderer.ts
1103
1132
  /**
1104
1133
  * Implements the cross-environment default {@link RendererInterface} — renders style data as ANSI
1105
- * SGR escape codes, exactly as `Scheduler` is the `setTimeout` default for its seam. It
1106
- * is the single styling output the whole console / terminal system uses in a terminal;
1107
- * the browser `%c` / CSS renderer implements the same contract over
1108
- * the same {@link Style}, so retargeting changes the renderer, never the style model.
1134
+ * SGR escape codes, stateless and event-free.
1109
1135
  *
1110
1136
  * @remarks
1137
+ * It is the single styling output the whole console / terminal system uses in a terminal; the
1138
+ * browser `%c` / CSS renderer implements the same contract over the same {@link Style}, so
1139
+ * retargeting changes the renderer, never the style model.
1140
+ *
1111
1141
  * - **Style is data in, SGR string out.** It reads the style's `foreground` /
1112
1142
  * `background` / `attributes` and emits one `ESC[…m` sequence whose parameters are the
1113
1143
  * mapped SGR numbers (foreground 30–37 / 90–97, background 40–47 / 100–107, attributes
@@ -1143,7 +1173,8 @@ var ANSIRenderer = class {
1143
1173
  //#endregion
1144
1174
  //#region src/core/Retention.ts
1145
1175
  /**
1146
- * Implements the bounded, level-keyed retention engine both captures buffer through one capped total buffer
1176
+ * Implements the bounded, level-keyed retention engine the console and process captures buffer
1177
+ * through — one capped total buffer
1147
1178
  * plus one capped bucket per level, generic over the record type each capture carries.
1148
1179
  *
1149
1180
  * @remarks
@@ -1431,7 +1462,7 @@ var Styler = class Styler {
1431
1462
  return this.#attribute(attribute).surface;
1432
1463
  }
1433
1464
  #isSurface(value) {
1434
- return typeof value === "function" && "style" in value && "enabled" in value && "render" in value && "red" in value && "bold" in value;
1465
+ return isFunction(value) && "style" in value && "enabled" in value && "render" in value && "red" in value && "bold" in value;
1435
1466
  }
1436
1467
  #foreground(color) {
1437
1468
  return new Styler(this.#renderer, this.#enabled, Object.freeze({
@@ -1450,10 +1481,13 @@ var Styler = class Styler {
1450
1481
  //#endregion
1451
1482
  //#region src/core/factories.ts
1452
1483
  /**
1453
- * Creates the fluent, composable {@link StylerInterface} — the consumer-facing styling
1454
- * API. It builds a {@link import('./types.js').Style} under the hood and renders it
1455
- * through a {@link import('./types.js').RendererInterface} (the ANSI default), so
1456
- * `styler.red.bold('hi')` yields styled text. Chains are immutable, so a base styler is freely reusable.
1484
+ * Creates the fluent, composable {@link StylerInterface} — ANSI by default, retargeted by a
1485
+ * `renderer` and stripped of color by `enabled: false`.
1486
+ *
1487
+ * @remarks
1488
+ * It builds a {@link import('./types.js').Style} under the hood and renders it through a
1489
+ * {@link import('./types.js').RendererInterface}, so `styler.red.bold('hi')` yields styled text.
1490
+ * Chains are immutable, so a base styler is freely reusable.
1457
1491
  *
1458
1492
  * @param options - See {@link StylerOptions}
1459
1493
  * @returns A base {@link StylerInterface}
@@ -1900,12 +1934,14 @@ var Reporter = class {
1900
1934
  /**
1901
1935
  * Implements a self-driving, observable activity spinner — a glyph cycle that advances on a
1902
1936
  * periodic timer, writing each `\r` + frame line to its {@link SinkInterface} and emitting it on
1903
- * `frame`. The leading `\r` is what an overwrite-capable sink (the TTY sink) redraws on; a plain
1904
- * sink degrades to a fresh, non-overwriting line — the line-overwrite is the sink's job, never
1905
- * the spinner's. Universal — `setInterval` + the one {@link StylerInterface} + the one
1906
- * {@link SinkInterface}, no `node:*`, no `process.stdout`.
1937
+ * `frame`. The timer is always cleared on an outcome, so the spinner is leak-free.
1907
1938
  *
1908
1939
  * @remarks
1940
+ * The leading `\r` is what an overwrite-capable sink (the TTY sink) redraws on; a plain sink
1941
+ * degrades to a fresh, non-overwriting line — the line-overwrite is the sink's job, never the
1942
+ * spinner's. Universal — `setInterval` + the one {@link StylerInterface} + the one
1943
+ * {@link SinkInterface}, no `node:*`, no `process.stdout`.
1944
+ *
1909
1945
  * - **Self-driving but deterministically testable.** `start()` arms a `setInterval` that calls
1910
1946
  * {@link tick} each `interval`; each {@link tick} builds the styled `glyph + message` line for the
1911
1947
  * current frame, emits it on `frame`, writes `'\r' + line` to the sink, then advances the frame
@@ -2016,13 +2052,16 @@ var Spinner = class {
2016
2052
  //#region src/core/Progress.ts
2017
2053
  /**
2018
2054
  * Implements an update-driven, observable progress bar — {@link update} recomputes the bar through
2019
- * {@link renderBar}, writes `\r` + bar to its {@link SinkInterface}, and emits the `{ current, total }`
2020
- * on `update`. The leading `\r` is what an overwrite-capable sink (the TTY sink) redraws on; a
2021
- * plain sink degrades to a fresh, non-overwriting line — the line-overwrite is the sink's job.
2022
- * Universal — the one {@link StylerInterface} + the one {@link SinkInterface}, no `node:*`, no
2023
- * `process.stdout`. No self-timer (unlike {@link import('./Spinner.js').Spinner}) — the caller drives it.
2055
+ * {@link renderBar}, writes `\r` + bar to its {@link SinkInterface}, and emits the `{ current,
2056
+ * total }` on `update`. No self-timer, unlike {@link import('./Spinner.js').Spinner} the caller
2057
+ * drives it.
2024
2058
  *
2025
2059
  * @remarks
2060
+ * The leading `\r` is what an overwrite-capable sink (the TTY sink) redraws on; a plain sink
2061
+ * degrades to a fresh, non-overwriting line — the line-overwrite is the sink's job. Universal —
2062
+ * the one {@link StylerInterface} + the one {@link SinkInterface}, no `node:*`, no
2063
+ * `process.stdout`.
2064
+ *
2026
2065
  * - **Update-driven.** Each {@link update} clamps `current` to `[0, total]`, renders the bar (filled
2027
2066
  * to `current / total`, with the trailing `percent (current/total)` + message) through {@link renderBar},
2028
2067
  * emits `update`, and writes `'\r' + bar`. Progress advances only when the caller reports it.