@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
@@ -0,0 +1,78 @@
1
+ import { ModelAsApp } from '../ModelAsApp.js'
2
+ import { resolvePositionalArgs } from '@nan0web/ui-cli'
3
+ import SnapshotAuditor from './SnapshotAuditor.js'
4
+ import GalleryCommand from './GalleryCommand.js'
5
+ import { show, result } from '../../core/Intent.js'
6
+
7
+ export class UIApp extends ModelAsApp {
8
+ static command = {
9
+ type: 'string',
10
+ help: 'Command to run (e.g. gallery)',
11
+ options: [GalleryCommand, SnapshotAuditor],
12
+ default: GalleryCommand.alias || GalleryCommand.name,
13
+ positional: true,
14
+ }
15
+
16
+ static UI = {
17
+ helpText: [
18
+ '# ✨ NaN•Web UI CLI',
19
+ '',
20
+ 'Zero-Hallucination Tools for the NaN•Web ecosystem.',
21
+ '',
22
+ '## Usage',
23
+ '',
24
+ '```bash',
25
+ '',
26
+ 'nan0ui gallery audit',
27
+ '',
28
+ 'nan0ui gallery render',
29
+ '',
30
+ '```',
31
+ ].join('\n'),
32
+ unknownCommand: 'Unknown command: {command}',
33
+ }
34
+
35
+ static help = {
36
+ help: 'Show help message',
37
+ default: false,
38
+ }
39
+
40
+ /**
41
+ * @param {Partial<UIApp> | Record<string, any>} [data={}]
42
+ * @param {import('@nan0web/types').ModelOptions} [options={}]
43
+ */
44
+ constructor(data = {}, options = {}) {
45
+ super(data, options)
46
+ /** @type {string[]} */ this._positionals = []
47
+ /** @type {string} Type of command to run */ this.command
48
+ /** @type {boolean} Show help message */ this.help
49
+ }
50
+
51
+ async *run() {
52
+ const t = this._.t
53
+ if (this.help || this.command === 'help') {
54
+ yield show(t(UIApp.UI.helpText, undefined))
55
+ return result({})
56
+ }
57
+
58
+ const TargetCommand = UIApp.command.options.find((opt) =>
59
+ [opt.alias, opt.name].includes(this.command),
60
+ )
61
+
62
+ if (!TargetCommand) {
63
+ yield show(t(UIApp.UI.unknownCommand, { command: this.command }), 'error')
64
+ return result({ status: 'error' })
65
+ }
66
+
67
+ // Pass remaining positionals down to the target action
68
+ const nextData = resolvePositionalArgs(
69
+ /** @type {any} */ (TargetCommand),
70
+ this._positionals || [],
71
+ this
72
+ )
73
+ const intent = new TargetCommand(nextData, this._)
74
+ return yield* intent.run()
75
+ }
76
+ }
77
+
78
+ export default UIApp
@@ -1,4 +1,5 @@
1
1
  import { Model } from '@nan0web/types'
2
+ import { show, result } from '../../core/Intent.js'
2
3
 
3
4
  /**
4
5
  * @typedef {Object} BreadcrumbItem
@@ -28,6 +29,12 @@ import { Model } from '@nan0web/types'
28
29
  * Ctrl+C = always exit (adapter responsibility).
29
30
  */
30
31
  export class BreadcrumbModel extends Model {
32
+ static $id = '@nan0web/ui/BreadcrumbModel'
33
+
34
+ static UI = {
35
+ navigated: 'Navigated to Context',
36
+ }
37
+
31
38
  // ==========================================
32
39
  // 1. MODEL AS SCHEMA (Static Definition)
33
40
  // ==========================================
@@ -243,17 +250,14 @@ export class BreadcrumbModel extends Model {
243
250
  // ==========================================
244
251
 
245
252
  /**
246
- * Yields a log intent with the current breadcrumb path.
253
+ * Yields a show intent with the current breadcrumb path.
247
254
  * This is a "display-only" run — it shows the navigation state.
248
255
  */
249
256
  async *run() {
250
- yield {
251
- type: 'log',
252
- level: 'info',
253
- message: this.toString(),
257
+ yield show(this._.t(BreadcrumbModel.UI.navigated, {}), 'info', {
254
258
  component: 'Breadcrumbs',
255
259
  model: /** @type {BreadcrumbModel} */ (this),
256
- }
260
+ })
257
261
 
258
262
  return {
259
263
  type: 'result',
@@ -0,0 +1,62 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * FeatureItemModel — OLMUI Component Model
5
+ * Represents a single features block entry with an icon, title, and description.
6
+ */
7
+ export class FeatureItemModel extends Model {
8
+ static $id = '@nan0web/ui/FeatureItemModel'
9
+
10
+ static icon = {
11
+ help: 'Icon name from @nan0web/icons or SVG path',
12
+ placeholder: 'stars',
13
+ default: '',
14
+ }
15
+ static title = {
16
+ help: 'Feature heading',
17
+ placeholder: 'High Performance',
18
+ default: '',
19
+ required: true,
20
+ }
21
+ static description = {
22
+ help: 'Detailed feature description',
23
+ placeholder: 'Built with the latest technologies...',
24
+ default: '',
25
+ }
26
+
27
+ /**
28
+ * @param {Partial<FeatureItemModel>} data
29
+ */
30
+ constructor(data = {}) {
31
+ super(data)
32
+ /** @type {string} Icon name */ this.icon
33
+ /** @type {string} Feature heading */ this.title
34
+ /** @type {string} Feature description */ this.description
35
+ }
36
+ }
37
+
38
+ /**
39
+ * FeatureGridModel — OLMUI Component Model
40
+ * Grid of features with icons and descriptions.
41
+ */
42
+ export class FeatureGridModel extends Model {
43
+ static $id = '@nan0web/ui/FeatureGridModel'
44
+
45
+ static items = {
46
+ help: 'Array of features to display in a grid',
47
+ type: 'FeatureItemModel[]',
48
+ hint: FeatureItemModel,
49
+ default: [],
50
+ }
51
+
52
+ /**
53
+ * @param {Partial<FeatureGridModel>} data
54
+ */
55
+ constructor(data = {}) {
56
+ super(data)
57
+ /** @type {FeatureItemModel[]} List of features */
58
+ this.items = Array.isArray(this.items)
59
+ ? this.items.map((i) => (i instanceof FeatureItemModel ? i : new FeatureItemModel(i)))
60
+ : []
61
+ }
62
+ }
@@ -0,0 +1,24 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * MarkdownModel — OLMUI Model-as-Schema
5
+ * Represents a rich text block powered by Markdown.
6
+ */
7
+ export class MarkdownModel extends Model {
8
+ static $id = '@nan0web/ui/MarkdownModel'
9
+
10
+ static content = {
11
+ help: 'Markdown content string',
12
+ type: 'textarea',
13
+ default: '',
14
+ }
15
+
16
+ /**
17
+ * @param {Partial<MarkdownModel> | Record<string, any>} data Model input data.
18
+ * @param {object} [options] Extended options
19
+ */
20
+ constructor(data = {}, options = {}) {
21
+ super(data, options)
22
+ /** @type {string} Markdown content string */ this.content
23
+ }
24
+ }
@@ -0,0 +1,243 @@
1
+ import { Model } from '@nan0web/types'
2
+ import { ask, log } from '../../core/Intent.js'
3
+
4
+ /**
5
+ * ShellModel — OLMUI Component Model for CLI Orchestration
6
+ * Canonical CLI entry that describes available operations as a schema.
7
+ */
8
+ export class ShellModel extends Model {
9
+ static $id = '@nan0web/ui/ShellModel'
10
+
11
+ static command = {
12
+ help: 'What do you want to do?',
13
+ type: 'select',
14
+ default: null,
15
+ positional: true,
16
+ // options: [
17
+ // BootEngine,
18
+ // InteractiveCLI,
19
+ // DevMode,
20
+ // BuildProject,
21
+ // TestSSG,
22
+ // SSGGallery,
23
+ // TestWeb,
24
+ // WebGallery,
25
+ // ConfigWizard,
26
+ // ],
27
+ options: [
28
+ { label: '📡 Boot Engine (Run OS)', value: 'run' },
29
+ { label: '🖥️ Interactive CLI', value: 'cli' },
30
+ { label: '🧬 Dev Mode (Hot-Reload)', value: 'dev' },
31
+ { label: '📦 Build Project (Data & UI)', value: 'build' },
32
+ { label: '🧪 Test SSG', value: 'test:ssg' },
33
+ { label: '🔭 SSG Gallery', value: 'ssg:gallery' },
34
+ { label: '🧪 Test Web', value: 'test:web' },
35
+ { label: '🔭 Web Gallery', value: 'web:gallery' },
36
+ { label: '🔧 Config Wizard', value: 'config' },
37
+ ],
38
+ required: true,
39
+ }
40
+
41
+ static data = {
42
+ help: 'Data source (DSN)',
43
+ type: 'string',
44
+ default: 'data/',
45
+ alias: 'dsn',
46
+ }
47
+
48
+ static index = {
49
+ help: 'Directory index file name (e.g. README or index)',
50
+ type: 'string',
51
+ default: 'index',
52
+ }
53
+
54
+ static locale = {
55
+ help: 'Application locale',
56
+ type: 'string',
57
+ default: 'en',
58
+ }
59
+
60
+ static port = {
61
+ help: 'Server port',
62
+ type: 'string',
63
+ default: '3000',
64
+ }
65
+
66
+
67
+ #options = {}
68
+
69
+ /**
70
+ * @param {object} data
71
+ * @param {object} [options] External dependencies (AppRunner, SSRServer, etc.)
72
+ */
73
+ constructor(data = {}, options = {}) {
74
+ super(data)
75
+ this.#options = options
76
+ /** @type {string|null} */ this.command
77
+ /** @type {string} */ this.data
78
+ /** @type {string} */ this.index
79
+ /** @type {string} */ this.locale
80
+ /** @type {string} */ this.port
81
+ }
82
+
83
+ async *run() {
84
+ yield log('info', '📡 NaN0Web Engine OLMUI Shell Ready')
85
+
86
+ if (!this.command || this.command === 'help') {
87
+ const res = yield ask('Shell', ShellModel)
88
+ if (res.cancelled) return
89
+ Object.assign(this, res.value)
90
+ }
91
+
92
+ if (this.command !== 'cli' && this.command !== 'help') {
93
+ yield log('info', `📡 Executing command: ${this.command}...`)
94
+ }
95
+
96
+ switch (this.command) {
97
+ case 'run':
98
+ return yield* this.#runEngine()
99
+ case 'cli':
100
+ return yield* this.#runCli()
101
+ case 'config':
102
+ return yield* this.#runConfig()
103
+ case 'build':
104
+ return yield* this.#runBuild()
105
+ case 'dev':
106
+ return yield* this.#runDev()
107
+ case 'test:ssg':
108
+ case 'ssg:gallery':
109
+ case 'test:web':
110
+ case 'web:gallery':
111
+ return yield* this.#runNpmScript(this.command)
112
+ default:
113
+ yield log('error', `Unknown command: ${this.command}`)
114
+ }
115
+
116
+ this.command = null
117
+ }
118
+
119
+ async *#runCli() {
120
+ const { spawn, locale, dsn } = this.#options
121
+ if (!spawn) {
122
+ yield log('error', 'Spawn utility missing. CLI mode requires Node environment.')
123
+ return
124
+ }
125
+
126
+ const bankFrame = ' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ '
127
+ yield log(
128
+ 'success',
129
+ `\x1b[1m\n${bankFrame}\n📡 Launching Sub-App CLI...\n${bankFrame}\n\x1b[22m`,
130
+ )
131
+
132
+ const absPath = (await import('node:path')).resolve('src/ui/cli/index.js')
133
+ const { existsSync } = await import('node:fs')
134
+ if (!existsSync(absPath)) {
135
+ yield log(
136
+ 'error',
137
+ `No CLI runner found at ${absPath}. Ensure you are in the application root.`,
138
+ )
139
+ return
140
+ }
141
+
142
+ const args = [absPath]
143
+ if (this.locale) args.push('--locale', this.locale)
144
+ if (this.data) args.push('--data', this.data)
145
+
146
+ const extra = (process.argv || []).slice(3)
147
+ args.push(...extra)
148
+
149
+ try {
150
+ const code = await spawn('node', args, { stdio: 'inherit' })
151
+ if (code !== 0) yield log('error', `CLI exited with code ${code}`)
152
+ } catch (/** @type {any} */ e) {
153
+ yield log('error', `Failed to spawn CLI: ${e.message}`)
154
+ }
155
+ }
156
+
157
+ async *#runEngine() {
158
+ const { AppRunner, SSRServer } = this.#options
159
+ if (!AppRunner) return yield log('error', 'AppRunner dependency missing')
160
+
161
+ const runner = new AppRunner({
162
+ dsn: this.data,
163
+ port: this.port,
164
+ locale: this.locale,
165
+ directoryIndex: this.index,
166
+ })
167
+ for await (const msg of runner.run()) {
168
+ yield log('info', msg)
169
+ }
170
+
171
+ const server = new SSRServer(runner)
172
+ const port = runner.config?.port || 3000
173
+ const { protocol } = await server.listen(port)
174
+
175
+ yield log('success', `\n🌐 Server running on ${protocol}://localhost:${port}`)
176
+
177
+ // Keep alive in CLI mode
178
+ if (typeof process !== 'undefined') {
179
+ while (true) {
180
+ await new Promise((r) => setTimeout(r, 60000))
181
+ }
182
+ }
183
+ }
184
+
185
+ async *#runConfig() {
186
+ const { NaN0WebConfig, DBwithFSDriver } = this.#options
187
+ const res = yield ask('config', NaN0WebConfig)
188
+ if (res.cancelled) return
189
+
190
+ const data = res.value
191
+ if (typeof process !== 'undefined' && DBwithFSDriver) {
192
+ const db = new DBwithFSDriver({ cwd: process.cwd() })
193
+ await db.connect()
194
+ await db.saveDocument('nan0web.config.yaml', {
195
+ name: data.name,
196
+ dsn: data.data || data.dsn,
197
+ locale: data.locale,
198
+ port: data.port,
199
+ directoryIndex: data.index,
200
+ })
201
+ yield log('success', '\n✅ Config saved to nan0web.config.yaml')
202
+ }
203
+ }
204
+
205
+ async *#runBuild() {
206
+ const { spawn, AppRunner, SSRServer } = this.#options
207
+ if (!spawn) return yield log('error', 'Spawn missing')
208
+
209
+ const { existsSync } = await import('node:fs')
210
+ const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
211
+ existsSync('vite.config.js') ? 'vite.config.js' : null
212
+
213
+ if (viteConfig) {
214
+ yield log('info', `🛠 Building UI (Vite using ${viteConfig})...`)
215
+ const exitCode = await spawn('npx', ['vite', 'build', '-c', viteConfig])
216
+ if (exitCode !== 0) yield log('error', '⚠️ Vite build failed.')
217
+ }
218
+
219
+ const runner = new AppRunner({
220
+ dsn: this.data,
221
+ locale: this.locale,
222
+ directoryIndex: this.index,
223
+ })
224
+ for await (const msg of runner.run()) yield log('info', msg)
225
+ const server = new SSRServer(runner)
226
+ const stats = await server.exportStatic('dist')
227
+ yield log('success', `✅ Built ${stats.count}/${stats.total} pages into /dist`)
228
+ }
229
+
230
+ async *#runDev() {
231
+ const { spawn } = this.#options
232
+ if (!spawn) return yield log('error', 'Dev mode requires spawn')
233
+ yield log('info', '🧬 Starting VITE Dev Server...')
234
+ await spawn('npx', ['vite'], { stdio: 'inherit' })
235
+ }
236
+
237
+ async *#runNpmScript(script) {
238
+ const { spawn } = this.#options
239
+ if (!spawn) return
240
+ yield log('info', `🔭 Running npm run ${script}...`)
241
+ await spawn('npm', ['run', script], { stdio: 'inherit' })
242
+ }
243
+ }
@@ -1,10 +1,17 @@
1
1
  import { Model } from '@nan0web/types'
2
+ import { show, result } from '../../core/Intent.js'
2
3
 
3
4
  /**
4
5
  * Model-as-Schema for Table Data component.
5
6
  * Displays tabular string data in rows and columns.
6
7
  */
7
8
  export class TableModel extends Model {
9
+ static $id = '@nan0web/ui/TableModel'
10
+
11
+ static UI = {
12
+ displayingTable: 'Displaying table with {count} rows',
13
+ }
14
+
8
15
  static columns = {
9
16
  help: 'Array of column headers',
10
17
  type: 'string[]',
@@ -34,14 +41,11 @@ export class TableModel extends Model {
34
41
  * @returns {AsyncGenerator<any, any, any>}
35
42
  */
36
43
  async *run() {
37
- yield {
38
- type: 'log',
39
- level: 'info',
40
- message: `Displaying table with ${this.rows?.length || 0} rows`,
44
+ yield show(this._.t(TableModel.UI.displayingTable, { count: this.rows?.length || 0 }), 'info', {
41
45
  component: 'Table',
42
46
  model: this,
43
- }
47
+ })
44
48
 
45
- return { type: 'result', data: { rowsCount: this.rows?.length || 0 } }
49
+ return result({ rowsCount: this.rows?.length || 0 })
46
50
  }
47
51
  }
@@ -1,9 +1,16 @@
1
1
  import { Model } from '@nan0web/types'
2
+ import { show, result } from '../../core/Intent.js'
2
3
 
3
4
  /**
4
5
  * Model-as-Schema for Toast notification.
5
6
  */
6
7
  export class ToastModel extends Model {
8
+ static $id = '@nan0web/ui/ToastModel'
9
+
10
+ static UI = {
11
+ toastLog: '{message}',
12
+ }
13
+
7
14
  static variant = {
8
15
  help: 'Notification color scheme',
9
16
  default: 'info',
@@ -37,14 +44,11 @@ export class ToastModel extends Model {
37
44
  * @returns {AsyncGenerator<any, any, any>}
38
45
  */
39
46
  async *run() {
40
- yield {
41
- type: 'log',
42
- level: this.variant === 'error' ? 'error' : 'info',
43
- message: this.message,
47
+ yield show(this._.t(ToastModel.UI.toastLog, { message: this.message }), this.variant === 'error' ? 'error' : 'info', {
44
48
  component: 'Toast',
45
49
  model: this,
46
- }
50
+ })
47
51
 
48
- return { type: 'result', data: { shown: true } }
52
+ return result({ dismissed: true })
49
53
  }
50
54
  }
@@ -20,6 +20,7 @@ export { GalleryModel } from './GalleryModel.js'
20
20
  export { PriceModel } from './PriceModel.js'
21
21
  export { PricingModel } from './PricingModel.js'
22
22
  export { PricingSectionModel } from './PricingSectionModel.js'
23
+ export { FeatureGridModel, FeatureItemModel } from './FeatureGridModel.js'
23
24
 
24
25
  // Hero
25
26
 
@@ -39,7 +40,8 @@ export { HeaderConfigModel } from './HeaderConfigModel.js'
39
40
  export { FooterVisibilityModel } from './FooterVisibilityModel.js'
40
41
  export { FooterConfigModel } from './FooterConfigModel.js'
41
42
 
42
- // Business Critical
43
43
  export { EmptyStateModel } from './EmptyStateModel.js'
44
44
  export { BannerModel } from './BannerModel.js'
45
45
  export { ProfileDropdownModel } from './ProfileDropdownModel.js'
46
+ export { MarkdownModel } from './MarkdownModel.js'
47
+ export { ShellModel } from './ShellModel.js'
@@ -1,13 +1,19 @@
1
+ // Model runner (Model as App)
2
+ /** @typedef {import('./ModelAsApp.js').ModelAsAppOptions} ModelAsAppOptions */
3
+ export { ModelAsApp } from './ModelAsApp.js'
4
+
1
5
  // Domain Models — OLMUI Model-as-Schema
2
6
  export { SandboxModel } from './SandboxModel.js'
7
+ export { Content } from './Content.js'
8
+ export { Document } from './Document.js'
3
9
  export { ShowcaseAppModel } from './ShowcaseAppModel.js'
4
10
  export { default as Navigation } from './Navigation.js'
5
- export { Language } from '@nan0web/i18n/src/domain/Language.js'
6
11
 
7
12
  // Layout Models (Phase 1)
8
- export { default as HeaderModel } from './HeaderModel.js'
9
- export { default as FooterModel } from './FooterModel.js'
10
- export { default as HeroModel } from './HeroModel.js'
13
+ export { LayoutModel } from './LayoutModel.js'
14
+ export { HeaderModel } from './HeaderModel.js'
15
+ export { FooterModel } from './FooterModel.js'
16
+ export { HeroModel } from './HeroModel.js'
11
17
 
12
18
  // Component Models
13
19
  export {
@@ -24,6 +30,8 @@ export {
24
30
  AccordionModel,
25
31
  GalleryModel,
26
32
  PriceModel,
33
+ FeatureGridModel,
34
+ FeatureItemModel,
27
35
  PricingModel,
28
36
  CommentModel,
29
37
  TestimonialModel,
@@ -39,3 +47,5 @@ export {
39
47
  BannerModel,
40
48
  ProfileDropdownModel,
41
49
  } from './components/index.js'
50
+
51
+ export { ShellModel } from './components/ShellModel.js'
package/src/index.js CHANGED
@@ -12,7 +12,6 @@ import App from './App/index.js'
12
12
  export { Frame, FrameProps, Locale, StdIn, StdOut, View, RenderOptions, Model, Component, App }
13
13
  export { format } from './format.js'
14
14
  export { default as Navigation } from './domain/Navigation.js'
15
- export { Language } from '@nan0web/i18n/src/domain/Language.js'
16
15
 
17
16
  // export default App
18
17
  export { default as FormMessage } from './core/Form/Message.js'
@@ -27,6 +26,28 @@ export { default as Error, CancelError } from './core/Error/index.js'
27
26
  export { default as UiAdapter } from './core/UiAdapter.js'
28
27
 
29
28
  // OLMUI Generator Engine
30
- export { validateIntent, ask, progress, log, result, INTENT_TYPES, isModelSchema } from './core/Intent.js'
29
+ /** @typedef {import('./core/Intent.js').LogLevel} LogLevel */
30
+ /** @typedef {import('./core/Intent.js').ShowLevel} ShowLevel */
31
+ /** @typedef {import('./core/Intent.js').FieldSchema} FieldSchema */
32
+ /** @typedef {import('./core/Intent.js').Intent} Intent */
33
+ /** @typedef {import('./core/Intent.js').IntentResponse} IntentResponse */
34
+ /** @typedef {import('./core/Intent.js').AskIntent} AskIntent */
35
+ /** @typedef {import('./core/Intent.js').ProgressIntent} ProgressIntent */
36
+ /** @typedef {import('./core/Intent.js').ProgressOptions} ProgressOptions */
37
+ /** @typedef {import('./core/Intent.js').LogIntent} LogIntent */
38
+ /** @typedef {import('./core/Intent.js').ShowIntent} ShowIntent */
39
+ /** @typedef {import('./core/Intent.js').RenderIntent} RenderIntent */
40
+ /** @typedef {import('./core/Intent.js').ResultIntent} ResultIntent */
41
+ /** @typedef {import('./core/Intent.js').IntentType} IntentType */
42
+ /** @typedef {import('./core/Intent.js').AskResponse} AskResponse */
43
+ /** @typedef {import('./core/Intent.js').AbortResponse} AbortResponse */
44
+ /** @typedef {import('./core/Intent.js').ShowData} ShowData */
45
+ /** @typedef {import('./core/InputAdapter.js').AskOptions} AskOptions */
46
+ export * from './core/Intent.js'
47
+
31
48
  export { IntentErrorModel } from './core/IntentErrorModel.js'
32
49
  export { runGenerator } from './core/GeneratorRunner.js'
50
+ export { buildNan0SpecFromTrace } from './testing/CrashReporter.js'
51
+
52
+ /** @typedef {import('./domain/index.js').ModelAsAppOptions} ModelAsAppOptions */
53
+ export * from './domain/index.js'
package/src/inspect.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './testing/SnapshotRunner.js'
2
+ export * from './testing/verifySnapshot.js'
@@ -0,0 +1,59 @@
1
+ import InputAdapter from '../core/InputAdapter.js'
2
+
3
+ /**
4
+ * Deterministic Scenario Adapter for OLMUI Testing.
5
+ *
6
+ * Drives the Model generator through a predefined script of responses,
7
+ * enabling millisecond-fast verification of complex business logic.
8
+ */
9
+ export default class ScenarioAdapter extends InputAdapter {
10
+ /**
11
+ * @param {Array<{field: string, value: any, cancelled?: boolean}>} [scenario=[]]
12
+ */
13
+ constructor(scenario = []) {
14
+ super()
15
+ this.scenario = scenario
16
+ this.intents = [] // Recorded intents for verification
17
+ this.console = {
18
+ info: () => {},
19
+ warn: () => {},
20
+ error: () => {},
21
+ debug: () => {},
22
+ log: () => {},
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @param {import('../core/Intent.js').AskIntent} intent
28
+ * @returns {Promise<import('../core/Intent.js').AskResponse>}
29
+ */
30
+ async askIntent(intent) {
31
+ this.intents.push(intent)
32
+ const match = this.scenario.find((s) => s.field === intent.field)
33
+ if (match) {
34
+ return { value: match.value, cancelled: !!match.cancelled }
35
+ }
36
+ // If no specific match, try to use the first available answer or default
37
+ return { value: null, cancelled: false }
38
+ }
39
+
40
+ /** @param {import('../core/Intent.js').ProgressIntent} intent */
41
+ async progressIntent(intent) {
42
+ this.intents.push(intent)
43
+ }
44
+
45
+ /** @param {import('../core/Intent.js').ShowIntent} intent */
46
+ async showIntent(intent) {
47
+ this.intents.push(intent)
48
+ }
49
+
50
+ /** @param {import('../core/Intent.js').RenderIntent} intent */
51
+ async renderIntent(intent) {
52
+ this.intents.push(intent)
53
+ }
54
+
55
+ /** @param {import('../core/Intent.js').ResultIntent} intent */
56
+ async resultIntent(intent) {
57
+ this.intents.push(intent)
58
+ }
59
+ }