@promptctl/rich-js 0.23.0 → 0.24.0

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.
Files changed (51) hide show
  1. package/dist/core/clock.d.ts +9 -1
  2. package/dist/core/clock.js +22 -2
  3. package/dist/core/color.d.ts +10 -0
  4. package/dist/core/color.js +36 -1
  5. package/dist/core/easing.d.ts +31 -0
  6. package/dist/core/easing.js +106 -1
  7. package/dist/core/segment.d.ts +10 -0
  8. package/dist/core/segment.js +15 -0
  9. package/dist/core/strip.d.ts +104 -27
  10. package/dist/core/strip.js +231 -124
  11. package/dist/core/style.d.ts +18 -1
  12. package/dist/core/style.js +32 -3
  13. package/dist/core/text.js +4 -1
  14. package/dist/host/app.js +2 -4
  15. package/dist/host/index.d.ts +1 -1
  16. package/dist/host/input.d.ts +37 -13
  17. package/dist/host/input.js +146 -56
  18. package/dist/host/terminal-host.d.ts +16 -0
  19. package/dist/host/terminal-host.js +1 -0
  20. package/dist/index.d.ts +11 -9
  21. package/dist/index.js +5 -4
  22. package/dist/node/terminal-host.d.ts +8 -1
  23. package/dist/node/terminal-host.js +2 -0
  24. package/dist/renderables/effect.d.ts +78 -10
  25. package/dist/renderables/effect.js +143 -39
  26. package/dist/renderables/effects.d.ts +73 -26
  27. package/dist/renderables/effects.js +412 -92
  28. package/dist/renderables/embed.d.ts +10 -0
  29. package/dist/renderables/embed.js +15 -7
  30. package/dist/renderables/frames.d.ts +95 -0
  31. package/dist/renderables/frames.js +106 -0
  32. package/dist/renderables/live.d.ts +23 -7
  33. package/dist/renderables/live.js +68 -34
  34. package/dist/renderables/markdown.d.ts +5 -2
  35. package/dist/renderables/markdown.js +210 -19
  36. package/dist/renderables/progress.d.ts +1 -1
  37. package/dist/renderables/progress.js +3 -1
  38. package/dist/renderables/progressBar.js +3 -3
  39. package/dist/renderables/spinner.d.ts +62 -28
  40. package/dist/renderables/spinner.js +99 -52
  41. package/dist/renderables/status.d.ts +8 -3
  42. package/dist/renderables/status.js +8 -5
  43. package/dist/template-bindings/animation-funcs.d.ts +37 -0
  44. package/dist/template-bindings/animation-funcs.js +83 -0
  45. package/dist/template-bindings/color-funcs.d.ts +13 -0
  46. package/dist/template-bindings/color-funcs.js +1 -1
  47. package/dist/template-bindings/helpers.d.ts +6 -0
  48. package/dist/template-bindings/helpers.js +12 -3
  49. package/dist/template-bindings/index.d.ts +6 -2
  50. package/dist/template-bindings/index.js +6 -2
  51. package/package.json +1 -1
@@ -82,6 +82,16 @@ export declare class InlineLabel {
82
82
  */
83
83
  text(options: RenderOptions, base?: TextStyle): RichText;
84
84
  }
85
+ /**
86
+ * `text`, changed in place into a fragment of a line it shares, as Rich's
87
+ * `_title` reads one: its own end is not drawn, newlines become spaces so it
88
+ * stays on its line, and tabs are widened to their stops before anything
89
+ * measures it, so a cut to a border and the width a label asks for count
90
+ * the cells it will draw. [LAW:single-enforcer] The one reading of "set into
91
+ * a line", for a label read through the crossing above and for text a
92
+ * caller hands over already read (`Frames`).
93
+ */
94
+ export declare function setIntoLine(text: RichText): RichText;
85
95
  /**
86
96
  * A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
87
97
  * text set to the width less `marker` — a wide character the cut splits leaves
@@ -114,15 +114,23 @@ export class InlineLabel {
114
114
  * caller's cut. A string reads with `base` as its style, as `EmbeddedText.text` reads one.
115
115
  */
116
116
  text(options, base) {
117
- const text = this._content.text(options, base);
118
- // A fragment of the line, so its own end is not drawn, as Rich's `_title` clears it.
119
- text.end = "";
120
- text.plain = text.plain.replaceAll("\n", " ");
121
- // Tabs widened before anything measures it, so a cut to the border and
122
- // the width a title asks for count the cells the label will draw.
123
- return text.expandTabs();
117
+ return setIntoLine(this._content.text(options, base));
124
118
  }
125
119
  }
120
+ /**
121
+ * `text`, changed in place into a fragment of a line it shares, as Rich's
122
+ * `_title` reads one: its own end is not drawn, newlines become spaces so it
123
+ * stays on its line, and tabs are widened to their stops before anything
124
+ * measures it, so a cut to a border and the width a label asks for count
125
+ * the cells it will draw. [LAW:single-enforcer] The one reading of "set into
126
+ * a line", for a label read through the crossing above and for text a
127
+ * caller hands over already read (`Frames`).
128
+ */
129
+ export function setIntoLine(text) {
130
+ text.end = "";
131
+ text.plain = text.plain.replaceAll("\n", " ");
132
+ return text.expandTabs();
133
+ }
126
134
  /**
127
135
  * A label cut to `width` cells as Rich's `Text.truncate` cuts one: its plain
128
136
  * text set to the width less `marker` — a wide character the cut splits leaves
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Frames — content that changes on a beat: a sequence of frames, each held
3
+ * for its own seconds and looping, drawn as the one in progress at a time
4
+ * the host hands in.
5
+ *
6
+ * An effect moves a cell's colours and may never change a glyph
7
+ * (`./effect.ts`: "It changes how cells look, never which cells exist or who
8
+ * drew them"). A frame is the other kind of motion — what the element shows
9
+ * changes — so it is its own primitive and not a wider `Effect`, and the two
10
+ * compose by nesting: `new Effected(new Frames(…), …)` lights whichever frame
11
+ * is up.
12
+ *
13
+ * [LAW:no-ambient-temporal-coupling] `t` is an argument, as
14
+ * `Effected`'s is, never a clock read: whoever draws frames hands each one
15
+ * its time (`core/clock.ts`), so the same `t` draws the same bytes at one
16
+ * redraw a second or sixty. The host's rate is the host's. At sixty a frame
17
+ * shows for exactly its hold; at one a second a hold shorter than a second is
18
+ * skipped and a period of whole seconds freezes on one frame, and nothing
19
+ * here adapts to that — the beat is `core/easing.ts`'s, the one timing
20
+ * vocabulary, and a draw costs the same at `t = 0` and `t = 10⁶`.
21
+ *
22
+ * The sequence and the moment are two values, because only one of them
23
+ * changes from frame to frame. A `FrameSequence` is the frames prepared once
24
+ * — the beat, the one width, each frame set into a line and padded — and a
25
+ * `Frames` is that sequence at a `t`, whose work is one frame's — finding
26
+ * which, and copying it: a host holds the sequence and asks it for a frame,
27
+ * as `Spinner` holds its two, so a draw costs the same over ninety frames as
28
+ * over one. The sequence is a value the host keeps, not a memo here: nothing
29
+ * is looked up by key.
30
+ *
31
+ * The frame drawn is drawn until the next begins, and its cells say so: they
32
+ * are stamped with that moment (`Style.nextChange`), which rides up through
33
+ * every container to whoever holds the composed frame (`Segment.nextChange`),
34
+ * so a host drawing at `t` knows when to draw again, and one drawing slower
35
+ * than the frames turn can see that it is skipping them.
36
+ *
37
+ * Every frame is drawn at one width, the widest frame's, each padded on the
38
+ * right to it, so whatever sits beside the frames never moves as they turn;
39
+ * and the frame is a fragment of a line (`setIntoLine`), ending no line of
40
+ * its own, so it sits among other cells. Frames are `RichText`, each free to
41
+ * carry its own style, and read once, here: markup is a string's reading
42
+ * under the console that draws it, which a frame handed to a template
43
+ * binding has no console for, so the frame arrives read, as a `Group`'s
44
+ * children do.
45
+ */
46
+ import { type Beat } from "../core/easing.js";
47
+ import type { Measurable, Renderable, RenderOptions } from "../core/protocol.js";
48
+ import type { Segment } from "../core/segment.js";
49
+ import type { RichText } from "../core/text.js";
50
+ /** One frame: what it shows, and how long it is held, in seconds. */
51
+ export interface Frame {
52
+ readonly text: RichText;
53
+ readonly seconds: number;
54
+ }
55
+ /** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
56
+ export declare class FrameSequence {
57
+ /** Which frame is in progress when: the holds are the frames' seconds, in order. */
58
+ readonly beat: Beat;
59
+ /** The width every frame draws at: the widest frame's, in cells. */
60
+ readonly width: number;
61
+ private readonly _shown;
62
+ constructor(frames: readonly Frame[]);
63
+ /**
64
+ * The frame in progress at `t`, at `width` cells, ending no line: a copy,
65
+ * the caller's to change, its every cell — padding included — stamped with
66
+ * when it gives way to the next, `beat.end(t)`, so a blank frame among
67
+ * drawn ones still turns. [LAW:one-source-of-truth] The stamp is the one
68
+ * answer to when the frame changes, read off the cells
69
+ * (`Segment.nextChange`): a sequence of nothing has no cell to carry one,
70
+ * and a field saying a time would disagree with cells saying never.
71
+ * `beat.at` refuses a `t` that is not finite.
72
+ */
73
+ text(t: number): RichText;
74
+ }
75
+ export interface FramesOptions {
76
+ /** The moment to draw, in seconds. */
77
+ readonly t: number;
78
+ }
79
+ /** A sequence's frame in progress at one moment, as a renderable. */
80
+ export declare class Frames implements Renderable, Measurable {
81
+ readonly sequence: FrameSequence;
82
+ readonly t: number;
83
+ private readonly _shown;
84
+ constructor(sequence: FrameSequence, options: FramesOptions);
85
+ render(options: RenderOptions): Iterable<Segment>;
86
+ /**
87
+ * One width both ways: a frame is one picture, never wrapped apart at a
88
+ * space or narrowed as the frames turn. Fitting it to the room is
89
+ * `Measurement.get`'s, as for every measurable.
90
+ */
91
+ measure(): {
92
+ minimum: number;
93
+ maximum: number;
94
+ };
95
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Frames — content that changes on a beat: a sequence of frames, each held
3
+ * for its own seconds and looping, drawn as the one in progress at a time
4
+ * the host hands in.
5
+ *
6
+ * An effect moves a cell's colours and may never change a glyph
7
+ * (`./effect.ts`: "It changes how cells look, never which cells exist or who
8
+ * drew them"). A frame is the other kind of motion — what the element shows
9
+ * changes — so it is its own primitive and not a wider `Effect`, and the two
10
+ * compose by nesting: `new Effected(new Frames(…), …)` lights whichever frame
11
+ * is up.
12
+ *
13
+ * [LAW:no-ambient-temporal-coupling] `t` is an argument, as
14
+ * `Effected`'s is, never a clock read: whoever draws frames hands each one
15
+ * its time (`core/clock.ts`), so the same `t` draws the same bytes at one
16
+ * redraw a second or sixty. The host's rate is the host's. At sixty a frame
17
+ * shows for exactly its hold; at one a second a hold shorter than a second is
18
+ * skipped and a period of whole seconds freezes on one frame, and nothing
19
+ * here adapts to that — the beat is `core/easing.ts`'s, the one timing
20
+ * vocabulary, and a draw costs the same at `t = 0` and `t = 10⁶`.
21
+ *
22
+ * The sequence and the moment are two values, because only one of them
23
+ * changes from frame to frame. A `FrameSequence` is the frames prepared once
24
+ * — the beat, the one width, each frame set into a line and padded — and a
25
+ * `Frames` is that sequence at a `t`, whose work is one frame's — finding
26
+ * which, and copying it: a host holds the sequence and asks it for a frame,
27
+ * as `Spinner` holds its two, so a draw costs the same over ninety frames as
28
+ * over one. The sequence is a value the host keeps, not a memo here: nothing
29
+ * is looked up by key.
30
+ *
31
+ * The frame drawn is drawn until the next begins, and its cells say so: they
32
+ * are stamped with that moment (`Style.nextChange`), which rides up through
33
+ * every container to whoever holds the composed frame (`Segment.nextChange`),
34
+ * so a host drawing at `t` knows when to draw again, and one drawing slower
35
+ * than the frames turn can see that it is skipping them.
36
+ *
37
+ * Every frame is drawn at one width, the widest frame's, each padded on the
38
+ * right to it, so whatever sits beside the frames never moves as they turn;
39
+ * and the frame is a fragment of a line (`setIntoLine`), ending no line of
40
+ * its own, so it sits among other cells. Frames are `RichText`, each free to
41
+ * carry its own style, and read once, here: markup is a string's reading
42
+ * under the console that draws it, which a frame handed to a template
43
+ * binding has no console for, so the frame arrives read, as a `Group`'s
44
+ * children do.
45
+ */
46
+ import { beat } from "../core/easing.js";
47
+ import { Style } from "../core/style.js";
48
+ import { setIntoLine } from "./embed.js";
49
+ /** A sequence of frames prepared to be drawn: built once, asked for the frame at a time. */
50
+ export class FrameSequence {
51
+ /** Which frame is in progress when: the holds are the frames' seconds, in order. */
52
+ beat;
53
+ /** The width every frame draws at: the widest frame's, in cells. */
54
+ width;
55
+ // Each frame as it draws: a line fragment padded to `width`, made once.
56
+ _shown;
57
+ constructor(frames) {
58
+ this.beat = beat(frames.map((frame) => frame.seconds));
59
+ const texts = frames.map((frame) => setIntoLine(frame.text.copy()));
60
+ const widths = texts.map((text) => text.cellLength);
61
+ this.width = Math.max(...widths);
62
+ this._shown = texts.map((text, i) => {
63
+ // One picture: a slot narrower than the frame crops it, never folds
64
+ // or wraps it onto a second line, which `Frames.measure` says it cannot take.
65
+ text.noWrap = true;
66
+ text.overflow = "crop";
67
+ return text.padRight(this.width - widths[i]);
68
+ });
69
+ }
70
+ /**
71
+ * The frame in progress at `t`, at `width` cells, ending no line: a copy,
72
+ * the caller's to change, its every cell — padding included — stamped with
73
+ * when it gives way to the next, `beat.end(t)`, so a blank frame among
74
+ * drawn ones still turns. [LAW:one-source-of-truth] The stamp is the one
75
+ * answer to when the frame changes, read off the cells
76
+ * (`Segment.nextChange`): a sequence of nothing has no cell to carry one,
77
+ * and a field saying a time would disagree with cells saying never.
78
+ * `beat.at` refuses a `t` that is not finite.
79
+ */
80
+ text(t) {
81
+ return this._shown[this.beat.at(t)].copy().stylize(new Style({ nextChange: this.beat.end(t) }));
82
+ }
83
+ }
84
+ /** A sequence's frame in progress at one moment, as a renderable. */
85
+ export class Frames {
86
+ sequence;
87
+ t;
88
+ // The frame `t` lands on, found once, here, so a bad time fails at construction.
89
+ _shown;
90
+ constructor(sequence, options) {
91
+ this.sequence = sequence;
92
+ this.t = options.t;
93
+ this._shown = sequence.text(options.t);
94
+ }
95
+ render(options) {
96
+ return this._shown.render(options);
97
+ }
98
+ /**
99
+ * One width both ways: a frame is one picture, never wrapped apart at a
100
+ * space or narrowed as the frames turn. Fitting it to the room is
101
+ * `Measurement.get`'s, as for every measurable.
102
+ */
103
+ measure() {
104
+ return { minimum: this.sequence.width, maximum: this.sequence.width };
105
+ }
106
+ }
@@ -27,10 +27,26 @@
27
27
  import { type Clock } from "../core/clock.js";
28
28
  import { Console } from "../core/console.js";
29
29
  import { type Renderable } from "../core/protocol.js";
30
+ /**
31
+ * What a Live draws: a renderable, or a view asked for one each frame with
32
+ * the frame's time in seconds on the Live's clock, as an `App`'s view is
33
+ * (`host/app.ts`). A renderable is the view that is the same at every `t` —
34
+ * a table mutated in place between frames — and a function is what a frame
35
+ * drawn for a time needs: `(t) => spinner.at(t)`.
36
+ * [LAW:no-ambient-temporal-coupling] The Live reads its clock once a frame
37
+ * and hands the time down as data, so nothing it draws reads one.
38
+ */
39
+ export type LiveView = Renderable | ((t: number) => Renderable);
30
40
  export interface LiveOptions {
31
- /** Frames a second while auto-refreshing: positive and finite, fractional included. */
41
+ /**
42
+ * How often the auto-refresh draws on its own, in frames a second:
43
+ * positive and finite, fractional included. The next frame comes a frame
44
+ * interval after the last one drawn, a `refresh()` of your own included,
45
+ * or sooner when that frame's content says it changes sooner
46
+ * (`Segment.nextChange`).
47
+ */
32
48
  refreshPerSecond?: number;
33
- /** What the auto-refresh ticks on. The platform's, unless given. */
49
+ /** What the auto-refresh waits on. The platform's, unless given. */
34
50
  clock?: Clock;
35
51
  autoRefresh?: boolean;
36
52
  transient?: boolean;
@@ -42,7 +58,7 @@ export interface LiveOptions {
42
58
  altScreen?: boolean;
43
59
  }
44
60
  export declare class Live {
45
- private _renderable;
61
+ private _view;
46
62
  private _console;
47
63
  private readonly _rate;
48
64
  private readonly _clock;
@@ -50,21 +66,21 @@ export declare class Live {
50
66
  private _transient;
51
67
  private _verticalOverflow;
52
68
  private readonly _painter;
53
- private _stopTicking;
69
+ private _waking;
54
70
  private _held;
55
- constructor(renderable?: Renderable, options?: LiveOptions);
71
+ constructor(view?: LiveView, options?: LiveOptions);
56
72
  /**
57
73
  * The console to print through while this Live runs: what it prints lands
58
74
  * above an inline frame. The alternate screen is all frame, and paints over it.
59
75
  */
60
76
  get console(): Console;
61
- get renderable(): Renderable | undefined;
62
77
  start(): void;
63
78
  stop(): void;
64
- update(renderable?: Renderable, options?: {
79
+ update(view?: LiveView, options?: {
65
80
  refresh?: boolean;
66
81
  }): void;
67
82
  refresh(): void;
83
+ private _wake;
68
84
  private _frame;
69
85
  private _stepAround;
70
86
  private _paint;
@@ -29,8 +29,12 @@ import { Console } from "../core/console.js";
29
29
  import { Segment } from "../core/segment.js";
30
30
  import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
31
31
  import { fitHeight } from "../core/protocol.js";
32
+ // [LAW:parse-dont-validate] The one shape Live holds: a renderable becomes
33
+ // the view that answers it at every t, where it is given, so no frame asks
34
+ // which it was.
35
+ const viewOf = (view) => view === undefined || typeof view === "function" ? view : () => view;
32
36
  export class Live {
33
- _renderable;
37
+ _view;
34
38
  _console;
35
39
  _rate;
36
40
  _clock;
@@ -38,13 +42,14 @@ export class Live {
38
42
  _transient;
39
43
  _verticalOverflow;
40
44
  _painter;
41
- _stopTicking;
45
+ // The one wait for the next auto-refresh frame, armed after each frame.
46
+ _waking;
42
47
  // [LAW:one-source-of-truth] The console's live region, held from `start()`
43
48
  // to the hand-back — which is what "started" means, so there is no separate
44
49
  // flag to disagree with it.
45
50
  _held;
46
- constructor(renderable, options) {
47
- this._renderable = renderable;
51
+ constructor(view, options) {
52
+ this._view = viewOf(view);
48
53
  this._console = options?.console ?? new Console();
49
54
  // [LAW:parse-dont-validate] Parsed whether or not it auto-refreshes, so a
50
55
  // rate that could never tick is refused where it is given.
@@ -69,9 +74,6 @@ export class Live {
69
74
  get console() {
70
75
  return this._console;
71
76
  }
72
- get renderable() {
73
- return this._renderable;
74
- }
75
77
  start() {
76
78
  if (this._held !== null)
77
79
  return;
@@ -80,21 +82,9 @@ export class Live {
80
82
  const held = this._console.claimLiveRegion({ around: (text) => this._stepAround(text) });
81
83
  this._held = held;
82
84
  this._painter.take();
83
- if (this._autoRefresh) {
84
- // No caller is on the stack to run `stop()` for a frame that throws
85
- // here, and in Node the throw ends the process — so the terminal is
86
- // handed back first, and the error then goes on to wherever an uncaught
87
- // one goes.
88
- this._stopTicking = this._clock.every(this._rate, () => {
89
- try {
90
- this.refresh();
91
- }
92
- catch (error) {
93
- this._release(held);
94
- throw error;
95
- }
96
- });
97
- }
85
+ // Nothing is drawn yet, so nothing has said when it changes: the first
86
+ // auto-refresh frame comes at the rate, from now.
87
+ this._wake(held, { t: this._clock.now(), nextChange: Number.POSITIVE_INFINITY });
98
88
  }
99
89
  stop() {
100
90
  const held = this._held;
@@ -114,9 +104,9 @@ export class Live {
114
104
  this._release(held);
115
105
  }
116
106
  }
117
- update(renderable, options) {
118
- if (renderable !== undefined) {
119
- this._renderable = renderable;
107
+ update(view, options) {
108
+ if (view !== undefined) {
109
+ this._view = viewOf(view);
120
110
  }
121
111
  if (options?.refresh) {
122
112
  this.refresh();
@@ -126,13 +116,57 @@ export class Live {
126
116
  // A frame is drawn only while this Live holds the terminal, as in Rich:
127
117
  // before `start()` or after the hand-back it would land on a screen that
128
118
  // belongs to someone else — the main buffer, under the user's own output.
129
- if (this._held === null)
119
+ const held = this._held;
120
+ if (held === null)
121
+ return;
122
+ // [LAW:no-ambient-temporal-coupling] The frame says when what it shows
123
+ // next changes; the next auto-refresh frame is then, or at the rate,
124
+ // whichever is sooner. The painter decides whether this frame is drawn
125
+ // now — a final-frame painter draws nothing before the hand-back — and a
126
+ // frame not drawn has said nothing.
127
+ let said;
128
+ this._paint(() => {
129
+ const { t, lines } = this._frame();
130
+ said = { t, nextChange: lines.reduce((soonest, row) => Math.min(soonest, Segment.nextChange(row)), Number.POSITIVE_INFINITY) };
131
+ return lines;
132
+ });
133
+ this._wake(held, said ?? { t: this._clock.now(), nextChange: Number.POSITIVE_INFINITY });
134
+ }
135
+ // Arm the one wait for the next auto-refresh frame while `held`: at the
136
+ // frame's `nextChange` when that is before the rate's next frame, else at
137
+ // the rate — both measured from the frame's own `t`, never from a second
138
+ // reading of the clock, so the rate does not stretch by what a frame cost
139
+ // to draw, and a change that fell due while it was drawn is drawn at once
140
+ // (`Clock.at` fires now for a time already past). Content that changes
141
+ // every frame answers `t` itself, and is drawn at the rate.
142
+ _wake(held, { t, nextChange }) {
143
+ this._waking?.();
144
+ this._waking = undefined;
145
+ if (!this._autoRefresh)
130
146
  return;
131
- this._paint(() => this._frame());
147
+ const at = Math.min(t + this._rate.interval, nextChange > t ? nextChange : Number.POSITIVE_INFINITY);
148
+ // No caller is on the stack to run `stop()` for a frame that throws
149
+ // here, and in Node the throw ends the process — so the terminal is
150
+ // handed back first, and the error then goes on to wherever an uncaught
151
+ // one goes.
152
+ this._waking = this._clock.at(at, () => {
153
+ try {
154
+ this.refresh();
155
+ }
156
+ catch (error) {
157
+ this._release(held);
158
+ throw error;
159
+ }
160
+ });
132
161
  }
162
+ // The frame at the clock's now, with that time: the one the view was handed, and the one the next wake is measured from.
133
163
  _frame() {
134
- if (!this._renderable)
135
- return [];
164
+ // [LAW:effects-at-boundaries] One read of the clock a frame, handed to
165
+ // the view as the frame's time.
166
+ const t = this._clock.now();
167
+ if (!this._view)
168
+ return { t, lines: [] };
169
+ const renderable = this._view(t);
136
170
  // [LAW:dataflow-not-control-flow] Both modes render under the terminal's
137
171
  // rows and differ only in what those rows are: the alternate screen is a
138
172
  // region the frame stands in, and an inline frame keeps its natural height
@@ -140,14 +174,14 @@ export class Live {
140
174
  // back — its overflow policy, then `fitHeight`, which pads only a region.
141
175
  const options = this._console.options;
142
176
  const height = this._painter.height(options.height.rows);
143
- const lines = Segment.splitLines(this._renderable.render({ ...options, height }));
144
- return fitHeight(this._overflow(lines, height.rows), height);
177
+ const lines = Segment.splitLines(renderable.render({ ...options, height }));
178
+ return { t, lines: fitHeight(this._overflow(lines, height.rows), height) };
145
179
  }
146
180
  // What the console writes goes where the frame was, and the frame is painted
147
181
  // again under it, as Rich's render hook does — in the bytes the console
148
182
  // writes, so a render that throws writes nothing.
149
183
  _stepAround(text) {
150
- return this._painter.around(text, () => this._frame(), this._screen(), this._console.destination);
184
+ return this._painter.around(text, () => this._frame().lines, this._screen(), this._console.destination);
151
185
  }
152
186
  // [LAW:effects-at-boundaries] The frame is fully rendered before a byte
153
187
  // reaches the terminal, and whatever takes the last frame away goes out in
@@ -177,8 +211,8 @@ export class Live {
177
211
  _release(held) {
178
212
  held();
179
213
  this._held = null;
180
- this._stopTicking?.();
181
- this._stopTicking = undefined;
214
+ this._waking?.();
215
+ this._waking = undefined;
182
216
  this._painter.handBack();
183
217
  }
184
218
  }
@@ -5,8 +5,11 @@
5
5
  * Block structure is read as CommonMark reads it: a blockquote and a list
6
6
  * item are both containers, their lines tokenized as Markdown of their own
7
7
  * once the container's marker or indentation is cut off, and drawn beside a
8
- * gutter — the quote's bar, the item's marker then its hang. Where CommonMark
9
- * leaves the drawing open, Rich's is followed.
8
+ * gutter — the quote's bar, the item's marker then its hang. A table, which
9
+ * CommonMark has none of, and an HTML block are read as markdown-it — Rich's
10
+ * parser — reads them. Where CommonMark leaves the drawing open, Rich's is
11
+ * followed: HTML, a block of it or a tag inline, is drawn as Rich draws it,
12
+ * not at all.
10
13
  */
11
14
  import { Segment } from "../core/segment.js";
12
15
  import { Style } from "../core/style.js";