@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.
- package/README.md +69 -3
- package/package.json +65 -29
- package/src/App/Command/DepsCommand.js +3 -4
- package/src/Frame/Props.js +12 -18
- package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
- package/src/Model/index.js +61 -6
- package/src/StdIn.js +2 -6
- package/src/cli.js +1 -0
- package/src/core/GeneratorRunner.js +67 -7
- package/src/core/InputAdapter.js +22 -5
- package/src/core/Intent.js +230 -18
- package/src/core/Message/Message.js +4 -7
- package/src/core/Message/OutputMessage.js +4 -9
- package/src/core/StreamEntry.js +20 -28
- package/src/core/index.js +4 -0
- package/src/domain/Content.js +198 -0
- package/src/domain/Document.js +25 -0
- package/src/domain/FooterModel.js +37 -19
- package/src/domain/HeaderModel.js +47 -21
- package/src/domain/HeroModel.js +24 -22
- package/src/domain/LayoutModel.js +43 -0
- package/src/domain/ModelAsApp.js +46 -0
- package/src/domain/SandboxModel.js +19 -16
- package/src/domain/app/GalleryCommand.js +53 -0
- package/src/domain/app/GalleryRenderIntent.js +77 -0
- package/src/domain/app/SnapshotAuditor.js +399 -0
- package/src/domain/app/SnapshotRunner.js +264 -0
- package/src/domain/app/UIApp.js +78 -0
- package/src/domain/components/BreadcrumbModel.js +10 -6
- package/src/domain/components/FeatureGridModel.js +62 -0
- package/src/domain/components/MarkdownModel.js +24 -0
- package/src/domain/components/ShellModel.js +243 -0
- package/src/domain/components/TableModel.js +10 -6
- package/src/domain/components/ToastModel.js +10 -6
- package/src/domain/components/index.js +3 -1
- package/src/domain/index.js +14 -4
- package/src/index.js +23 -2
- package/src/inspect.js +2 -0
- package/src/test/ScenarioAdapter.js +59 -0
- package/src/test/ScenarioTest.js +51 -0
- package/src/test/ScenarioTest.story.js +56 -0
- package/src/testing/CrashReporter.js +56 -0
- package/src/testing/GalleryGenerator.js +15 -71
- package/src/testing/LogicInspector.js +4 -4
- package/src/testing/SnapshotRunner.js +22 -0
- package/src/testing/SpecAdapter.js +114 -0
- package/src/testing/SpecRunner.js +121 -0
- package/src/testing/VisualAdapter.js +24 -19
- package/src/testing/index.js +5 -1
- package/src/testing/verifySnapshot.js +17 -0
- package/types/App/Command/DepsCommand.d.ts +0 -2
- package/types/Model/index.d.ts +56 -62
- package/types/StdIn.d.ts +3 -3
- package/types/cli.d.ts +1 -0
- package/types/core/GeneratorRunner.d.ts +14 -1
- package/types/core/InputAdapter.d.ts +50 -6
- package/types/core/Intent.d.ts +280 -32
- package/types/core/Message/Message.d.ts +2 -2
- package/types/core/Message/OutputMessage.d.ts +0 -2
- package/types/core/index.d.ts +4 -0
- package/types/domain/Content.d.ts +344 -0
- package/types/domain/Document.d.ts +40 -0
- package/types/domain/FooterModel.d.ts +22 -12
- package/types/domain/HeaderModel.d.ts +36 -13
- package/types/domain/HeroModel.d.ts +19 -17
- package/types/domain/LayoutModel.d.ts +34 -0
- package/types/domain/ModelAsApp.d.ts +23 -0
- package/types/domain/SandboxModel.d.ts +10 -0
- package/types/domain/app/GalleryCommand.d.ts +55 -0
- package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
- package/types/domain/app/SnapshotAuditor.d.ts +99 -0
- package/types/domain/app/SnapshotRunner.d.ts +45 -0
- package/types/domain/app/UIApp.d.ts +60 -0
- package/types/domain/components/BreadcrumbModel.d.ts +6 -8
- package/types/domain/components/FeatureGridModel.d.ts +50 -0
- package/types/domain/components/MarkdownModel.d.ts +19 -0
- package/types/domain/components/ShellModel.d.ts +56 -0
- package/types/domain/components/TableModel.d.ts +4 -0
- package/types/domain/components/ToastModel.d.ts +4 -0
- package/types/domain/components/index.d.ts +3 -0
- package/types/domain/index.d.ts +10 -4
- package/types/index.d.ts +21 -1
- package/types/inspect.d.ts +2 -0
- package/types/test/ScenarioAdapter.d.ts +43 -0
- package/types/test/ScenarioTest.d.ts +24 -0
- package/types/test/ScenarioTest.story.d.ts +1 -0
- package/types/testing/CrashReporter.d.ts +13 -0
- package/types/testing/SnapshotRunner.d.ts +7 -0
- package/types/testing/SpecAdapter.d.ts +58 -0
- package/types/testing/SpecRunner.d.ts +41 -0
- package/types/testing/VisualAdapter.d.ts +0 -6
- package/types/testing/index.d.ts +5 -1
- package/types/testing/verifySnapshot.d.ts +14 -0
- package/src/testing/SnapshotInspector.js +0 -84
- package/types/App/Command/Options.d.ts +0 -43
- package/types/App/Command/index.d.ts +0 -8
- package/types/App/User/Command/Options.d.ts +0 -34
- package/types/core/Message/InputMessage.d.ts +0 -71
- package/types/domain/components/HeroModel.d.ts +0 -24
- package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
- 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
|
-
###
|
|
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.
|
|
3
|
+
"version": "1.12.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,55 @@
|
|
|
16
16
|
"import": "./src/index.js",
|
|
17
17
|
"types": "./types/index.d.ts"
|
|
18
18
|
},
|
|
19
|
-
"./components":
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
"./src/core/Intent.js": {
|
|
28
|
+
"import": "./src/core/Intent.js",
|
|
29
|
+
"types": "./types/core/Intent.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./domain": {
|
|
32
|
+
"import": "./src/domain/index.js",
|
|
33
|
+
"types": "./types/domain/index.d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./inspect": {
|
|
36
|
+
"import": "./src/inspect.js",
|
|
37
|
+
"types": "./types/inspect.d.ts"
|
|
38
|
+
},
|
|
39
|
+
"./testing": {
|
|
40
|
+
"import": "./src/testing/index.js",
|
|
41
|
+
"types": "./types/testing/index.d.ts"
|
|
42
|
+
},
|
|
23
43
|
"./cli-app": "./apps/cli/src/index.js",
|
|
24
44
|
"./mobile-app": "./apps/mobile/src/index.js",
|
|
25
|
-
"./web-app": "./apps/web/src/App.jsx"
|
|
45
|
+
"./web-app": "./apps/web/src/App.jsx",
|
|
46
|
+
"./ui/cli": "./src/cli.js"
|
|
26
47
|
},
|
|
27
48
|
"keywords": [
|
|
28
49
|
"ui",
|
|
29
50
|
"nanoweb"
|
|
30
51
|
],
|
|
52
|
+
"nan0web": {
|
|
53
|
+
"workflowDir": "docs/{locale}/workflows",
|
|
54
|
+
"workflows": [
|
|
55
|
+
"olm-ui-architecture.md",
|
|
56
|
+
"olm-ui-architecture-core.md",
|
|
57
|
+
"olm-ui-architecture-adapters.md",
|
|
58
|
+
"forge-component.md",
|
|
59
|
+
"interface-welding.md"
|
|
60
|
+
],
|
|
61
|
+
"inspectors": [
|
|
62
|
+
"./src/domain/app/SnapshotAuditor.js"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
31
65
|
"author": "YaRaSLove (ЯRаСлав) <support@yaro.page>",
|
|
32
66
|
"license": "ISC",
|
|
33
67
|
"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
68
|
"@playwright/test": "^1.58.2",
|
|
41
69
|
"@rollup/plugin-yaml": "^4.1.2",
|
|
42
70
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -45,43 +73,51 @@
|
|
|
45
73
|
"knip": "^5.86.0",
|
|
46
74
|
"lit": "^3.3.2",
|
|
47
75
|
"vite": "^6.4.1",
|
|
48
|
-
"vitest": "^3.2.4"
|
|
76
|
+
"vitest": "^3.2.4",
|
|
77
|
+
"@nan0web/db-fs": "1.2.1",
|
|
78
|
+
"@nan0web/event": "1.0.1",
|
|
79
|
+
"@nan0web/icons": "1.1.0",
|
|
80
|
+
"@nan0web/i18n": "1.5.0",
|
|
81
|
+
"@nan0web/ui-cli": "2.13.0",
|
|
82
|
+
"@nan0web/inspect": "1.0.0",
|
|
83
|
+
"@nan0web/test": "1.1.4",
|
|
84
|
+
"@nan0web/nan0web.app": "0.1.0",
|
|
85
|
+
"@nan0web/release": "1.0.3",
|
|
86
|
+
"@nan0web/ui-lit": "1.1.0"
|
|
49
87
|
},
|
|
50
88
|
"dependencies": {
|
|
51
|
-
"@nan0web/co": "^2.0.0",
|
|
52
|
-
"@nan0web/log": "^1.1.1",
|
|
53
|
-
"@nan0web/types": "^1.4.0",
|
|
54
89
|
"string-width": "^7.2.0",
|
|
55
|
-
"@nan0web/
|
|
90
|
+
"@nan0web/co": "2.0.1",
|
|
91
|
+
"@nan0web/core": "1.1.3",
|
|
92
|
+
"@nan0web/log": "1.1.1",
|
|
93
|
+
"@nan0web/types": "1.7.2"
|
|
56
94
|
},
|
|
57
95
|
"scripts": {
|
|
96
|
+
"prebuild": "rm -rf types/",
|
|
58
97
|
"build": "tsc",
|
|
59
98
|
"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:
|
|
99
|
+
"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\"",
|
|
100
|
+
"test": "npm run test:unit && npm run test:audit && npm run build",
|
|
62
101
|
"test:nan0test": "node --test --test-timeout=3333 \"src/**/*.test.js\" | nan0test parse --fail",
|
|
63
102
|
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**/*.js\" --test-coverage-exclude=\"src/**/*.test.js\" --test \"src/**/*.test.js\"",
|
|
64
103
|
"test:coverage:collect": "nan0test coverage",
|
|
65
104
|
"test:docs": "node --test src/README.md.js",
|
|
66
|
-
"release:spec": "node --test \"releases/**/*.
|
|
67
|
-
"test:release": "node --test \"releases/**/*.test.js\"",
|
|
105
|
+
"release:spec": "node --test \"src/test/releases/**/*.test.js\"",
|
|
106
|
+
"test:release": "node --test \"src/test/releases/**/*.test.js\"",
|
|
68
107
|
"test:status": "nan0test status --hide-name",
|
|
69
108
|
"test:play": "node --test --test-timeout=3333 \"play/**/*.test.js\"",
|
|
70
109
|
"test:snapshots": "node src/testing/GalleryGenerator.js",
|
|
71
110
|
"test:gallery": "npm run test:snapshots && npx nan0gallery --dir=snapshots/core",
|
|
72
|
-
"test:audit": "
|
|
73
|
-
"test:
|
|
74
|
-
"test:
|
|
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",
|
|
111
|
+
"test:audit": "nan0cli audit --dir snapshots/core --data .",
|
|
112
|
+
"test:stories": "node --test \"src/**/*.story.js\"",
|
|
113
|
+
"test:all": "npm run test && npm run test:docs && npm run test:play && npm run test:stories && npm run knip",
|
|
77
114
|
"knip": "knip --production",
|
|
78
115
|
"precommit": "npm test",
|
|
79
116
|
"prepush": "npm test",
|
|
80
117
|
"release": "nan0release publish",
|
|
81
118
|
"clean": "rm -rf .cache/ && rm -rf dist/",
|
|
82
119
|
"clean:modules": "rm -rf node_modules",
|
|
83
|
-
"docs:
|
|
84
|
-
"docs:
|
|
85
|
-
"docs:build": "node docs/site/scripts/generate-data.js && node docs/site/scripts/generate-pages.js && vite build docs/site"
|
|
120
|
+
"docs:dev": "nan0web run --data docs --index README --port 4270",
|
|
121
|
+
"docs:build": "nan0web build --data docs --index README --locale uk"
|
|
86
122
|
}
|
|
87
123
|
}
|
|
@@ -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 =
|
|
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
|
}
|
package/src/Frame/Props.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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.
|
package/src/Model/index.js
CHANGED
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
import User from './User/User.js'
|
|
2
|
-
import
|
|
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
|
-
|
|
31
|
+
export {
|
|
5
32
|
User,
|
|
6
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
}
|
package/src/core/InputAdapter.js
CHANGED
|
@@ -2,13 +2,25 @@ import Event from '@nan0web/event/oop'
|
|
|
2
2
|
import CancelError from './Error/CancelError.js'
|
|
3
3
|
import UiMessage from './Message/Message.js'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} AskOptions
|
|
7
|
+
* @property {boolean} [silent] - Suppress logs or output.
|
|
8
|
+
* @property {string} [title] - Custom title for the prompt.
|
|
9
|
+
* @property {string} [hint] - Presentation hint (e.g., 'password', 'tree', 'markdown').
|
|
10
|
+
* @property {any} [default] - Default value if no input is provided.
|
|
11
|
+
* @property {Array<string|Object>} [options] - Array of options for select inputs.
|
|
12
|
+
* @property {Record<string, any>} [UI] - Localization dictionary/overrides.
|
|
13
|
+
* @property {string} [component] - Target specific component override.
|
|
14
|
+
*/
|
|
15
|
+
/** @typedef {import('./index.js').AskResponse} AskResponse */
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Abstract input adapter for UI implementations.
|
|
7
19
|
*
|
|
8
20
|
* @class InputAdapter
|
|
9
21
|
* @extends Event
|
|
10
22
|
*/
|
|
11
|
-
export
|
|
23
|
+
export class InputAdapter extends Event {
|
|
12
24
|
static CancelError = CancelError
|
|
13
25
|
/** @returns {typeof CancelError} */
|
|
14
26
|
get CancelError() {
|
|
@@ -41,21 +53,26 @@ export default class InputAdapter extends Event {
|
|
|
41
53
|
return true
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
|
|
57
|
+
|
|
44
58
|
/**
|
|
45
59
|
* Helper to ask a question.
|
|
46
|
-
* @param {string} question - Question to ask.
|
|
47
|
-
* @
|
|
60
|
+
* @param {string|import('./Message/Message.js').default|any} question - Question to ask, Form instance, or AskIntent.
|
|
61
|
+
* @param {AskOptions} [options] - Additional options.
|
|
62
|
+
* @returns {Promise<AskResponse>}
|
|
48
63
|
*/
|
|
49
|
-
async ask(question) {
|
|
64
|
+
async ask(question, options) {
|
|
50
65
|
throw new Error('ask() method must be implemented in subclass')
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
/**
|
|
54
69
|
* Generic selection prompt.
|
|
55
70
|
* @param {Object} config - Selection configuration.
|
|
56
|
-
* @returns {Promise<{ index
|
|
71
|
+
* @returns {Promise<{ index?: number, value: string | null }>}
|
|
57
72
|
*/
|
|
58
73
|
async select(config) {
|
|
59
74
|
throw new Error('select() method must be implemented in subclass')
|
|
60
75
|
}
|
|
61
76
|
}
|
|
77
|
+
|
|
78
|
+
export default InputAdapter
|