@nan0web/ui 1.10.0 → 1.12.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 (101) hide show
  1. package/README.md +69 -3
  2. package/package.json +65 -29
  3. package/src/App/Command/DepsCommand.js +3 -4
  4. package/src/Frame/Props.js +12 -18
  5. package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
  6. package/src/Model/index.js +61 -6
  7. package/src/StdIn.js +2 -6
  8. package/src/cli.js +1 -0
  9. package/src/core/GeneratorRunner.js +67 -7
  10. package/src/core/InputAdapter.js +22 -5
  11. package/src/core/Intent.js +230 -18
  12. package/src/core/Message/Message.js +4 -7
  13. package/src/core/Message/OutputMessage.js +4 -9
  14. package/src/core/StreamEntry.js +20 -28
  15. package/src/core/index.js +4 -0
  16. package/src/domain/Content.js +198 -0
  17. package/src/domain/Document.js +25 -0
  18. package/src/domain/FooterModel.js +37 -19
  19. package/src/domain/HeaderModel.js +47 -21
  20. package/src/domain/HeroModel.js +24 -22
  21. package/src/domain/LayoutModel.js +43 -0
  22. package/src/domain/ModelAsApp.js +46 -0
  23. package/src/domain/SandboxModel.js +19 -16
  24. package/src/domain/app/GalleryCommand.js +53 -0
  25. package/src/domain/app/GalleryRenderIntent.js +77 -0
  26. package/src/domain/app/SnapshotAuditor.js +399 -0
  27. package/src/domain/app/SnapshotRunner.js +264 -0
  28. package/src/domain/app/UIApp.js +78 -0
  29. package/src/domain/components/BreadcrumbModel.js +10 -6
  30. package/src/domain/components/FeatureGridModel.js +62 -0
  31. package/src/domain/components/MarkdownModel.js +24 -0
  32. package/src/domain/components/ShellModel.js +243 -0
  33. package/src/domain/components/TableModel.js +10 -6
  34. package/src/domain/components/ToastModel.js +10 -6
  35. package/src/domain/components/index.js +3 -1
  36. package/src/domain/index.js +14 -4
  37. package/src/index.js +23 -2
  38. package/src/inspect.js +2 -0
  39. package/src/test/ScenarioAdapter.js +59 -0
  40. package/src/test/ScenarioTest.js +51 -0
  41. package/src/test/ScenarioTest.story.js +56 -0
  42. package/src/testing/CrashReporter.js +56 -0
  43. package/src/testing/GalleryGenerator.js +15 -71
  44. package/src/testing/LogicInspector.js +4 -4
  45. package/src/testing/SnapshotRunner.js +22 -0
  46. package/src/testing/SpecAdapter.js +114 -0
  47. package/src/testing/SpecRunner.js +121 -0
  48. package/src/testing/VisualAdapter.js +24 -19
  49. package/src/testing/index.js +5 -1
  50. package/src/testing/verifySnapshot.js +17 -0
  51. package/types/App/Command/DepsCommand.d.ts +0 -2
  52. package/types/Model/index.d.ts +56 -62
  53. package/types/StdIn.d.ts +3 -3
  54. package/types/cli.d.ts +1 -0
  55. package/types/core/GeneratorRunner.d.ts +14 -1
  56. package/types/core/InputAdapter.d.ts +50 -6
  57. package/types/core/Intent.d.ts +280 -32
  58. package/types/core/Message/Message.d.ts +2 -2
  59. package/types/core/Message/OutputMessage.d.ts +0 -2
  60. package/types/core/index.d.ts +4 -0
  61. package/types/domain/Content.d.ts +344 -0
  62. package/types/domain/Document.d.ts +40 -0
  63. package/types/domain/FooterModel.d.ts +22 -12
  64. package/types/domain/HeaderModel.d.ts +36 -13
  65. package/types/domain/HeroModel.d.ts +19 -17
  66. package/types/domain/LayoutModel.d.ts +34 -0
  67. package/types/domain/ModelAsApp.d.ts +23 -0
  68. package/types/domain/SandboxModel.d.ts +10 -0
  69. package/types/domain/app/GalleryCommand.d.ts +55 -0
  70. package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
  71. package/types/domain/app/SnapshotAuditor.d.ts +99 -0
  72. package/types/domain/app/SnapshotRunner.d.ts +45 -0
  73. package/types/domain/app/UIApp.d.ts +60 -0
  74. package/types/domain/components/BreadcrumbModel.d.ts +6 -8
  75. package/types/domain/components/FeatureGridModel.d.ts +50 -0
  76. package/types/domain/components/MarkdownModel.d.ts +19 -0
  77. package/types/domain/components/ShellModel.d.ts +56 -0
  78. package/types/domain/components/TableModel.d.ts +4 -0
  79. package/types/domain/components/ToastModel.d.ts +4 -0
  80. package/types/domain/components/index.d.ts +3 -0
  81. package/types/domain/index.d.ts +10 -4
  82. package/types/index.d.ts +21 -1
  83. package/types/inspect.d.ts +2 -0
  84. package/types/test/ScenarioAdapter.d.ts +43 -0
  85. package/types/test/ScenarioTest.d.ts +24 -0
  86. package/types/test/ScenarioTest.story.d.ts +1 -0
  87. package/types/testing/CrashReporter.d.ts +13 -0
  88. package/types/testing/SnapshotRunner.d.ts +7 -0
  89. package/types/testing/SpecAdapter.d.ts +58 -0
  90. package/types/testing/SpecRunner.d.ts +41 -0
  91. package/types/testing/VisualAdapter.d.ts +0 -6
  92. package/types/testing/index.d.ts +5 -1
  93. package/types/testing/verifySnapshot.d.ts +14 -0
  94. package/src/testing/SnapshotInspector.js +0 -84
  95. package/types/App/Command/Options.d.ts +0 -43
  96. package/types/App/Command/index.d.ts +0 -8
  97. package/types/App/User/Command/Options.d.ts +0 -34
  98. package/types/core/Message/InputMessage.d.ts +0 -71
  99. package/types/domain/components/HeroModel.d.ts +0 -24
  100. package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
  101. package/types/testing/SnapshotInspector.d.ts +0 -17
@@ -38,17 +38,33 @@ import { IntentErrorModel } from './IntentErrorModel.js'
38
38
  * @typedef {Object} ProgressIntent
39
39
  * @property {'progress'} type
40
40
  * @property {number} [value] - Progress value (0-1).
41
- * @property {string} [id] - Progress ID for tracking multiple parallel operations.
41
+ * @property {number} [total] - Absolute total (if value is absolute).
42
+ * @property {string} [id] - Progress ID for tracking by Adapter to calculate speed/eta.
42
43
  * @property {string} message - Status message from Model (i18n static field value).
44
+ * @property {ProgressOptions} [options] - Additional options for progress intent.
43
45
  */
44
46
 
45
47
  /**
46
- * Model emits a log message. No response expected.
48
+ * @typedef {'info' | 'warn' | 'error' | 'success'} ShowLevel
49
+ */
50
+
51
+ /** @typedef {ShowLevel} LogLevel */
52
+
53
+ /**
54
+ * Model emits a show message. No response expected.
47
55
  * Message MUST come from the Model (i18n static field value).
56
+ * @typedef {Object} ShowIntent
57
+ * @property {'show'} type
58
+ * @property {ShowLevel} level
59
+ * @property {string} message - Show message from Model (i18n static field value).
60
+ */
61
+
62
+ /**
63
+ * Model emits a log message intended for debugging/developer (Not UI).
48
64
  * @typedef {Object} LogIntent
49
65
  * @property {'log'} type
50
- * @property {'info' | 'warn' | 'error' | 'success'} level
51
- * @property {string} message - Log message from Model (i18n static field value).
66
+ * @property {LogLevel} level
67
+ * @property {string} message - Internal log message.
52
68
  */
53
69
 
54
70
  /**
@@ -67,9 +83,33 @@ import { IntentErrorModel } from './IntentErrorModel.js'
67
83
  * @property {object} props - Static props for the component.
68
84
  */
69
85
 
86
+ /**
87
+ * Contextual data and attachments for the AI subagent.
88
+ * @typedef {Object} AgentContext
89
+ * @property {string[]} [instructions] - List of instructions or guidelines (e.g. ['Use 1-char emojis only']).
90
+ * @property {Record<string, string>} [files] - Hash map of file paths to their string contents.
91
+ * @property {Record<string, any>} [data] - Any arbitrary JSON data (e.g. parsed errors, ASTs, metadata) useful for the task.
92
+ */
93
+
94
+ /**
95
+ * Model delegates a task to an AI subagent. The Adapter should launch the agent
96
+ * with the provided task and context, and return the result. If the agent is skipped,
97
+ * it returns { success: false } but allows user to generate a prompt.
98
+ * @typedef {Object} AgentIntent
99
+ * @property {'agent'} type
100
+ * @property {string} task - The instructional task for the AI agent.
101
+ * @property {AgentContext} context - Contextual data, files, and instructions for the task.
102
+ * @property {() => string} toPrompt - Helper to format task and context as an LLM prompt.
103
+ */
104
+
70
105
  /**
71
106
  * Union of all possible yielded intents.
72
- * @typedef {AskIntent | ProgressIntent | LogIntent | RenderIntent} Intent
107
+ * @typedef {(AskIntent | ProgressIntent | LogIntent | ShowIntent | RenderIntent | AgentIntent | ResultIntent) & {
108
+ * $value?: any;
109
+ * $success?: boolean;
110
+ * $files?: Record<string, string>;
111
+ * $message?: string;
112
+ * }} Intent
73
113
  */
74
114
 
75
115
  // ─── Response Types (Adapter → Model) ───
@@ -79,7 +119,26 @@ import { IntentErrorModel } from './IntentErrorModel.js'
79
119
  * The value MUST conform to the type described in the requested FieldSchema.
80
120
  * @typedef {Object} AskResponse
81
121
  * @property {*} value - The value matching schema.type (collected from user / LLM / test fixture).
82
- * @property {boolean} [cancelled] - Whether the user cancelled this interaction (e.g. pressed ESC).
122
+ * @property {boolean} cancelled - Whether the user cancelled this interaction (e.g. pressed ESC).
123
+ * @property {string} [action] - The action identifier (e.g., 'submit', 'exit', 'back').
124
+ * @property {any} [body] - Additional payload or form data.
125
+ * @property {any} [form] - The form model instance (if applicable).
126
+ * @property {number} [index] - The selected index for lists/tables.
127
+ */
128
+
129
+ /**
130
+ * Response to an AgentIntent.
131
+ * The underlying Adapter (Orchestrator) is responsible for communicating with the LLM,
132
+ * enforcing output formats (e.g. Unified Diff or Tool Calls like `updateFile`),
133
+ * and resolving common LLM hallucinations (like Grok truncating code with `// ...`).
134
+ *
135
+ * The Model (e.g. IconsAuditor) receives this clean, resolved response and does not
136
+ * need to parse Markdown or interpret diffs itself.
137
+ *
138
+ * @typedef {Object} AgentResponse
139
+ * @property {boolean} success - True if the agent successfully processed the task.
140
+ * @property {Record<string, string>} [files] - Hash map of fully resolved, updated file contents.
141
+ * @property {string} [message] - Optional summary or explanation returned by the AI.
83
142
  */
84
143
 
85
144
  // ─── Abort Support ───
@@ -101,10 +160,14 @@ import { IntentErrorModel } from './IntentErrorModel.js'
101
160
 
102
161
  /**
103
162
  * Union of all possible responses an Adapter can send back via iterator.next().
104
- * @typedef {AskResponse | AbortResponse | undefined} IntentResponse
163
+ * @typedef {AskResponse | AgentResponse | AbortResponse | undefined} IntentResponse
164
+ */
165
+
166
+ /**
167
+ * @typedef {'ask' | 'show' | 'progress' | 'render' | 'agent'} IntentType
105
168
  */
106
169
 
107
- export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'log', 'render'])
170
+ export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'show', 'log', 'render', 'agent'])
108
171
 
109
172
  /**
110
173
  * Detects if a value is a Model-as-Schema class (has static fields with `help`).
@@ -113,7 +176,7 @@ export const INTENT_TYPES = /** @type {const} */ (['ask', 'progress', 'log', 're
113
176
  */
114
177
  export function isModelSchema(schema) {
115
178
  if (typeof schema !== 'function') return false
116
- return Object.keys(schema).some((key) => {
179
+ return Object.getOwnPropertyNames(schema).some((key) => {
117
180
  const meta = schema[key]
118
181
  return meta && typeof meta === 'object' && 'help' in meta
119
182
  })
@@ -142,13 +205,16 @@ export function validateIntent(intent) {
142
205
  }
143
206
  // Accept both: plain schema {help: '...'} and Model-as-Schema class/instance
144
207
  const isModel = !!intent.model
145
- if (!isModel && (!intent.schema || typeof intent.schema !== 'object' || !('help' in intent.schema))) {
208
+ if (
209
+ !isModel &&
210
+ (!intent.schema || typeof intent.schema !== 'object' || !('help' in intent.schema))
211
+ ) {
146
212
  throw IntentErrorModel.error('ask_missing_schema_help')
147
213
  }
148
214
  }
149
- if (intent.type === 'progress' || intent.type === 'log') {
150
- const isComponentLog = intent.type === 'log' && intent.component
151
- if (!isComponentLog && typeof intent.message !== 'string') {
215
+ if (intent.type === 'progress' || intent.type === 'show' || intent.type === 'log') {
216
+ const isComponentShow = (intent.type === 'show' || intent.type === 'log') && intent.component
217
+ if (!isComponentShow && typeof intent.message !== 'string') {
152
218
  throw IntentErrorModel.error('intent_missing_message', { type: intent.type })
153
219
  }
154
220
  }
@@ -157,6 +223,11 @@ export function validateIntent(intent) {
157
223
  throw IntentErrorModel.error('render_missing_component')
158
224
  }
159
225
  }
226
+ if (intent.type === 'agent') {
227
+ if (typeof intent.task !== 'string' || !intent.task) {
228
+ throw IntentErrorModel.error('agent_missing_task')
229
+ }
230
+ }
160
231
  return true
161
232
  }
162
233
 
@@ -171,14 +242,155 @@ export function validateIntent(intent) {
171
242
  * @param {object | Function} schema - Field descriptor or Model-as-Schema class.
172
243
  * @returns {AskIntent}
173
244
  */
174
- export const ask = (field, schema) => {
245
+ export function ask(field, schema) {
175
246
  if (isModelSchema(schema)) {
176
247
  return { type: 'ask', field, schema, model: true }
177
248
  }
178
249
  return { type: 'ask', field, schema }
179
250
  }
180
251
 
181
- export const progress = (message) => ({ type: 'progress', message })
182
- export const log = (level, message, data = {}) => ({ type: 'log', level, message, ...data })
183
- export const render = (component, props = {}) => ({ type: 'render', component, props })
184
- export const result = (data) => ({ type: 'result', data })
252
+ /**
253
+ * @typedef {Object} ProgressOptions
254
+ * @property {number} [total] - Absolute total steps.
255
+ * @property {string} [id] - Progress tracking ID.
256
+ * @property {number} [width] - Width of the progress bar in terminal characters.
257
+ * @property {number} [fps] - Frames per second update rate limit.
258
+ * @property {string} [format] - Custom format string (e.g. '{time} {bar} {percent} {title}').
259
+ * @property {number} [columns] - Number of columns (terminal width).
260
+ * @property {boolean} [forceOneLine] - Prevent wrapping and truncate instead.
261
+ * @property {boolean|'success'|'error'} [stop] - Set to true to stop, or 'success'/'error' to stop with a status icon (for spinners).
262
+ */
263
+
264
+ /**
265
+ * Create a progress intent.
266
+ * @param {string} message - Status message from Model (i18n static field value).
267
+ * @param {number} [value=0] - Progress value (current step or percentage).
268
+ * @param {ProgressOptions|number|string} [optionsOrTotalOrId] - Options object, or absolute total steps (number), or progress tracking ID (string).
269
+ * @param {string} [id='default'] - Progress ID (if total is provided).
270
+ * @returns {ProgressIntent}
271
+ */
272
+ export function progress(message, value = 0, optionsOrTotalOrId, id) {
273
+ /** @type {ProgressOptions} */
274
+ let options = {}
275
+
276
+ if (typeof optionsOrTotalOrId === 'object' && optionsOrTotalOrId !== null) {
277
+ options = optionsOrTotalOrId
278
+ } else {
279
+ // legacy support
280
+ if (typeof optionsOrTotalOrId === 'number') {
281
+ options.total = optionsOrTotalOrId
282
+ if (typeof id === 'string') options.id = id
283
+ } else if (typeof optionsOrTotalOrId === 'string') {
284
+ options.id = optionsOrTotalOrId
285
+ }
286
+ }
287
+
288
+ return {
289
+ type: 'progress',
290
+ message,
291
+ value,
292
+ total: options.total,
293
+ id: options.id || 'default',
294
+ options
295
+ }
296
+ }
297
+
298
+ export function log(level, message, data = {}) {
299
+ return { type: 'log', level, message, ...data }
300
+ }
301
+
302
+ /**
303
+ * Create a render intent.
304
+ * @param {string} component - Component name (e.g. 'App.Layout.Header').
305
+ * @param {object} [props] - Static props for the component.
306
+ * @returns {RenderIntent}
307
+ */
308
+ export function render(component, props = {}) {
309
+ return { type: 'render', component, props }
310
+ }
311
+
312
+ /**
313
+ * Create a result intent.
314
+ * @param {*} data - The raw result data.
315
+ * @returns {ResultIntent}
316
+ */
317
+ export function result(data) {
318
+ return { type: 'result', data }
319
+ }
320
+
321
+ /**
322
+ * @typedef {Object} ShowData
323
+ * @property {any} [component]
324
+ * @property {import('@nan0web/types').Model} [model]
325
+ */
326
+ /**
327
+ * Create a show intent.
328
+ * @param {string | any} message Message to display.
329
+ * @param {ShowLevel|ShowData} [level='info'] Level of message or additional data then `level = 'info'`.
330
+ * @param {ShowData} [data={}] Additional data to display.
331
+ * @returns {ShowIntent}
332
+ */
333
+ export function show(message, level = 'info', data = {}) {
334
+ if ('string' === typeof level) {
335
+ return { type: 'show', level, message, ...data }
336
+ }
337
+ return { type: 'show', level: 'info', message, ...level, ...data }
338
+ }
339
+
340
+ /**
341
+ * Create an agent intent to delegate a task to an AI subagent.
342
+ * @param {string} task - The instructional task for the AI agent.
343
+ * @param {AgentContext} [context={}] - Contextual data (files, errors, docs).
344
+ * @returns {AgentIntent}
345
+ */
346
+ export function agent(task, context = {}) {
347
+ return {
348
+ type: 'agent',
349
+ task,
350
+ context,
351
+ toPrompt() {
352
+ let ctxStr = ''
353
+ if (this.context.data) {
354
+ try {
355
+ ctxStr = JSON.stringify(this.context.data, null, 2)
356
+ } catch (e) {
357
+ ctxStr = String(this.context.data)
358
+ }
359
+ }
360
+
361
+ // Format input files for the LLM using the boundary concept
362
+ const filesStr = Object.entries(this.context.files || {})
363
+ .map(([path, content]) => `---boundary:${path}---\n${content}\n---boundary---`)
364
+ .join('\n\n')
365
+
366
+ const outputRules = `
367
+ [Output Format Rules]
368
+ You must return your code modifications using the following strictly parsable boundary format. Do NOT use markdown code blocks (\`\`\`) or JSON for your code outputs.
369
+
370
+ To replace an ENTIRE file:
371
+ ---boundary:path/to/file.js---
372
+ <full new file content here>
373
+ ---boundary---
374
+
375
+ To replace a SPECIFIC SNIPPET (e.g. replacing 3 lines starting at line 33):
376
+ ---boundary:path/to/file.js:33:3---
377
+ <new snippet content here>
378
+ ---boundary---
379
+ `
380
+ const inst = Array.isArray(this.context.instructions)
381
+ ? this.context.instructions.join('\n')
382
+ : this.context.instructions
383
+
384
+ return [
385
+ `[Subagent Task]`,
386
+ this.task,
387
+ inst ? `\n[Instructions]\n${inst}` : '',
388
+ ctxStr ? `\n[Context]\n${ctxStr}` : '',
389
+ filesStr ? `\n[Files]\n${filesStr}` : '',
390
+ outputRules,
391
+ ]
392
+ .filter(Boolean)
393
+ .join('\n')
394
+ },
395
+ }
396
+ }
@@ -43,11 +43,6 @@ export default class UiMessage extends Message {
43
43
  NAVIGATION: 'navigation',
44
44
  }
45
45
 
46
- /** @type {string} */
47
- type = ''
48
- /** @type {string} */
49
- id = ''
50
-
51
46
  /**
52
47
  * Creates a UiMessage.
53
48
  *
@@ -56,9 +51,11 @@ export default class UiMessage extends Message {
56
51
  constructor(input = {}) {
57
52
  super(input)
58
53
 
59
- const { type = this.type, id = this.id } = input
54
+ const { type, id } = input
55
+ /** @type {string} */
60
56
  this.id = id || `ui-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`
61
- this.type = String(type)
57
+ /** @type {string} */
58
+ this.type = type ? String(type) : ''
62
59
 
63
60
  if (!('body' in input) && 'content' in input) {
64
61
  this.body = Array.isArray(input.content) ? input.content : [input.content]
@@ -14,15 +14,6 @@ export default class OutputMessage extends UiMessage {
14
14
  CRITICAL: 3,
15
15
  }
16
16
 
17
- /** @type {string[]} */
18
- body
19
- /** @type {Object} */
20
- meta = {}
21
- /** @type {Error|null} */
22
- error = null
23
- /** @type {number} */
24
- priority = OutputMessage.PRIORITY.NORMAL
25
-
26
17
  /**
27
18
  * Creates an OutputMessage.
28
19
  *
@@ -41,14 +32,18 @@ export default class OutputMessage extends UiMessage {
41
32
 
42
33
  const contentSource = 'body' in input ? body : 'content' in input ? content : []
43
34
 
35
+ /** @type {string[]} */
44
36
  this.body = Array.isArray(contentSource)
45
37
  ? contentSource
46
38
  : contentSource
47
39
  ? [String(contentSource)]
48
40
  : []
49
41
 
42
+ /** @type {Object} */
50
43
  this.meta = meta
44
+ /** @type {Error|null} */
51
45
  this.error = error instanceof Error ? error : error ? new Error(String(error)) : null
46
+ /** @type {number} */
52
47
  this.priority = Number(priority)
53
48
 
54
49
  if (!this.type && this.error) {
@@ -2,30 +2,6 @@
2
2
  * Represents an entry in a stream with value, completion status, cancellation status, and error message.
3
3
  */
4
4
  export default class StreamEntry {
5
- /**
6
- * The value of the stream entry.
7
- * @type {any}
8
- */
9
- value = undefined
10
-
11
- /**
12
- * Indicates if the stream entry is done (completed).
13
- * @type {boolean}
14
- */
15
- done = false
16
-
17
- /**
18
- * Indicates if the stream entry has been cancelled.
19
- * @type {boolean}
20
- */
21
- cancelled = false
22
-
23
- /**
24
- * Error message associated with the stream entry.
25
- * @type {string}
26
- */
27
- error = ''
28
-
29
5
  /**
30
6
  * Creates a new StreamEntry instance.
31
7
  * @param {Object} [input={}] - Input object to initialize the stream entry.
@@ -36,14 +12,30 @@ export default class StreamEntry {
36
12
  */
37
13
  constructor(input = {}) {
38
14
  const {
39
- value = this.value,
40
- done = this.done,
41
- cancelled = this.cancelled,
42
- error = this.error,
15
+ value = undefined,
16
+ done = false,
17
+ cancelled = false,
18
+ error = '',
43
19
  } = input
20
+ /**
21
+ * The value of the stream entry.
22
+ * @type {any}
23
+ */
44
24
  this.value = value
25
+ /**
26
+ * Indicates if the stream entry is done (completed).
27
+ * @type {boolean}
28
+ */
45
29
  this.done = Boolean(done)
30
+ /**
31
+ * Indicates if the stream entry has been cancelled.
32
+ * @type {boolean}
33
+ */
46
34
  this.cancelled = Boolean(cancelled)
35
+ /**
36
+ * Error message associated with the stream entry.
37
+ * @type {string}
38
+ */
47
39
  this.error = String(error)
48
40
  }
49
41
 
package/src/core/index.js CHANGED
@@ -42,7 +42,11 @@ export { default as Flow } from './Flow.js'
42
42
 
43
43
  // OLMUI Generator Engine — Intent-based Model→Adapter contract
44
44
  export { validateIntent, ask, progress, log, render, result, INTENT_TYPES, isModelSchema } from './Intent.js'
45
+ /** @typedef {import('./Intent.js').Intent} Intent */
46
+ /** @typedef {import('./Intent.js').AskResponse} AskResponse */
47
+ /** @typedef {import('./InputAdapter.js').AskOptions} AskOptions */
45
48
  export { IntentErrorModel } from './IntentErrorModel.js'
46
49
  export { runGenerator } from './GeneratorRunner.js'
47
50
 
48
51
  export { MaskHandler } from './MaskHandler.js'
52
+ export { LayoutModel } from '../domain/LayoutModel.js'
@@ -0,0 +1,198 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * @typedef {Object} HTML5Elements
5
+ * @property {string|ContentData[]} [a]
6
+ * @property {string|ContentData[]} [abbr]
7
+ * @property {string|ContentData[]} [address]
8
+ * @property {string|ContentData[]} [area]
9
+ * @property {string|ContentData[]} [article]
10
+ * @property {string|ContentData[]} [aside]
11
+ * @property {string|ContentData[]} [audio]
12
+ * @property {string|ContentData[]} [b]
13
+ * @property {string|ContentData[]} [base]
14
+ * @property {string|ContentData[]} [bdi]
15
+ * @property {string|ContentData[]} [bdo]
16
+ * @property {string|ContentData[]} [blockquote]
17
+ * @property {string|ContentData[]} [body]
18
+ * @property {boolean|object} [br]
19
+ * @property {string|ContentData[]} [canvas]
20
+ * @property {string|ContentData[]} [caption]
21
+ * @property {string|ContentData[]} [cite]
22
+ * @property {string|ContentData[]} [code]
23
+ * @property {string|ContentData[]} [col]
24
+ * @property {string|ContentData[]} [colgroup]
25
+ * @property {string|ContentData[]} [data]
26
+ * @property {string|ContentData[]} [datalist]
27
+ * @property {string|ContentData[]} [dd]
28
+ * @property {string|ContentData[]} [del]
29
+ * @property {string|ContentData[]} [details]
30
+ * @property {string|ContentData[]} [dfn]
31
+ * @property {string|ContentData[]} [dialog]
32
+ * @property {string|ContentData[]} [div]
33
+ * @property {string|ContentData[]} [dl]
34
+ * @property {string|ContentData[]} [dt]
35
+ * @property {string|ContentData[]} [em]
36
+ * @property {string|ContentData[]} [embed]
37
+ * @property {string|ContentData[]} [fieldset]
38
+ * @property {string|ContentData[]} [figcaption]
39
+ * @property {string|ContentData[]} [figure]
40
+ * @property {any} [footer]
41
+ * @property {string|ContentData[]} [form]
42
+ * @property {string|ContentData[]} [h1]
43
+ * @property {string|ContentData[]} [h2]
44
+ * @property {string|ContentData[]} [h3]
45
+ * @property {string|ContentData[]} [h4]
46
+ * @property {string|ContentData[]} [h5]
47
+ * @property {string|ContentData[]} [h6]
48
+ * @property {string|ContentData[]} [head]
49
+ * @property {any} [header]
50
+ * @property {string|ContentData[]} [hgroup]
51
+ * @property {boolean|object} [hr]
52
+ * @property {string|ContentData[]} [html]
53
+ * @property {string|ContentData[]} [i]
54
+ * @property {string|ContentData[]} [iframe]
55
+ * @property {string|ContentData[]} [img]
56
+ * @property {string|ContentData[]} [ins]
57
+ * @property {string|ContentData[]} [kbd]
58
+ * @property {string|ContentData[]} [label]
59
+ * @property {string|ContentData[]} [legend]
60
+ * @property {string|ContentData[]} [li]
61
+ * @property {string|ContentData[]} [link]
62
+ * @property {string|ContentData[]} [main]
63
+ * @property {string|ContentData[]} [map]
64
+ * @property {string|ContentData[]} [mark]
65
+ * @property {string|ContentData[]} [meta]
66
+ * @property {string|ContentData[]} [meter]
67
+ * @property {boolean|any} [input]
68
+ * @property {boolean|any} [button]
69
+ * @property {boolean|any} [select]
70
+ * @property {string|ContentData[]} [nav]
71
+ * @property {string|ContentData[]} [noscript]
72
+ * @property {string|ContentData[]} [object]
73
+ * @property {string|ContentData[]} [ol]
74
+ * @property {string|ContentData[]} [optgroup]
75
+ * @property {string|ContentData[]} [option]
76
+ * @property {string|ContentData[]} [output]
77
+ * @property {string|ContentData[]} [p]
78
+ * @property {string|ContentData[]} [picture]
79
+ * @property {string|ContentData[]} [pre]
80
+ * @property {string|ContentData[]} [progress]
81
+ * @property {string|ContentData[]} [q]
82
+ * @property {string|ContentData[]} [rp]
83
+ * @property {string|ContentData[]} [rt]
84
+ * @property {string|ContentData[]} [ruby]
85
+ * @property {string|ContentData[]} [s]
86
+ * @property {string|ContentData[]} [samp]
87
+ * @property {string|ContentData[]} [script]
88
+ * @property {string|ContentData[]} [section]
89
+ * @property {string|ContentData[]} [slot]
90
+ * @property {string|ContentData[]} [small]
91
+ * @property {string|ContentData[]} [source]
92
+ * @property {string|ContentData[]} [span]
93
+ * @property {string|ContentData[]} [strong]
94
+ * @property {string|ContentData[]} [style]
95
+ * @property {string|ContentData[]} [sub]
96
+ * @property {string|ContentData[]} [summary]
97
+ * @property {string|ContentData[]} [sup]
98
+ * @property {string|ContentData[]} [table]
99
+ * @property {string|ContentData[]} [tbody]
100
+ * @property {string|ContentData[]} [td]
101
+ * @property {string|ContentData[]} [template]
102
+ * @property {string|ContentData[]} [textarea]
103
+ * @property {string|ContentData[]} [tfoot]
104
+ * @property {string|ContentData[]} [th]
105
+ * @property {string|ContentData[]} [thead]
106
+ * @property {string|ContentData[]} [time]
107
+ * @property {string|ContentData[]} [title]
108
+ * @property {string|ContentData[]} [tr]
109
+ * @property {string|ContentData[]} [track]
110
+ * @property {string|ContentData[]} [u]
111
+ * @property {string|ContentData[]} [ul]
112
+ * @property {string|ContentData[]} [var]
113
+ * @property {string|ContentData[]} [video]
114
+ * @property {string|ContentData[]} [wbr]
115
+ * @property {string|ContentData[]} [svg]
116
+ * @property {string|ContentData[]} [path]
117
+ * @property {string|ContentData[]} [circle]
118
+ * @property {string|ContentData[]} [rect]
119
+ * @property {string|ContentData[]} [line]
120
+ * @property {string|ContentData[]} [polyline]
121
+ * @property {string|ContentData[]} [polygon]
122
+ * @property {string|ContentData[]} [g]
123
+ * @property {string|ContentData[]} [defs]
124
+ * @property {string|ContentData[]} [symbol]
125
+ * @property {string|ContentData[]} [use]
126
+ * @property {string|ContentData[]} [text]
127
+ */
128
+
129
+ /**
130
+ * @typedef {Object} CoreUIElements
131
+ * @property {import('./components/AccordionModel.js').AccordionModel} [accordion]
132
+ * @property {import('./components/AutocompleteModel.js').AutocompleteModel} [autocomplete]
133
+ * @property {import('./components/BannerModel.js').BannerModel} [banner]
134
+ * @property {import('./components/BreadcrumbModel.js').BreadcrumbModel} [breadcrumb]
135
+ * @property {import('./components/ButtonModel.js').ButtonModel} [button]
136
+ * @property {import('./components/CommentModel.js').CommentModel} [comment]
137
+ * @property {import('./components/ConfirmModel.js').ConfirmModel} [confirm]
138
+ * @property {import('./components/EmptyStateModel.js').EmptyStateModel} [emptyState]
139
+ * @property {import('./components/FAQModel.js').FAQModel} [faq]
140
+ * @property {import('./components/FeatureGridModel.js').FeatureGridModel} [featureGrid]
141
+ * @property {import('./components/GalleryModel.js').GalleryModel} [gallery]
142
+ * @property {import('./HeaderModel.js').HeaderModel} [header]
143
+ * @property {import('./FooterModel.js').FooterModel} [footer]
144
+ * @property {import('./components/InputModel.js').InputModel} [input]
145
+ * @property {import('./components/MarkdownModel.js').MarkdownModel} [markdown]
146
+ * @property {import('./components/PriceModel.js').PriceModel} [price]
147
+ * @property {import('./components/PricingModel.js').PricingModel} [pricing]
148
+ * @property {import('./components/PricingSectionModel.js').PricingSectionModel} [pricingSection]
149
+ * @property {import('./components/ProfileDropdownModel.js').ProfileDropdownModel} [profileDropdown]
150
+ * @property {import('./components/SelectModel.js').SelectModel} [select]
151
+ * @property {import('./components/ShellModel.js').ShellModel} [shell]
152
+ * @property {import('./components/SpinnerModel.js').SpinnerModel} [spinner]
153
+ * @property {import('./components/StatsItemModel.js').StatsItemModel} [statsItem]
154
+ * @property {import('./components/StatsModel.js').StatsModel} [stats]
155
+ * @property {import('./components/TableModel.js').TableModel} [tableUI]
156
+ * @property {import('./components/TabsModel.js').TabsModel} [tabs]
157
+ * @property {import('./components/TestimonialModel.js').TestimonialModel} [testimonial]
158
+ * @property {import('./components/TimelineItemModel.js').TimelineItemModel} [timelineItem]
159
+ * @property {import('./components/TimelineModel.js').TimelineModel} [timeline]
160
+ * @property {import('./components/ToastModel.js').ToastModel} [toast]
161
+ * @property {import('./components/TreeModel.js').TreeModel} [tree]
162
+ * @property {ContentData[]} [sortable] - Інтерактивний Drag-n-Drop контейнер
163
+ */
164
+
165
+ /**
166
+ * @typedef {Partial<Content & HTML5Elements & CoreUIElements> & Record<string, any>} ContentData
167
+ */
168
+
169
+ export class Content extends Model {
170
+ static content = { type: 'string', help: 'Content' }
171
+ static children = { type: 'array', model: Content, help: 'Children' }
172
+
173
+ /**
174
+ * @param {ContentData | string} [data={}]
175
+ * @param {import('@nan0web/types').ModelOptions} [options={}]
176
+ */
177
+ constructor(data = {}, options = {}) {
178
+ if ('string' === typeof data) {
179
+ data = { content: data }
180
+ }
181
+ super(data, options)
182
+
183
+ // ── Base Fields ──
184
+ /** @type {string|undefined} Content */ this.content
185
+ /** @type {Array<Content>|undefined} Children */ this.children
186
+
187
+ // ── Hydration ──
188
+ if (Array.isArray(this.children)) {
189
+ this.children = this.children.map((child) => new Content(child, options))
190
+ }
191
+
192
+ const { content, children, ...rest } = /** @type {any} */ (data)
193
+
194
+ for (const [key, value] of Object.entries(rest)) {
195
+ /** @type {any} */ (this)[key] = value
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,25 @@
1
+ import { Model } from '@nan0web/types'
2
+ import { Content } from './Content.js'
3
+ import Navigation from './Navigation.js'
4
+ import { Language } from '@nan0web/i18n'
5
+
6
+ export class Document extends Model {
7
+ static title = { type: 'string', help: 'Title' }
8
+ static content = { type: 'array', model: Content, help: 'Content' }
9
+ static $content = { type: 'array', model: Content, help: 'Layout configuration' }
10
+ static nav = { type: 'any', model: Navigation, help: 'Navigation config or reference' }
11
+ static langs = { type: 'array', model: Language, help: 'Supported languages array' }
12
+ /**
13
+ *
14
+ * @param {Partial<Document>} [data]
15
+ * @param {import('@nan0web/types').ModelOptions} [options]
16
+ */
17
+ constructor(data = {}, options = {}) {
18
+ super(data, options)
19
+ /** @type {string} Title */ this.title
20
+ /** @type {Array<Content>} Content */ this.content
21
+ /** @type {Array<Content>} Layout configuration */ this.$content
22
+ /** @type {Navigation|string|Array<Navigation>} Navigation config */ this.nav
23
+ /** @type {Array<Language>} Supported languages */ this.langs
24
+ }
25
+ }