@mdxui/terminal 2.0.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 (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. package/src/types.ts +103 -0
@@ -0,0 +1,672 @@
1
+ /**
2
+ * @mdxui/terminal UINode Type System
3
+ *
4
+ * Core type definitions and Zod schemas for the Universal Terminal UI's
5
+ * multi-tier rendering architecture.
6
+ */
7
+ import { z } from 'zod'
8
+
9
+ // ============================================================================
10
+ // TypeScript Interfaces
11
+ // ============================================================================
12
+
13
+ /**
14
+ * UINode - The fundamental building block of the terminal UI tree.
15
+ *
16
+ * Represents a component with type, props, optional children, data, and key.
17
+ * This is the core abstraction that allows universal rendering across different
18
+ * output tiers (text, markdown, ascii, unicode, ansi, interactive).
19
+ *
20
+ * @remarks
21
+ * UINode follows a tree-based component model similar to React:
22
+ * - Each node has a `type` that identifies the component (e.g., 'box', 'text', 'panel')
23
+ * - Props are passed as a generic key-value record
24
+ * - Children can be nested to arbitrary depth
25
+ * - Optional `data` field can hold query results for data-driven components
26
+ * - Optional `key` field helps with list reconciliation
27
+ *
28
+ * @example
29
+ * Simple text node:
30
+ * ```tsx
31
+ * const node: UINode = {
32
+ * type: 'text',
33
+ * props: { content: 'Hello, World!' }
34
+ * }
35
+ * ```
36
+ *
37
+ * @example
38
+ * Nested structure with children:
39
+ * ```tsx
40
+ * const node: UINode = {
41
+ * type: 'box',
42
+ * props: { padding: 2, border: 'rounded' },
43
+ * children: [
44
+ * { type: 'text', props: { content: 'Title' } },
45
+ * { type: 'text', props: { content: 'Subtitle' } }
46
+ * ]
47
+ * }
48
+ * ```
49
+ *
50
+ * @example
51
+ * Data-driven component:
52
+ * ```tsx
53
+ * const node: UINode = {
54
+ * type: 'table',
55
+ * props: { headers: ['Name', 'Age'] },
56
+ * data: [
57
+ * { name: 'Alice', age: 30 },
58
+ * { name: 'Bob', age: 28 }
59
+ * ]
60
+ * }
61
+ * ```
62
+ */
63
+ export interface UINode {
64
+ /**
65
+ * Component type identifier
66
+ * @remarks
67
+ * String that identifies the component type. Common examples: 'text', 'box', 'panel',
68
+ * 'table', 'list', 'button'. Custom component types are supported.
69
+ */
70
+ type: string
71
+
72
+ /**
73
+ * Component props as a generic record
74
+ * @remarks
75
+ * Props are component-specific configuration. All values must be serializable
76
+ * for cross-tier rendering. Examples:
77
+ * - { content: 'text content' }
78
+ * - { padding: 2, border: 'single' }
79
+ * - { columns: ['Name', 'Email'] }
80
+ */
81
+ props?: Record<string, unknown>
82
+
83
+ /**
84
+ * Optional child UINodes or string content for nested components
85
+ * @remarks
86
+ * Children are rendered as the component's content. Nested to arbitrary depth.
87
+ * Can be an array of UINode objects or a direct string for simple text content.
88
+ * Undefined if the component has no children.
89
+ */
90
+ children?: UINode[] | string
91
+
92
+ /**
93
+ * Optional shorthand for text content
94
+ * @remarks
95
+ * Convenience property for setting text content directly on a node without
96
+ * using props.content. Commonly used for simple text nodes.
97
+ * Example: { type: 'text', text: 'Hello World' }
98
+ */
99
+ text?: string
100
+
101
+ /**
102
+ * Optional bound query data for data-driven components
103
+ * @remarks
104
+ * Used for components that render arrays of items (tables, lists, grids).
105
+ * The data can be any shape; the component implementation defines how to use it.
106
+ * Examples: array of records, nested objects, primitive values.
107
+ */
108
+ data?: unknown
109
+
110
+ /**
111
+ * Optional key for React-style reconciliation
112
+ * @remarks
113
+ * Used in lists to maintain identity across renders. If a node's key changes,
114
+ * the component is recreated. Useful for preserving component state.
115
+ * Should be stable across renders for the same logical item.
116
+ */
117
+ key?: string
118
+ }
119
+
120
+ /**
121
+ * RenderTier - Defines the capability level for rendering output.
122
+ *
123
+ * Tiers are ordered by increasing capability:
124
+ * `text` < `markdown` < `ascii` < `unicode` < `ansi` < `interactive`
125
+ *
126
+ * Each tier builds on the previous one, enabling progressively richer
127
+ * terminal output while maintaining backward compatibility.
128
+ *
129
+ * @remarks
130
+ * **Tier Definitions:**
131
+ *
132
+ * - **text** - Plain text output without any formatting or special characters.
133
+ * No colors, no box drawing, no markdown. Useful for piping output or
134
+ * basic terminal environments with no formatting support.
135
+ * Example: `Hello World`
136
+ *
137
+ * - **markdown** - Text with Markdown syntax for basic formatting:
138
+ * **bold**, *italic*, `code`, links. No color support. Good for converting
139
+ * terminal output to documentation or emails.
140
+ * Example: `**Hello** _World_`
141
+ *
142
+ * - **ascii** - ASCII art characters (/, \, |, -, +, =) for drawing
143
+ * boxes, borders, and diagrams. No unicode box drawing characters.
144
+ * Broader terminal compatibility than unicode.
145
+ * Example: `+---+\n| A |\n+---+`
146
+ *
147
+ * - **unicode** - Unicode box drawing characters (┌, ─, │, └, etc.)
148
+ * for elegant borders and tables. Better appearance than ASCII but
149
+ * requires UTF-8 terminal support.
150
+ * Example: `┌───┐\n│ A │\n└───┘`
151
+ *
152
+ * - **ansi** - ANSI escape sequences for 256-color or truecolor output.
153
+ * Supports foreground/background colors, bold, underline, etc.
154
+ * Requires modern terminal supporting ANSI codes.
155
+ * Example: `\x1b[31mRed Text\x1b[0m`
156
+ *
157
+ * - **interactive** - Full interactive terminal UI with keyboard input,
158
+ * mouse events, and real-time updates. Requires a terminal library like
159
+ * Ink or Blessed. Most capable tier but requires active user interaction.
160
+ * Example: Menu navigation, live dashboards, text input.
161
+ *
162
+ * @example
163
+ * Checking tier capability:
164
+ * ```tsx
165
+ * const canUseColors = tier === 'ansi' || tier === 'interactive'
166
+ * const canUseUnicode = tier === 'unicode' || tier === 'ansi' || tier === 'interactive'
167
+ * const isPlainText = tier === 'text'
168
+ * ```
169
+ */
170
+ export type RenderTier =
171
+ | 'text'
172
+ | 'markdown'
173
+ | 'ascii'
174
+ | 'unicode'
175
+ | 'ansi'
176
+ | 'interactive'
177
+
178
+ /**
179
+ * ThemeTokens - Color tokens for theming terminal output.
180
+ *
181
+ * All tokens are strings containing ANSI escape sequences (or empty for text tier).
182
+ * This interface defines a complete semantic color palette for terminal applications.
183
+ *
184
+ * @remarks
185
+ * **Format and Values:**
186
+ * - For `text` tier: Empty string `''` (no color support)
187
+ * - For `markdown` tier: Empty string `''` (colors not supported in markdown)
188
+ * - For `ascii`/`unicode`/`ansi`/`interactive` tiers:
189
+ * ANSI escape sequences like `'\x1b[31m'` (red) or `'\x1b[1;32m'` (bold green)
190
+ *
191
+ * **Semantic Categories:**
192
+ * - **Primary/Secondary**: Brand colors for emphasis and hierarchy
193
+ * - **Foreground/Background**: Text and surface colors
194
+ * - **Status Colors**: Semantic meaning (green=success, red=error, yellow=warning, blue=info)
195
+ * - **Muted**: De-emphasized text (help text, secondary content)
196
+ * - **Border**: For drawing boxes and separators
197
+ *
198
+ * @example
199
+ * Light ANSI theme:
200
+ * ```tsx
201
+ * const lightTheme: ThemeTokens = {
202
+ * primary: '\x1b[34m', // Blue
203
+ * secondary: '\x1b[36m', // Cyan
204
+ * muted: '\x1b[90m', // Bright black
205
+ * foreground: '\x1b[37m', // White
206
+ * background: '\x1b[40m', // Black background
207
+ * border: '\x1b[90m', // Gray
208
+ * success: '\x1b[32m', // Green
209
+ * warning: '\x1b[33m', // Yellow
210
+ * error: '\x1b[31m', // Red
211
+ * info: '\x1b[34m', // Blue
212
+ * }
213
+ * ```
214
+ *
215
+ * @example
216
+ * Text tier (no colors):
217
+ * ```tsx
218
+ * const textTheme: ThemeTokens = {
219
+ * primary: '',
220
+ * secondary: '',
221
+ * muted: '',
222
+ * foreground: '',
223
+ * background: '',
224
+ * border: '',
225
+ * success: '',
226
+ * warning: '',
227
+ * error: '',
228
+ * info: '',
229
+ * }
230
+ * ```
231
+ */
232
+ export interface ThemeTokens {
233
+ /**
234
+ * Primary brand color
235
+ * @remarks
236
+ * Used for primary CTAs, headings, and key UI elements.
237
+ * ANSI code string, empty for text/markdown tiers.
238
+ */
239
+ primary: string
240
+
241
+ /**
242
+ * Secondary accent color
243
+ * @remarks
244
+ * Used for secondary actions and accents.
245
+ * ANSI code string, empty for text/markdown tiers.
246
+ */
247
+ secondary: string
248
+
249
+ /**
250
+ * Muted/dimmed text color
251
+ * @remarks
252
+ * Used for helper text, secondary content, disabled states.
253
+ * Lower contrast than foreground color.
254
+ * ANSI code string, empty for text/markdown tiers.
255
+ */
256
+ muted: string
257
+
258
+ /**
259
+ * Default foreground text color
260
+ * @remarks
261
+ * Primary text color for body content and labels.
262
+ * Should have high contrast with background.
263
+ * ANSI code string, empty for text/markdown tiers.
264
+ */
265
+ foreground: string
266
+
267
+ /**
268
+ * Background color
269
+ * @remarks
270
+ * Used for panels, boxes, and background surfaces.
271
+ * Should contrast well with foreground for readability.
272
+ * ANSI code string, empty for text/markdown tiers.
273
+ */
274
+ background: string
275
+
276
+ /**
277
+ * Border/separator color
278
+ * @remarks
279
+ * Used for drawing borders, dividers, and separators.
280
+ * Often same as muted for subtle appearance.
281
+ * ANSI code string, empty for text/markdown tiers.
282
+ */
283
+ border: string
284
+
285
+ /**
286
+ * Success state color (typically green)
287
+ * @remarks
288
+ * Used for success messages, checkmarks, confirmations.
289
+ * Semantic meaning: positive outcome, success state.
290
+ * ANSI code string, empty for text/markdown tiers.
291
+ */
292
+ success: string
293
+
294
+ /**
295
+ * Warning state color (typically yellow/orange)
296
+ * @remarks
297
+ * Used for warnings, alerts, caution messages.
298
+ * Semantic meaning: attention needed, non-critical issue.
299
+ * ANSI code string, empty for text/markdown tiers.
300
+ */
301
+ warning: string
302
+
303
+ /**
304
+ * Error state color (typically red)
305
+ * @remarks
306
+ * Used for errors, failures, destructive actions.
307
+ * Semantic meaning: critical issue, failure state.
308
+ * ANSI code string, empty for text/markdown tiers.
309
+ */
310
+ error: string
311
+
312
+ /**
313
+ * Info state color (typically blue)
314
+ * @remarks
315
+ * Used for information, hints, explanatory messages.
316
+ * Semantic meaning: additional context, non-critical info.
317
+ * ANSI code string, empty for text/markdown tiers.
318
+ */
319
+ info: string
320
+ }
321
+
322
+ /**
323
+ * RenderContext - Context passed to renderers during tree traversal.
324
+ *
325
+ * Contains information about the rendering environment and is passed down
326
+ * through the UINode tree as components render. This allows renderers to
327
+ * make decisions about layout, styling, and interactivity based on the
328
+ * current environment.
329
+ *
330
+ * @remarks
331
+ * **Context Propagation:**
332
+ * - Passed to each component's renderer function
333
+ * - Updated for nested components (depth increments)
334
+ * - Allows renderers to adapt output to terminal capabilities
335
+ * - Enables responsive layouts based on terminal dimensions
336
+ *
337
+ * **Tier-Dependent Behavior:**
338
+ * - Lower tiers (text, markdown) ignore complex styling
339
+ * - Higher tiers (ansi, interactive) use full theme and interactivity
340
+ * - Renderers should gracefully degrade for lower tiers
341
+ *
342
+ * @example
343
+ * Using context in a renderer:
344
+ * ```tsx
345
+ * function renderBox(node: UINode, ctx: RenderContext): string {
346
+ * if (ctx.tier === 'text') {
347
+ * // No styling, return plain text
348
+ * return node.props.content as string
349
+ * }
350
+ *
351
+ * if (ctx.tier === 'ansi' || ctx.tier === 'interactive') {
352
+ * // Can use colors and styling
353
+ * return `${ctx.theme.primary}[Box]${RESET}${node.props.content}`
354
+ * }
355
+ *
356
+ * // Other tiers: use unicode or ascii
357
+ * return drawBox(node, ctx)
358
+ * }
359
+ * ```
360
+ *
361
+ * @example
362
+ * Responsive layout based on terminal width:
363
+ * ```tsx
364
+ * function renderGrid(node: UINode, ctx: RenderContext): string {
365
+ * const columns = ctx.width < 80 ? 1 : ctx.width < 120 ? 2 : 3
366
+ * // ... render grid with calculated columns
367
+ * }
368
+ * ```
369
+ */
370
+ export interface RenderContext {
371
+ /**
372
+ * Current rendering tier
373
+ * @remarks
374
+ * Determines what output capabilities are available (colors, unicode, etc).
375
+ * Renderers should adapt their output based on this tier.
376
+ */
377
+ tier: RenderTier
378
+
379
+ /**
380
+ * Terminal width in columns
381
+ * @remarks
382
+ * Used for responsive layouts and line wrapping decisions.
383
+ * Standard values: 80 (legacy), 120 (modern), 160+ (wide monitors).
384
+ * Must be positive integer.
385
+ */
386
+ width: number
387
+
388
+ /**
389
+ * Terminal height in rows
390
+ * @remarks
391
+ * Used for pagination and viewport-aware rendering.
392
+ * Standard values: 24 (legacy), 40 (modern).
393
+ * Must be positive integer.
394
+ */
395
+ height: number
396
+
397
+ /**
398
+ * Current nesting depth
399
+ * @remarks
400
+ * Starts at 0 for root components, increments for each nested level.
401
+ * Used for indentation, padding, and determining when to truncate output.
402
+ * Must be non-negative integer.
403
+ */
404
+ depth: number
405
+
406
+ /**
407
+ * Theme tokens for styling
408
+ * @remarks
409
+ * Contains ANSI color codes for the current tier.
410
+ * For text/markdown tiers, all values are empty strings.
411
+ * Renderers should use these tokens instead of hardcoding colors.
412
+ */
413
+ theme: ThemeTokens
414
+
415
+ /**
416
+ * Whether interactive input is enabled
417
+ * @remarks
418
+ * True only for the 'interactive' tier. Indicates that the renderer
419
+ * should support keyboard input, mouse events, and real-time updates.
420
+ * Always false for non-interactive tiers.
421
+ */
422
+ interactive: boolean
423
+ }
424
+
425
+ // ============================================================================
426
+ // Zod Schemas
427
+ // ============================================================================
428
+
429
+ /**
430
+ * Zod schema for RenderTier validation.
431
+ *
432
+ * @remarks
433
+ * Validates that a value is one of the six defined render tiers.
434
+ * Rejects any other string or non-string values.
435
+ *
436
+ * **Validation Rules:**
437
+ * - Must be exactly one of: 'text', 'markdown', 'ascii', 'unicode', 'ansi', 'interactive'
438
+ * - Case-sensitive (e.g., 'TEXT' is rejected)
439
+ * - Rejects null, undefined, numbers, objects
440
+ *
441
+ * @example
442
+ * ```tsx
443
+ * // Valid
444
+ * RenderTierSchema.parse('ansi')
445
+ * RenderTierSchema.parse('text')
446
+ *
447
+ * // Invalid - throws ZodError
448
+ * RenderTierSchema.parse('html')
449
+ * RenderTierSchema.parse('TEXT')
450
+ * RenderTierSchema.parse(123)
451
+ * ```
452
+ */
453
+ export const RenderTierSchema = z.enum([
454
+ 'text',
455
+ 'markdown',
456
+ 'ascii',
457
+ 'unicode',
458
+ 'ansi',
459
+ 'interactive',
460
+ ])
461
+
462
+ /**
463
+ * Zod schema for ThemeTokens validation.
464
+ *
465
+ * @remarks
466
+ * Validates that all 10 color tokens are present and are strings.
467
+ * No validation of ANSI code format - any string is accepted
468
+ * (including empty strings for text tier).
469
+ *
470
+ * **Validation Rules:**
471
+ * - All 10 fields are required: primary, secondary, muted, foreground,
472
+ * background, border, success, warning, error, info
473
+ * - Each field must be a string (empty string is valid)
474
+ * - Rejects objects with missing fields or non-string values
475
+ * - No validation of ANSI code validity
476
+ *
477
+ * **Required Fields:**
478
+ * ```
479
+ * primary, secondary, muted, foreground, background,
480
+ * border, success, warning, error, info
481
+ * ```
482
+ *
483
+ * @example
484
+ * ```tsx
485
+ * // Valid theme
486
+ * const validTheme = {
487
+ * primary: '\x1b[34m',
488
+ * secondary: '\x1b[36m',
489
+ * muted: '\x1b[90m',
490
+ * foreground: '\x1b[37m',
491
+ * background: '\x1b[40m',
492
+ * border: '\x1b[90m',
493
+ * success: '\x1b[32m',
494
+ * warning: '\x1b[33m',
495
+ * error: '\x1b[31m',
496
+ * info: '\x1b[34m',
497
+ * }
498
+ * ThemeTokensSchema.parse(validTheme) // OK
499
+ *
500
+ * // Invalid - missing field
501
+ * ThemeTokensSchema.parse({ primary: '\x1b[34m' }) // Throws
502
+ *
503
+ * // Invalid - non-string value
504
+ * ThemeTokensSchema.parse({ ...validTheme, primary: 123 }) // Throws
505
+ * ```
506
+ */
507
+ export const ThemeTokensSchema = z.object({
508
+ primary: z.string(),
509
+ secondary: z.string(),
510
+ muted: z.string(),
511
+ foreground: z.string(),
512
+ background: z.string(),
513
+ border: z.string(),
514
+ success: z.string(),
515
+ warning: z.string(),
516
+ error: z.string(),
517
+ info: z.string(),
518
+ })
519
+
520
+ /**
521
+ * Zod schema for UINode validation.
522
+ *
523
+ * @remarks
524
+ * Validates the complete UINode structure including recursive children.
525
+ * Uses `z.lazy()` to support arbitrary nesting depth.
526
+ *
527
+ * **Validation Rules:**
528
+ * - `type`: Required, must be string
529
+ * - `props`: Required, must be object with string keys and any values
530
+ * - `children`: Optional, must be array of UINode objects
531
+ * - `data`: Optional, can be any value (unknown)
532
+ * - `key`: Optional, must be string if provided
533
+ * - Extra fields are not allowed (strict validation)
534
+ *
535
+ * **Recursion:**
536
+ * Uses `z.lazy()` to support infinitely nested children without
537
+ * causing infinite type computation. Each child must also be valid UINode.
538
+ *
539
+ * @example
540
+ * ```tsx
541
+ * // Valid - simple node
542
+ * UINodeSchema.parse({
543
+ * type: 'text',
544
+ * props: { content: 'Hello' }
545
+ * }) // OK
546
+ *
547
+ * // Valid - nested children
548
+ * UINodeSchema.parse({
549
+ * type: 'box',
550
+ * props: {},
551
+ * children: [
552
+ * { type: 'text', props: { content: 'Child' } }
553
+ * ]
554
+ * }) // OK
555
+ *
556
+ * // Valid - all optional fields
557
+ * UINodeSchema.parse({
558
+ * type: 'table',
559
+ * props: { columns: ['A', 'B'] },
560
+ * children: [],
561
+ * data: [{ a: 1, b: 2 }],
562
+ * key: 'table-1'
563
+ * }) // OK
564
+ *
565
+ * // Invalid - missing type
566
+ * UINodeSchema.parse({ props: {} }) // Throws ZodError
567
+ *
568
+ * // Invalid - non-string type
569
+ * UINodeSchema.parse({ type: 123, props: {} }) // Throws ZodError
570
+ *
571
+ * // Invalid - invalid child
572
+ * UINodeSchema.parse({
573
+ * type: 'box',
574
+ * props: {},
575
+ * children: [{ props: {} }] // Missing type
576
+ * }) // Throws ZodError
577
+ * ```
578
+ */
579
+ export const UINodeSchema: z.ZodType<UINode> = z.lazy(() =>
580
+ z.object({
581
+ type: z.string(),
582
+ props: z.record(z.string(), z.unknown()).optional(),
583
+ children: z.union([z.array(UINodeSchema), z.string()]).optional(),
584
+ text: z.string().optional(),
585
+ data: z.unknown().optional(),
586
+ key: z.string().optional(),
587
+ })
588
+ )
589
+
590
+ /**
591
+ * Zod schema for RenderContext validation.
592
+ *
593
+ * @remarks
594
+ * Validates the complete RenderContext structure with all required fields.
595
+ * Ensures numeric constraints are met and nested types are valid.
596
+ *
597
+ * **Validation Rules:**
598
+ * - `tier`: Required, must be valid RenderTier
599
+ * - `width`: Required, must be non-negative number (positive in practice)
600
+ * - `height`: Required, must be non-negative number (positive in practice)
601
+ * - `depth`: Required, must be non-negative number
602
+ * - `theme`: Required, must be valid ThemeTokens with all fields
603
+ * - `interactive`: Required, must be boolean
604
+ * - All fields are required (no optional fields)
605
+ * - Extra fields are not allowed
606
+ *
607
+ * **Numeric Constraints:**
608
+ * - width, height, depth must all satisfy `.nonnegative()`
609
+ * - No upper bounds enforced (size validation handled elsewhere)
610
+ *
611
+ * @example
612
+ * ```tsx
613
+ * // Valid context
614
+ * RenderContextSchema.parse({
615
+ * tier: 'ansi',
616
+ * width: 80,
617
+ * height: 24,
618
+ * depth: 0,
619
+ * theme: {
620
+ * primary: '\x1b[34m',
621
+ * secondary: '\x1b[36m',
622
+ * muted: '\x1b[90m',
623
+ * foreground: '\x1b[37m',
624
+ * background: '\x1b[40m',
625
+ * border: '\x1b[90m',
626
+ * success: '\x1b[32m',
627
+ * warning: '\x1b[33m',
628
+ * error: '\x1b[31m',
629
+ * info: '\x1b[34m',
630
+ * },
631
+ * interactive: false
632
+ * }) // OK
633
+ *
634
+ * // Invalid - negative width
635
+ * RenderContextSchema.parse({
636
+ * tier: 'text',
637
+ * width: -10, // ERROR
638
+ * height: 24,
639
+ * depth: 0,
640
+ * theme: { primary: '', secondary: '' }, // truncated
641
+ * interactive: false
642
+ * }) // Throws ZodError
643
+ *
644
+ * // Invalid - invalid tier
645
+ * RenderContextSchema.parse({
646
+ * tier: 'html', // ERROR - not a valid tier
647
+ * width: 80,
648
+ * height: 24,
649
+ * depth: 0,
650
+ * theme: { primary: '', secondary: '' }, // truncated
651
+ * interactive: false
652
+ * }) // Throws ZodError
653
+ *
654
+ * // Invalid - missing theme field
655
+ * RenderContextSchema.parse({
656
+ * tier: 'text',
657
+ * width: 80,
658
+ * height: 24,
659
+ * depth: 0,
660
+ * interactive: false
661
+ * // Missing: theme field
662
+ * }) // Throws ZodError
663
+ * ```
664
+ */
665
+ export const RenderContextSchema = z.object({
666
+ tier: RenderTierSchema,
667
+ width: z.number().nonnegative(),
668
+ height: z.number().nonnegative(),
669
+ depth: z.number().nonnegative(),
670
+ theme: ThemeTokensSchema,
671
+ interactive: z.boolean(),
672
+ })