@sigrun/tui 0.4.1 → 0.5.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 (114) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/dist/actions.d.ts +119 -0
  3. package/dist/actions.js +12 -0
  4. package/dist/api.d.ts +209 -0
  5. package/dist/api.js +282 -0
  6. package/dist/blocks/braille.d.ts +41 -0
  7. package/dist/blocks/braille.js +131 -0
  8. package/dist/blocks/chart.d.ts +20 -0
  9. package/dist/blocks/chart.js +397 -0
  10. package/dist/blocks/chat-transport.d.ts +44 -0
  11. package/dist/blocks/chat-transport.js +116 -0
  12. package/dist/blocks/collection/calendar.d.ts +28 -0
  13. package/dist/blocks/collection/calendar.js +232 -0
  14. package/dist/blocks/collection/card.d.ts +36 -0
  15. package/dist/blocks/collection/card.js +128 -0
  16. package/dist/blocks/collection/collection.d.ts +9 -0
  17. package/dist/blocks/collection/collection.js +26 -0
  18. package/dist/blocks/collection/grid.d.ts +18 -0
  19. package/dist/blocks/collection/grid.js +72 -0
  20. package/dist/blocks/collection/lanes.d.ts +32 -0
  21. package/dist/blocks/collection/lanes.js +320 -0
  22. package/dist/blocks/collection/list.d.ts +30 -0
  23. package/dist/blocks/collection/list.js +273 -0
  24. package/dist/blocks/collection/move.d.ts +67 -0
  25. package/dist/blocks/collection/move.js +96 -0
  26. package/dist/blocks/collection/shared.d.ts +87 -0
  27. package/dist/blocks/collection/shared.js +177 -0
  28. package/dist/blocks/collection/table.d.ts +26 -0
  29. package/dist/blocks/collection/table.js +221 -0
  30. package/dist/blocks/content.d.ts +48 -0
  31. package/dist/blocks/content.js +253 -0
  32. package/dist/blocks/context.d.ts +117 -0
  33. package/dist/blocks/context.js +65 -0
  34. package/dist/blocks/diff.d.ts +21 -0
  35. package/dist/blocks/diff.js +25 -0
  36. package/dist/blocks/ext-transport.d.ts +70 -0
  37. package/dist/blocks/ext-transport.js +165 -0
  38. package/dist/blocks/form/choice.d.ts +54 -0
  39. package/dist/blocks/form/choice.js +175 -0
  40. package/dist/blocks/form/fields.d.ts +56 -0
  41. package/dist/blocks/form/fields.js +136 -0
  42. package/dist/blocks/form/file.d.ts +61 -0
  43. package/dist/blocks/form/file.js +121 -0
  44. package/dist/blocks/form/form.d.ts +66 -0
  45. package/dist/blocks/form/form.js +764 -0
  46. package/dist/blocks/form/list.d.ts +37 -0
  47. package/dist/blocks/form/list.js +70 -0
  48. package/dist/blocks/form/values.d.ts +20 -0
  49. package/dist/blocks/form/values.js +106 -0
  50. package/dist/blocks/icons.d.ts +14 -0
  51. package/dist/blocks/icons.js +74 -0
  52. package/dist/blocks/image.d.ts +39 -0
  53. package/dist/blocks/image.js +82 -0
  54. package/dist/blocks/interaction-transport.d.ts +42 -0
  55. package/dist/blocks/interaction-transport.js +79 -0
  56. package/dist/blocks/layout.d.ts +31 -0
  57. package/dist/blocks/layout.js +155 -0
  58. package/dist/blocks/log.d.ts +24 -0
  59. package/dist/blocks/log.js +111 -0
  60. package/dist/blocks/meter.d.ts +16 -0
  61. package/dist/blocks/meter.js +50 -0
  62. package/dist/blocks/page.d.ts +54 -0
  63. package/dist/blocks/page.js +360 -0
  64. package/dist/blocks/render.d.ts +31 -0
  65. package/dist/blocks/render.js +163 -0
  66. package/dist/blocks/surface-view.d.ts +41 -0
  67. package/dist/blocks/surface-view.js +137 -0
  68. package/dist/blocks/surface.d.ts +80 -0
  69. package/dist/blocks/surface.js +506 -0
  70. package/dist/blocks/tones.d.ts +23 -0
  71. package/dist/blocks/tones.js +63 -0
  72. package/dist/blocks/treeview.d.ts +22 -0
  73. package/dist/blocks/treeview.js +121 -0
  74. package/dist/blocks/width.d.ts +32 -0
  75. package/dist/blocks/width.js +70 -0
  76. package/dist/clipboard.d.ts +20 -0
  77. package/dist/clipboard.js +19 -0
  78. package/dist/editor-launch.d.ts +34 -0
  79. package/dist/editor-launch.js +70 -0
  80. package/dist/keymaps/emacs.d.ts +18 -0
  81. package/dist/keymaps/emacs.js +172 -0
  82. package/dist/keymaps/standard.d.ts +17 -0
  83. package/dist/keymaps/standard.js +175 -0
  84. package/dist/keymaps/types.d.ts +43 -0
  85. package/dist/keymaps/types.js +28 -0
  86. package/dist/keymaps/vim.d.ts +48 -0
  87. package/dist/keymaps/vim.js +291 -0
  88. package/dist/launch.d.ts +159 -0
  89. package/dist/launch.js +478 -0
  90. package/dist/sse.d.ts +112 -0
  91. package/dist/sse.js +418 -0
  92. package/dist/theme.d.ts +51 -0
  93. package/dist/theme.js +119 -0
  94. package/dist/ui/ambient.d.ts +78 -0
  95. package/dist/ui/ambient.js +264 -0
  96. package/dist/ui/app.d.ts +319 -0
  97. package/dist/ui/app.js +1539 -0
  98. package/dist/ui/composer.d.ts +66 -0
  99. package/dist/ui/composer.js +133 -0
  100. package/dist/ui/conversation-pane.d.ts +143 -0
  101. package/dist/ui/conversation-pane.js +615 -0
  102. package/dist/ui/help.d.ts +15 -0
  103. package/dist/ui/help.js +80 -0
  104. package/dist/ui/interactions.d.ts +91 -0
  105. package/dist/ui/interactions.js +477 -0
  106. package/dist/ui/picker.d.ts +26 -0
  107. package/dist/ui/picker.js +130 -0
  108. package/dist/ui/settings.d.ts +68 -0
  109. package/dist/ui/settings.js +254 -0
  110. package/dist/ui/sidebar.d.ts +99 -0
  111. package/dist/ui/sidebar.js +269 -0
  112. package/dist/ui/status-bar.d.ts +77 -0
  113. package/dist/ui/status-bar.js +146 -0
  114. package/package.json +4 -3
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The secret and file fields (UI-blocks U2 Task 5, spec §5.6).
3
+ *
4
+ * `secret` is write-only: it NEVER renders a value, only the bound
5
+ * status — `set (stored)` / `set (env)` / `not set` — and a masked
6
+ * editor (`•` per typed character; ASCII `*` with ctx.unicode false).
7
+ *
8
+ * `file` prompts for a path, checks `accept` (MIME patterns) and
9
+ * `maxBytes`, reads the file's bytes, dispatches `onUpload` (a
10
+ * `submit` action, turn false) with the file descriptor plus the base64
11
+ * bytes, and hands the bytes to the surface's upload seam
12
+ * (`uploadBytes`) when the host provides one (§7.2). A failed check
13
+ * shows its error under the field and dispatches nothing.
14
+ */
15
+ import { readFileSync, statSync } from "node:fs";
16
+ import { bindRaw } from "../context.js";
17
+ import { truncate } from "../width.js";
18
+ /** The secret status text (spec §5.6's exact strings). */
19
+ export function secretStatusText(status) {
20
+ if (status === "stored")
21
+ return "set (stored)";
22
+ if (status === "env")
23
+ return "set (env)";
24
+ return "not set";
25
+ }
26
+ /** The status line of a secret field: the bound status (the field's
27
+ * name resolved against the form's bind, as U1's SecretField does). */
28
+ export function secretStatusLine(field, formBind, ctx) {
29
+ const pointer = `${formBind === "" ? "" : formBind}/${field.name}`.replace(/^\/+/, "/");
30
+ const status = bindRaw({ $: pointer }, ctx);
31
+ return secretStatusText(status);
32
+ }
33
+ /** The masked editor text: one dot per typed character. */
34
+ export function maskText(textValue, unicode) {
35
+ return (unicode ? "•" : "*").repeat(textValue.length);
36
+ }
37
+ /** Whether a file matches the accept patterns (comma-separated:
38
+ * `image/*`, `image/png`, `.png`) — U1's `acceptsMime` rule, extended
39
+ * with an extension fallback when the MIME guess is empty. */
40
+ export function acceptsMime(accept, file) {
41
+ if (accept === undefined || accept.trim() === "")
42
+ return true;
43
+ return accept.split(",").some((raw) => {
44
+ const pattern = raw.trim();
45
+ if (pattern === "")
46
+ return false;
47
+ if (pattern.startsWith("."))
48
+ return file.name.endsWith(pattern);
49
+ if (pattern.endsWith("/*")) {
50
+ return file.type !== "" && file.type.startsWith(pattern.slice(0, -1));
51
+ }
52
+ return file.type === pattern;
53
+ });
54
+ }
55
+ /** A small extension → MIME map (the TUI has no file picker; the type
56
+ * is guessed from the name, empty when unknown). */
57
+ const EXT_MIME = {
58
+ ".png": "image/png",
59
+ ".jpg": "image/jpeg",
60
+ ".jpeg": "image/jpeg",
61
+ ".gif": "image/gif",
62
+ ".webp": "image/webp",
63
+ ".svg": "image/svg+xml",
64
+ ".txt": "text/plain",
65
+ ".md": "text/markdown",
66
+ ".json": "application/json",
67
+ ".pdf": "application/pdf",
68
+ };
69
+ /** The MIME guess from a file name ("" when unknown). */
70
+ export function mimeOf(name) {
71
+ const dot = name.lastIndexOf(".");
72
+ if (dot < 0)
73
+ return "";
74
+ return EXT_MIME[name.slice(dot).toLowerCase()] ?? "";
75
+ }
76
+ /**
77
+ * Check `accept`/`maxBytes`, read the file and build the descriptor +
78
+ * payload. A failure answers `{ok: false, error}` and nothing else
79
+ * happens (no dispatch).
80
+ */
81
+ export function pickFile(field, path, read = (p) => new Uint8Array(readFileSync(p)), sizeOf = (p) => statSync(p).size) {
82
+ const name = path.split("/").pop() ?? path;
83
+ let size;
84
+ let bytes;
85
+ try {
86
+ size = sizeOf(path);
87
+ bytes = read(path);
88
+ }
89
+ catch {
90
+ return { ok: false, error: `${path}: cannot read the file` };
91
+ }
92
+ const type = mimeOf(name);
93
+ if (!acceptsMime(field.accept, { type, name })) {
94
+ return { ok: false, error: `${name}: not an accepted type` };
95
+ }
96
+ if (field.maxBytes !== undefined && size > field.maxBytes) {
97
+ return { ok: false, error: `${name} is larger than ${field.maxBytes} bytes` };
98
+ }
99
+ return {
100
+ ok: true,
101
+ descriptor: { name, size, type },
102
+ bytes,
103
+ payloadFile: {
104
+ name,
105
+ size,
106
+ type,
107
+ bytesBase64: Buffer.from(bytes).toString("base64"),
108
+ },
109
+ };
110
+ }
111
+ /** The per-field secret/file editor state (the path/masked buffer). */
112
+ export class FileFieldState {
113
+ error;
114
+ uploaded = [];
115
+ }
116
+ /** The status line a file field shows after an upload. */
117
+ export function fileStatusLine(state, width) {
118
+ if (state.uploaded.length === 0)
119
+ return undefined;
120
+ return truncate(`uploaded: ${state.uploaded.join(", ")}`, width);
121
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The `form` block (UI-blocks U2 Task 5, spec §5.6/§6.2/§9.1 — the TUI
3
+ * port of U1's `Form.tsx`): modes (submit / live / inline), client
4
+ * validation before dispatch, the live-mode 300 ms debounce with the
5
+ * `/view/` view-only branch, and the interaction stepper — several
6
+ * sections each holding a `choice` in an interaction placement present
7
+ * one question at a time with a review step, submitting ONCE with every
8
+ * field's value.
9
+ *
10
+ * TUI editing model (the app owns keymaps; the form owns text editing):
11
+ * Up/Down move between fields (the stepper moves BETWEEN sections);
12
+ * the focused field edits inline — characters insert, Backspace
13
+ * deletes, Enter submits (text/number/date/secret), toggles (toggle)
14
+ * or activates an option (choice); Tab moves to the next field.
15
+ * `syntax: "code"` longtext opens the host's $EDITOR seam with `e`.
16
+ * Editing is per-mode: `submit` edits locally until Enter on the button
17
+ * row; `live` dispatches every change after 300 ms (a `/view/`-bound
18
+ * live form writes through ctx.setView and never dispatches); `inline`
19
+ * renders one row (field plus the submit label) and clears on success.
20
+ *
21
+ * The focused field survives a data update (Review Focus 4): values
22
+ * and focus live in the component, not the render; a re-render after a
23
+ * server data change keeps both.
24
+ */
25
+ import type { Component } from "@earendil-works/pi-tui";
26
+ import type { FormBlock, JsonObject, JsonValue, SectionBlock } from "@sigrun/shared/blocks";
27
+ import { type BlockCtx } from "../context.js";
28
+ /** The live-mode debounce (spec §5.6: "every change is applied after
29
+ * 300 ms"). */
30
+ export declare const LIVE_DEBOUNCE_MS = 300;
31
+ /** The stepper: does this form qualify (several sections each holding
32
+ * a choice, interaction placement)? (§9.1) */
33
+ export declare function stepperSections(block: FormBlock, interaction: boolean): readonly SectionBlock[] | null;
34
+ /** The form component. */
35
+ export declare class FormComponent implements Component {
36
+ #private;
37
+ constructor(block: FormBlock, ctx: BlockCtx, opts?: {
38
+ interaction?: boolean;
39
+ });
40
+ /** Render (the form re-renders against the current ctx data — the
41
+ * host re-creates or re-renders on SurfaceState changes). */
42
+ render(width: number): string[];
43
+ /** Input handling (the host routes keys while the form has focus). */
44
+ handleInput(data: string): void;
45
+ invalidate(): void;
46
+ /** Cancel any pending live debounce (the host's dispose seam). */
47
+ dispose(): void;
48
+ /** Test/host introspection: the current values and focus. */
49
+ get values(): JsonObject;
50
+ get focusIndex(): number;
51
+ /** Move focus to the field with this name (the settings view's
52
+ * search-resolve seam, U4 Task 4: a "naming" hit lands the
53
+ * caret on the naming-model row). Returns false when no field
54
+ * carries the name. */
55
+ focusName(name: string): boolean;
56
+ /** The flattened field names (the host's search-resolve check). */
57
+ get fieldNames(): readonly string[];
58
+ get step(): number;
59
+ get submittedOnce(): boolean;
60
+ /** Set one field's value from the host (the interaction prompter's
61
+ * digit accelerator writes the pick; the form's own key handling
62
+ * stays the editor). Read-only forms ignore the write. */
63
+ setFieldValue(name: string, value: JsonValue): void;
64
+ /** One field's current value (the host's review/dispatch reads). */
65
+ fieldValue(name: string): JsonValue | undefined;
66
+ }