@nan0web/ui 1.9.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.
- package/README.md +97 -12
- package/package.json +54 -25
- 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 +86 -2
- package/src/StdIn.js +2 -6
- package/src/cli.js +1 -0
- package/src/core/Form/Form.js +8 -7
- package/src/core/Form/Message.js +1 -1
- package/src/core/GeneratorRunner.js +77 -7
- package/src/core/InputAdapter.js +3 -1
- package/src/core/Intent.js +214 -16
- package/src/core/IntentErrorModel.js +6 -1
- package/src/core/Message/Message.js +4 -7
- package/src/core/Message/OutputMessage.js +4 -9
- package/src/core/Stream.js +16 -5
- package/src/core/StreamEntry.js +20 -28
- package/src/core/index.js +2 -1
- package/src/domain/Content.js +196 -0
- package/src/domain/Document.js +17 -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 +401 -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 +21 -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 +29 -0
- package/src/testing/LogicInspector.js +55 -0
- package/src/testing/SnapshotRunner.js +22 -0
- package/src/testing/SpecAdapter.js +115 -0
- package/src/testing/SpecRunner.js +121 -0
- package/src/testing/VisualAdapter.js +46 -0
- package/src/testing/index.js +7 -0
- package/src/testing/verifySnapshot.js +17 -0
- package/types/App/Command/DepsCommand.d.ts +0 -2
- package/types/Model/index.d.ts +56 -4
- package/types/StdIn.d.ts +3 -3
- package/types/cli.d.ts +1 -0
- package/types/core/Form/Form.d.ts +2 -2
- package/types/core/GeneratorRunner.d.ts +18 -1
- package/types/core/InputAdapter.d.ts +2 -1
- package/types/core/Intent.d.ts +232 -26
- package/types/core/IntentErrorModel.d.ts +4 -0
- package/types/core/Message/Message.d.ts +2 -2
- package/types/core/Message/OutputMessage.d.ts +0 -2
- package/types/core/index.d.ts +2 -1
- package/types/domain/Content.d.ts +340 -0
- package/types/domain/Document.d.ts +21 -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 +19 -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/GalleryGenerator.d.ts +1 -0
- package/types/testing/LogicInspector.d.ts +22 -0
- package/types/testing/SnapshotRunner.d.ts +7 -0
- package/types/testing/SpecAdapter.d.ts +57 -0
- package/types/testing/SpecRunner.d.ts +41 -0
- package/types/testing/VisualAdapter.d.ts +9 -0
- package/types/testing/index.d.ts +7 -0
- package/types/testing/verifySnapshot.d.ts +14 -0
- package/src/README.md.js +0 -436
- 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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { runGenerator } from '../core/GeneratorRunner.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LogicInspector
|
|
5
|
+
*
|
|
6
|
+
* Базовий клас для захоплення "Логічних зліпків" (Intent Stream) будь-яких моделей OLMUI.
|
|
7
|
+
* Дозволяє виконувати чисто-логічне тестування без прив'язки до рендерингу.
|
|
8
|
+
*/
|
|
9
|
+
export class LogicInspector {
|
|
10
|
+
/**
|
|
11
|
+
* Виконує генератор моделі та записує послідовність усіх інтенцій.
|
|
12
|
+
* @param {AsyncGenerator<import('../core/Intent.js').Intent, import('../core/Intent.js').ResultIntent, import('../core/Intent.js').IntentResponse>} modelStream - результат виклику model.run()
|
|
13
|
+
* @param {object} options
|
|
14
|
+
* @param {Array<any> | ((locale: string) => Array<any>)} [options.inputs] - черга вхідних значень для askIntent
|
|
15
|
+
* @param {string} [options.locale] - локаль для тестів
|
|
16
|
+
* @param {function} [options.t] - функція перекладу
|
|
17
|
+
* @returns {Promise<Array<any>>} Intent Stream Log
|
|
18
|
+
*/
|
|
19
|
+
static async capture(modelStream, { inputs = [], locale = 'uk', t = (k) => k } = {}) {
|
|
20
|
+
const intents = []
|
|
21
|
+
let inputIdx = 0
|
|
22
|
+
const resolvedInputs = typeof inputs === 'function' ? inputs(locale) : inputs
|
|
23
|
+
|
|
24
|
+
const recordingAdapter = {
|
|
25
|
+
/** @param {import('../core/Intent.js').AskIntent} i */
|
|
26
|
+
ask: async (i) => {
|
|
27
|
+
const value = resolvedInputs[inputIdx++]
|
|
28
|
+
const entry = { type: 'ask', field: i.field, schema: i.schema, input: value }
|
|
29
|
+
intents.push(entry)
|
|
30
|
+
return { value }
|
|
31
|
+
},
|
|
32
|
+
/** @param {import('../core/Intent.js').ShowIntent} i */
|
|
33
|
+
show: async (i) => {
|
|
34
|
+
intents.push({ type: 'show', level: i.level || 'info', message: i.message })
|
|
35
|
+
},
|
|
36
|
+
/** @param {import('../core/Intent.js').ProgressIntent} i */
|
|
37
|
+
progress: async (i) => {
|
|
38
|
+
intents.push({ type: 'progress', message: i.message })
|
|
39
|
+
},
|
|
40
|
+
/** @param {import('../core/Intent.js').RenderIntent} i */
|
|
41
|
+
render: async (i) => {
|
|
42
|
+
intents.push({ type: 'render', component: i.component, props: i.props })
|
|
43
|
+
},
|
|
44
|
+
/** @param {import('../core/Intent.js').ResultIntent} i */
|
|
45
|
+
result: async (i) => {
|
|
46
|
+
intents.push({ type: 'result', data: i.data })
|
|
47
|
+
},
|
|
48
|
+
t
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Викликаємо базовий раннер з нашим записуючим адаптером
|
|
52
|
+
await runGenerator(modelStream, recordingAdapter)
|
|
53
|
+
return intents
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SnapshotRunner as Runner } from '../domain/app/SnapshotRunner.js'
|
|
2
|
+
import DBFS from '@nan0web/db-fs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Legacy bridge for SnapshotRunner.
|
|
6
|
+
* @deprecated Use SnapshotRunner model from domain/app
|
|
7
|
+
*/
|
|
8
|
+
export class SnapshotRunner {
|
|
9
|
+
static async generateAndAudit(options) {
|
|
10
|
+
const db = options.db || new DBFS({ root: options.dataDir })
|
|
11
|
+
const runner = new Runner(options, { db })
|
|
12
|
+
if (options.getCategory) runner.getCategory = options.getCategory
|
|
13
|
+
if (options.createModelStream) runner.createModelStream = options.createModelStream
|
|
14
|
+
|
|
15
|
+
const gen = runner.run()
|
|
16
|
+
let res = await gen.next()
|
|
17
|
+
while (!res.done) {
|
|
18
|
+
res = await gen.next()
|
|
19
|
+
}
|
|
20
|
+
return res.value
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} SpecAdapterOptions
|
|
5
|
+
* @property {typeof import('node:assert/strict')} [assert] Custom assertion library (falls back to node:assert in Node runtime)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class SpecAdapter {
|
|
9
|
+
/** @type {Array<object>} */
|
|
10
|
+
stream
|
|
11
|
+
/** @type {typeof import('node:assert/strict')} */
|
|
12
|
+
assert
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Array<object>} stream The remaining nan0 array without the first element.
|
|
16
|
+
* @param {SpecAdapterOptions} [options={}]
|
|
17
|
+
*/
|
|
18
|
+
constructor(stream, options = {}) {
|
|
19
|
+
this.stream = stream
|
|
20
|
+
this.assert = options.assert || assert
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper to get the next step and assert type match.
|
|
25
|
+
* @param {string} intentType
|
|
26
|
+
*/
|
|
27
|
+
#popExpected(intentType) {
|
|
28
|
+
const step = this.stream.shift()
|
|
29
|
+
if (!step) {
|
|
30
|
+
this.assert.fail(`Model yielded '${intentType}' intent, but Nan0Spec stream is exhausted (no more steps expected).`)
|
|
31
|
+
}
|
|
32
|
+
if (!(intentType in step)) {
|
|
33
|
+
// Find what step type it actually is
|
|
34
|
+
const actualType = Object.keys(step).find(k => !k.startsWith('$'))
|
|
35
|
+
this.assert.fail(`Strict mismatch: Model yielded '${intentType}', but Nan0Spec stream expected '${actualType}'. Trace: ${JSON.stringify(step)}`)
|
|
36
|
+
}
|
|
37
|
+
return step
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {import('../core/Intent.js').AskIntent} intent
|
|
42
|
+
*/
|
|
43
|
+
async ask(intent) {
|
|
44
|
+
const step = this.#popExpected('ask')
|
|
45
|
+
this.assert.equal(step.ask, intent.field, `Field mismatch on ask. Expected '${step.ask}', got '${intent.field}'`)
|
|
46
|
+
|
|
47
|
+
return { value: step.$value }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {import('../core/Intent.js').ShowIntent} intent
|
|
52
|
+
*/
|
|
53
|
+
async show(intent) {
|
|
54
|
+
const step = this.#popExpected('show')
|
|
55
|
+
if (step.show && typeof step.show === 'string') {
|
|
56
|
+
const activeMessage = intent.message
|
|
57
|
+
if (activeMessage && step.show !== '*' && step.show !== '') {
|
|
58
|
+
this.assert.equal(activeMessage, step.show, `Show message mismatch`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {import('../core/Intent.js').LogIntent} intent
|
|
65
|
+
*/
|
|
66
|
+
async log(intent) {
|
|
67
|
+
const step = this.#popExpected('log')
|
|
68
|
+
if (step.log && typeof step.log === 'string') {
|
|
69
|
+
const activeMessage = intent.message
|
|
70
|
+
if (activeMessage && step.log !== '*' && step.log !== '') {
|
|
71
|
+
this.assert.equal(activeMessage, step.log, `Log message mismatch`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {import('../core/Intent.js').ProgressIntent} intent
|
|
78
|
+
*/
|
|
79
|
+
async progress(intent) {
|
|
80
|
+
this.#popExpected('progress')
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {import('../core/Intent.js').RenderIntent} intent
|
|
85
|
+
*/
|
|
86
|
+
async render(intent) {
|
|
87
|
+
const step = this.#popExpected('render')
|
|
88
|
+
const activeComponent = intent.component
|
|
89
|
+
this.assert.equal(activeComponent, step.render, `Render component mismatch. Expected '${step.render}'.`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {import('../core/Intent.js').AgentIntent} intent
|
|
94
|
+
*/
|
|
95
|
+
async agent(intent) {
|
|
96
|
+
const step = this.#popExpected('agent')
|
|
97
|
+
this.assert.equal(step.agent, intent.task, `Agent task mismatch. Expected '${step.agent}'.`)
|
|
98
|
+
|
|
99
|
+
return { success: step.$success !== false, files: step.$files, message: step.$message }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {import('../core/Intent.js').ResultIntent} intent
|
|
104
|
+
*/
|
|
105
|
+
async result(intent) {
|
|
106
|
+
// Only pop result if there's a result recorded in the spec stream
|
|
107
|
+
if (this.stream.length > 0 && typeof this.stream[0] === 'object' && 'result' in this.stream[0]) {
|
|
108
|
+
const step = this.#popExpected('result')
|
|
109
|
+
if (step.result !== undefined && step.result !== '*') {
|
|
110
|
+
const actualData = intent?.data ?? intent
|
|
111
|
+
this.assert.deepEqual(actualData, step.result, `Result object deep mismatch`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { runGenerator } from '../core/GeneratorRunner.js'
|
|
3
|
+
import { ModelAsApp } from '../domain/ModelAsApp.js'
|
|
4
|
+
import { result, progress } from '../core/Intent.js'
|
|
5
|
+
import { SpecAdapter } from './SpecAdapter.js'
|
|
6
|
+
|
|
7
|
+
export class SpecRunner extends ModelAsApp {
|
|
8
|
+
static stream = { help: 'The .nan0 intent stream array', default: [] }
|
|
9
|
+
static registry = { help: 'A registry of Model Classes that can be mounted', default: {} }
|
|
10
|
+
static UI = {
|
|
11
|
+
invalidStream: 'Invalid Nan0Spec: stream must be a non-empty array',
|
|
12
|
+
invalidFirstStep: 'Invalid Nan0Spec: first step missing or invalid',
|
|
13
|
+
missingAppName:
|
|
14
|
+
'Invalid Nan0Spec: first step must define the AppName key (e.g. ShoppingCartApp)',
|
|
15
|
+
appNotFound:
|
|
16
|
+
"SpecRunner: AppName '{app}' not found in provided registry. Did you forget to import it?",
|
|
17
|
+
invalidGenerator: "SpecRunner: Model '{app}' does not have a valid run() async generator",
|
|
18
|
+
running: 'Running {app}...',
|
|
19
|
+
unhandledSteps: 'Model finished execution, but stream still has {count} unhandled steps',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @type {typeof import('node:assert/strict')} */
|
|
23
|
+
#assert
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Partial<SpecRunner>} [data={}]
|
|
27
|
+
* @param {Partial<import('../index.js').ModelAsAppOptions> & { assert?: typeof import('node:assert/strict') }} [options={}]
|
|
28
|
+
*/
|
|
29
|
+
constructor(data = {}, options = {}) {
|
|
30
|
+
super(data, /** @type {import('../index.js').ModelAsAppOptions} */ (options))
|
|
31
|
+
this.#assert = options.assert || assert
|
|
32
|
+
/** @type {Array<object>} The Nan0Spec stream */
|
|
33
|
+
this.stream
|
|
34
|
+
/** @type {Record<string, any>} The registry of Model Classes */
|
|
35
|
+
this.registry
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @throws {Error}
|
|
40
|
+
* @returns {AsyncGenerator<import('../core/Intent.js').Intent, import('../core/Intent.js').ResultIntent, any>}
|
|
41
|
+
*/
|
|
42
|
+
async *run() {
|
|
43
|
+
const { t } = this._
|
|
44
|
+
const stream = this.stream
|
|
45
|
+
const registry = this.registry
|
|
46
|
+
|
|
47
|
+
if (!Array.isArray(stream) || stream.length === 0) {
|
|
48
|
+
throw new Error(t(SpecRunner.UI.invalidStream))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Clone the stream so we can shift without destroying the original reference
|
|
52
|
+
const localStream = [...stream]
|
|
53
|
+
|
|
54
|
+
const firstStep = localStream.shift()
|
|
55
|
+
if (!firstStep || typeof firstStep !== 'object') {
|
|
56
|
+
throw new Error(t(SpecRunner.UI.invalidFirstStep))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const appName = Object.keys(firstStep).find((k) => !k.startsWith('$'))
|
|
60
|
+
if (!appName) {
|
|
61
|
+
throw new Error(t(SpecRunner.UI.missingAppName))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ModelClass = /** @type {any} */ (registry[appName])
|
|
65
|
+
if (!ModelClass) {
|
|
66
|
+
throw new Error(t(SpecRunner.UI.appNotFound, { app: appName }))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const appData = firstStep[appName] || {}
|
|
70
|
+
const adapter = new SpecAdapter(localStream, { assert: this.#assert })
|
|
71
|
+
|
|
72
|
+
// Create the model, passing our own context so it inherits translations etc
|
|
73
|
+
const model = new ModelClass(appData, this._)
|
|
74
|
+
const generator = typeof model.run === 'function' ? model.run() : null
|
|
75
|
+
|
|
76
|
+
if (!generator || typeof generator.next !== 'function') {
|
|
77
|
+
throw new Error(t(SpecRunner.UI.invalidGenerator, { app: appName }))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
yield progress(t(SpecRunner.UI.running, { app: appName }))
|
|
82
|
+
await runGenerator(generator, {
|
|
83
|
+
ask: adapter.ask.bind(adapter),
|
|
84
|
+
show: adapter.show.bind(adapter),
|
|
85
|
+
log: adapter.log.bind(adapter),
|
|
86
|
+
progress: adapter.progress.bind(adapter),
|
|
87
|
+
render: adapter.render.bind(adapter),
|
|
88
|
+
agent: adapter.agent.bind(adapter),
|
|
89
|
+
result: adapter.result.bind(adapter),
|
|
90
|
+
})
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// Bubbling assertion errors correctly
|
|
93
|
+
throw err
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The stream must be fully consumed
|
|
97
|
+
if (localStream.length > 0) {
|
|
98
|
+
assert.fail(t(SpecRunner.UI.unhandledSteps, { count: localStream.length }))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result({ success: true, appName })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run a Nan0Spec sequence programmatically (for unit tests).
|
|
106
|
+
*
|
|
107
|
+
* @param {Array<object>} stream The .nan0 intent stream array
|
|
108
|
+
* @param {Record<string, any>} registry A registry of Model Classes that can be mounted
|
|
109
|
+
* @param {typeof import('node:assert/strict')} [asserter] Custom assertion library
|
|
110
|
+
*/
|
|
111
|
+
static async execute(stream, registry, asserter = assert) {
|
|
112
|
+
const runner = new SpecRunner({ stream, registry }, { assert: asserter })
|
|
113
|
+
const it = runner.run()
|
|
114
|
+
while (true) {
|
|
115
|
+
const { value, done } = await it.next()
|
|
116
|
+
if (done) return value?.data
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default SpecRunner
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VisualAdapter (Base)
|
|
3
|
+
*
|
|
4
|
+
* Базовий клас для візуальної трансформації інтенцій OLMUI.
|
|
5
|
+
*/
|
|
6
|
+
import { NaN0 } from '@nan0web/types'
|
|
7
|
+
|
|
8
|
+
export class VisualAdapter {
|
|
9
|
+
/**
|
|
10
|
+
* Конвертує одну інтенцію у просте текстове представлення.
|
|
11
|
+
* @param {object} intent - Intent entry from LogicInspector
|
|
12
|
+
* @param {function} [t] - i18n translate function
|
|
13
|
+
* @returns {string} Raw description
|
|
14
|
+
*/
|
|
15
|
+
static render(intent, t = (k) => k) {
|
|
16
|
+
let node;
|
|
17
|
+
switch (intent.type) {
|
|
18
|
+
case 'ask':
|
|
19
|
+
node = { ask: { field: intent.field, input: intent.input !== undefined ? intent.input : '...' } }
|
|
20
|
+
break
|
|
21
|
+
case 'progress':
|
|
22
|
+
node = { progress: { message: intent.message || '' } }
|
|
23
|
+
break
|
|
24
|
+
case 'log':
|
|
25
|
+
node = { log: { level: intent.level?.toUpperCase() || 'INFO', message: intent.message } }
|
|
26
|
+
break
|
|
27
|
+
case 'render':
|
|
28
|
+
node = { render: { [intent.component]: intent.props || {} } }
|
|
29
|
+
break
|
|
30
|
+
case 'result': {
|
|
31
|
+
const data = intent.data || {}
|
|
32
|
+
node = { result: (typeof data === 'object' && data !== null && Object.keys(data).length === 0) ? {} : data }
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
node = { [intent.type || 'unknown']: intent }
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
return NaN0.stringify([node]).trim()
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return `- error: ${JSON.stringify(node)}`
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './LogicInspector.js'
|
|
2
|
+
export * from './VisualAdapter.js'
|
|
3
|
+
export * from '../domain/app/SnapshotAuditor.js'
|
|
4
|
+
export * from './verifySnapshot.js'
|
|
5
|
+
export { SpecRunner } from './SpecRunner.js'
|
|
6
|
+
export { SpecAdapter } from './SpecAdapter.js'
|
|
7
|
+
export * from './CrashReporter.js'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saves a logical JSONL snapshot sequence
|
|
3
|
+
* @param {Object} options
|
|
4
|
+
* @param {string} options.name Snapshot file name (e.g. 'uk/ExploreCatalog.jsonl')
|
|
5
|
+
* @param {Array<Object>} options.data Array of intents to serialize
|
|
6
|
+
* @param {typeof import('node:fs/promises')} [options.fs] Injected filesystem
|
|
7
|
+
* @param {typeof import('node:path')} [options.path] Injected path module
|
|
8
|
+
*/
|
|
9
|
+
export async function verifySnapshot({ name, data, fs, path }) {
|
|
10
|
+
if (!fs) fs = await import('node:fs/promises')
|
|
11
|
+
if (!path) path = await import('node:path')
|
|
12
|
+
|
|
13
|
+
const outPath = path.resolve(process.cwd(), 'snapshots/jsonl', name)
|
|
14
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true })
|
|
15
|
+
const content = data.map((i) => JSON.stringify(i)).join('\n')
|
|
16
|
+
await fs.writeFile(outPath, content + '\n')
|
|
17
|
+
}
|
package/types/Model/index.d.ts
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export default Model;
|
|
2
|
+
import User from './User/User.js';
|
|
3
|
+
import { HeaderModel } from '../domain/HeaderModel.js';
|
|
4
|
+
import { FooterModel } from '../domain/FooterModel.js';
|
|
5
|
+
import { HeroModel } from '../domain/HeroModel.js';
|
|
6
|
+
import { ButtonModel } from '../domain/components/index.js';
|
|
7
|
+
import { ConfirmModel } from '../domain/components/index.js';
|
|
8
|
+
import { InputModel } from '../domain/components/index.js';
|
|
9
|
+
import { SpinnerModel } from '../domain/components/index.js';
|
|
10
|
+
import { TableModel } from '../domain/components/index.js';
|
|
11
|
+
import { ToastModel } from '../domain/components/index.js';
|
|
12
|
+
import { SelectModel } from '../domain/components/index.js';
|
|
13
|
+
import { AutocompleteModel } from '../domain/components/index.js';
|
|
14
|
+
import { TreeModel } from '../domain/components/index.js';
|
|
15
|
+
import { TabsModel } from '../domain/components/index.js';
|
|
16
|
+
import { AccordionModel } from '../domain/components/index.js';
|
|
17
|
+
import { GalleryModel } from '../domain/components/index.js';
|
|
18
|
+
import { PriceModel } from '../domain/components/index.js';
|
|
19
|
+
import { PricingModel } from '../domain/components/index.js';
|
|
20
|
+
import { CommentModel } from '../domain/components/index.js';
|
|
21
|
+
import { TestimonialModel } from '../domain/components/index.js';
|
|
22
|
+
import { StatsItemModel } from '../domain/components/index.js';
|
|
23
|
+
import { StatsModel } from '../domain/components/index.js';
|
|
24
|
+
import { TimelineItemModel } from '../domain/components/index.js';
|
|
25
|
+
import { TimelineModel } from '../domain/components/index.js';
|
|
26
|
+
import { EmptyStateModel } from '../domain/components/index.js';
|
|
27
|
+
import { BannerModel } from '../domain/components/index.js';
|
|
28
|
+
import { ProfileDropdownModel } from '../domain/components/index.js';
|
|
29
|
+
declare namespace Model {
|
|
3
30
|
export { User };
|
|
31
|
+
export { HeaderModel };
|
|
32
|
+
export { FooterModel };
|
|
33
|
+
export { HeroModel };
|
|
34
|
+
export { ButtonModel };
|
|
35
|
+
export { ConfirmModel };
|
|
36
|
+
export { InputModel };
|
|
37
|
+
export { SpinnerModel };
|
|
38
|
+
export { TableModel };
|
|
39
|
+
export { ToastModel };
|
|
40
|
+
export { SelectModel };
|
|
41
|
+
export { AutocompleteModel };
|
|
42
|
+
export { TreeModel };
|
|
43
|
+
export { TabsModel };
|
|
44
|
+
export { AccordionModel };
|
|
45
|
+
export { GalleryModel };
|
|
46
|
+
export { PriceModel };
|
|
47
|
+
export { PricingModel };
|
|
48
|
+
export { CommentModel };
|
|
49
|
+
export { TestimonialModel };
|
|
50
|
+
export { StatsItemModel };
|
|
51
|
+
export { StatsModel };
|
|
52
|
+
export { TimelineItemModel };
|
|
53
|
+
export { TimelineModel };
|
|
54
|
+
export { EmptyStateModel };
|
|
55
|
+
export { BannerModel };
|
|
56
|
+
export { ProfileDropdownModel };
|
|
4
57
|
}
|
|
5
|
-
export
|
|
6
|
-
import User from './User/User.js';
|
|
58
|
+
export { User, HeaderModel, FooterModel, HeroModel, ButtonModel, ConfirmModel, InputModel, SpinnerModel, TableModel, ToastModel, SelectModel, AutocompleteModel, TreeModel, TabsModel, AccordionModel, GalleryModel, PriceModel, PricingModel, CommentModel, TestimonialModel, StatsItemModel, StatsModel, TimelineItemModel, TimelineModel, EmptyStateModel, BannerModel, ProfileDropdownModel };
|
package/types/StdIn.d.ts
CHANGED
|
@@ -22,10 +22,10 @@ export default class StdIn extends EventProcessor {
|
|
|
22
22
|
processor?: Processor | undefined;
|
|
23
23
|
stream?: UiMessage[] | undefined;
|
|
24
24
|
});
|
|
25
|
-
/** @type {UiMessage[]} Input message buffer */
|
|
26
|
-
stream: UiMessage[];
|
|
27
25
|
/** @type {Processor} Input processor */
|
|
28
26
|
processor: Processor;
|
|
27
|
+
/** @type {UiMessage[]} Input message buffer */
|
|
28
|
+
stream: UiMessage[];
|
|
29
29
|
/**
|
|
30
30
|
* Checks if there are messages waiting in the input stream.
|
|
31
31
|
* @returns {boolean} True if waiting messages, false otherwise
|
|
@@ -56,7 +56,7 @@ export default class StdIn extends EventProcessor {
|
|
|
56
56
|
decode(message: UiMessage | string[] | any): UiMessage;
|
|
57
57
|
}
|
|
58
58
|
import EventProcessor from '@nan0web/event/oop';
|
|
59
|
-
import { UiMessage } from './core/index.js';
|
|
60
59
|
declare class Processor extends EventProcessor {
|
|
61
60
|
}
|
|
61
|
+
import { UiMessage } from './core/index.js';
|
|
62
62
|
export {};
|
package/types/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UIApp as default } from "./domain/app/UIApp.js";
|
|
@@ -89,9 +89,9 @@ export default class UIForm extends FormMessage {
|
|
|
89
89
|
/**
|
|
90
90
|
* Validates the entire form.
|
|
91
91
|
*
|
|
92
|
-
* @returns {
|
|
92
|
+
* @returns {any}
|
|
93
93
|
*/
|
|
94
|
-
validate():
|
|
94
|
+
validate(): any;
|
|
95
95
|
/**
|
|
96
96
|
* Validates a single field.
|
|
97
97
|
*
|
|
@@ -29,9 +29,21 @@ export type AdapterHandlers = {
|
|
|
29
29
|
*/
|
|
30
30
|
progress?: ((intent: import("./Intent.js").ProgressIntent) => void | Promise<void>) | undefined;
|
|
31
31
|
/**
|
|
32
|
-
* Handler for '
|
|
32
|
+
* Handler for 'show' intents. Optional (defaults to no-op).
|
|
33
|
+
*/
|
|
34
|
+
show?: ((intent: import("./Intent.js").ShowIntent) => void | Promise<void>) | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Handler for 'log' intents. Optional.
|
|
33
37
|
*/
|
|
34
38
|
log?: ((intent: import("./Intent.js").LogIntent) => void | Promise<void>) | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Handler for 'agent' intents (AI Subagents). Optional (fallback to show if not implemented).
|
|
41
|
+
*/
|
|
42
|
+
agent?: ((intent: import("./Intent.js").AgentIntent) => Promise<import("./Intent.js").AgentResponse>) | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Handler for 'render' intents (visual component injection). Optional.
|
|
45
|
+
*/
|
|
46
|
+
render?: ((intent: import("./Intent.js").RenderIntent) => void | Promise<void>) | undefined;
|
|
35
47
|
/**
|
|
36
48
|
* Handler for the final 'result'. Optional (defaults to no-op).
|
|
37
49
|
*/
|
|
@@ -48,4 +60,9 @@ export type RunnerOptions = {
|
|
|
48
60
|
* External AbortSignal for cancellation from outside.
|
|
49
61
|
*/
|
|
50
62
|
signal?: AbortSignal | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Array where all executed intents will be sequentially recorded.
|
|
65
|
+
* Useful for generating 'crash reports' or Nan0Spec files on failure.
|
|
66
|
+
*/
|
|
67
|
+
trace?: import("./Intent.js").Intent[] | undefined;
|
|
51
68
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @class InputAdapter
|
|
5
5
|
* @extends Event
|
|
6
6
|
*/
|
|
7
|
-
export
|
|
7
|
+
export class InputAdapter extends Event {
|
|
8
8
|
static CancelError: typeof CancelError;
|
|
9
9
|
/** @returns {typeof CancelError} */
|
|
10
10
|
get CancelError(): typeof CancelError;
|
|
@@ -42,5 +42,6 @@ export default class InputAdapter extends Event {
|
|
|
42
42
|
value: string | null;
|
|
43
43
|
}>;
|
|
44
44
|
}
|
|
45
|
+
export default InputAdapter;
|
|
45
46
|
import Event from '@nan0web/event/oop';
|
|
46
47
|
import CancelError from './Error/CancelError.js';
|