@nan0web/ui 1.10.0 → 1.11.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 +61 -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 +3 -1
  11. package/src/core/Intent.js +200 -17
  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 +1 -0
  16. package/src/domain/Content.js +196 -0
  17. package/src/domain/Document.js +17 -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 +401 -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 +21 -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 +3 -3
  45. package/src/testing/SnapshotRunner.js +22 -0
  46. package/src/testing/SpecAdapter.js +115 -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 +2 -1
  57. package/types/core/Intent.d.ts +209 -31
  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 +1 -0
  61. package/types/domain/Content.d.ts +340 -0
  62. package/types/domain/Document.d.ts +21 -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 +19 -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 +57 -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
package/README.md CHANGED
@@ -158,8 +158,13 @@ These models follow the **Model-as-Schema** pattern.
158
158
  - `FooterModel` — copyright, version, social links
159
159
  - `HeroModel` — prominent call-to-action
160
160
 
161
+ #### HTML5 Base Elements
162
+ Fully typed zero-cost support for standard tags: `div`, `span`, `p`, `h1`-`h6`, `a`, `ul`, `table`, etc., plus SVG basics (`svg`, `path`, `rect`). Data must be standard `camelCase`.
163
+
161
164
  #### Component Models
162
- - `PricingModel` — plans with features and prices
165
+ - `PricingModel` / `PricingSection` — plans with features and prices
166
+ - `FeatureGrid` — grid of feature highlights
167
+ - `ProfileDropdown` — user avatar and settings menu
163
168
  - `CommentModel` & `TestimonialModel` — social proof
164
169
  - `StatsModel` — data visualizations
165
170
  - `TimelineModel` — event history
@@ -180,14 +185,75 @@ const hero = new HeroModel({
180
185
  console.info(header.title) // ← NaN•Web
181
186
  console.info(hero.actions[0].title) // ← Get Started
182
187
  ```
183
- ### Testing UI
188
+ ### Intent Generators (v1.11.0)
189
+
190
+ From v1.11.0, Intent creators are standard named functions generating
191
+ strict interactions (ask, progress, show, render, result).
192
+
193
+ - `ask(field, schema)` — requests input from the environment.
194
+ - `progress(message)` — updates a visual loader.
195
+ - `show(message, level, data)` — displays a notification (replaces deprecated `log`).
196
+ - `render(component, props)` — renders a specific component view.
197
+ - `result(data)` — ends the model execution cleanly.
198
+
199
+ How to use Intent generators? (v1.11.0)
200
+ ```js
201
+ import { ask, show, result } from '@nan0web/ui'
202
+ const nameIntent = ask('name', { help: 'Your name' })
203
+ const msgIntent = show('Processing...', 'info')
204
+ const endIntent = result({ ok: true })
205
+ ```
206
+ ### Testing UI (v1.11.0 Deterministic Testing)
184
207
 
185
208
  Core unit-tested to ensure stability in different environments.
209
+ With **v1.11.0**, the architecture formally introduces `ScenarioTest` for zero-I/O deterministic testing.
186
210
 
211
+ By lifting the asynchronous logic and providing an explicit scenario array, models are evaluated instantly without waiting on user prompt delays.
212
+
213
+ How to test Model pipelines deterministically?
214
+ ```js
215
+ import { ModelAsApp, ask, result, show } from '@nan0web/ui'
216
+ import { ScenarioTest } from '@nan0web/ui/test/ScenarioTest.js'
217
+ const { ModelAsApp, ask, result, show } = await import('./index.js')
218
+ const { ScenarioTest } = await import('./test/ScenarioTest.js')
219
+ class ShoppingCartApp extends ModelAsApp {
220
+ *run() {
221
+ const product = yield ask('product', { help: 'Select product' })
222
+ if (product?.value === 'laptop') {
223
+ yield show('Good choice!', 'ok')
224
+ }
225
+ const confirm = yield ask('confirm', { help: 'Confirm purchase?' })
226
+ return result({ product: product?.value, confirm: confirm?.value })
227
+ }
228
+ }
229
+ const res = await ScenarioTest.run(ShoppingCartApp, [
230
+ { field: 'product', value: 'laptop' },
231
+ { field: 'confirm', value: true }
232
+ ])
233
+ ```
234
+ You can also verify exceptions and validation rules by observing the final error in ScenarioTest.
235
+
236
+ How to test validation errors with ScenarioTest?
237
+ ```js
238
+ import { ModelAsApp, ask, result } from '@nan0web/ui'
239
+ import { ScenarioTest } from '@nan0web/ui/test/ScenarioTest.js'
240
+ const { ModelAsApp, ask, result } = await import('./index.js')
241
+ const { ScenarioTest } = await import('./test/ScenarioTest.js')
242
+ class ValidatedApp extends ModelAsApp {
243
+ *run() {
244
+ const code = yield ask('code', { help: 'Enter code', required: true })
245
+ if (!code?.value) throw new Error('Code is mandatory')
246
+ return result({ code: code?.value })
247
+ }
248
+ }
249
+ const res = await ScenarioTest.run(ValidatedApp, [
250
+ { field: 'code', value: '' } // Simulating empty response
251
+ ])
252
+ ```
187
253
  All components, adapters, and models are designed to be testable
188
254
  with minimal setup.
189
255
 
190
- How to test UI components with assertions?
256
+ How to test visual UI components with assertions?
191
257
  ```js
192
258
  import { Welcome } from '@nan0web/ui'
193
259
  const output = Welcome({ user: { name: 'Test' } })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/ui",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "NaN•Web UI. One application logic (algorithm) and many UI.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -16,27 +16,51 @@
16
16
  "import": "./src/index.js",
17
17
  "types": "./types/index.d.ts"
18
18
  },
19
- "./components": "./src/Component/index.js",
20
- "./core": "./src/core/index.js",
21
- "./domain": "./src/domain/index.js",
22
- "./testing": "./src/testing/index.js",
19
+ "./components": {
20
+ "import": "./src/Component/index.js",
21
+ "types": "./types/Component/index.d.ts"
22
+ },
23
+ "./core": {
24
+ "import": "./src/core/index.js",
25
+ "types": "./types/core/index.d.ts"
26
+ },
27
+ "./domain": {
28
+ "import": "./src/domain/index.js",
29
+ "types": "./types/domain/index.d.ts"
30
+ },
31
+ "./inspect": {
32
+ "import": "./src/inspect.js",
33
+ "types": "./types/inspect.d.ts"
34
+ },
35
+ "./testing": {
36
+ "import": "./src/testing/index.js",
37
+ "types": "./types/testing/index.d.ts"
38
+ },
23
39
  "./cli-app": "./apps/cli/src/index.js",
24
40
  "./mobile-app": "./apps/mobile/src/index.js",
25
- "./web-app": "./apps/web/src/App.jsx"
41
+ "./web-app": "./apps/web/src/App.jsx",
42
+ "./ui/cli": "./src/cli.js"
26
43
  },
27
44
  "keywords": [
28
45
  "ui",
29
46
  "nanoweb"
30
47
  ],
48
+ "nan0web": {
49
+ "workflowDir": "docs/{locale}/workflows",
50
+ "workflows": [
51
+ "olm-ui-architecture.md",
52
+ "olm-ui-architecture-core.md",
53
+ "olm-ui-architecture-adapters.md",
54
+ "forge-component.md",
55
+ "interface-welding.md"
56
+ ],
57
+ "inspectors": [
58
+ "./src/domain/app/SnapshotAuditor.js"
59
+ ]
60
+ },
31
61
  "author": "YaRaSLove (ЯRаСлав) <support@yaro.page>",
32
62
  "license": "ISC",
33
63
  "devDependencies": {
34
- "@nan0web/event": "^1.0.1",
35
- "@nan0web/i18n": "^1.1.0",
36
- "@nan0web/release": "^1.0.2",
37
- "@nan0web/test": "^1.1.3",
38
- "@nan0web/ui-cli": "^2.9.0",
39
- "@nan0web/ui-lit": "^1.1.0",
40
64
  "@playwright/test": "^1.58.2",
41
65
  "@rollup/plugin-yaml": "^4.1.2",
42
66
  "@vitest/coverage-v8": "^3.2.4",
@@ -45,43 +69,51 @@
45
69
  "knip": "^5.86.0",
46
70
  "lit": "^3.3.2",
47
71
  "vite": "^6.4.1",
48
- "vitest": "^3.2.4"
72
+ "vitest": "^3.2.4",
73
+ "@nan0web/db-fs": "1.2.1",
74
+ "@nan0web/event": "1.0.1",
75
+ "@nan0web/i18n": "1.5.0",
76
+ "@nan0web/inspect": "1.0.0",
77
+ "@nan0web/ui-cli": "2.12.3",
78
+ "@nan0web/test": "1.1.4",
79
+ "@nan0web/icons": "1.1.0",
80
+ "@nan0web/release": "1.0.3",
81
+ "@nan0web/nan0web.app": "0.1.0",
82
+ "@nan0web/ui-lit": "1.1.0"
49
83
  },
50
84
  "dependencies": {
51
- "@nan0web/co": "^2.0.0",
52
- "@nan0web/log": "^1.1.1",
53
- "@nan0web/types": "^1.4.0",
54
85
  "string-width": "^7.2.0",
55
- "@nan0web/core": "1.1.2"
86
+ "@nan0web/co": "2.0.1",
87
+ "@nan0web/log": "1.1.1",
88
+ "@nan0web/types": "1.7.2",
89
+ "@nan0web/core": "1.1.3"
56
90
  },
57
91
  "scripts": {
92
+ "prebuild": "rm -rf types/",
58
93
  "build": "tsc",
59
94
  "play": "node play/main.js",
60
- "test:unit": "node --test --test-timeout=3333 \"src/**/*.test.js\"",
61
- "test": "npm run test:unit && npm run test:ssg && npm run build",
95
+ "test:unit": "node --test --test-timeout=3333 \"src/*.test.js\" \"src/core/**/*.test.js\" \"src/domain/**/*.test.js\" \"src/test/*.test.js\" \"src/testing/**/*.test.js\"",
96
+ "test": "npm run test:unit && npm run test:audit && npm run build",
62
97
  "test:nan0test": "node --test --test-timeout=3333 \"src/**/*.test.js\" | nan0test parse --fail",
63
98
  "test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.js\" --test-coverage-exclude=\"src/**/*.test.js\" --test \"src/**/*.test.js\"",
64
99
  "test:coverage:collect": "nan0test coverage",
65
100
  "test:docs": "node --test src/README.md.js",
66
- "release:spec": "node --test \"releases/**/*.spec.js\"",
67
- "test:release": "node --test \"releases/**/*.test.js\"",
101
+ "release:spec": "node --test \"src/test/releases/**/*.test.js\"",
102
+ "test:release": "node --test \"src/test/releases/**/*.test.js\"",
68
103
  "test:status": "nan0test status --hide-name",
69
104
  "test:play": "node --test --test-timeout=3333 \"play/**/*.test.js\"",
70
105
  "test:snapshots": "node src/testing/GalleryGenerator.js",
71
106
  "test:gallery": "npm run test:snapshots && npx nan0gallery --dir=snapshots/core",
72
- "test:audit": "node --test src/test/gallery_audit.test.js",
73
- "test:ssg": "node --test --test-timeout=5000 \"src/test/ssg.test.js\"",
74
- "test:e2e": "playwright test --ignore-snapshots test/e2e/components.spec.js test/e2e/debug-label.spec.js",
75
- "test:e2e:slow": "E2E_SLOW=1 playwright test test/e2e/visual.spec.js",
76
- "test:all": "npm run test && npm run test:docs && npm run test:play && npm run test:gallery && npm run test:audit && npm run test:e2e && npm run knip",
107
+ "test:audit": "nan0cli audit --dir snapshots/core --data .",
108
+ "test:stories": "node --test \"src/**/*.story.js\"",
109
+ "test:all": "npm run test && npm run test:docs && npm run test:play && npm run test:stories && npm run knip",
77
110
  "knip": "knip --production",
78
111
  "precommit": "npm test",
79
112
  "prepush": "npm test",
80
113
  "release": "nan0release publish",
81
114
  "clean": "rm -rf .cache/ && rm -rf dist/",
82
115
  "clean:modules": "rm -rf node_modules",
83
- "docs:build-data": "node docs/site/scripts/generate-data.js",
84
- "docs:dev": "node docs/site/scripts/generate-data.js && node docs/site/scripts/generate-pages.js && vite docs/site",
85
- "docs:build": "node docs/site/scripts/generate-data.js && node docs/site/scripts/generate-pages.js && vite build docs/site"
116
+ "docs:dev": "nan0web run --data docs --index README --port 4270",
117
+ "docs:build": "nan0web build --data docs --index README --locale uk"
86
118
  }
87
119
  }
@@ -1,23 +1,22 @@
1
1
  import UiMessage from '../../core/Message/Message.js'
2
2
 
3
3
  class DepsCommandBody {
4
- fix = false
5
4
  static fix = {
6
5
  help: 'Fix dependencies',
7
6
  defaultValue: false,
8
7
  }
9
8
  constructor(input = {}) {
10
- const { fix = this.fix } = input
9
+ const { fix = DepsCommandBody.fix.defaultValue } = input
10
+ this.fix = !!fix
11
11
  }
12
12
  }
13
13
 
14
14
  export class DepsCommand extends UiMessage {
15
15
  static Body = DepsCommandBody
16
- /** @type {DepsCommandBody} */
17
- body
18
16
  constructor(input = {}) {
19
17
  const { body = new DepsCommandBody() } = UiMessage.parseBody(input, DepsCommandBody)
20
18
  super(input)
19
+ /** @type {DepsCommandBody} */
21
20
  this.body = body
22
21
  }
23
22
  }
@@ -53,24 +53,6 @@ class FrameProps extends ObjectWithAlias {
53
53
  s: 'strikethrough',
54
54
  }
55
55
 
56
- /** @type {string} Text color */
57
- color = ''
58
-
59
- /** @type {string} Background color */
60
- bgColor = ''
61
-
62
- /** @type {boolean} Bold text flag */
63
- bold = false
64
-
65
- /** @type {boolean} Italic text flag */
66
- italic = false
67
-
68
- /** @type {boolean} Underline text flag */
69
- underline = false
70
-
71
- /** @type {boolean} Strikethrough text flag */
72
- strikethrough = false
73
-
74
56
  /**
75
57
  * @param {object} props - Frame properties
76
58
  * @param {string} [props.color=""] - Text color
@@ -90,11 +72,23 @@ class FrameProps extends ObjectWithAlias {
90
72
  underline = false,
91
73
  strikethrough = false,
92
74
  } = props
75
+
76
+ /** @type {string} Text color */
93
77
  this.color = color
78
+
79
+ /** @type {string} Background color */
94
80
  this.bgColor = bgColor
81
+
82
+ /** @type {boolean} Bold text flag */
95
83
  this.bold = bold
84
+
85
+ /** @type {boolean} Italic text flag */
96
86
  this.italic = italic
87
+
88
+ /** @type {boolean} Underline text flag */
97
89
  this.underline = underline
90
+
91
+ /** @type {boolean} Strikethrough text flag */
98
92
  this.strikethrough = strikethrough
99
93
  }
100
94
  }
@@ -22,13 +22,15 @@ export default class InterfaceTemplate {
22
22
  */
23
23
  static requiredMethods = ['render', 'ask']
24
24
 
25
- /**
26
- * The name of this interface (e.g. 'cli', 'web', 'mobile').
27
- * Override in subclass.
28
- *
29
- * @type {string}
30
- */
31
- name = 'base'
25
+ constructor() {
26
+ /**
27
+ * The name of this interface (e.g. 'cli', 'web', 'mobile').
28
+ * Override in subclass.
29
+ *
30
+ * @type {string}
31
+ */
32
+ this.name = 'base'
33
+ }
32
34
 
33
35
  /**
34
36
  * Render data to the user through the interface.
@@ -1,13 +1,65 @@
1
1
  import User from './User/User.js'
2
- import * as DomainModels from '../domain/index.js'
2
+ import { HeaderModel } from '../domain/HeaderModel.js'
3
+ import { FooterModel } from '../domain/FooterModel.js'
4
+ import { HeroModel } from '../domain/HeroModel.js'
5
+ import {
6
+ ButtonModel,
7
+ ConfirmModel,
8
+ InputModel,
9
+ SpinnerModel,
10
+ TableModel,
11
+ ToastModel,
12
+ SelectModel,
13
+ AutocompleteModel,
14
+ TreeModel,
15
+ TabsModel,
16
+ AccordionModel,
17
+ GalleryModel,
18
+ PriceModel,
19
+ PricingModel,
20
+ CommentModel,
21
+ TestimonialModel,
22
+ StatsItemModel,
23
+ StatsModel,
24
+ TimelineItemModel,
25
+ TimelineModel,
26
+ EmptyStateModel,
27
+ BannerModel,
28
+ ProfileDropdownModel,
29
+ } from '../domain/components/index.js'
3
30
 
4
- const Model = {
31
+ export {
5
32
  User,
6
- ...DomainModels,
33
+ HeaderModel,
34
+ FooterModel,
35
+ HeroModel,
36
+ ButtonModel,
37
+ ConfirmModel,
38
+ InputModel,
39
+ SpinnerModel,
40
+ TableModel,
41
+ ToastModel,
42
+ SelectModel,
43
+ AutocompleteModel,
44
+ TreeModel,
45
+ TabsModel,
46
+ AccordionModel,
47
+ GalleryModel,
48
+ PriceModel,
49
+ PricingModel,
50
+ CommentModel,
51
+ TestimonialModel,
52
+ StatsItemModel,
53
+ StatsModel,
54
+ TimelineItemModel,
55
+ TimelineModel,
56
+ EmptyStateModel,
57
+ BannerModel,
58
+ ProfileDropdownModel,
7
59
  }
8
60
 
9
- export { User }
10
- export const {
61
+ const Model = {
62
+ User,
11
63
  HeaderModel,
12
64
  FooterModel,
13
65
  HeroModel,
@@ -31,6 +83,9 @@ export const {
31
83
  StatsModel,
32
84
  TimelineItemModel,
33
85
  TimelineModel,
34
- } = DomainModels
86
+ EmptyStateModel,
87
+ BannerModel,
88
+ ProfileDropdownModel,
89
+ }
35
90
 
36
91
  export default Model
package/src/StdIn.js CHANGED
@@ -14,12 +14,6 @@ export default class StdIn extends EventProcessor {
14
14
  /** @type {string[]} Messages to ignore */
15
15
  static IGNORE_MESSAGES = ['', 'undefined']
16
16
 
17
- /** @type {UiMessage[]} Input message buffer */
18
- stream = []
19
-
20
- /** @type {Processor} Input processor */
21
- processor
22
-
23
17
  /**
24
18
  * Creates a new StdIn instance.
25
19
  * @param {object} props - StdIn properties
@@ -29,7 +23,9 @@ export default class StdIn extends EventProcessor {
29
23
  constructor(props = {}) {
30
24
  super()
31
25
  const { processor = new Processor(), stream = [] } = props
26
+ /** @type {Processor} Input processor */
32
27
  this.processor = processor
28
+ /** @type {UiMessage[]} Input message buffer */
33
29
  this.stream = stream
34
30
  this.processor?.on('data', (data) => {
35
31
  this.write(data)
package/src/cli.js ADDED
@@ -0,0 +1 @@
1
+ export { UIApp as default } from './domain/app/UIApp.js'
@@ -12,7 +12,7 @@
12
12
  * instead of writing its own while(true) loop.
13
13
  */
14
14
 
15
- import { validateIntent } from './Intent.js'
15
+ import { validateIntent, result } from './Intent.js'
16
16
  import { IntentErrorModel } from './IntentErrorModel.js'
17
17
 
18
18
  /**
@@ -21,8 +21,12 @@ import { IntentErrorModel } from './IntentErrorModel.js'
21
21
  * Handler for 'ask' intents. Must return { value: ... }.
22
22
  * @property {(intent: import('./Intent.js').ProgressIntent) => void | Promise<void>} [progress]
23
23
  * Handler for 'progress' intents. Optional (defaults to no-op).
24
+ * @property {(intent: import('./Intent.js').ShowIntent) => void | Promise<void>} [show]
25
+ * Handler for 'show' intents. Optional (defaults to no-op).
24
26
  * @property {(intent: import('./Intent.js').LogIntent) => void | Promise<void>} [log]
25
- * Handler for 'log' intents. Optional (defaults to no-op).
27
+ * Handler for 'log' intents. Optional.
28
+ * @property {(intent: import('./Intent.js').AgentIntent) => Promise<import('./Intent.js').AgentResponse>} [agent]
29
+ * Handler for 'agent' intents (AI Subagents). Optional (fallback to show if not implemented).
26
30
  * @property {(intent: import('./Intent.js').RenderIntent) => void | Promise<void>} [render]
27
31
  * Handler for 'render' intents (visual component injection). Optional.
28
32
  * @property {(intent: import('./Intent.js').ResultIntent) => void | Promise<void>} [result]
@@ -37,6 +41,9 @@ import { IntentErrorModel } from './IntentErrorModel.js'
37
41
  * Set to a positive value for CLI/Chat adapters where hanging is unacceptable.
38
42
  * @property {AbortSignal} [signal]
39
43
  * External AbortSignal for cancellation from outside.
44
+ * @property {import('./Intent.js').Intent[]} [trace]
45
+ * Array where all executed intents will be sequentially recorded.
46
+ * Useful for generating 'crash reports' or Nan0Spec files on failure.
40
47
  */
41
48
 
42
49
  /**
@@ -53,18 +60,18 @@ function withTimeout(promise, ms, label) {
53
60
  if (!ms || ms <= 0) return promise
54
61
 
55
62
  return new Promise((resolve, reject) => {
56
- const timer = setTimeout(() => {
63
+ const timer = globalThis.setTimeout(() => {
57
64
  const error = IntentErrorModel.error('timeout', { label, ms })
58
65
  reject(error)
59
66
  }, ms)
60
67
 
61
68
  promise.then(
62
69
  (val) => {
63
- clearTimeout(timer)
70
+ globalThis.clearTimeout(timer)
64
71
  resolve(val)
65
72
  },
66
73
  (err) => {
67
- clearTimeout(timer)
74
+ globalThis.clearTimeout(timer)
68
75
  reject(err)
69
76
  },
70
77
  )
@@ -105,7 +112,7 @@ export async function runGenerator(generator, handlers, options = {}) {
105
112
  while (true) {
106
113
  // ─── Check external abort signal ───
107
114
  if (signal?.aborted) {
108
- await generator.return({ type: 'result', data: null })
115
+ await generator.return(/** @type {any} */ (result(null)))
109
116
  const error = IntentErrorModel.error('aborted')
110
117
  error.name = 'AbortError'
111
118
  throw error
@@ -140,6 +147,11 @@ export async function runGenerator(generator, handlers, options = {}) {
140
147
  // ─── Validate intent structure (the Judge) ───
141
148
  validateIntent(intent)
142
149
 
150
+ // Record intent into the global trace if provided (for Crash Reports in Nan0Spec format).
151
+ if (options.trace && Array.isArray(options.trace)) {
152
+ options.trace.push(intent)
153
+ }
154
+
143
155
  // ─── Dispatch to adapter handler ───
144
156
  switch (intent.type) {
145
157
  case 'ask': {
@@ -167,7 +179,7 @@ export async function runGenerator(generator, handlers, options = {}) {
167
179
  break
168
180
  }
169
181
 
170
- // Run field validation if schema has a validator (the Judge again)
182
+ // Run field validation if schema has a validator
171
183
  if (!intent.model) {
172
184
  /** @type {import('./Intent.js').FieldSchema} */
173
185
  const fieldSchema = /** @type {import('./Intent.js').FieldSchema} */ (intent.schema)
@@ -182,6 +194,17 @@ export async function runGenerator(generator, handlers, options = {}) {
182
194
  }
183
195
  }
184
196
 
197
+ if (options.trace && Array.isArray(options.trace)) {
198
+ const lastTrace = options.trace[options.trace.length - 1]
199
+ if (lastTrace && lastTrace.type === 'ask') {
200
+ // Store raw data for Crash Reports & Nan0Spec serialization
201
+ lastTrace.$value =
202
+ typeof response.value === 'object' && response.value !== null
203
+ ? JSON.parse(JSON.stringify(response.value))
204
+ : response.value
205
+ }
206
+ }
207
+
185
208
  // Instantiate Model-as-Schema class with collected data
186
209
  if (intent.model && typeof intent.schema === 'function') {
187
210
  const ModelClass = /** @type {new (data: any) => any} */ (intent.schema)
@@ -200,6 +223,14 @@ export async function runGenerator(generator, handlers, options = {}) {
200
223
  break
201
224
  }
202
225
 
226
+ case 'show': {
227
+ if (handlers.show) {
228
+ await handlers.show(intent)
229
+ }
230
+ nextVal = undefined
231
+ break
232
+ }
233
+
203
234
  case 'log': {
204
235
  if (handlers.log) {
205
236
  await handlers.log(intent)
@@ -216,6 +247,35 @@ export async function runGenerator(generator, handlers, options = {}) {
216
247
  break
217
248
  }
218
249
 
250
+ case 'agent': {
251
+ if (handlers.agent) {
252
+ const response = await handlers.agent(intent)
253
+
254
+ if (options.trace && Array.isArray(options.trace)) {
255
+ const lastTrace = options.trace[options.trace.length - 1]
256
+ if (lastTrace && lastTrace.type === 'agent') {
257
+ // Record agent response for full trace replayability
258
+ /** @type {any} */ ;(lastTrace).$success = response.success
259
+ if (response.files) /** @type {any} */ (lastTrace).$files = response.files
260
+ if (response.message) /** @type {any} */ (lastTrace).$message = response.message
261
+ }
262
+ }
263
+
264
+ nextVal = response
265
+ } else {
266
+ // Fallback to show if agent goes unsupported by adapter
267
+ if (handlers.show) {
268
+ await handlers.show({
269
+ type: 'show',
270
+ level: 'warn',
271
+ message: `[Agent Task] ${intent.task}`,
272
+ })
273
+ }
274
+ nextVal = { success: false, message: 'Agent not supported by current UI adapter' }
275
+ }
276
+ break
277
+ }
278
+
219
279
  default:
220
280
  throw IntentErrorModel.error('unhandled_intent', { type: /** @type {any} */ (intent).type })
221
281
  }
@@ -8,7 +8,7 @@ import UiMessage from './Message/Message.js'
8
8
  * @class InputAdapter
9
9
  * @extends Event
10
10
  */
11
- export default class InputAdapter extends Event {
11
+ export class InputAdapter extends Event {
12
12
  static CancelError = CancelError
13
13
  /** @returns {typeof CancelError} */
14
14
  get CancelError() {
@@ -59,3 +59,5 @@ export default class InputAdapter extends Event {
59
59
  throw new Error('select() method must be implemented in subclass')
60
60
  }
61
61
  }
62
+
63
+ export default InputAdapter