@promptctl/rich-js 0.26.0 → 0.28.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 (132) hide show
  1. package/README.md +1 -0
  2. package/THIRD-PARTY-NOTICES +42 -0
  3. package/dist/core/animation.d.ts +52 -15
  4. package/dist/core/animation.js +82 -37
  5. package/dist/core/ansi.d.ts +15 -8
  6. package/dist/core/ansi.js +106 -28
  7. package/dist/core/bidi-data.d.ts +14 -0
  8. package/dist/core/bidi-data.js +385 -0
  9. package/dist/core/bidi.d.ts +89 -0
  10. package/dist/core/bidi.js +615 -0
  11. package/dist/core/box.d.ts +9 -1
  12. package/dist/core/box.js +11 -0
  13. package/dist/core/cell-widths.d.ts +3 -2
  14. package/dist/core/cell-widths.js +93 -93
  15. package/dist/core/cells.d.ts +20 -0
  16. package/dist/core/cells.js +93 -5
  17. package/dist/core/color.d.ts +26 -18
  18. package/dist/core/color.js +37 -34
  19. package/dist/core/console.d.ts +26 -1
  20. package/dist/core/console.js +26 -6
  21. package/dist/core/control-codes.d.ts +61 -0
  22. package/dist/core/control-codes.js +157 -0
  23. package/dist/core/easing.js +6 -1
  24. package/dist/core/env.d.ts +6 -0
  25. package/dist/core/env.js +9 -1
  26. package/dist/core/export-html.js +4 -3
  27. package/dist/core/export-lines.d.ts +2 -0
  28. package/dist/core/export-lines.js +7 -3
  29. package/dist/core/export-svg.js +6 -5
  30. package/dist/core/json.js +9 -2
  31. package/dist/core/markup.js +4 -3
  32. package/dist/core/oklch.d.ts +2 -0
  33. package/dist/core/oklch.js +3 -3
  34. package/dist/core/orient.d.ts +68 -0
  35. package/dist/core/orient.js +224 -0
  36. package/dist/core/osc8.d.ts +12 -5
  37. package/dist/core/osc8.js +14 -7
  38. package/dist/core/pretty.js +22 -18
  39. package/dist/core/protocol.d.ts +53 -22
  40. package/dist/core/protocol.js +14 -6
  41. package/dist/core/render.d.ts +8 -3
  42. package/dist/core/render.js +19 -5
  43. package/dist/core/seal.d.ts +26 -0
  44. package/dist/core/seal.js +75 -0
  45. package/dist/core/segment.d.ts +9 -1
  46. package/dist/core/segment.js +11 -1
  47. package/dist/core/style.d.ts +29 -1
  48. package/dist/core/style.js +41 -5
  49. package/dist/core/text-order.d.ts +51 -0
  50. package/dist/core/text-order.js +50 -0
  51. package/dist/core/text.d.ts +10 -0
  52. package/dist/core/text.js +133 -87
  53. package/dist/core/timeline.js +6 -6
  54. package/dist/core/view.d.ts +23 -9
  55. package/dist/core/view.js +37 -12
  56. package/dist/core/wrap.d.ts +24 -6
  57. package/dist/core/wrap.js +82 -16
  58. package/dist/host/app.d.ts +48 -7
  59. package/dist/host/app.js +116 -11
  60. package/dist/host/index.d.ts +3 -2
  61. package/dist/host/index.js +1 -0
  62. package/dist/host/input.d.ts +35 -2
  63. package/dist/host/input.js +88 -4
  64. package/dist/host/terminal-colors.d.ts +18 -0
  65. package/dist/host/terminal-colors.js +46 -0
  66. package/dist/host/terminal-host.d.ts +2 -1
  67. package/dist/index.d.ts +10 -8
  68. package/dist/index.js +2 -2
  69. package/dist/renderables/change.d.ts +1 -3
  70. package/dist/renderables/change.js +4 -6
  71. package/dist/renderables/effect.d.ts +61 -60
  72. package/dist/renderables/effect.js +91 -81
  73. package/dist/renderables/effects.d.ts +37 -8
  74. package/dist/renderables/effects.js +193 -55
  75. package/dist/renderables/frames.d.ts +1 -10
  76. package/dist/renderables/frames.js +1 -16
  77. package/dist/renderables/glyph-effect.d.ts +12 -13
  78. package/dist/renderables/glyph-effect.js +34 -32
  79. package/dist/renderables/glyph-effects.d.ts +27 -8
  80. package/dist/renderables/glyph-effects.js +75 -7
  81. package/dist/renderables/lifecycle.d.ts +40 -32
  82. package/dist/renderables/lifecycle.js +87 -48
  83. package/dist/renderables/live.d.ts +3 -3
  84. package/dist/renderables/live.js +4 -4
  85. package/dist/renderables/markdown.js +4 -2
  86. package/dist/renderables/panel.js +6 -4
  87. package/dist/renderables/progress.d.ts +11 -4
  88. package/dist/renderables/progress.js +29 -17
  89. package/dist/renderables/progressBar.d.ts +3 -3
  90. package/dist/renderables/progressBar.js +16 -13
  91. package/dist/renderables/syntax.js +7 -2
  92. package/dist/renderables/table.js +2 -1
  93. package/dist/template-bindings/animation-funcs.d.ts +49 -18
  94. package/dist/template-bindings/animation-funcs.js +115 -32
  95. package/dist/template-bindings/index.d.ts +7 -5
  96. package/dist/template-bindings/index.js +8 -5
  97. package/dist/template-bindings/palette-funcs.d.ts +1 -1
  98. package/dist/template-bindings/palette-funcs.js +1 -1
  99. package/dist/themes/buildPalette.d.ts +15 -39
  100. package/dist/themes/buildPalette.js +196 -67
  101. package/dist/themes/colorMath.d.ts +10 -4
  102. package/dist/themes/colorMath.js +33 -1
  103. package/dist/themes/data/index.js +1 -4
  104. package/dist/themes/data/textual-ansi.js +0 -143
  105. package/dist/themes/data/types.d.ts +3 -1
  106. package/dist/themes/palette.d.ts +22 -6
  107. package/dist/themes/palette.js +13 -21
  108. package/dist/themes/ramp.d.ts +1 -1
  109. package/dist/themes/ramp.js +1 -1
  110. package/dist/themes/registry.d.ts +7 -10
  111. package/dist/themes/registry.js +9 -30
  112. package/dist/themes/terminalThemes.d.ts +4 -10
  113. package/dist/themes/terminalThemes.js +31 -46
  114. package/dist/themes/transpose.d.ts +5 -10
  115. package/dist/themes/transpose.js +13 -24
  116. package/dist/widgets/button.d.ts +1 -1
  117. package/dist/widgets/button.js +6 -6
  118. package/dist/widgets/checkbox.js +4 -4
  119. package/dist/widgets/dropdown.d.ts +1 -1
  120. package/dist/widgets/dropdown.js +7 -5
  121. package/dist/widgets/event-router.js +2 -2
  122. package/dist/widgets/ink.d.ts +5 -4
  123. package/dist/widgets/ink.js +6 -9
  124. package/dist/widgets/text-input.d.ts +1 -1
  125. package/dist/widgets/text-input.js +41 -32
  126. package/dist/widgets/themed-widget.js +3 -3
  127. package/dist/widgets/toggle.d.ts +1 -1
  128. package/dist/widgets/toggle.js +5 -5
  129. package/dist/widgets/types.d.ts +2 -2
  130. package/dist/widgets/widget-app.d.ts +3 -3
  131. package/dist/widgets/widget-app.js +5 -5
  132. package/package.json +5 -4
@@ -26,7 +26,7 @@
26
26
  */
27
27
  import { type Clock } from "../core/clock.js";
28
28
  import { Console } from "../core/console.js";
29
- import { type FrameHost } from "../core/protocol.js";
29
+ import { type HostFrame, type FrameHost } from "../core/protocol.js";
30
30
  import { Timeline } from "../core/timeline.js";
31
31
  import { type View } from "../core/view.js";
32
32
  export interface LiveOptions {
@@ -79,8 +79,8 @@ export declare class Live implements FrameHost {
79
79
  get clock(): Clock;
80
80
  /** The map from the clock to the time the view is drawn for. */
81
81
  get timeline(): Timeline;
82
- /** The time a frame drawn now is drawn for: the clock's now, through the timeline. */
83
- get frameTime(): number;
82
+ /** The frame this Live draws now (`FrameHost`): the clock's now, through the timeline, on a frame it is the host of. */
83
+ frameNow(): HostFrame;
84
84
  private _time;
85
85
  /**
86
86
  * Replace the timeline and draw the frame for the time it now maps the
@@ -30,7 +30,7 @@ import { Segment } from "../core/segment.js";
30
30
  import { FinalFramePainter, SurfacePainter } from "../core/paint.js";
31
31
  import { fitHeight, render } from "../core/protocol.js";
32
32
  import { Timeline } from "../core/timeline.js";
33
- import { drawView, frameTime, hostView, undrawn } from "../core/view.js";
33
+ import { drawView, frameNow, hostView, undrawn } from "../core/view.js";
34
34
  import { State } from "../core/state.js";
35
35
  // Nothing: what a Live made with no view draws until `update` gives it one.
36
36
  const NOTHING = { render: () => [] };
@@ -89,9 +89,9 @@ export class Live {
89
89
  get timeline() {
90
90
  return this._timeline.get();
91
91
  }
92
- /** The time a frame drawn now is drawn for: the clock's now, through the timeline. */
93
- get frameTime() {
94
- return frameTime(this._time());
92
+ /** The frame this Live draws now (`FrameHost`): the clock's now, through the timeline, on a frame it is the host of. */
93
+ frameNow() {
94
+ return frameNow(this, this._time());
95
95
  }
96
96
  _time() {
97
97
  return { now: this._clock.now(), epoch: this._epoch, timeline: this._timeline.get() };
@@ -8,6 +8,7 @@
8
8
  * followed: HTML, a block of it or a tag inline, is drawn as Rich draws it,
9
9
  * not at all.
10
10
  */
11
+ import { withoutExplicit } from "../core/bidi.js";
11
12
  import { cellCount, cellLen } from "../core/cells.js";
12
13
  import { Segment } from "../core/segment.js";
13
14
  import { Style } from "../core/style.js";
@@ -107,7 +108,8 @@ function appendInline(result, inlines, settings, options) {
107
108
  };
108
109
  const write = (text, styles) => {
109
110
  const start = result.length;
110
- result.append(text);
111
+ // Each inline node is its own content, so what it holds open ends with it.
112
+ result.append(new RichText(text));
111
113
  paint(start, styles);
112
114
  };
113
115
  const enter = (styles) => {
@@ -156,7 +158,7 @@ function appendInline(result, inlines, settings, options) {
156
158
  write("\n");
157
159
  break;
158
160
  case "code":
159
- write(inline.code, [groundedStyle(options, resolveStyle(options, settings.inlineCodeStyle))]);
161
+ write(withoutExplicit(inline.code), [groundedStyle(options, resolveStyle(options, settings.inlineCodeStyle))]);
160
162
  break;
161
163
  case "html":
162
164
  kbd(inline.content);
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Panel — a bordered box that wraps content, with optional title and subtitle.
3
3
  */
4
+ import { labelled } from "../core/orient.js";
4
5
  import { cellLen, setCellSize, asCellCol, cellCount } from "../core/cells.js";
5
6
  import { Segment } from "../core/segment.js";
6
7
  import { NULL_STYLE } from "../core/style.js";
@@ -42,10 +43,11 @@ function borderTitle(label, options) {
42
43
  * its own laid over the border's, the one rule for "what colour is the title
43
44
  * text in". Laid over rather than in place of it, as Rich's `stylize_before`
44
45
  * does, because the border's style carries the panel's own: a title that
45
- * replaced it cut a hole in the panel's background on the top row.
46
+ * replaced it cut a hole in the panel's background on the top row. The text
47
+ * is set into the frame and is not of it.
46
48
  */
47
49
  function borderTextStyle(border, own) {
48
- return own.isNull ? border : (border ?? NULL_STYLE).add(own);
50
+ return border.add(own).withBoxFrame(undefined);
49
51
  }
50
52
  /**
51
53
  * A border row: the opening corner, the rule across `span` cells with `label`
@@ -129,7 +131,7 @@ export class Panel {
129
131
  // and the content is rendered on top of it. Applied to the padding alone,
130
132
  // "white on dark_blue" drew a blue ring with a hole where the text was.
131
133
  const borderStyle = getStyle(options, this.style).add(getStyle(options, this.borderStyle));
132
- const border = borderStyle.isNull ? undefined : borderStyle;
134
+ const border = box.frame(borderStyle);
133
135
  const geometry = layoutPanel(this._getPanelWidth(options));
134
136
  const bodyLines = this._renderBody(options, geometry.spanWidth);
135
137
  yield* this._renderTopBorder(options, box, geometry, border);
@@ -241,7 +243,7 @@ export class Panel {
241
243
  const corner = box.bottom.right.repeat(geometry.right);
242
244
  const [close, afterAccessory] = accessoryText === "" ? [corner, ""] : ["", corner];
243
245
  yield* borderRow(options, box.bottom, geometry, geometry.spanWidth - cellLen(accessoryText), this._subtitleLabel, subtitleStyle, border, close);
244
- yield new Segment(accessoryText, borderTextStyle(border, accessoryOwn));
246
+ yield* labelled("", accessoryText, "", borderTextStyle(border, accessoryOwn), options.textOrder);
245
247
  yield new Segment(afterAccessory, border);
246
248
  yield Segment.line();
247
249
  }
@@ -7,7 +7,7 @@ import { Console } from "../core/console.js";
7
7
  import type { Style } from "../core/style.js";
8
8
  import type { Clock } from "../core/clock.js";
9
9
  import { type ColumnOptions } from "./table.js";
10
- import type { FrameHost, Renderable, RenderOptions } from "../core/protocol.js";
10
+ import type { HostFrame, FrameHost, Renderable, RenderOptions } from "../core/protocol.js";
11
11
  import { Animation } from "../core/animation.js";
12
12
  import type { Timing } from "../core/easing.js";
13
13
  export interface TaskOptions {
@@ -156,7 +156,7 @@ export declare class MofNCompleteColumn implements ProgressColumn {
156
156
  }
157
157
  /**
158
158
  * How a `Progress` is set up. It is drawn by one host, and every time it
159
- * keeps or draws at is that host's `frameTime`: by its own `Live`, which the
159
+ * keeps or draws at is that host's `frameNow()`: by its own `Live`, which the
160
160
  * options for that `Live` configure, or, given `drawnBy`, by another host —
161
161
  * which then does all the drawing, so none of them apply.
162
162
  */
@@ -185,7 +185,7 @@ interface OwnLiveOptions {
185
185
  interface DrawnByOptions {
186
186
  /**
187
187
  * The host — a `Live`, an `App` — whose frames this `Progress` is drawn in:
188
- * tasks are timed by its `frameTime`, so they hold while its timeline is
188
+ * tasks are timed by its `frameNow()`, so they hold while its timeline is
189
189
  * paused and run back while it is reversed. Drawn in any other host's
190
190
  * frame, the `Progress` throws.
191
191
  */
@@ -207,6 +207,13 @@ export declare class Progress implements Renderable {
207
207
  constructor(...columns: (ProgressColumn | ProgressOptions)[]);
208
208
  /** The console its own `Live` prints above the bars through. */
209
209
  get console(): Console;
210
+ /**
211
+ * The frame its host draws now (`FrameHost.frameNow`): what a print of
212
+ * this display is drawn in — `console.print(progress, { frame:
213
+ * progress.frameNow() })` — so a still of it is drawn for a moment on the
214
+ * clock its tasks are timed on, the one the program asked at.
215
+ */
216
+ frameNow(): HostFrame;
210
217
  /** Rich's `Progress.finished`: every task finished, and so true with none. */
211
218
  get finished(): boolean;
212
219
  static getDefaultColumns(): ProgressColumn[];
@@ -224,7 +231,7 @@ export declare class Progress implements Renderable {
224
231
  refresh(): void;
225
232
  private _ownLive;
226
233
  render(options: RenderOptions): Iterable<Segment>;
227
- private _framed;
234
+ private _ownFrame;
228
235
  private _table;
229
236
  }
230
237
  export declare function track<T>(iterable: Iterable<T>, options?: {
@@ -321,9 +321,20 @@ export class Progress {
321
321
  get console() {
322
322
  return this._ownLive("console").console;
323
323
  }
324
+ /**
325
+ * The frame its host draws now (`FrameHost.frameNow`): what a print of
326
+ * this display is drawn in — `console.print(progress, { frame:
327
+ * progress.frameNow() })` — so a still of it is drawn for a moment on the
328
+ * clock its tasks are timed on, the one the program asked at.
329
+ */
330
+ frameNow() {
331
+ return this._host.frameNow();
332
+ }
324
333
  /** Rich's `Progress.finished`: every task finished, and so true with none. */
325
334
  get finished() {
326
- return this._tasks.get().every((task) => finished(taskAt(task.get(), this._host.frameTime)));
335
+ // [LAW:one-source-of-truth] One moment, so every task is judged at it.
336
+ const now = this._host.frameNow().time;
337
+ return this._tasks.get().every((task) => finished(taskAt(task.get(), now)));
327
338
  }
328
339
  static getDefaultColumns() {
329
340
  return [
@@ -342,7 +353,7 @@ export class Progress {
342
353
  completed: 0,
343
354
  fill: Animation.constant(0),
344
355
  visible: options?.visible !== false,
345
- startTime: options?.start === false ? undefined : this._host.frameTime,
356
+ startTime: options?.start === false ? undefined : this._host.frameNow().time,
346
357
  finishedAt: undefined,
347
358
  samples: [],
348
359
  }));
@@ -364,7 +375,7 @@ export class Progress {
364
375
  const completed = options.completed === undefined
365
376
  ? finiteCount("completed + advance", task.completed + advance)
366
377
  : finiteCount("completed", options.completed);
367
- const now = this._host.frameTime;
378
+ const now = this._host.frameNow().time;
368
379
  const reached = task.startTime !== undefined && task.total !== undefined && completed >= task.total;
369
380
  held.set({
370
381
  ...task,
@@ -385,7 +396,7 @@ export class Progress {
385
396
  const held = this._task(taskId);
386
397
  // Rich's `start_task`: a task already running keeps the time it started.
387
398
  if (held)
388
- held.set({ ...held.get(), startTime: held.get().startTime ?? this._host.frameTime });
399
+ held.set({ ...held.get(), startTime: held.get().startTime ?? this._host.frameNow().time });
389
400
  }
390
401
  start() {
391
402
  this._ownLive("start").start();
@@ -410,23 +421,24 @@ export class Progress {
410
421
  return this._live;
411
422
  }
412
423
  *render(options) {
413
- const framed = this._framed(options);
414
- yield* render(this._table(framed.frame.time), framed);
424
+ yield* render(this._table(this._ownFrame(options).time), options);
415
425
  }
416
- // Inside a frame, the time its host read for it: a second read here would
417
- // land a moving bar's stamp a hair after the frame, a moment the host takes
418
- // for one still to come. A frame is only ever this display's host's,
419
- // because the tasks were stamped on that host's time and a frame of any
420
- // other is drawn for a moment on a different clock. A print is drawn for no
421
- // moment, so it is drawn in a frame for the one the program asks at, read
422
- // as `updateTask` reads it, and every bar and spinner placed in the table
423
- // draws at that moment too. [LAW:one-source-of-truth]
424
- _framed(options) {
426
+ // The frame this display is drawn in, which is only ever its host's: the
427
+ // tasks were stamped on that host's time, and a frame of any other host is
428
+ // drawn for a moment on a different clock. A render reads no clock, so a
429
+ // print names its frame (`frameNow`, `PrintOptions.frame`): drawn for no
430
+ // moment — 0, as everything else is — every task would read as not yet
431
+ // started, so that print is refused with the call that draws it right.
432
+ // [LAW:parse-dont-validate] [LAW:no-silent-failure]
433
+ _ownFrame(options) {
425
434
  const { frame } = options;
426
- if (frame !== undefined && frame.host !== this._host) {
435
+ if (frame === undefined) {
436
+ throw new Error("a Progress is drawn for a moment on its host's clock, and this print named none; print it in its frame: console.print(progress, { frame: progress.frameNow() })");
437
+ }
438
+ if (frame.host !== this._host) {
427
439
  throw new Error("a Progress was drawn in a frame of a host other than its own; give it that host as `drawnBy`");
428
440
  }
429
- return { ...options, frame: frame ?? { time: this._host.frameTime, host: this._host } };
441
+ return frame;
430
442
  }
431
443
  // Rich's `make_tasks_table`: each column laid out as it says, at `now` on
432
444
  // this display's clock.
@@ -44,9 +44,9 @@ export declare class ProgressBar implements Renderable, Measurable {
44
44
  set finished(value: boolean | undefined);
45
45
  render(options: RenderOptions): Iterable<Segment>;
46
46
  /**
47
- * The shimmer over this bar's back colour as `Effected` sees it drawn at
48
- * `depth`, toward `bar.pulse`. A bar has no words to keep legible, so its
49
- * back takes the whole of the light.
47
+ * The shimmer over this bar's back colour as `Effected` sees it drawn,
48
+ * toward `bar.pulse`. A bar has no words to keep legible, so its back takes
49
+ * the whole of the light.
50
50
  */
51
51
  private pulseEffect;
52
52
  /** Rich 9d8f9a3 `__rich_console__`: the fill is counted in half cells, and a total of zero is a bar already full. */
@@ -71,30 +71,33 @@ export class ProgressBar {
71
71
  }
72
72
  const depth = options.colorSystem ?? ColorDepth.TRUECOLOR;
73
73
  // Rich's pulse resolves its colours on the terminal a depth assumes, as
74
- // `get_truecolor` does with no theme; so does this.
75
- const theme = resolveTerminal(undefined, depth);
74
+ // `get_truecolor` does with no theme; so does this with none named.
75
+ const terminal = resolveTerminal(options.terminal, depth);
76
76
  // Rich writes the pulse's back as a colour of its own, black where the
77
77
  // back style names none, so the light always has the bar's colour to fall
78
78
  // on: one the terminal supplies takes no light.
79
79
  const style = getStyle(options, this.style);
80
- const ink = style.color?.isDefault === false ? style.color : ColorSpec.fromRgba((style.color ?? PULSE_BACK).getTruecolor(theme, true));
80
+ const ink = style.color?.isDefault === false ? style.color : ColorSpec.fromRgba((style.color ?? PULSE_BACK).getTruecolor(terminal, true));
81
81
  const back = style.add(Style.fromColor(ink));
82
82
  // [LAW:no-ambient-temporal-coupling] The moment is the frame's; the bar
83
83
  // only says what it looks like then. An empty track, lit by the shimmer.
84
84
  const track = { render: (o) => this.track(o, width, 0, back) };
85
- yield* render(new Effected(track, this.pulseEffect(options, back, depth, theme), { key: "progress-bar", theme }), options);
85
+ yield* render(new Effected(track, this.pulseEffect(options, back), { key: "progress-bar" }), options);
86
86
  }
87
87
  /**
88
- * The shimmer over this bar's back colour as `Effected` sees it drawn at
89
- * `depth`, toward `bar.pulse`. A bar has no words to keep legible, so its
90
- * back takes the whole of the light.
88
+ * The shimmer over this bar's back colour as `Effected` sees it drawn,
89
+ * toward `bar.pulse`. A bar has no words to keep legible, so its back takes
90
+ * the whole of the light.
91
91
  */
92
- pulseEffect(options, back, depth, theme) {
93
- const ink = back.drawnColors(depth).color ?? ColorSpec.default();
94
- const glow = getStyle(options, "bar.pulse").drawnColors(depth).color ?? ColorSpec.default();
95
- const loop = shimmer(PULSE_CURVE, PULSE_REACH, glow.getTruecolor(theme, true), 0);
96
- const whole = new Map([[ink.getTruecolor(theme, true).hex, 1]]);
97
- return atShares(loop, () => whole);
92
+ pulseEffect(options, back) {
93
+ return (drawn) => {
94
+ const { depth, terminal } = drawn;
95
+ const ink = back.drawnColors(depth).color ?? ColorSpec.default();
96
+ const glow = getStyle(options, "bar.pulse").drawnColors(depth).color ?? ColorSpec.default();
97
+ const loop = shimmer(PULSE_CURVE, PULSE_REACH, glow.getTruecolor(terminal, true), 0);
98
+ const whole = new Map([[ink.getTruecolor(terminal, true).hex, 1]]);
99
+ return atShares(loop, () => whole)(drawn);
100
+ };
98
101
  }
99
102
  /** Rich 9d8f9a3 `__rich_console__`: the fill is counted in half cells, and a total of zero is a bar already full. */
100
103
  completeHalves(width) {
@@ -7,7 +7,8 @@
7
7
  * other renderable here is restyled, and a second styling vocabulary on this
8
8
  * one class would be a second answer to "what colour is a keyword".
9
9
  */
10
- import { cellLen, expandTabs, parseTabSize } from "../core/cells.js";
10
+ import { escaped } from "../core/bidi.js";
11
+ import { cellLen, expandTabs, parseTabSize, unicodeEscape } from "../core/cells.js";
11
12
  import { Segment } from "../core/segment.js";
12
13
  import { RichText } from "../core/text.js";
13
14
  import { drawable, groundedStyle, getStyle, withBoundedWidth, withCellWidth, } from "../core/protocol.js";
@@ -89,7 +90,11 @@ export class Syntax {
89
90
  this.wordWrap = options?.wordWrap ?? false;
90
91
  const tabSize = parseTabSize(options?.tabSize ?? 4);
91
92
  this.tabSize = tabSize;
92
- this._lines = expandTabs(code, tabSize).text.split("\n");
93
+ // Code on show: what the wire would drop is escaped, and an explicit bidi
94
+ // control with it. That is the Trojan Source attack: the order and the lines on
95
+ // screen differing from what the compiler reads. A joiner stays, since a Persian
96
+ // comment or an emoji string needs it. Only a CRLF's CR goes, with its line ending.
97
+ this._lines = expandTabs(escaped(code.replace(/\r\n/g, "\n"), unicodeEscape), tabSize).text.split("\n");
93
98
  }
94
99
  /**
95
100
  * The lines shown, as a 0-based half-open slice of `_lines`, and the number
@@ -454,11 +454,12 @@ export class Table {
454
454
  // that is all it styles: the reference reads it as `table_style` for the
455
455
  // frame and nowhere else, so a cell is untouched by it.
456
456
  const borderStyle = getStyle(options, this.style).add(getStyle(options, this.borderStyle));
457
- const border = borderStyle.isNull ? undefined : borderStyle;
458
457
  // The one division of the width every row below is measured against.
459
458
  const outerWidth = this._outerWidth(options);
460
459
  const geometry = this._geometry(options, outerWidth);
461
460
  const edge = geometry.edge === 1;
461
+ // Only an edge closes a frame: without one, a table's rules are not a border.
462
+ const border = box && edge ? box.frame(borderStyle) : borderStyle.isNull ? undefined : borderStyle;
462
463
  // Title
463
464
  if (this._title) {
464
465
  yield* this._renderTitle(options, this._title, geometry.totalWidth, this.titleStyle, this.titleJustify);
@@ -20,7 +20,7 @@
20
20
  * number the time is turned into first: a frame's stamp says when it changes
21
21
  * on the host's clock, which only `Timeline.run` can carry it back onto —
22
22
  * paused, never; backwards, at the hold it is about to drop under.
23
- * [LAW:single-enforcer] `drawing` is the one place the time, the timeline
23
+ * [LAW:single-enforcer] `drawn` is the one place the time, the timeline
24
24
  * and the reader's preference for motion are read, for every function.
25
25
  *
26
26
  * `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
@@ -57,31 +57,52 @@
57
57
  * exact arity of typed slots, so a slot of the wrong kind fails at the gate.
58
58
  *
59
59
  * Every function says when what it drew changes, so a host paints a
60
- * template while it moves and not after. `frames` and `glyphs` stamp the
61
- * fragment they draw (`textFrameAt`), covering its own glyphs. A number or a
62
- * colour carries no cells to stamp, so `tween`, `loop` and `stepped` tell
63
- * their hold to the frame instead (`TemplateFrame.onHold`), as a placed
64
- * animation of no cells does (`RenderOptions.onHold`). Nothing here claims
65
- * a moment it does not know.
60
+ * template while it moves and not after — and says it only for what the
61
+ * output depends on. A number or a colour has no cells to carry a hold, and
62
+ * a fragment's cells lose theirs the moment it is flattened (`printf "%s"`),
63
+ * so no function stamps cells or tells the frame itself. Each marks its
64
+ * return with the hold its value is in (`marked`, the engine's evaluation
65
+ * provenance), and the engine carries the mark beside the value into every
66
+ * variable, dot and function the value passes through, reporting it where
67
+ * the output depends on it. `emitted` is where a template's cells take a
68
+ * hold: every fragment the template emits, stamped with the holds of the
69
+ * marked values its action read. `decided` is the route for a hold no cell
70
+ * can carry, told to the frame (`TemplateFrame.onHold`): a mark an
71
+ * `if`/`with`/`range` consumed, whichever arm it took, because the frame may
72
+ * show cells for it later and must say when without a cell to say it on, as
73
+ * a placed animation of no cells does (`placed`) — and, by the same rule, a
74
+ * mark a writing function or a throwing action read; `EngineConfig.decided`
75
+ * is the authority on when it is called. A mark bound somewhere nobody reads
76
+ * is reported nowhere, so a value a template computes and never draws or
77
+ * decides on tells the frame nothing (rich-animation-27tq.0gd). Early, never
78
+ * late: an emission may carry a hold it did not strictly need, and a hold
79
+ * the output depends on is never dropped. [LAW:single-enforcer]
80
+ *
81
+ * The functions and the two hooks go into the host's engine config together,
82
+ * by `withAnimation`: the functions wired without the hooks would compile,
83
+ * draw and never say they move. [LAW:no-silent-failure]
66
84
  */
67
- import type { FuncMap } from "@promptctl/go-template-js";
85
+ import { type EngineConfig } from "@promptctl/go-template-js";
68
86
  import type { RenderOptions } from "../core/protocol.js";
87
+ import type { RichText } from "../core/text.js";
69
88
  import { Timeline } from "../core/timeline.js";
70
- /** What a registration is drawn on, beside the frame each evaluation is handed. */
71
- export interface AnimationFuncsOptions {
89
+ /** What the animation functions are drawn on, beside the frame each evaluation is handed. */
90
+ export interface AnimationOptions {
72
91
  /** The timeline to draw on, read once per call; the frame's own time when omitted. */
73
92
  readonly timeline?: () => Timeline;
74
93
  }
75
94
  /**
76
95
  * The frame a template is evaluated for, in the fields a render's options
77
- * carry under the same names: `time` in seconds, `onHold`, told when what a
78
- * function drew there changes, and the reader's preferences —
96
+ * carry under the same names: `time` in seconds, `onHold`, told the hold of
97
+ * an animated value the output may depend on with no cell to carry it — one
98
+ * a decision read, whichever arm it took, a writing function's input, a
99
+ * throwing action's (the engine's `decided`) — and the reader's preferences,
79
100
  * `reducedMotion`, and `asciiOnly`, which the glyphs an effect picks for
80
101
  * itself go through. They arrive with every frame and nowhere else, so a
81
102
  * template drawn in an `App` is drawn as the app's preferences say.
82
103
  * [LAW:one-source-of-truth]
83
104
  *
84
- * `time` is on the clock `AnimationFuncsOptions.timeline` maps from, and
105
+ * `time` is on the clock `AnimationOptions.timeline` maps from, and
85
106
  * `onHold` hears on that clock. A host outside rich-js hands its own clock's
86
107
  * time. Inside a rich-js frame the time is the frame's, which its host's
87
108
  * timeline has already mapped, so a template drawn there registers no
@@ -93,14 +114,24 @@ export interface TemplateFrame extends Pick<RenderOptions, "onHold" | "reducedMo
93
114
  /** The frame a renderable is drawn in, as the frame a template it evaluates there is drawn for. */
94
115
  export declare function templateFrame(options: RenderOptions): TemplateFrame;
95
116
  /**
96
- * Register `frames`, `stepped`, `tween`, `loop` and `glyphs`, each drawn for
97
- * the frame `frame` reports when the template is evaluated — its time in
98
- * seconds, from whatever zero the host counts from — through
99
- * `options.timeline`, and each telling that frame when what it drew changes.
117
+ * `config` with `frames`, `stepped`, `tween`, `loop` and `glyphs` registered
118
+ * beside its own functions, each drawn for the frame `frame` reports when
119
+ * the template is evaluated — its time in seconds, from whatever zero the
120
+ * host counts from — through `options.timeline`, and each marking its
121
+ * return with the hold it is in there; and with the two hooks that carry
122
+ * those holds to the cells (`emitted`) and the frame (`decided`) installed
123
+ * over any `config` already has, which still hear everything they did. One
124
+ * call, so the functions and the hooks that report for them cannot be wired
125
+ * apart: the functions alone would compile, draw, and never say they move.
126
+ * [LAW:types-are-the-program] [LAW:no-silent-failure] [LAW:composability]
127
+ * A `config` that already has one of the five names is refused, which is
128
+ * also what a second `withAnimation` over its own result meets: its template
129
+ * calls would otherwise reach these with no word said.
130
+ *
100
131
  * The functions exist whatever `frame` returns, so a template compiles once
101
132
  * and is evaluated at every moment the host draws. In a frame that says
102
133
  * `reducedMotion` each draws its animation reduced (`Animation.reduced`): a
103
134
  * loop at its start, a one-shot cut from where it starts to where it arrives
104
135
  * when it starts.
105
136
  */
106
- export declare function animationFuncs(frame: () => TemplateFrame, options?: AnimationFuncsOptions): FuncMap;
137
+ export declare function withAnimation(config: EngineConfig<RichText>, frame: () => TemplateFrame, options?: AnimationOptions): EngineConfig<RichText>;