@orkestrel/terminal 0.0.13 → 0.0.15

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.
@@ -6,44 +6,41 @@ let _orkestrel_form = require("@orkestrel/form");
6
6
  let _orkestrel_sse = require("@orkestrel/sse");
7
7
  let _orkestrel_database = require("@orkestrel/database");
8
8
  //#region src/core/constants.ts
9
- /** Carriage return (`\r`, U+000D) — Enter on most terminals. */
9
+ /** Names the carriage return byte (`\r`, U+000D) — Enter on most terminals. */
10
10
  var RETURN = String.fromCharCode(13);
11
- /** Line feed (`\n`, U+000A) — Enter on some terminals / pasted input. */
11
+ /** Names the line feed byte (`\n`, U+000A) — Enter on some terminals / pasted input. */
12
12
  var NEWLINE = String.fromCharCode(10);
13
- /** Tab (`\t`, U+0009). */
13
+ /** Names the tab byte (`\t`, U+0009). */
14
14
  var TAB = String.fromCharCode(9);
15
- /** Escape (ESC, U+001B) — the lone byte, and the lead byte of every CSI / SS3 sequence. */
16
- var ESCAPE = String.fromCharCode(27);
17
- /** Backspace (BS, U+0008) — Ctrl+H / some terminals' Backspace. */
15
+ /** Names the backspace byte (BS, U+0008) — Ctrl+H / some terminals' Backspace. */
18
16
  var BACKSPACE = String.fromCharCode(8);
19
- /** Delete (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */
17
+ /** Names the delete byte (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */
20
18
  var DELETE = String.fromCharCode(127);
21
- /** Space (U+0020). */
19
+ /** Names the space byte (U+0020). */
22
20
  var SPACE = " ";
23
- /** Ctrl+C (ETX, U+0003) — interrupt / cancel. */
21
+ /** Names the Ctrl+C byte (ETX, U+0003) — interrupt / cancel. */
24
22
  var CTRL_C = String.fromCharCode(3);
25
- /** Ctrl+D (EOT, U+0004) — end-of-transmission / finish (the editor's commit key). */
23
+ /** Names the Ctrl+D byte (EOT, U+0004) — end-of-transmission / finish (the editor's commit key). */
26
24
  var CTRL_D = String.fromCharCode(4);
27
- /** Ctrl+U (NAK, U+0015) — clear the current line. */
25
+ /** Names the Ctrl+U byte (NAK, U+0015) — clear the current line. */
28
26
  var CTRL_U = String.fromCharCode(21);
29
- /** Ctrl+A (SOH, U+0001) — move to start of line. */
27
+ /** Names the Ctrl+A byte (SOH, U+0001) — move to start of line. */
30
28
  var CTRL_A = String.fromCharCode(1);
31
- /** Ctrl+E (ENQ, U+0005) — move to end of line. */
29
+ /** Names the Ctrl+E byte (ENQ, U+0005) — move to end of line. */
32
30
  var CTRL_E = String.fromCharCode(5);
33
31
  /**
34
- * The Control Sequence Introducer lead (`ESC[`) for the navigation keys — the prefix of the
35
- * arrow / home / end / delete sequences {@link SEQUENCE_NAMES} is keyed by. Named `KEY_CSI`
36
- * (not `CSI`) so it never collides with the console module's SGR `CSI` (both barrel through
37
- * `@src/core`).
32
+ * Names the Single Shift Three lead (`ESCO`) — the alternate arrow-key prefix some terminals emit
33
+ * (`ESC O A`). Built from the console module's own {@link import('@orkestrel/console').ESC}; the
34
+ * navigation keys' CSI lead is that module's {@link import('@orkestrel/console').CSI}, which this
35
+ * package reuses rather than redeclaring.
38
36
  */
39
- var KEY_CSI = `[`;
40
- /** The Single Shift Three lead (`ESCO`) — the alternate arrow-key prefix some terminals emit (`ESC O A`). */
41
- var KEY_SS3 = `O`;
37
+ var KEY_SS3 = `${_orkestrel_console.ESC}O`;
42
38
  /**
43
- * The exact escape SEQUENCE → canonical key NAME table {@link import('./helpers.js').parseKey}
44
- * consults for the navigation / editing keys. Covers BOTH the CSI form (`ESC[A`…) and the SS3
45
- * form (`ESCOA`…) of the four arrows, plus the `home` / `end` / `delete` CSI sequences (with
46
- * their numeric-tilde variants). The source of truth for the multi-byte key decode; frozen.
39
+ * Holds the exact escape sequence to canonical key name table
40
+ * {@link import('./helpers.js').parseKey} consults for the navigation and editing keys. Covers the
41
+ * CSI form (`ESC[A`…) and the SS3 form (`ESCOA`…) of the arrows, plus the `home` / `end` / `delete`
42
+ * CSI sequences with their numeric-tilde variants. The source of truth for the multi-byte key
43
+ * decode; frozen.
47
44
  *
48
45
  * @remarks
49
46
  * Terminals disagree on these: a cursor key is `ESC[A` (normal) or `ESCOA` (application mode),
@@ -52,32 +49,32 @@ var KEY_SS3 = `O`;
52
49
  * sees the wire encoding.
53
50
  */
54
51
  var SEQUENCE_NAMES = Object.freeze({
55
- [`${KEY_CSI}A`]: "up",
56
- [`${KEY_CSI}B`]: "down",
57
- [`${KEY_CSI}C`]: "right",
58
- [`${KEY_CSI}D`]: "left",
52
+ [`${_orkestrel_console.CSI}A`]: "up",
53
+ [`${_orkestrel_console.CSI}B`]: "down",
54
+ [`${_orkestrel_console.CSI}C`]: "right",
55
+ [`${_orkestrel_console.CSI}D`]: "left",
59
56
  [`${KEY_SS3}A`]: "up",
60
57
  [`${KEY_SS3}B`]: "down",
61
58
  [`${KEY_SS3}C`]: "right",
62
59
  [`${KEY_SS3}D`]: "left",
63
- [`${KEY_CSI}H`]: "home",
64
- [`${KEY_CSI}F`]: "end",
65
- [`${KEY_CSI}1~`]: "home",
66
- [`${KEY_CSI}4~`]: "end",
67
- [`${KEY_CSI}3~`]: "delete",
68
- [`${KEY_CSI}7~`]: "home",
69
- [`${KEY_CSI}8~`]: "end"
60
+ [`${_orkestrel_console.CSI}H`]: "home",
61
+ [`${_orkestrel_console.CSI}F`]: "end",
62
+ [`${_orkestrel_console.CSI}1~`]: "home",
63
+ [`${_orkestrel_console.CSI}4~`]: "end",
64
+ [`${_orkestrel_console.CSI}3~`]: "delete",
65
+ [`${_orkestrel_console.CSI}7~`]: "home",
66
+ [`${_orkestrel_console.CSI}8~`]: "end"
70
67
  });
71
68
  /**
72
- * The control BYTE (or CRLF pair) → key descriptor table {@link import('./helpers.js').parseKey}
73
- * consults for the one-byte keys and the two-byte CRLF Enter chunk. Each entry carries the
74
- * canonical `name` and whether it is a `ctrl` combination. The source of truth for that decode;
75
- * frozen.
69
+ * Holds the control byte (or CRLF pair) to key descriptor table
70
+ * {@link import('./helpers.js').parseKey} consults for the one-byte keys and the two-byte CRLF
71
+ * Enter chunk. Each entry carries the canonical `name` and whether it is a `ctrl` combination. The
72
+ * source of truth for that decode; frozen.
76
73
  *
77
74
  * @remarks
78
75
  * `return` / `newline` / `return + newline` all map to `return` (one canonical Enter name) — a
79
76
  * terminal or a paste can deliver Enter as `\r`, `\n`, or the `\r\n` pair in one chunk;
80
- * `delete` / `backspace` both map to `backspace` (the two Backspace bytes); the Ctrl combos
77
+ * `delete` / `backspace` both map to `backspace` (the Backspace bytes); the Ctrl combos
81
78
  * (`c` / `d` / `u` / `a` / `e`) carry `ctrl: true` so a reducer can match
82
79
  * `key.ctrl && key.name === 'c'`. `escape` / `tab` / `space` are plain named keys.
83
80
  */
@@ -99,7 +96,7 @@ var CONTROL_NAMES = Object.freeze({
99
96
  name: "tab",
100
97
  ctrl: false
101
98
  }),
102
- ["\x1B"]: Object.freeze({
99
+ [_orkestrel_console.ESC]: Object.freeze({
103
100
  name: "escape",
104
101
  ctrl: false
105
102
  }),
@@ -136,11 +133,12 @@ var CONTROL_NAMES = Object.freeze({
136
133
  ctrl: true
137
134
  })
138
135
  });
139
- /** The default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
136
+ /** Names the default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
140
137
  var DEFAULT_MASK = "*";
141
138
  /**
142
- * The six default glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from. Read only when
143
- * the default theme is assembled; a view reads its resolved theme and never this constant. Frozen.
139
+ * Holds the terminal-owned glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from, beside
140
+ * the console module's own success and error marks. Read only when the default theme is assembled;
141
+ * a view reads its resolved theme and never this constant. Frozen.
144
142
  *
145
143
  * @remarks
146
144
  * - `question` — the leading mark on a prompt's message line.
@@ -157,7 +155,7 @@ var PROMPT_ICONS = Object.freeze({
157
155
  unchecked: "☐"
158
156
  });
159
157
  /**
160
- * Every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of
158
+ * Holds every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of
161
159
  * truth. {@link import('./helpers.js').createPromptTheme} walks it to merge a partial theme, and
162
160
  * a consumer building a complete role map reads it rather than retyping every name.
163
161
  */
@@ -175,7 +173,7 @@ var PROMPT_ROLES = Object.freeze([
175
173
  "description"
176
174
  ]);
177
175
  /**
178
- * The {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply
176
+ * Holds the {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply
179
177
  * another — the glyph set assembled from {@link PROMPT_ICONS} plus the console
180
178
  * {@link import('@orkestrel/console').STATUS_ICONS} `success` / `error` marks, and the console
181
179
  * {@link import('@orkestrel/console').Style} each role is painted with. Deeply frozen through the
@@ -229,30 +227,34 @@ var DEFAULT_PROMPT_THEME = Object.freeze({
229
227
  description: (0, _orkestrel_console.freezeStyle)({ attributes: ["dim"] })
230
228
  })
231
229
  });
232
- /** How long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered prompt before it expires — 5 minutes. */
230
+ /** Holds how long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered form before it expires — 5 minutes. */
233
231
  var DEFAULT_PROMPT_TIMEOUT_MS = 3e5;
234
- /** How long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */
232
+ /** Holds how long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */
235
233
  var DEFAULT_RECONNECT_DELAY_MS = 2e3;
236
234
  /**
237
- * The SSE `event:` names the broker emits and the {@link import('./types.js').PromptClientInterface}
238
- * dispatches on. Frozen; the source of truth for the wire event vocabulary.
235
+ * Holds the SSE `event:` names the broker emits and the
236
+ * {@link import('./types.js').PromptClientInterface} dispatches on — `pending`, `expire`, and
237
+ * `destroy`. Frozen; the source of truth for the wire event vocabulary.
239
238
  *
240
239
  * @remarks
241
240
  * - `pending` — a serialized {@link import('./types.js').PendingForm} to dispatch and answer.
242
- * - `expire` — an `{ id }` payload: the broker expired or released a parked prompt (the client drops it).
243
- * - `shutdown` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
241
+ * - `expire` — an `{ id }` payload: the broker expired or released a parked form (the client drops it).
242
+ * - `destroy` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
244
243
  */
245
244
  var SSE_EVENTS = Object.freeze({
246
245
  pending: "pending",
247
246
  expire: "expire",
248
- shutdown: "shutdown"
247
+ destroy: "destroy"
249
248
  });
250
- /** The auth-token request header the {@link import('./types.js').PromptClientInterface} sends. */
249
+ /**
250
+ * Names the auth-token request header the {@link import('./types.js').PromptClientInterface} sends
251
+ * when a `token` is configured — `x-orkestrel-token`.
252
+ */
251
253
  var HEADER_TOKEN = "x-orkestrel-token";
252
- /** The `Accept` header value that opens the broker's SSE stream. */
254
+ /** Names the `Accept` header value that opens the broker's SSE stream — `text/event-stream`. */
253
255
  var ACCEPT_EVENT_STREAM = "text/event-stream";
254
256
  /**
255
- * The maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
257
+ * Sets the maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
256
258
  * SSE parser buffer before treating the stream as hostile — 1 MiB, comfortably above any
257
259
  * legitimate prompt payload. Passed as the `limit` to `createSSEParser` so an unterminated
258
260
  * or oversized `data:` field cannot grow the buffer without bound (a memory-exhaustion guard).
@@ -261,7 +263,7 @@ var SSE_BUFFER_LIMIT = 1048576;
261
263
  //#endregion
262
264
  //#region src/core/errors.ts
263
265
  /**
264
- * The error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an
266
+ * Represents the error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an
265
267
  * unusable driver stream, a manager routing fault, or a ctrl-c cancellation. A parked form's own
266
268
  * lifecycle failures reject through the form's `answer` with the form package's error, never with
267
269
  * this one.
@@ -272,10 +274,17 @@ var SSE_BUFFER_LIMIT = 1048576;
272
274
  * a caught value with {@link isTerminalError} and branch on `error.code`.
273
275
  */
274
276
  var TerminalError = class extends Error {
275
- /** The machine-readable condition — see {@link TerminalErrorCode}. */
277
+ /** Holds the machine-readable condition — see {@link TerminalErrorCode}. */
276
278
  code;
277
- /** An optional context bag naming the offending values — see the class {@link TerminalError remarks}. */
279
+ /** Holds an optional context bag naming the offending values — see the class {@link TerminalError remarks}. */
278
280
  context;
281
+ /**
282
+ * Builds one terminal refusal.
283
+ *
284
+ * @param code - The machine-readable {@link TerminalErrorCode} a caller branches on
285
+ * @param message - The human-readable reason
286
+ * @param context - The optional bag naming the offending values — see the class {@link TerminalError remarks}
287
+ */
279
288
  constructor(code, message, context) {
280
289
  super(message);
281
290
  this.name = "TerminalError";
@@ -284,10 +293,10 @@ var TerminalError = class extends Error {
284
293
  }
285
294
  };
286
295
  /**
287
- * Narrow an unknown caught value to a {@link TerminalError}.
296
+ * Narrows an unknown caught value to a {@link TerminalError}, so a caller can branch on its `code`.
288
297
  *
289
298
  * @param value - The value to test (typically a `catch` binding or a rejected prompt call)
290
- * @returns `true` when `value` is a {@link TerminalError}
299
+ * @returns True if `value` is a {@link TerminalError}; false otherwise
291
300
  *
292
301
  * @example
293
302
  * ```ts
@@ -305,17 +314,23 @@ function isTerminalError(value) {
305
314
  }
306
315
  //#endregion
307
316
  //#region src/core/validators.ts
308
- /** Narrow an unknown value to a {@link PendingFormStatus}. */
317
+ /**
318
+ * Narrows an unknown value to a {@link PendingFormStatus}.
319
+ *
320
+ * @param value - The candidate ticket status
321
+ * @returns True if the value is one of the declared ticket statuses; false otherwise
322
+ */
309
323
  var isPendingFormStatus = (0, _orkestrel_contract.literalOf)("pending", "answered", "expired");
310
324
  /**
311
- * Narrow an unknown wire value to a {@link PendingForm} envelope.
325
+ * Narrows an unknown wire value to a {@link PendingForm} envelope — the envelope alone, because the
326
+ * form package's `parseForm` owns the schema payload.
312
327
  *
313
328
  * @remarks
314
329
  * This guard checks the transport record and proves only that `schema` is a record. The Form
315
330
  * package's `parseForm` owns the schema payload and its semantic audit.
316
331
  *
317
332
  * @param value - The decoded wire value to inspect
318
- * @returns Whether the value is a complete pending-form envelope
333
+ * @returns True if the value is a complete pending-form envelope; false otherwise
319
334
  */
320
335
  function isPendingForm(value) {
321
336
  return (0, _orkestrel_contract.recordOf)({
@@ -328,17 +343,24 @@ function isPendingForm(value) {
328
343
  }, ["from", "to"])(value);
329
344
  }
330
345
  /**
331
- * Narrow an unknown value to a transport-neutral {@link WireEvent}.
346
+ * Narrows an unknown value to a transport-neutral {@link WireEvent} — the guard a consumer's own
347
+ * transport applies to an inbound frame.
332
348
  *
333
349
  * @param value - The candidate wire event
334
- * @returns Whether the value carries an event name, serialized data, and an optional id
350
+ * @returns True if the value carries an event name, serialized data, and an optional id; false otherwise
335
351
  */
336
352
  var isWireEvent = (0, _orkestrel_contract.recordOf)({
337
353
  event: _orkestrel_contract.isString,
338
354
  data: _orkestrel_contract.isString,
339
355
  id: _orkestrel_contract.isString
340
356
  }, ["id"]);
341
- /** Narrow an unknown value to a {@link TerminalSnapshot}. */
357
+ /**
358
+ * Narrows an unknown value to a {@link TerminalSnapshot} — a non-empty `id` and an optional numeric
359
+ * `timeout`, the read boundary a store applies to an untrusted persisted row.
360
+ *
361
+ * @param value - The candidate snapshot read back from storage
362
+ * @returns True if the value carries a non-empty `id` and an optional numeric `timeout`; false otherwise
363
+ */
342
364
  var isTerminalSnapshot = (0, _orkestrel_contract.recordOf)({
343
365
  id: _orkestrel_contract.isNonEmptyString,
344
366
  timeout: _orkestrel_contract.isNumber
@@ -346,24 +368,24 @@ var isTerminalSnapshot = (0, _orkestrel_contract.recordOf)({
346
368
  //#endregion
347
369
  //#region src/core/helpers.ts
348
370
  /**
349
- * Decode one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
350
- * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF
351
- * pair ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a
352
- * single printable character. An unrecognized sequence yields `name: ''` with the raw `sequence`
371
+ * Decodes one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
372
+ * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF pair
373
+ * ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a single
374
+ * printable character. An unrecognized sequence carries no `name`, with the raw `sequence`
353
375
  * preserved.
354
376
  *
355
377
  * @remarks
356
378
  * - **Single control byte.** A one-character control input (`return` / `backspace` / `tab` /
357
379
  * `escape` / `space`, or a Ctrl combo `c` / `d` / `u` / `a` / `e`), or the two-byte `\r\n`
358
380
  * CRLF pair, is looked up in {@link CONTROL_NAMES}, carrying its `ctrl` flag.
359
- * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in BOTH the
381
+ * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in both the
360
382
  * `ESC[A` and `ESCOA` forms, plus `home` / `end` / `delete`) is looked up in
361
383
  * {@link SEQUENCE_NAMES} and flagged `meta`.
362
384
  * - **Printable character.** A single printable character becomes `name` = that character, with
363
385
  * `shift` set when it is an uppercase letter. A multi-code-point printable (an emoji, a pasted
364
386
  * run) keeps its first code point as the name and the whole input as `sequence`.
365
- * - **Unknown.** Anything else (an unrecognized escape, an empty input) yields `name: ''` —
366
- * total, so the driver never crashes on a stray byte.
387
+ * - **Unknown.** Anything else (an unrecognized escape, an empty input) yields an event with NO
388
+ * `name` — absence, never an empty string — total, so the driver never crashes on a stray byte.
367
389
  *
368
390
  * @param input - The raw keypress bytes, as a string or `Uint8Array`
369
391
  * @returns The decoded {@link KeyEvent}
@@ -404,14 +426,19 @@ function parseKey(input) {
404
426
  shift: first !== first.toLowerCase()
405
427
  };
406
428
  return {
407
- name: "",
408
429
  sequence,
409
430
  ctrl: false,
410
431
  meta: false,
411
432
  shift: false
412
433
  };
413
434
  }
414
- /** Whether a single character is a printable (non-control) character — used by {@link parseKey}'s char fallback. */
435
+ /**
436
+ * Checks whether a single character is printable — the fallback test {@link parseKey} applies after
437
+ * the control bytes and the escape sequences, so the C0 controls and DEL are excluded.
438
+ *
439
+ * @param character - The single character to test
440
+ * @returns True if the character is at or above space and is not DEL; false otherwise
441
+ */
415
442
  function isPrintable(character) {
416
443
  if (character.length === 0) return false;
417
444
  const code = character.codePointAt(0);
@@ -419,7 +446,7 @@ function isPrintable(character) {
419
446
  return code >= 32 && code !== 127;
420
447
  }
421
448
  /**
422
- * Build a complete {@link PromptTheme} by merging a partial one over
449
+ * Builds a complete {@link PromptTheme} by merging a partial one over
423
450
  * {@link DEFAULT_PROMPT_THEME}, leaf by leaf — each supplied icon replaces that glyph, each
424
451
  * supplied role replaces that {@link Style}, and everything else keeps its default. Each supplied
425
452
  * style is snapshotted through the console module's own
@@ -454,7 +481,7 @@ function createPromptTheme(options) {
454
481
  });
455
482
  }
456
483
  /**
457
- * Sanitize text for one single-line display slot. Composes console's ANSI {@link strip} and C0
484
+ * Sanitizes text for one single-line display slot. Composes console's ANSI {@link strip} and C0
458
485
  * {@link stripControls} passes with removal of tab, line feed, and carriage return.
459
486
  *
460
487
  * @param text - The text to sanitize for a glyph or hint slot
@@ -469,7 +496,8 @@ function sanitizeDisplayText(text) {
469
496
  return (0, _orkestrel_console.stripControls)((0, _orkestrel_console.strip)(text)).replaceAll(" ", "").replaceAll("\n", "").replaceAll("\r", "");
470
497
  }
471
498
  /**
472
- * Sanitize every terminal-readable string in a parsed form schema.
499
+ * Sanitizes every terminal-readable string in a parsed form schema, keeping every identity and
500
+ * answer string verbatim and dropping field metadata.
473
501
  *
474
502
  * @remarks
475
503
  * Display strings pass through {@link sanitizeDisplayText}: labels, help, placeholders, masks,
@@ -511,6 +539,7 @@ function sanitizeSchema(schema) {
511
539
  switch (field.control) {
512
540
  case "text":
513
541
  case "editor":
542
+ case "number":
514
543
  fields.push({
515
544
  ...field,
516
545
  ...shared,
@@ -524,22 +553,10 @@ function sanitizeSchema(schema) {
524
553
  ...field.mask !== void 0 ? { mask: sanitizeDisplayText(field.mask) } : {}
525
554
  });
526
555
  break;
527
- case "number":
528
- fields.push({
529
- ...field,
530
- ...shared,
531
- ...field.placeholder !== void 0 ? { placeholder: sanitizeDisplayText(field.placeholder) } : {}
532
- });
533
- break;
534
556
  case "date":
535
557
  case "time":
536
558
  case "datetime":
537
559
  case "color":
538
- fields.push({
539
- ...field,
540
- ...shared
541
- });
542
- break;
543
560
  case "confirm":
544
561
  fields.push({
545
562
  ...field,
@@ -547,17 +564,6 @@ function sanitizeSchema(schema) {
547
564
  });
548
565
  break;
549
566
  case "select":
550
- fields.push({
551
- ...field,
552
- ...shared,
553
- choices: field.choices.map((choice) => ({
554
- ...choice,
555
- value: choice.value,
556
- label: sanitizeDisplayText(choice.label),
557
- ...choice.help !== void 0 ? { help: sanitizeDisplayText(choice.help) } : {}
558
- }))
559
- });
560
- break;
561
567
  case "checkbox":
562
568
  fields.push({
563
569
  ...field,
@@ -586,7 +592,7 @@ function sanitizeSchema(schema) {
586
592
  };
587
593
  }
588
594
  /**
589
- * Sanitize every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display
595
+ * Sanitizes every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display
590
596
  * slot. Only the icons need it: a role is guard-narrowed to a console {@link Style}, whose colors
591
597
  * and attributes are fixed name sets, so no role can carry a byte a terminal would act on.
592
598
  *
@@ -603,13 +609,21 @@ function sanitizeThemeIcons(theme) {
603
609
  icons: sanitized
604
610
  };
605
611
  }
606
- /** The styled prompt-message header (`? message`) — the leading line every active prompt view shares, themed by the `question` + `message` roles. */
607
- function promptHeader(styler, theme, message) {
612
+ /**
613
+ * Renders the styled question header (`? message`) — the leading line every active prompt view
614
+ * shares, themed by the `question` + `message` roles.
615
+ *
616
+ * @param styler - The console styler that renders each role
617
+ * @param theme - The resolved prompt theme
618
+ * @param message - The prompt's question text
619
+ * @returns The rendered question header
620
+ */
621
+ function renderPromptHeader(styler, theme, message) {
608
622
  return `${styler.render(theme.roles.question, theme.icons.question)} ${styler.render(theme.roles.message, message)}`;
609
623
  }
610
624
  /**
611
- * Render a prompt header followed by a key hint painted with the `hint` role, or the header alone
612
- * when no hint is supplied.
625
+ * Renders a question header followed by a key hint painted with the `hint` role, or the header
626
+ * alone when no hint is supplied.
613
627
  *
614
628
  * @param styler - The console styler that renders each role
615
629
  * @param theme - The resolved prompt theme
@@ -617,20 +631,37 @@ function promptHeader(styler, theme, message) {
617
631
  * @param hint - The optional key hint to append
618
632
  * @returns The rendered header with the optional hint
619
633
  */
620
- function hintedHeader(styler, theme, message, hint) {
621
- const head = promptHeader(styler, theme, message);
634
+ function renderHintedHeader(styler, theme, message, hint) {
635
+ const head = renderPromptHeader(styler, theme, message);
622
636
  return hint === void 0 ? head : `${head} ${styler.render(theme.roles.hint, hint)}`;
623
637
  }
624
- /** The styled submit line (`✔ message`) — the committed header an interactive prompt shows once resolved, themed by the `success` + `message` roles. */
625
- function submitHeader(styler, theme, message) {
638
+ /**
639
+ * Renders the styled submit line (`✔ message`) — the committed header an interactive prompt shows
640
+ * after it resolves, themed by the `success` + `message` roles.
641
+ *
642
+ * @param styler - The console styler that renders each role
643
+ * @param theme - The resolved prompt theme
644
+ * @param message - The settled prompt's question text
645
+ * @returns The rendered committed header
646
+ */
647
+ function renderSubmitHeader(styler, theme, message) {
626
648
  return `${styler.render(theme.roles.success, theme.icons.success)} ${styler.render(theme.roles.message, message)}`;
627
649
  }
628
- /** The styled error line (`✖ message`) a form driver appends for a field failure. */
629
- function errorLine(styler, theme, message) {
650
+ /**
651
+ * Renders the styled failure line (`✖ message`) a form driver writes for each refused field before
652
+ * it asks that field again.
653
+ *
654
+ * @param styler - The console styler that renders each role
655
+ * @param theme - The resolved prompt theme
656
+ * @param message - The failure text, normally the field's label and the reason
657
+ * @returns The rendered failure line
658
+ */
659
+ function renderErrorLine(styler, theme, message) {
630
660
  return `${styler.render(theme.roles.error, theme.icons.error)} ${styler.render(theme.roles.error, message)}`;
631
661
  }
632
662
  /**
633
- * Build the initial text-field key state.
663
+ * Builds the initial text-field reducer state — the sanitized label, the declared default, the
664
+ * styler, and the resolved theme.
634
665
  *
635
666
  * @param field - The text field to render
636
667
  * @param styler - The styler used to render the view
@@ -646,22 +677,32 @@ function createInputState(field, styler = (0, _orkestrel_console.createStyler)()
646
677
  value: ""
647
678
  };
648
679
  }
649
- /** Render a text-field key state as a styled view. */
650
- function inputView(state) {
680
+ /**
681
+ * Renders a text-field reducer state as a styled view — the header, the pointer, and the typed
682
+ * value, or the default shown as a hint while nothing is typed.
683
+ *
684
+ * @param state - The text field's current reducer state
685
+ * @returns The rendered single-line view — header, pointer, and the typed value or the default
686
+ */
687
+ function renderInputView(state) {
651
688
  const content = state.value.length > 0 ? state.value : state.default;
652
689
  const role = state.value.length > 0 ? state.theme.roles.content : state.theme.roles.hint;
653
690
  const shown = state.styler.render(role, sanitizeDisplayText(content));
654
- return `${promptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${shown}`;
691
+ return `${renderPromptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${shown}`;
655
692
  }
656
693
  /**
657
- * Advance an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
694
+ * Advances an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
658
695
  * reducer. Printable characters extend the value; backspace shrinks it; ctrl-u clears it; ctrl-c
659
696
  * cancels; return produces the candidate value, with an empty line falling back to the default.
697
+ *
698
+ * @param state - The text field's current reducer state
699
+ * @param key - The decoded keypress to apply
700
+ * @returns The next step — the new state, the rendered view, the status, and the value on submit
660
701
  */
661
- function inputReduce(state, key) {
702
+ function reduceInput(state, key) {
662
703
  if (key.ctrl && key.name === "c") return {
663
704
  state,
664
- view: inputView(state),
705
+ view: renderInputView(state),
665
706
  status: "cancel"
666
707
  };
667
708
  if (key.name === "return") {
@@ -671,7 +712,7 @@ function inputReduce(state, key) {
671
712
  ...state,
672
713
  value: answer
673
714
  },
674
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, sanitizeDisplayText(answer))}`,
715
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, sanitizeDisplayText(answer))}`,
675
716
  status: "submit",
676
717
  value: answer
677
718
  };
@@ -679,7 +720,7 @@ function inputReduce(state, key) {
679
720
  const value = editLine(state.value, key);
680
721
  if (value === void 0) return {
681
722
  state,
682
- view: inputView(state),
723
+ view: renderInputView(state),
683
724
  status: "active"
684
725
  };
685
726
  const next = {
@@ -688,12 +729,13 @@ function inputReduce(state, key) {
688
729
  };
689
730
  return {
690
731
  state: next,
691
- view: inputView(next),
732
+ view: renderInputView(next),
692
733
  status: "active"
693
734
  };
694
735
  }
695
736
  /**
696
- * Build the initial password-field key state.
737
+ * Builds the initial password-field reducer state — the text-field state, plus the mask glyph each
738
+ * typed character renders as.
697
739
  *
698
740
  * @param field - The password field to render
699
741
  * @param styler - The styler used to render the view
@@ -709,32 +751,42 @@ function createPasswordState(field, styler = (0, _orkestrel_console.createStyler
709
751
  value: ""
710
752
  };
711
753
  }
712
- /** Render a password-field key state as a styled view. */
713
- function passwordView(state) {
754
+ /**
755
+ * Renders a password-field reducer state as a styled view, with the value replaced by the mask
756
+ * repeated so the secret is never echoed.
757
+ *
758
+ * @param state - The password field's current reducer state
759
+ * @returns The rendered view, with the mask repeated in place of the typed value
760
+ */
761
+ function renderPasswordView(state) {
714
762
  const masked = state.styler.render(state.theme.roles.content, state.mask.repeat(state.value.length));
715
- return `${promptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${masked}`;
763
+ return `${renderPromptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${masked}`;
716
764
  }
717
765
  /**
718
- * Advance a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
719
- * reducer. Identical line-editing to {@link inputReduce} (printable extends, backspace shrinks,
766
+ * Advances a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
767
+ * reducer. Identical line-editing to {@link reduceInput} (printable extends, backspace shrinks,
720
768
  * ctrl-u clears, ctrl-c cancels) but the view masks the value. Return produces the candidate value.
769
+ *
770
+ * @param state - The password field's current reducer state
771
+ * @param key - The decoded keypress to apply
772
+ * @returns The next step — the new state, the masked view, the status, and the value on submit
721
773
  */
722
- function passwordReduce(state, key) {
774
+ function reducePassword(state, key) {
723
775
  if (key.ctrl && key.name === "c") return {
724
776
  state,
725
- view: passwordView(state),
777
+ view: renderPasswordView(state),
726
778
  status: "cancel"
727
779
  };
728
780
  if (key.name === "return") return {
729
781
  state,
730
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, state.mask.repeat(state.value.length))}`,
782
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, state.mask.repeat(state.value.length))}`,
731
783
  status: "submit",
732
784
  value: state.value
733
785
  };
734
786
  const value = editLine(state.value, key);
735
787
  if (value === void 0) return {
736
788
  state,
737
- view: passwordView(state),
789
+ view: renderPasswordView(state),
738
790
  status: "active"
739
791
  };
740
792
  const next = {
@@ -743,12 +795,13 @@ function passwordReduce(state, key) {
743
795
  };
744
796
  return {
745
797
  state: next,
746
- view: passwordView(next),
798
+ view: renderPasswordView(next),
747
799
  status: "active"
748
800
  };
749
801
  }
750
802
  /**
751
- * Build the initial confirm-field key state.
803
+ * Builds the initial confirm-field reducer state — the sanitized label and the declared default
804
+ * answer.
752
805
  *
753
806
  * @param field - The confirm field to render
754
807
  * @param styler - The styler used to render the view
@@ -764,43 +817,52 @@ function createConfirmState(field, styler = (0, _orkestrel_console.createStyler)
764
817
  };
765
818
  }
766
819
  /**
767
- * Render a confirm-field key state as a styled view. The selected role paints the default letter.
820
+ * Renders a confirm-field reducer state as a styled view — the header and the yes/no group, with
821
+ * the default letter capitalized and painted by the `selected` role.
822
+ *
823
+ * @param state - The confirm field's current reducer state
824
+ * @returns The rendered view — the header and the yes/no group with the default capitalized
768
825
  */
769
- function confirmView(state) {
770
- const head = promptHeader(state.styler, state.theme, state.message);
826
+ function renderConfirmView(state) {
827
+ const head = renderPromptHeader(state.styler, state.theme, state.message);
771
828
  const answer = state.default ? `${state.styler.render(state.theme.roles.selected, "Y")}${state.styler.render(state.theme.roles.hint, "/n")}` : `${state.styler.render(state.theme.roles.hint, "y/")}${state.styler.render(state.theme.roles.selected, "N")}`;
772
829
  return `${head} ${state.styler.render(state.theme.roles.hint, "(")}${answer}${state.styler.render(state.theme.roles.hint, ")")}`;
773
830
  }
774
831
  /**
775
- * Advance a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<boolean>`
832
+ * Advances a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<boolean>`
776
833
  * reducer. `y` / `Y` submits `true`, `n` / `N` submits `false`, return on an empty line submits
777
834
  * the `default`, ctrl-c cancels; any other key is ignored (stays active).
835
+ *
836
+ * @param state - The confirm field's current reducer state
837
+ * @param key - The decoded keypress to apply
838
+ * @returns The next step — the state, the rendered view, the status, and the answer on submit
778
839
  */
779
- function confirmReduce(state, key) {
840
+ function reduceConfirm(state, key) {
780
841
  if (key.ctrl && key.name === "c") return {
781
842
  state,
782
- view: confirmView(state),
843
+ view: renderConfirmView(state),
783
844
  status: "cancel"
784
845
  };
785
846
  let answer;
786
- const choice = key.name.toLowerCase();
847
+ const choice = key.name?.toLowerCase();
787
848
  if (key.name === "return") answer = state.default;
788
849
  else if (choice === "y") answer = true;
789
850
  else if (choice === "n") answer = false;
790
851
  if (answer === void 0) return {
791
852
  state,
792
- view: confirmView(state),
853
+ view: renderConfirmView(state),
793
854
  status: "active"
794
855
  };
795
856
  return {
796
857
  state,
797
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, answer ? "yes" : "no")}`,
858
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, answer ? "yes" : "no")}`,
798
859
  status: "submit",
799
860
  value: answer
800
861
  };
801
862
  }
802
863
  /**
803
- * Build the initial select-field key state.
864
+ * Builds the initial select-field reducer state — the offered choices, with the focus pre-placed on
865
+ * the declared default.
804
866
  *
805
867
  * @param field - The select field to render
806
868
  * @param styler - The styler used to render the view
@@ -818,30 +880,40 @@ function createSelectState(field, styler = (0, _orkestrel_console.createStyler)(
818
880
  focused: index >= 0 ? index : 0
819
881
  };
820
882
  }
821
- /** Render a select-field key state as a multi-line styled view. */
822
- function selectView(state) {
883
+ /**
884
+ * Renders a select-field reducer state as a multi-line styled view — one row per choice, with the
885
+ * focused row marked and its help shown.
886
+ *
887
+ * @param state - The select field's current reducer state
888
+ * @returns The rendered view — the header, then one row per choice with the focused row marked
889
+ */
890
+ function renderSelectView(state) {
823
891
  const lines = state.choices.map((choice, index) => {
824
892
  const active = index === state.focused;
825
893
  return `${active ? state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer) : " "} ${active ? state.styler.render(state.theme.roles.selected, state.theme.icons.selected) : state.styler.render(state.theme.roles.muted, state.theme.icons.dot)} ${active ? state.styler.render(state.theme.roles.focus, choice.label) : state.styler.render(state.theme.roles.content, choice.label)}${choice.help === void 0 ? "" : ` ${state.styler.render(state.theme.roles.description, choice.help)}`}`;
826
894
  });
827
- return [promptHeader(state.styler, state.theme, state.message), ...lines].join("\n");
895
+ return [renderPromptHeader(state.styler, state.theme, state.message), ...lines].join("\n");
828
896
  }
829
897
  /**
830
- * Advance a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
831
- * reducer. `up` / `down` (and `k` / `j`) move the focus, WRAPPING at the ends; return submits the
898
+ * Advances a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
899
+ * reducer. `up` / `down` (and `k` / `j`) move the focus, wrapping at the ends; return submits the
832
900
  * focused choice's `value`; ctrl-c cancels. An empty choice list can never submit (a higher layer
833
901
  * guards against it); any other key is ignored.
902
+ *
903
+ * @param state - The select field's current reducer state
904
+ * @param key - The decoded keypress to apply
905
+ * @returns The next step — the state, the rendered view, the status, and the chosen value on submit
834
906
  */
835
- function selectReduce(state, key) {
907
+ function reduceSelect(state, key) {
836
908
  if (key.ctrl && key.name === "c") return {
837
909
  state,
838
- view: selectView(state),
910
+ view: renderSelectView(state),
839
911
  status: "cancel"
840
912
  };
841
913
  const count = state.choices.length;
842
914
  if (count === 0) return {
843
915
  state,
844
- view: selectView(state),
916
+ view: renderSelectView(state),
845
917
  status: "active"
846
918
  };
847
919
  if (key.name === "up" || key.name === "k") {
@@ -851,7 +923,7 @@ function selectReduce(state, key) {
851
923
  };
852
924
  return {
853
925
  state: next,
854
- view: selectView(next),
926
+ view: renderSelectView(next),
855
927
  status: "active"
856
928
  };
857
929
  }
@@ -862,7 +934,7 @@ function selectReduce(state, key) {
862
934
  };
863
935
  return {
864
936
  state: next,
865
- view: selectView(next),
937
+ view: renderSelectView(next),
866
938
  status: "active"
867
939
  };
868
940
  }
@@ -871,19 +943,20 @@ function selectReduce(state, key) {
871
943
  const value = choice?.value ?? "";
872
944
  return {
873
945
  state,
874
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, choice?.label ?? "")}`,
946
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, choice?.label ?? "")}`,
875
947
  status: "submit",
876
948
  value
877
949
  };
878
950
  }
879
951
  return {
880
952
  state,
881
- view: selectView(state),
953
+ view: renderSelectView(state),
882
954
  status: "active"
883
955
  };
884
956
  }
885
957
  /**
886
- * Build the initial checkbox-field key state.
958
+ * Builds the initial checkbox-field reducer state — the offered choices, with every value in the
959
+ * field's `default` list pre-checked.
887
960
  *
888
961
  * @param field - The checkbox field to render
889
962
  * @param styler - The styler used to render the view
@@ -905,8 +978,14 @@ function createCheckboxState(field, styler = (0, _orkestrel_console.createStyler
905
978
  checked
906
979
  };
907
980
  }
908
- /** Render a checkbox-field key state as a multi-line styled view. */
909
- function checkboxView(state) {
981
+ /**
982
+ * Renders a checkbox-field reducer state as a multi-line styled view — one box per choice, and the
983
+ * selected count beneath them.
984
+ *
985
+ * @param state - The checkbox field's current reducer state
986
+ * @returns The rendered view — the header, one box per choice, and the selected count
987
+ */
988
+ function renderCheckboxView(state) {
910
989
  const lines = state.choices.map((choice, index) => {
911
990
  const active = index === state.focused;
912
991
  const ticked = state.checked.includes(index);
@@ -914,21 +993,25 @@ function checkboxView(state) {
914
993
  });
915
994
  const summary = state.styler.render(state.theme.roles.hint, `${state.checked.length} selected`);
916
995
  return [
917
- promptHeader(state.styler, state.theme, state.message),
996
+ renderPromptHeader(state.styler, state.theme, state.message),
918
997
  ...lines,
919
998
  summary
920
999
  ].join("\n");
921
1000
  }
922
1001
  /**
923
- * Advance a checkbox prompt by one {@link KeyEvent} — the pure
1002
+ * Advances a checkbox prompt by one {@link KeyEvent} — the pure
924
1003
  * `(state, key) → PromptStep<readonly string[]>` reducer. `up` / `down` (and `k` / `j`) move the
925
1004
  * focus (wrapping); `space` toggles the focused index in the checked set; return submits the
926
1005
  * checked values in choice order; ctrl-c cancels. The form applies selection-count rules.
1006
+ *
1007
+ * @param state - The checkbox field's current reducer state
1008
+ * @param key - The decoded keypress to apply
1009
+ * @returns The next step — the state, the rendered view, the status, and the ticked values on submit
927
1010
  */
928
- function checkboxReduce(state, key) {
1011
+ function reduceCheckbox(state, key) {
929
1012
  if (key.ctrl && key.name === "c") return {
930
1013
  state,
931
- view: checkboxView(state),
1014
+ view: renderCheckboxView(state),
932
1015
  status: "cancel"
933
1016
  };
934
1017
  const count = state.choices.length;
@@ -939,7 +1022,7 @@ function checkboxReduce(state, key) {
939
1022
  };
940
1023
  return {
941
1024
  state: next,
942
- view: checkboxView(next),
1025
+ view: renderCheckboxView(next),
943
1026
  status: "active"
944
1027
  };
945
1028
  }
@@ -950,7 +1033,7 @@ function checkboxReduce(state, key) {
950
1033
  };
951
1034
  return {
952
1035
  state: next,
953
- view: checkboxView(next),
1036
+ view: renderCheckboxView(next),
954
1037
  status: "active"
955
1038
  };
956
1039
  }
@@ -962,7 +1045,7 @@ function checkboxReduce(state, key) {
962
1045
  };
963
1046
  return {
964
1047
  state: next,
965
- view: checkboxView(next),
1048
+ view: renderCheckboxView(next),
966
1049
  status: "active"
967
1050
  };
968
1051
  }
@@ -972,23 +1055,31 @@ function checkboxReduce(state, key) {
972
1055
  const summary = ordered.map((index) => state.choices[index]?.label).filter((name) => name !== void 0).join(", ");
973
1056
  return {
974
1057
  state,
975
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, summary)}`,
1058
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, summary)}`,
976
1059
  status: "submit",
977
1060
  value: values
978
1061
  };
979
1062
  }
980
1063
  return {
981
1064
  state,
982
- view: checkboxView(state),
1065
+ view: renderCheckboxView(state),
983
1066
  status: "active"
984
1067
  };
985
1068
  }
986
- /** Toggle `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion list. */
1069
+ /**
1070
+ * Toggles `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion
1071
+ * list; the primitive {@link reduceCheckbox} calls.
1072
+ *
1073
+ * @param indices - The ticked indices, in tick order
1074
+ * @param index - The index to add when absent, or drop when present
1075
+ * @returns A new list carrying the toggled membership; the input is never mutated
1076
+ */
987
1077
  function toggleIndex(indices, index) {
988
1078
  return indices.includes(index) ? indices.filter((i) => i !== index) : [...indices, index];
989
1079
  }
990
1080
  /**
991
- * Build the initial editor-field key state.
1081
+ * Builds the initial editor-field reducer state — the committed lines empty, and the declared
1082
+ * default held for a finish with nothing typed.
992
1083
  *
993
1084
  * @param field - The editor field to render
994
1085
  * @param styler - The styler used to render the view
@@ -1006,23 +1097,31 @@ function createEditorState(field, styler = (0, _orkestrel_console.createStyler)(
1006
1097
  };
1007
1098
  }
1008
1099
  /**
1009
- * Render an editor-field key state as a multi-line styled view with its finish hint.
1100
+ * Renders an editor-field reducer state as a multi-line styled view — the finish hint, the
1101
+ * committed lines, and the line in progress.
1102
+ *
1103
+ * @param state - The editor field's current reducer state
1104
+ * @returns The rendered view — the hinted header, the committed lines, and the line in progress
1010
1105
  */
1011
- function editorView(state) {
1012
- const head = hintedHeader(state.styler, state.theme, state.message, "(Ctrl+D to finish)");
1106
+ function renderEditorView(state) {
1107
+ const head = renderHintedHeader(state.styler, state.theme, state.message, "(Ctrl+D to finish)");
1013
1108
  const pointer = state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer);
1014
1109
  return [head, ...[...state.lines.map((line) => state.styler.render(state.theme.roles.content, line)), `${pointer} ${state.styler.render(state.theme.roles.content, state.current)}`]].join("\n");
1015
1110
  }
1016
1111
  /**
1017
- * Advance an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
1112
+ * Advances an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
1018
1113
  * reducer. Printable characters extend the current line; backspace shrinks it; return commits the
1019
- * current line and starts a fresh one; ctrl-d FINISHES (joining all lines, falling back to the
1020
- * default when empty); ctrl-c cancels. The form validates the candidate after the driver fills it.
1114
+ * current line and starts a fresh one; ctrl-d finishes, joining every line and falling back to the
1115
+ * default when empty; ctrl-c cancels. The form validates the candidate after the driver fills it.
1116
+ *
1117
+ * @param state - The editor field's current reducer state
1118
+ * @param key - The decoded keypress to apply
1119
+ * @returns The next step — the state, the rendered view, the status, and the joined text on submit
1021
1120
  */
1022
- function editorReduce(state, key) {
1121
+ function reduceEditor(state, key) {
1023
1122
  if (key.ctrl && key.name === "c") return {
1024
1123
  state,
1025
- view: editorView(state),
1124
+ view: renderEditorView(state),
1026
1125
  status: "cancel"
1027
1126
  };
1028
1127
  if (key.ctrl && key.name === "d") {
@@ -1031,7 +1130,7 @@ function editorReduce(state, key) {
1031
1130
  const answer = joined.length > 0 ? joined : state.default;
1032
1131
  return {
1033
1132
  state,
1034
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, `${String(lines.length)} line${lines.length === 1 ? "" : "s"}`)}`,
1133
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, `${String(lines.length)} line${lines.length === 1 ? "" : "s"}`)}`,
1035
1134
  status: "submit",
1036
1135
  value: answer
1037
1136
  };
@@ -1044,14 +1143,14 @@ function editorReduce(state, key) {
1044
1143
  };
1045
1144
  return {
1046
1145
  state: next,
1047
- view: editorView(next),
1146
+ view: renderEditorView(next),
1048
1147
  status: "active"
1049
1148
  };
1050
1149
  }
1051
1150
  const current = editLine(state.current, key);
1052
1151
  if (current === void 0) return {
1053
1152
  state,
1054
- view: editorView(state),
1153
+ view: renderEditorView(state),
1055
1154
  status: "active"
1056
1155
  };
1057
1156
  const next = {
@@ -1060,48 +1159,69 @@ function editorReduce(state, key) {
1060
1159
  };
1061
1160
  return {
1062
1161
  state: next,
1063
- view: editorView(next),
1162
+ view: renderEditorView(next),
1064
1163
  status: "active"
1065
1164
  };
1066
1165
  }
1067
1166
  /**
1068
- * Apply a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input /
1069
- * password / editor. A printable key appends its character; `backspace` drops the last character;
1070
- * `space` appends a space; ctrl-u clears the line. Returns the new buffer, or `undefined` when the
1071
- * key does not edit the line (so the caller can leave the state untouched).
1167
+ * Applies a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input,
1168
+ * password, and editor. A printable key appends its character; `backspace` drops the last
1169
+ * character; `space` appends a space; ctrl-u clears the line; a key that edits nothing returns
1170
+ * `undefined`.
1171
+ *
1172
+ * @param value - The buffer the field holds so far
1173
+ * @param key - The decoded keypress to apply
1174
+ * @returns The new buffer, or `undefined` when the key does not edit the line, so the caller can
1175
+ * leave the state untouched
1072
1176
  */
1073
1177
  function editLine(value, key) {
1074
1178
  if (key.ctrl && key.name === "u") return "";
1075
1179
  if (key.name === "backspace") return value.slice(0, -1);
1076
1180
  if (key.name === "space") return `${value} `;
1077
- if (!key.ctrl && !key.meta && [...key.name].length === 1 && isPrintable(key.name)) return `${value}${key.sequence}`;
1181
+ if (!key.ctrl && !key.meta && key.name !== void 0 && [...key.name].length === 1 && isPrintable(key.name)) return `${value}${key.sequence}`;
1078
1182
  }
1079
1183
  /**
1080
- * The default {@link import('./types.js').TimerHandler} — a thin host `setTimeout` / `clearTimeout`
1081
- * wrapper that arms `callback` after `ms` and returns a {@link TimerCancel}. The deadline seam
1184
+ * Implements the default {@link import('./types.js').TimerHandler} — a thin host `setTimeout` / `clearTimeout`
1185
+ * wrapper that arms `callback` after `ms` and returns a {@link TimerCancelFunction}. The deadline seam
1082
1186
  * behind both the {@link import('./Prompt.js').Prompt} broker (its expiry) and the
1083
1187
  * {@link import('./PromptClient.js').PromptClient} (its reconnect backoff); a test injects a
1084
1188
  * deterministic timer instead, so neither entity touches real time.
1189
+ *
1190
+ * @param callback - The deadline callback to arm
1191
+ * @param ms - How long to wait before firing it, in milliseconds
1192
+ * @returns The {@link TimerCancelFunction} that clears the armed deadline
1085
1193
  */
1086
1194
  function defaultTimer(callback, ms) {
1087
1195
  const handle = setTimeout(callback, ms);
1088
1196
  return () => clearTimeout(handle);
1089
1197
  }
1090
- /** The default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses. */
1198
+ /**
1199
+ * Implements the default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to
1200
+ * the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses.
1201
+ *
1202
+ * @param input - The request URL
1203
+ * @param init - The request init the client sets — method, headers, body, and abort signal
1204
+ * @returns The host `fetch` promise for that request
1205
+ */
1091
1206
  function globalFetch(input, init) {
1092
1207
  return fetch(input, init);
1093
1208
  }
1094
1209
  /**
1095
- * Whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient}
1210
+ * Checks whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient}
1096
1211
  * distinguishes a deliberate `disconnect` / teardown (an aborted `fetch`) from a real fault, so it
1097
1212
  * exits its connect loop quietly instead of emitting `error` / reconnecting.
1213
+ *
1214
+ * @param error - The caught value to test
1215
+ * @returns True if the value is a host `Error` or `DOMException` named `AbortError`; false otherwise
1098
1216
  */
1099
1217
  function isAbortError(error) {
1100
1218
  return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
1101
1219
  }
1102
1220
  /**
1103
- * Whether `url` is an INSECURE remote endpoint — a plain `http://` URL whose host is NOT a
1104
- * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input.
1221
+ * Checks whether `url` is an insecure remote endpoint — a plain `http://` URL whose host is not a
1222
+ * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input;
1223
+ * the {@link import('./PromptClient.js').PromptClient} warns once when a `token` would cross such
1224
+ * an endpoint in cleartext.
1105
1225
  *
1106
1226
  * @remarks
1107
1227
  * A loopback host (`localhost`, `127.0.0.1`, `[::1]`) over `http://` is exempt (local
@@ -1109,7 +1229,7 @@ function isAbortError(error) {
1109
1229
  * An `https://` URL (or any non-`http://` scheme) is never flagged.
1110
1230
  *
1111
1231
  * @param url - The candidate endpoint URL
1112
- * @returns `true` when `url` is a non-loopback `http://` endpoint
1232
+ * @returns True if `url` is a non-loopback `http://` endpoint; false otherwise
1113
1233
  *
1114
1234
  * @example
1115
1235
  * ```ts
@@ -1127,7 +1247,13 @@ function isInsecureRemote(url) {
1127
1247
  const hostname = host.startsWith("[") ? host.slice(0, host.indexOf("]") + 1) : host.split(":")[0] ?? "";
1128
1248
  return hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "[::1]";
1129
1249
  }
1130
- /** Serialize a parked {@link PendingForm} into a {@link WireEvent}. */
1250
+ /**
1251
+ * Serializes a parked {@link PendingForm} into a `pending` {@link WireEvent}, whose frame `id` is
1252
+ * the form's own id.
1253
+ *
1254
+ * @param form - The parked form's wire-safe record
1255
+ * @returns The `pending` frame — the JSON-stringified record as `data`, and the form's own `id`
1256
+ */
1131
1257
  function serializePending(form) {
1132
1258
  return {
1133
1259
  event: "pending",
@@ -1135,25 +1261,37 @@ function serializePending(form) {
1135
1261
  id: form.id
1136
1262
  };
1137
1263
  }
1138
- /** Serialize a parked prompt's expiry or release into a {@link WireEvent} — event `'expire'`, `data` the JSON-stringified `{ id }` payload. */
1264
+ /**
1265
+ * Serializes a parked form's expiry or release into an `expire` {@link WireEvent}, whose `data` is
1266
+ * the JSON `{ id }` payload.
1267
+ *
1268
+ * @param id - The id of the parked form that expired or was released
1269
+ * @returns The `expire` frame, carrying the JSON-stringified `{ id }` payload as `data`
1270
+ */
1139
1271
  function serializeExpire(id) {
1140
1272
  return {
1141
1273
  event: "expire",
1142
1274
  data: JSON.stringify({ id })
1143
1275
  };
1144
1276
  }
1145
- /** The {@link WireEvent} a broker/manager sends when it is going away — event `'shutdown'`, no payload. */
1146
- function serializeShutdown() {
1277
+ /**
1278
+ * Serializes the `destroy` {@link WireEvent} a broker or manager sends when it is going away, which
1279
+ * carries no payload.
1280
+ *
1281
+ * @returns The `destroy` frame, whose `data` is empty because the signal carries no payload
1282
+ */
1283
+ function serializeDestroy() {
1147
1284
  return {
1148
- event: "shutdown",
1285
+ event: "destroy",
1149
1286
  data: ""
1150
1287
  };
1151
1288
  }
1152
1289
  //#endregion
1153
1290
  //#region src/core/Prompt.ts
1154
1291
  /**
1155
- * The headless form broker. It parks live forms, exposes their serialized schemas, and applies
1156
- * remote answers to the authoritative form.
1292
+ * Implements the headless form broker. It parks live forms, exposes their serialized schemas,
1293
+ * applies remote answers to the authoritative form, and abandons a parked form on timeout, release,
1294
+ * or teardown.
1157
1295
  *
1158
1296
  * @remarks
1159
1297
  * A parked record carries one call to `serializeForm`. A failed fill or submit leaves the record
@@ -1343,8 +1481,9 @@ var Prompt = class {
1343
1481
  //#endregion
1344
1482
  //#region src/core/PromptClient.ts
1345
1483
  /**
1346
- * The SSE form bridge. It ingests serialized forms from a remote broker, renders them through a
1347
- * local terminal, and posts answers back without blocking the event stream.
1484
+ * Implements the SSE form bridge. It ingests serialized forms from a remote broker without waiting
1485
+ * on a render, drives one form at a time through a local terminal, posts each answer back, and asks
1486
+ * again when the authoritative form refuses one.
1348
1487
  *
1349
1488
  * @remarks
1350
1489
  * - **Connect + reconnect.** {@link connect} opens the SSE stream and reconnects after a transport
@@ -1352,12 +1491,13 @@ var Prompt = class {
1352
1491
  * {@link disconnect} deliberately stopped it.
1353
1492
  * - **Ingest + render.** Each `pending` envelope passes through `isPendingForm`, Form's `parseForm`,
1354
1493
  * and terminal's `sanitizeSchema`, then enters a serial render queue. The SSE reader never awaits
1355
- * that queue, so `expire` and `shutdown` remain live while a person is answering.
1494
+ * that queue, so `expire` and `destroy` remain live while a person is answering.
1356
1495
  * - **Safe local form.** The rendering copy omits every wire `pattern`, because Form compiles a
1357
1496
  * pattern during local evaluation. The broker's parked form retains it and remains authoritative.
1358
1497
  * - **Refusal retry.** A structured `rejected` response seeds a new rendering form with the values
1359
- * just submitted, applies every {@link FieldError} through `invalidate`, and asks again. No retry
1360
- * counter truncates the loop; acceptance, expiry, and shutdown are its bounds.
1498
+ * the refused attempt submitted, applies every {@link FieldError} through `invalidate`, and asks
1499
+ * again. No retry counter truncates the loop; acceptance, expiry, and the broker's own teardown
1500
+ * are its bounds.
1361
1501
  * - **Replay safety.** A replayed id is skipped while it is queued, rendering, or posting. Once an
1362
1502
  * attempt ends, a later delivery of that id may be rendered again.
1363
1503
  *
@@ -1495,7 +1635,7 @@ var PromptClient = class {
1495
1635
  if ((0, _orkestrel_contract.isRecord)(parsed) && (0, _orkestrel_contract.isString)(parsed.id)) this.#expire(parsed.id);
1496
1636
  return;
1497
1637
  }
1498
- if (event.event === SSE_EVENTS.shutdown) {
1638
+ if (event.event === SSE_EVENTS.destroy) {
1499
1639
  this.disconnect();
1500
1640
  this.#interrupt();
1501
1641
  }
@@ -1648,25 +1788,24 @@ var PromptClient = class {
1648
1788
  //#endregion
1649
1789
  //#region src/core/TerminalManager.ts
1650
1790
  /**
1651
- * The multi-endpoint terminal MANAGER — a registry of named {@link PromptInterface} brokers (one
1652
- * per endpoint), so several parties can `ask` forms of each other by NAME with a `from` → `to`
1653
- * attribution edge on every parked form, and a transitive DEADLOCK check across all in-flight
1654
- * asks.
1791
+ * Registers named {@link PromptInterface} brokers, one per endpoint, so several parties can `ask`
1792
+ * forms of each other by name with a `from` → `to` attribution edge on every parked form, and
1793
+ * refuses `DEADLOCK` on a transitive cycle across every in-flight ask.
1655
1794
  *
1656
1795
  * @remarks
1657
1796
  * - **Registry.** `add(name, options?)` mints (or, if `name` is already mounted, returns the
1658
- * EXISTING broker UNCHANGED — idempotent, never clobbers a live/parked endpoint). Every mounted
1797
+ * existing broker unchanged — idempotent, never clobbers a live/parked endpoint). Every mounted
1659
1798
  * broker's `pending` / `answer` / `expire` events are re-emitted on the manager, attributed by
1660
1799
  * `name`.
1661
- * - **`ask`.** The target must already be mounted via {@link add} — `ask` never auto-adds it;
1800
+ * - **`ask`.** The target must already be mounted through {@link add} — `ask` never auto-adds it;
1662
1801
  * rejects `TARGET` for an unknown `to` (listing the known names). Rejects `DEADLOCK` when parking
1663
- * `from → to` would close a cycle over the CURRENT in-flight edge set (walked transitively);
1802
+ * `from → to` would close a cycle over the current in-flight edge set (walked transitively);
1664
1803
  * otherwise parks the caller's live form through the target's broker and returns that form's own
1665
1804
  * `answer` promise. Edge cleanup never alters the value or rejection the caller observes.
1666
1805
  * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked
1667
1806
  * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's
1668
1807
  * configured `timeout`.
1669
- * - **Removal.** `remove` drops one endpoint, a batch (§9.2, array overload FIRST), or every
1808
+ * - **Removal.** `remove` drops one endpoint, a batch (the array overload declared first), or every
1670
1809
  * endpoint when called without an argument. It destroys each broker, which expires every form
1671
1810
  * still parked on it. `destroy` is idempotent.
1672
1811
  *
@@ -1711,7 +1850,7 @@ var TerminalManager = class {
1711
1850
  return this.#terminals.get(name);
1712
1851
  }
1713
1852
  terminals() {
1714
- return [...this.#terminals.keys()];
1853
+ return [...this.#terminals.values()];
1715
1854
  }
1716
1855
  add(name, options) {
1717
1856
  if (this.#destroyed) throw new TerminalError("DESTROYED", "manager destroyed");
@@ -1743,7 +1882,7 @@ var TerminalManager = class {
1743
1882
  ask(from, to, form) {
1744
1883
  const broker = this.#terminals.get(to);
1745
1884
  if (broker === void 0) {
1746
- const known = this.terminals();
1885
+ const known = [...this.#terminals.keys()];
1747
1886
  return Promise.reject(new TerminalError("TARGET", `unknown terminal '${to}' (known: ${known.length > 0 ? known.join(", ") : "none"})`, {
1748
1887
  to,
1749
1888
  known
@@ -1781,7 +1920,7 @@ var TerminalManager = class {
1781
1920
  const broker = this.#terminals.get(to);
1782
1921
  if (broker === void 0) return {
1783
1922
  success: false,
1784
- error: { reason: "terminal" }
1923
+ error: { reason: "target" }
1785
1924
  };
1786
1925
  const result = broker.answer(id, values);
1787
1926
  if (result.success) this.#edges.delete(id);
@@ -1814,8 +1953,8 @@ var TerminalManager = class {
1814
1953
  return;
1815
1954
  }
1816
1955
  if ((0, _orkestrel_contract.isArray)(names)) {
1817
- let removed = false;
1818
- for (const name of names) if (this.#removeOne(name)) removed = true;
1956
+ let removed = true;
1957
+ for (const name of names) if (!this.#removeOne(name)) removed = false;
1819
1958
  return removed;
1820
1959
  }
1821
1960
  return this.#removeOne(names);
@@ -1890,16 +2029,17 @@ var TerminalManager = class {
1890
2029
  }
1891
2030
  };
1892
2031
  //#endregion
1893
- //#region src/core/MemoryTerminalStore.ts
2032
+ //#region src/core/stores/MemoryTerminalStore.ts
1894
2033
  /**
1895
- * The in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
1896
- * {@link TerminalSnapshot}s keyed by endpoint id, the DEFAULT store
1897
- * {@link import('./factories.js').createMemoryTerminalStore} builds. The EXACT twin of
1898
- * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}.
2034
+ * Implements the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
2035
+ * {@link TerminalSnapshot} records keyed by endpoint id, the default store
2036
+ * {@link import('../factories.js').createMemoryTerminalStore} builds and the exact twin of
2037
+ * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}. It carries no idle expiry and
2038
+ * no eviction.
1899
2039
  *
1900
2040
  * @remarks
1901
- * A plain `Map<string, TerminalSnapshot>` (AGENTS §21 — the snapshot is already pure, self-contained
1902
- * CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO idle-TTL and NO
2041
+ * A plain `Map<string, TerminalSnapshot>` — the snapshot is already pure, self-contained CONFIG-only
2042
+ * JSON, so the memory tier needs no encoding. There is NO idle-TTL and NO
1903
2043
  * eviction: a persisted config lives until an explicit `delete`. A durable backend (JSON / SQLite /
1904
2044
  * IndexedDB) swaps in through the SAME interface without touching the manager — its
1905
2045
  * driver-pluggable twin is {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore} (the
@@ -1909,13 +2049,13 @@ var TerminalManager = class {
1909
2049
  * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
1910
2050
  * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
1911
2051
  *
1912
- * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1913
- * bijection with {@link TerminalStoreInterface}). Hydration is a caller concern: `open` always
2052
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and
2053
+ * {@link TerminalStoreInterface} carry the same methods. Hydration is a caller concern: `open` always
1914
2054
  * restores an EMPTY broker — parked Promises are process-bound and never resurrected.
1915
2055
  *
1916
2056
  * @example
1917
2057
  * ```ts
1918
- * import { createMemoryTerminalStore } from '@src/core'
2058
+ * import { createMemoryTerminalStore } from '@orkestrel/terminal'
1919
2059
  *
1920
2060
  * const store = createMemoryTerminalStore()
1921
2061
  * await store.set({ id: 'shell', timeout: 5000 }) // persist a config
@@ -1925,32 +2065,51 @@ var TerminalManager = class {
1925
2065
  */
1926
2066
  var MemoryTerminalStore = class {
1927
2067
  #snapshots = /* @__PURE__ */ new Map();
2068
+ /**
2069
+ * Resolves the persisted snapshot for `id`.
2070
+ *
2071
+ * @param id - The endpoint name the snapshot is keyed by
2072
+ * @returns The stored snapshot, or `undefined` when none is held
2073
+ */
1928
2074
  get(id) {
1929
2075
  return Promise.resolve(this.#snapshots.get(id));
1930
2076
  }
2077
+ /**
2078
+ * Inserts or replaces under the snapshot's OWN `id` (no separate id param).
2079
+ *
2080
+ * @param snapshot - The config snapshot to persist, carrying its own `id`
2081
+ * @returns A promise that settles once the snapshot is held
2082
+ */
1931
2083
  set(snapshot) {
1932
2084
  this.#snapshots.set(snapshot.id, snapshot);
1933
2085
  return Promise.resolve();
1934
2086
  }
2087
+ /**
2088
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
2089
+ *
2090
+ * @param id - The endpoint name to drop
2091
+ * @returns A promise that settles once the snapshot is gone
2092
+ */
1935
2093
  delete(id) {
1936
2094
  this.#snapshots.delete(id);
1937
2095
  return Promise.resolve();
1938
2096
  }
1939
2097
  };
1940
2098
  //#endregion
1941
- //#region src/core/DatabaseTerminalStore.ts
2099
+ //#region src/core/stores/DatabaseTerminalStore.ts
1942
2100
  /**
1943
- * A {@link TerminalStoreInterface} backed by one table of the `databases` layer — an endpoint's
1944
- * durable CONFIG state IS a row, so persistence reduces to keyed point-access (`get` / `set` /
1945
- * `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
1946
- * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}.
2101
+ * Implements a {@link TerminalStoreInterface} backed by one table of the `databases` layer — an
2102
+ * endpoint's durable config state is a row, so persistence reduces to keyed point-access (`get` /
2103
+ * `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
2104
+ * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. A stored `snapshot` is narrowed
2105
+ * with {@link import('../validators.js').isTerminalSnapshot} on read.
1947
2106
  *
1948
2107
  * @remarks
1949
2108
  * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
1950
2109
  * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
1951
2110
  * IndexedDB backend swaps in WITHOUT touching the manager — the same seam as
1952
2111
  * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. The driver defaults to memory
1953
- * ({@link import('./factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so
2112
+ * ({@link import('../factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so
1954
2113
  * it ALSO works in memory out of the box; you opt into the durable plumbing by passing a JSON /
1955
2114
  * SQLite / IndexedDB driver.
1956
2115
  *
@@ -1962,18 +2121,19 @@ var MemoryTerminalStore = class {
1962
2121
  * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
1963
2122
  * the row `{ id: snapshot.id, snapshot }`.
1964
2123
  * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
1965
- * a {@link TerminalSnapshot} ({@link import('./validators.js').isTerminalSnapshot} — the AGENTS §14
1966
- * boundary narrow for an untrusted storage read), or `undefined` if none is stored.
2124
+ * a {@link TerminalSnapshot} ({@link import('../validators.js').isTerminalSnapshot} — the total
2125
+ * guard that narrows an untrusted storage read), or `undefined` if none is stored.
1967
2126
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
1968
2127
  *
1969
2128
  * There is NO idle-TTL / eviction — a persisted config lives until an explicit `delete`. The public
1970
- * surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method bijection with
1971
- * {@link TerminalStoreInterface}). Hydration stays a caller concern: `open` always restores an EMPTY
2129
+ * surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and
2130
+ * {@link TerminalStoreInterface} carry the same methods. Hydration stays a caller concern: `open` always restores an EMPTY
1972
2131
  * broker — parked Promises are process-bound and never resurrected.
1973
2132
  *
1974
2133
  * @example
1975
2134
  * ```ts
1976
- * import { createDatabaseTerminalStore, createMemoryDriver } from '@src/core'
2135
+ * import { createDatabaseTerminalStore } from '@orkestrel/terminal'
2136
+ * import { createMemoryDriver } from '@orkestrel/database'
1977
2137
  *
1978
2138
  * const store = createDatabaseTerminalStore(createMemoryDriver()) // a durable driver swaps in here
1979
2139
  * await store.set({ id: 'shell', timeout: 5000 }) // persist the config (one JSON column)
@@ -1984,7 +2144,7 @@ var MemoryTerminalStore = class {
1984
2144
  var DatabaseTerminalStore = class {
1985
2145
  #table;
1986
2146
  /**
1987
- * Wrap a table as a terminal store.
2147
+ * Wraps a table as a terminal store.
1988
2148
  *
1989
2149
  * @param table - The {@link TableInterface} holding the snapshots — its row is the
1990
2150
  * {@link TerminalSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
@@ -1992,20 +2152,37 @@ var DatabaseTerminalStore = class {
1992
2152
  constructor(table) {
1993
2153
  this.#table = table;
1994
2154
  }
1995
- /** Resolve the persisted snapshot for `id`, narrowing the opaque JSON column back to a `TerminalSnapshot`. */
2155
+ /**
2156
+ * Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a
2157
+ * `TerminalSnapshot`.
2158
+ *
2159
+ * @param id - The endpoint name the row is keyed by
2160
+ * @returns The stored snapshot, or `undefined` when none is held or the column is off-shape
2161
+ */
1996
2162
  async get(id) {
1997
2163
  const row = await this.#table.get(id);
1998
2164
  if (row === void 0) return void 0;
1999
2165
  return isTerminalSnapshot(row.snapshot) ? row.snapshot : void 0;
2000
2166
  }
2001
- /** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
2167
+ /**
2168
+ * Inserts or replaces under the snapshot's OWN `id` (no separate id param) — the row is
2169
+ * `{ id, snapshot }`.
2170
+ *
2171
+ * @param snapshot - The config snapshot to persist, carrying its own `id`
2172
+ * @returns A promise that settles once the row is written
2173
+ */
2002
2174
  async set(snapshot) {
2003
2175
  await this.#table.set({
2004
2176
  id: snapshot.id,
2005
2177
  snapshot
2006
2178
  });
2007
2179
  }
2008
- /** Drop a snapshot by id; an absent id is a no-op (no throw). */
2180
+ /**
2181
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
2182
+ *
2183
+ * @param id - The endpoint name to drop
2184
+ * @returns A promise that settles once the row is gone
2185
+ */
2009
2186
  async delete(id) {
2010
2187
  await this.#table.remove(id);
2011
2188
  }
@@ -2013,7 +2190,7 @@ var DatabaseTerminalStore = class {
2013
2190
  //#endregion
2014
2191
  //#region src/core/factories.ts
2015
2192
  /**
2016
- * Create the headless {@link PromptInterface} broker. It parks live forms and applies remote
2193
+ * Creates the headless {@link PromptInterface} broker. It parks live forms and applies remote
2017
2194
  * answers to the authoritative instances.
2018
2195
  *
2019
2196
  * @param options - See {@link PromptOptions}
@@ -2026,7 +2203,7 @@ var DatabaseTerminalStore = class {
2026
2203
  *
2027
2204
  * @example
2028
2205
  * ```ts
2029
- * import { createPrompt } from '@src/core'
2206
+ * import { createPrompt } from '@orkestrel/terminal'
2030
2207
  * import { createForm } from '@orkestrel/form'
2031
2208
  *
2032
2209
  * const prompt = createPrompt()
@@ -2039,9 +2216,10 @@ function createPrompt(options) {
2039
2216
  return new Prompt(options);
2040
2217
  }
2041
2218
  /**
2042
- * Create the SSE prompt {@link PromptClientInterface} BRIDGE — it connects to a remote broker's SSE
2043
- * endpoint, dispatches each received form to a local {@link import('./types.js').TerminalInterface},
2044
- * and POSTs the answer back. Universal — `fetch` / SSE are web-standard.
2219
+ * Creates the SSE prompt {@link PromptClientInterface} bridge — it connects to a remote broker's
2220
+ * SSE endpoint, dispatches each received form to a local
2221
+ * {@link import('./types.js').TerminalInterface}, and POSTs the answer back. Universal — `fetch`
2222
+ * and SSE are web standards.
2045
2223
  *
2046
2224
  * @param options - See {@link PromptClientOptions} (`url` + `terminal` required)
2047
2225
  * @returns A {@link PromptClientInterface}
@@ -2051,11 +2229,11 @@ function createPrompt(options) {
2051
2229
  * ends; it reconnects with the `delay` backoff unless `reconnect` is `false` / the client was
2052
2230
  * `destroy`ed. Inject `options.fetch` (a scripted `fetch`) and `options.timer` to drive it
2053
2231
  * deterministically in tests — no real network.
2054
- * - **§14 wire narrowing.** Every decoded prompt is guard-narrowed before dispatch (never an `as`).
2232
+ * - **Wire narrowing.** Every decoded prompt is guard-narrowed before dispatch (never an `as`).
2055
2233
  *
2056
2234
  * @example
2057
2235
  * ```ts
2058
- * import { createPromptClient } from '@src/core'
2236
+ * import { createPromptClient } from '@orkestrel/terminal'
2059
2237
  *
2060
2238
  * const client = createPromptClient({ url: 'http://host/prompts', terminal })
2061
2239
  * await client.connect()
@@ -2065,16 +2243,16 @@ function createPromptClient(options) {
2065
2243
  return new PromptClient(options);
2066
2244
  }
2067
2245
  /**
2068
- * Create the multi-endpoint {@link TerminalManager} — a named registry of
2069
- * {@link PromptInterface} brokers so several parties can `ask` prompts of each other by name,
2070
- * with a transitive DEADLOCK check across every in-flight ask.
2246
+ * Creates the multi-endpoint {@link TerminalManager} — a named registry of {@link PromptInterface}
2247
+ * brokers so several parties can `ask` forms of each other by name, with a transitive cycle check
2248
+ * that refuses `DEADLOCK` across every in-flight ask.
2071
2249
  *
2072
2250
  * @param options - See {@link TerminalManagerOptions}
2073
2251
  * @returns A {@link TerminalManager}
2074
2252
  *
2075
2253
  * @example
2076
2254
  * ```ts
2077
- * import { createTerminalManager } from '@src/core'
2255
+ * import { createTerminalManager } from '@orkestrel/terminal'
2078
2256
  *
2079
2257
  * const manager = createTerminalManager()
2080
2258
  * manager.add('agent')
@@ -2084,14 +2262,14 @@ function createTerminalManager(options) {
2084
2262
  return new TerminalManager(options);
2085
2263
  }
2086
2264
  /**
2087
- * Create the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
2265
+ * Creates the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
2088
2266
  * config snapshots, the default store backing a {@link TerminalManagerInterface}'s `open` / `save`.
2089
2267
  *
2090
2268
  * @returns A {@link TerminalStoreInterface}
2091
2269
  *
2092
2270
  * @example
2093
2271
  * ```ts
2094
- * import { createMemoryTerminalStore } from '@src/core'
2272
+ * import { createMemoryTerminalStore } from '@orkestrel/terminal'
2095
2273
  *
2096
2274
  * const store = createMemoryTerminalStore()
2097
2275
  * ```
@@ -2100,16 +2278,17 @@ function createMemoryTerminalStore() {
2100
2278
  return new MemoryTerminalStore();
2101
2279
  }
2102
2280
  /**
2103
- * Create a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
2281
+ * Creates a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
2104
2282
  * driver-pluggable twin of {@link createMemoryTerminalStore}, storing each endpoint's config
2105
- * snapshot as one opaque JSON column.
2283
+ * snapshot as one opaque JSON column. The default driver is an in-memory `@orkestrel/database`
2284
+ * driver.
2106
2285
  *
2107
2286
  * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
2108
2287
  * @returns A {@link TerminalStoreInterface}
2109
2288
  *
2110
2289
  * @example
2111
2290
  * ```ts
2112
- * import { createDatabaseTerminalStore } from '@src/core'
2291
+ * import { createDatabaseTerminalStore } from '@orkestrel/terminal'
2113
2292
  *
2114
2293
  * const store = createDatabaseTerminalStore() // in-memory by default
2115
2294
  * ```
@@ -2139,9 +2318,7 @@ exports.DEFAULT_PROMPT_TIMEOUT_MS = DEFAULT_PROMPT_TIMEOUT_MS;
2139
2318
  exports.DEFAULT_RECONNECT_DELAY_MS = DEFAULT_RECONNECT_DELAY_MS;
2140
2319
  exports.DELETE = DELETE;
2141
2320
  exports.DatabaseTerminalStore = DatabaseTerminalStore;
2142
- exports.ESCAPE = ESCAPE;
2143
2321
  exports.HEADER_TOKEN = HEADER_TOKEN;
2144
- exports.KEY_CSI = KEY_CSI;
2145
2322
  exports.KEY_SS3 = KEY_SS3;
2146
2323
  exports.MemoryTerminalStore = MemoryTerminalStore;
2147
2324
  exports.NEWLINE = NEWLINE;
@@ -2157,10 +2334,6 @@ exports.SSE_EVENTS = SSE_EVENTS;
2157
2334
  exports.TAB = TAB;
2158
2335
  exports.TerminalError = TerminalError;
2159
2336
  exports.TerminalManager = TerminalManager;
2160
- exports.checkboxReduce = checkboxReduce;
2161
- exports.checkboxView = checkboxView;
2162
- exports.confirmReduce = confirmReduce;
2163
- exports.confirmView = confirmView;
2164
2337
  exports.createCheckboxState = createCheckboxState;
2165
2338
  exports.createConfirmState = createConfirmState;
2166
2339
  exports.createDatabaseTerminalStore = createDatabaseTerminalStore;
@@ -2175,13 +2348,7 @@ exports.createSelectState = createSelectState;
2175
2348
  exports.createTerminalManager = createTerminalManager;
2176
2349
  exports.defaultTimer = defaultTimer;
2177
2350
  exports.editLine = editLine;
2178
- exports.editorReduce = editorReduce;
2179
- exports.editorView = editorView;
2180
- exports.errorLine = errorLine;
2181
2351
  exports.globalFetch = globalFetch;
2182
- exports.hintedHeader = hintedHeader;
2183
- exports.inputReduce = inputReduce;
2184
- exports.inputView = inputView;
2185
2352
  exports.isAbortError = isAbortError;
2186
2353
  exports.isInsecureRemote = isInsecureRemote;
2187
2354
  exports.isPendingForm = isPendingForm;
@@ -2191,18 +2358,28 @@ exports.isTerminalError = isTerminalError;
2191
2358
  exports.isTerminalSnapshot = isTerminalSnapshot;
2192
2359
  exports.isWireEvent = isWireEvent;
2193
2360
  exports.parseKey = parseKey;
2194
- exports.passwordReduce = passwordReduce;
2195
- exports.passwordView = passwordView;
2196
- exports.promptHeader = promptHeader;
2361
+ exports.reduceCheckbox = reduceCheckbox;
2362
+ exports.reduceConfirm = reduceConfirm;
2363
+ exports.reduceEditor = reduceEditor;
2364
+ exports.reduceInput = reduceInput;
2365
+ exports.reducePassword = reducePassword;
2366
+ exports.reduceSelect = reduceSelect;
2367
+ exports.renderCheckboxView = renderCheckboxView;
2368
+ exports.renderConfirmView = renderConfirmView;
2369
+ exports.renderEditorView = renderEditorView;
2370
+ exports.renderErrorLine = renderErrorLine;
2371
+ exports.renderHintedHeader = renderHintedHeader;
2372
+ exports.renderInputView = renderInputView;
2373
+ exports.renderPasswordView = renderPasswordView;
2374
+ exports.renderPromptHeader = renderPromptHeader;
2375
+ exports.renderSelectView = renderSelectView;
2376
+ exports.renderSubmitHeader = renderSubmitHeader;
2197
2377
  exports.sanitizeDisplayText = sanitizeDisplayText;
2198
2378
  exports.sanitizeSchema = sanitizeSchema;
2199
2379
  exports.sanitizeThemeIcons = sanitizeThemeIcons;
2200
- exports.selectReduce = selectReduce;
2201
- exports.selectView = selectView;
2380
+ exports.serializeDestroy = serializeDestroy;
2202
2381
  exports.serializeExpire = serializeExpire;
2203
2382
  exports.serializePending = serializePending;
2204
- exports.serializeShutdown = serializeShutdown;
2205
- exports.submitHeader = submitHeader;
2206
2383
  exports.toggleIndex = toggleIndex;
2207
2384
 
2208
2385
  //# sourceMappingURL=index.cjs.map