@nan0web/ui 1.12.3 → 3.1.1

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 (136) hide show
  1. package/package.json +29 -20
  2. package/src/Component/index.js +1 -5
  3. package/src/Model/Element.js +183 -0
  4. package/src/Theme/AppTheme.js +19 -0
  5. package/src/Theme/CustomTheme.js +32 -0
  6. package/src/Theme/DarkLightTheme.js +34 -0
  7. package/src/Theme/Theme.js +25 -0
  8. package/src/Theme/atoms/Avatar.js +20 -0
  9. package/src/Theme/atoms/Badge.js +28 -0
  10. package/src/Theme/atoms/Button.js +88 -0
  11. package/src/Theme/atoms/Checkbox.js +26 -0
  12. package/src/Theme/atoms/Input.js +28 -0
  13. package/src/Theme/atoms/Radio.js +26 -0
  14. package/src/Theme/atoms/Select.js +16 -0
  15. package/src/Theme/atoms/TextArea.js +17 -0
  16. package/src/Theme/atoms/Typography.js +26 -0
  17. package/src/Theme/atoms/index.js +11 -0
  18. package/src/Theme/createTheme.js +22 -0
  19. package/src/Theme/index.js +20 -0
  20. package/src/Theme/molecules/Card.js +24 -0
  21. package/src/Theme/molecules/index.js +3 -0
  22. package/src/Theme/organisms/Modal.js +24 -0
  23. package/src/Theme/organisms/index.js +3 -0
  24. package/src/Theme/presets/HighContrastTheme.js +65 -0
  25. package/src/Theme/presets/NightTheme.js +66 -0
  26. package/src/Theme/presets/index.js +4 -0
  27. package/src/Theme/tokens.js +115 -0
  28. package/src/core/InputAdapter.js +1 -2
  29. package/src/core/Intent.js +22 -8
  30. package/src/core/Message/Message.js +3 -0
  31. package/src/core/OutputAdapter.js +9 -13
  32. package/src/core/index.js +7 -4
  33. package/src/domain/ModelAsApp.js +1 -1
  34. package/src/domain/app/IntentAuditor.js +53 -0
  35. package/src/domain/app/JsIntentAuditor.js +145 -0
  36. package/src/domain/app/PyIntentAuditor.js +144 -0
  37. package/src/domain/app/SnapshotAuditor.js +8 -8
  38. package/src/domain/components/ShellModel.js +2 -2
  39. package/src/index.js +35 -9
  40. package/src/inspect.js +3 -0
  41. package/src/utils/format.js +21 -0
  42. package/src/utils/processI18n.js +27 -0
  43. package/src/utils/resolveContext.js +79 -0
  44. package/types/Component/index.d.ts +1 -5
  45. package/types/Model/Element.d.ts +87 -0
  46. package/types/Theme/AppTheme.d.ts +14 -0
  47. package/types/Theme/CustomTheme.d.ts +21 -0
  48. package/types/Theme/DarkLightTheme.d.ts +16 -0
  49. package/types/Theme/Theme.d.ts +18 -0
  50. package/types/Theme/atoms/Avatar.d.ts +14 -0
  51. package/types/Theme/atoms/Badge.d.ts +22 -0
  52. package/types/Theme/atoms/Button.d.ts +144 -0
  53. package/types/Theme/atoms/Checkbox.d.ts +20 -0
  54. package/types/Theme/atoms/Input.d.ts +22 -0
  55. package/types/Theme/atoms/Radio.d.ts +20 -0
  56. package/types/Theme/atoms/Select.d.ts +15 -0
  57. package/types/Theme/atoms/TextArea.d.ts +17 -0
  58. package/types/Theme/atoms/Typography.d.ts +47 -0
  59. package/types/Theme/atoms/index.d.ts +10 -0
  60. package/types/Theme/createTheme.d.ts +7 -0
  61. package/types/Theme/index.d.ts +10 -0
  62. package/types/Theme/molecules/Card.d.ts +18 -0
  63. package/types/Theme/molecules/index.d.ts +2 -0
  64. package/types/Theme/organisms/Modal.d.ts +18 -0
  65. package/types/Theme/organisms/index.d.ts +2 -0
  66. package/types/Theme/presets/HighContrastTheme.d.ts +2 -0
  67. package/types/Theme/presets/NightTheme.d.ts +2 -0
  68. package/types/Theme/presets/index.d.ts +3 -0
  69. package/types/Theme/tokens.d.ts +119 -0
  70. package/types/core/Intent.d.ts +10 -7
  71. package/types/core/Message/Message.d.ts +3 -0
  72. package/types/core/OutputAdapter.d.ts +2 -4
  73. package/types/core/index.d.ts +5 -2
  74. package/types/domain/Document.d.ts +2 -1
  75. package/types/domain/FooterModel.d.ts +2 -1
  76. package/types/domain/ModelAsApp.d.ts +48 -48
  77. package/types/domain/app/IntentAuditor.d.ts +23 -0
  78. package/types/domain/app/JsIntentAuditor.d.ts +22 -0
  79. package/types/domain/app/PyIntentAuditor.d.ts +22 -0
  80. package/types/domain/app/SnapshotAuditor.d.ts +5 -6
  81. package/types/domain/components/ShellModel.d.ts +1 -5
  82. package/types/index.d.ts +7 -9
  83. package/types/inspect.d.ts +3 -0
  84. package/types/utils/format.d.ts +5 -0
  85. package/types/utils/processI18n.d.ts +8 -0
  86. package/types/utils/resolveContext.d.ts +21 -0
  87. package/src/App/Command/DepsCommand.js +0 -24
  88. package/src/App/Core/CoreApp.js +0 -125
  89. package/src/App/Core/UI.js +0 -63
  90. package/src/App/Core/Widget.js +0 -61
  91. package/src/App/Core/index.js +0 -11
  92. package/src/App/Scenario.js +0 -45
  93. package/src/App/User/Command/Message.js +0 -3
  94. package/src/App/User/Command/index.js +0 -5
  95. package/src/App/User/UserApp.js +0 -85
  96. package/src/App/User/UserUI.js +0 -20
  97. package/src/App/User/index.js +0 -9
  98. package/src/App/index.js +0 -14
  99. package/src/Component/Process/Input.js +0 -63
  100. package/src/Component/Process/Process.js +0 -24
  101. package/src/Component/Process/index.js +0 -5
  102. package/src/Component/Welcome/Input.js +0 -48
  103. package/src/Component/Welcome/Welcome.js +0 -22
  104. package/src/Component/Welcome/index.js +0 -5
  105. package/src/Frame/Frame.js +0 -608
  106. package/src/Frame/Props.js +0 -96
  107. package/src/StdIn.js +0 -100
  108. package/src/StdOut.js +0 -95
  109. package/src/View/RenderOptions.js +0 -48
  110. package/src/View/View.js +0 -306
  111. package/src/core/Message/index.js +0 -6
  112. package/types/App/Command/DepsCommand.d.ts +0 -14
  113. package/types/App/Core/CoreApp.d.ts +0 -70
  114. package/types/App/Core/UI.d.ts +0 -38
  115. package/types/App/Core/Widget.d.ts +0 -39
  116. package/types/App/Core/index.d.ts +0 -10
  117. package/types/App/Scenario.d.ts +0 -26
  118. package/types/App/User/Command/Message.d.ts +0 -2
  119. package/types/App/User/Command/index.d.ts +0 -3
  120. package/types/App/User/UserApp.d.ts +0 -41
  121. package/types/App/User/UserUI.d.ts +0 -9
  122. package/types/App/User/index.d.ts +0 -8
  123. package/types/App/index.d.ts +0 -12
  124. package/types/Component/Process/Input.d.ts +0 -48
  125. package/types/Component/Process/Process.d.ts +0 -13
  126. package/types/Component/Process/index.d.ts +0 -4
  127. package/types/Component/Welcome/Input.d.ts +0 -34
  128. package/types/Component/Welcome/Welcome.d.ts +0 -13
  129. package/types/Component/Welcome/index.d.ts +0 -4
  130. package/types/Frame/Frame.d.ts +0 -186
  131. package/types/Frame/Props.d.ts +0 -77
  132. package/types/StdIn.d.ts +0 -62
  133. package/types/StdOut.d.ts +0 -52
  134. package/types/View/RenderOptions.d.ts +0 -29
  135. package/types/View/View.d.ts +0 -124
  136. package/types/core/Message/index.d.ts +0 -4
@@ -0,0 +1,145 @@
1
+ import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
2
+ import { result, show, progress } from '../../core/Intent.js'
3
+ import { IntentAuditor } from './IntentAuditor.js'
4
+
5
+ /**
6
+ * JsIntentAuditor — Specialized auditor for JS/TS output hygiene.
7
+ */
8
+ export class JsIntentAuditor extends AuditorModel {
9
+ /** @type {string[]} Directories to ignore during scanning */
10
+ static IGNORE_DIRS = ['node_modules', '.git', '.venv', '.datasets', 'dist', 'build', 'types', 'play', 'test']
11
+
12
+ /**
13
+ * Checks if a directory or file should be ignored.
14
+ * @param {string} name
15
+ * @returns {boolean}
16
+ */
17
+ static isIgnored(name) {
18
+ return name.startsWith('.') || JsIntentAuditor.IGNORE_DIRS.includes(name)
19
+ }
20
+
21
+ /**
22
+ * Run the JS-specific intent audit.
23
+ * @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
24
+ */
25
+ async *run() {
26
+ const { t } = /** @type {any} */ (this)._
27
+ yield show(t(IntentAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
28
+
29
+ const fsDb = /** @type {any} */ (this)._.db
30
+ if (!fsDb) {
31
+ yield show(t(IntentAuditor.UI.errorDb), 'error')
32
+ return result({ success: false })
33
+ }
34
+
35
+ const files = []
36
+ const targetDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
37
+
38
+ try {
39
+ for await (const entry of fsDb.browse(targetDir, { depth: Infinity })) {
40
+ if (JsIntentAuditor.isIgnored(entry.name)) continue
41
+
42
+ if (
43
+ entry.isFile &&
44
+ /\.(js|ts|jsx|tsx)$/.test(entry.name) &&
45
+ !entry.name.endsWith('.test.js') &&
46
+ !entry.name.endsWith('.story.js') &&
47
+ !entry.path.includes('/test/') &&
48
+ !entry.path.includes('/play/')
49
+ ) {
50
+ files.push(entry.path)
51
+ }
52
+ }
53
+ } catch (e) {
54
+ // Directory might be missing
55
+ }
56
+
57
+ if (files.length === 0) {
58
+ yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
59
+ return result({ success: true })
60
+ }
61
+
62
+ let hasErrors = false
63
+ const allErrors = []
64
+
65
+ for (const file of files) {
66
+ const displayFile = file.startsWith('@app/') ? file.slice(5) : file
67
+ const content = await fsDb.fetch(file)
68
+ const contentString = typeof content === 'string' ? content : JSON.stringify(content)
69
+
70
+ const fileErrors = JsIntentAuditor.inspectFileContent(contentString, t)
71
+ if (fileErrors.length > 0) {
72
+ const errorMessages = fileErrors.join('; ')
73
+ yield show(
74
+ t(IntentAuditor.UI.auditFailed, { file: displayFile, errors: errorMessages }),
75
+ 'error',
76
+ )
77
+ allErrors.push(...fileErrors.map((e) => ({ file: displayFile, error: e })))
78
+ hasErrors = true
79
+ } else {
80
+ yield progress(t(IntentAuditor.UI.auditPassed, { file: displayFile }))
81
+ }
82
+ }
83
+
84
+ if (hasErrors) {
85
+ yield show(t(IntentAuditor.UI.doneErrors, {}), 'error')
86
+ return result({ success: false, errors: allErrors })
87
+ }
88
+
89
+ yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
90
+ return result({ success: true })
91
+ }
92
+
93
+ /**
94
+ * Inspects file content for console.* or process.* writes.
95
+ * @param {string} content Content of the file.
96
+ * @param {import('@nan0web/i18n').TFunction} t Translate function.
97
+ * @returns {string[]} List of error messages.
98
+ */
99
+ static inspectFileContent(content, t) {
100
+ const errors = []
101
+ const lines = content.split('\n')
102
+
103
+ lines.forEach((lineText, index) => {
104
+ const lineNum = index + 1
105
+ const trimmed = lineText.trim()
106
+
107
+ // Skip comments
108
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
109
+ return
110
+ }
111
+
112
+ // 1. Thorough console.* statement scan (including table, trace, etc.)
113
+ const consoleMatch = trimmed.match(/console\.(log|error|warn|info|debug|dir|table|trace|assert)\(/)
114
+ if (consoleMatch) {
115
+ const codeBeforeComment = trimmed.split('//')[0]
116
+ if (codeBeforeComment.includes(consoleMatch[0])) {
117
+ errors.push(
118
+ t(IntentAuditor.UI.errorConsoleLeak, {
119
+ line: lineNum,
120
+ match: consoleMatch[0] + '...',
121
+ }),
122
+ )
123
+ }
124
+ }
125
+
126
+ // 2. process.stdout/stderr write scan
127
+ const processMatch = trimmed.match(/process\.(stdout|stderr)\.write\(/)
128
+ if (processMatch) {
129
+ const codeBeforeComment = trimmed.split('//')[0]
130
+ if (codeBeforeComment.includes(processMatch[0])) {
131
+ errors.push(
132
+ t(IntentAuditor.UI.errorProcessLeak, {
133
+ line: lineNum,
134
+ match: processMatch[0] + '...',
135
+ }),
136
+ )
137
+ }
138
+ }
139
+ })
140
+
141
+ return errors
142
+ }
143
+ }
144
+
145
+ export default JsIntentAuditor
@@ -0,0 +1,144 @@
1
+ import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
2
+ import { result, show, progress } from '../../core/Intent.js'
3
+ import { IntentAuditor } from './IntentAuditor.js'
4
+
5
+ /**
6
+ * PyIntentAuditor — Specialized auditor for Python output hygiene.
7
+ */
8
+ export class PyIntentAuditor extends AuditorModel {
9
+ /** @type {string[]} Directories to ignore during scanning */
10
+ static IGNORE_DIRS = ['node_modules', '.git', '.venv', '.datasets', 'dist', 'build', 'types', 'play', 'test', 'tests']
11
+
12
+ /**
13
+ * Checks if a directory or file should be ignored.
14
+ * @param {string} name
15
+ * @returns {boolean}
16
+ */
17
+ static isIgnored(name) {
18
+ return name.startsWith('.') || PyIntentAuditor.IGNORE_DIRS.includes(name)
19
+ }
20
+
21
+ /**
22
+ * Run the Python-specific intent audit.
23
+ * @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
24
+ */
25
+ async *run() {
26
+ const { t } = /** @type {any} */ (this)._
27
+ yield show(t(IntentAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
28
+
29
+ const fsDb = /** @type {any} */ (this)._.db
30
+ if (!fsDb) {
31
+ yield show(t(IntentAuditor.UI.errorDb), 'error')
32
+ return result({ success: false })
33
+ }
34
+
35
+ const files = []
36
+ const targetDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
37
+
38
+ try {
39
+ for await (const entry of fsDb.browse(targetDir, { depth: Infinity })) {
40
+ if (PyIntentAuditor.isIgnored(entry.name)) continue
41
+
42
+ if (
43
+ entry.isFile &&
44
+ /\.py$/.test(entry.name) &&
45
+ !entry.name.startsWith('test_') &&
46
+ !entry.name.endsWith('_test.py') &&
47
+ !entry.path.includes('/tests/')
48
+ ) {
49
+ files.push(entry.path)
50
+ }
51
+ }
52
+ } catch (e) {
53
+ // Directory might be missing
54
+ }
55
+
56
+ if (files.length === 0) {
57
+ yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
58
+ return result({ success: true })
59
+ }
60
+
61
+ let hasErrors = false
62
+ const allErrors = []
63
+
64
+ for (const file of files) {
65
+ const displayFile = file.startsWith('@app/') ? file.slice(5) : file
66
+ const content = await fsDb.fetch(file)
67
+ const contentString = typeof content === 'string' ? content : JSON.stringify(content)
68
+
69
+ const fileErrors = PyIntentAuditor.inspectFileContent(contentString, t)
70
+ if (fileErrors.length > 0) {
71
+ const errorMessages = fileErrors.join('; ')
72
+ yield show(
73
+ t(IntentAuditor.UI.auditFailed, { file: displayFile, errors: errorMessages }),
74
+ 'error',
75
+ )
76
+ allErrors.push(...fileErrors.map((e) => ({ file: displayFile, error: e })))
77
+ hasErrors = true
78
+ } else {
79
+ yield progress(t(IntentAuditor.UI.auditPassed, { file: displayFile }))
80
+ }
81
+ }
82
+
83
+ if (hasErrors) {
84
+ yield show(t(IntentAuditor.UI.doneErrors, {}), 'error')
85
+ return result({ success: false, errors: allErrors })
86
+ }
87
+
88
+ yield show(t(IntentAuditor.UI.doneSuccess, {}), 'success')
89
+ return result({ success: true })
90
+ }
91
+
92
+ /**
93
+ * Inspects Python file content for print or sys.stdout/stderr writes.
94
+ * @param {string} content Content of the file.
95
+ * @param {import('@nan0web/i18n').TFunction} t Translate function.
96
+ * @returns {string[]} List of error messages.
97
+ */
98
+ static inspectFileContent(content, t) {
99
+ const errors = []
100
+ const lines = content.split('\n')
101
+
102
+ lines.forEach((lineText, index) => {
103
+ const lineNum = index + 1
104
+ const trimmed = lineText.trim()
105
+
106
+ // Python Comment skip
107
+ if (trimmed.startsWith('#')) {
108
+ return
109
+ }
110
+
111
+ // 1. Python print statement check
112
+ const printMatch = trimmed.match(/\bprint\(/)
113
+ if (printMatch) {
114
+ const codeBeforeComment = trimmed.split('#')[0]
115
+ if (codeBeforeComment.includes(printMatch[0])) {
116
+ errors.push(
117
+ t(IntentAuditor.UI.errorPrintLeak, {
118
+ line: lineNum,
119
+ match: 'print(...)',
120
+ }),
121
+ )
122
+ }
123
+ }
124
+
125
+ // 2. sys.stdout/stderr write check
126
+ const sysMatch = trimmed.match(/\bsys\.(stdout|stderr)\.write\(/)
127
+ if (sysMatch) {
128
+ const codeBeforeComment = trimmed.split('#')[0]
129
+ if (codeBeforeComment.includes(sysMatch[0])) {
130
+ errors.push(
131
+ t(IntentAuditor.UI.errorSysWriteLeak, {
132
+ line: lineNum,
133
+ match: sysMatch[0] + '...',
134
+ }),
135
+ )
136
+ }
137
+ }
138
+ })
139
+
140
+ return errors
141
+ }
142
+ }
143
+
144
+ export default PyIntentAuditor
@@ -1,14 +1,14 @@
1
1
  import { NaN0 } from '@nan0web/types'
2
2
  import { AuditorModel } from '@nan0web/inspect/domain/AuditorModel'
3
3
 
4
- import { result, show } from '../../core/Intent.js'
4
+ import { result, show, progress } from '../../core/Intent.js'
5
5
 
6
6
  /**
7
7
  * SnapshotAuditor — Zero-Hallucination Snapshot Validation (Model-as-Schema v2).
8
8
  * Parses snapshots without evaluating the app logic and detects artifacts.
9
9
  */
10
10
  export class SnapshotAuditor extends AuditorModel {
11
- static alias = 'audit'
11
+ static alias = 'snapshots'
12
12
 
13
13
  static dir = {
14
14
  type: 'string',
@@ -122,7 +122,7 @@ export class SnapshotAuditor extends AuditorModel {
122
122
 
123
123
  /**
124
124
  * Scans data directories to build a word set for each language.
125
- * @param {import('@nan0web/db').DB} fsDb FileSystem DB.
125
+ * @param {any} fsDb FileSystem DB.
126
126
  * @param {string} data
127
127
  * @returns {Promise<Record<string, Set<string>>>}
128
128
  */
@@ -170,11 +170,11 @@ export class SnapshotAuditor extends AuditorModel {
170
170
  * @returns {AsyncGenerator<import('@nan0web/ui').Intent, import('@nan0web/ui').ResultIntent, any>}
171
171
  */
172
172
  async *run() {
173
- const { t } = this._
173
+ const { t } = /** @type {any} */ (this)._
174
174
 
175
- yield show(t(SnapshotAuditor.UI.starting, { dir: this.dir }))
175
+ yield show(t(SnapshotAuditor.UI.starting, { dir: /** @type {any} */ (this).dir }))
176
176
 
177
- const fsDb = this._.db
177
+ const fsDb = /** @type {any} */ (this)._.db
178
178
 
179
179
  if (!fsDb) {
180
180
  yield show(t(SnapshotAuditor.UI.errorDb), 'error')
@@ -182,7 +182,7 @@ export class SnapshotAuditor extends AuditorModel {
182
182
  }
183
183
 
184
184
  const files = []
185
- const snapshotsDir = '@app/' + (this.dir || 'snapshots/core')
185
+ const snapshotsDir = fsDb.resolveSync(/** @type {any} */ (this).dir)
186
186
 
187
187
  // Use robust DB.browse for recursive snapshot detection
188
188
  try {
@@ -234,7 +234,7 @@ export class SnapshotAuditor extends AuditorModel {
234
234
  allErrors.push(...audit.errors.map((e) => ({ file: displayFile, error: e })))
235
235
  hasErrors = true
236
236
  } else {
237
- yield show(t(SnapshotAuditor.UI.auditPassed, { file: displayFile }), 'success')
237
+ yield progress(t(SnapshotAuditor.UI.auditPassed, { file: displayFile }))
238
238
  }
239
239
  }
240
240
 
@@ -37,7 +37,7 @@ export class ShellModel extends Model {
37
37
  ],
38
38
  required: true,
39
39
  }
40
-
40
+
41
41
  static data = {
42
42
  help: 'Data source (DSN)',
43
43
  type: 'string',
@@ -207,7 +207,7 @@ export class ShellModel extends Model {
207
207
  if (!spawn) return yield log('error', 'Spawn missing')
208
208
 
209
209
  const { existsSync } = await import('node:fs')
210
- const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
210
+ const viteConfig = existsSync('vite.docs.js') ? 'vite.docs.js' :
211
211
  existsSync('vite.config.js') ? 'vite.config.js' : null
212
212
 
213
213
  if (viteConfig) {
package/src/index.js CHANGED
@@ -1,16 +1,20 @@
1
- import Frame from './Frame/Frame.js'
2
1
  import Locale from './Locale.js'
3
- import StdIn from './StdIn.js'
4
- import StdOut from './StdOut.js'
5
- import View from './View/View.js'
6
- import RenderOptions from './View/RenderOptions.js'
7
- import FrameProps from './Frame/Props.js'
8
2
  import { Model } from '@nan0web/types'
9
3
  import Models from './Model/index.js'
10
4
  import Component from './Component/index.js'
11
- import App from './App/index.js'
12
5
 
13
- export { Frame, FrameProps, Locale, StdIn, StdOut, View, RenderOptions, Model, Models, Component, App }
6
+ export { Locale, Model, Models, Component }
7
+ export { default as Element } from './Model/Element.js'
8
+ export {
9
+ default as Theme,
10
+ getUserTheme,
11
+ CustomTheme,
12
+ DarkLightTheme,
13
+ NightTheme,
14
+ createTheme,
15
+ } from './Theme/index.js'
16
+ export { resolveContext } from './utils/resolveContext.js'
17
+ export { processI18n } from './utils/processI18n.js'
14
18
  export { format } from './format.js'
15
19
  export { default as Navigation } from './domain/Navigation.js'
16
20
 
@@ -22,10 +26,10 @@ export { default as OutputAdapter } from './core/OutputAdapter.js'
22
26
  export { default as OutputMessage } from './core/Message/OutputMessage.js'
23
27
  export { default as UiForm } from './core/Form/Form.js'
24
28
  export { default as UiMessage } from './core/Message/Message.js'
25
- export { default as UiStream } from './core/Stream.js'
26
29
  export { default as Error, CancelError } from './core/Error/index.js'
27
30
  export { default as UiAdapter } from './core/UiAdapter.js'
28
31
  export { resolvePositionalArgs } from './core/resolvePositionalArgs.js'
32
+ export { tokens } from './Theme/tokens.js'
29
33
 
30
34
  // OLMUI Generator Engine
31
35
  /** @typedef {import('./core/Intent.js').LogLevel} LogLevel */
@@ -51,5 +55,27 @@ export { IntentErrorModel } from './core/IntentErrorModel.js'
51
55
  export { runGenerator } from './core/GeneratorRunner.js'
52
56
  export { buildNan0SpecFromTrace } from './testing/CrashReporter.js'
53
57
 
58
+ // Flow components
59
+ export {
60
+ runFlow,
61
+ flow,
62
+ Prompt,
63
+ Stream,
64
+ Alert,
65
+ Toast,
66
+ Badge,
67
+ Text,
68
+ Table,
69
+ Input,
70
+ Select,
71
+ Confirm,
72
+ Multiselect,
73
+ Mask,
74
+ Password,
75
+ Spinner,
76
+ Progress,
77
+ } from './core/Flow.js'
78
+ export { default as Flow } from './core/Flow.js'
79
+
54
80
  /** @typedef {import('./domain/index.js').ModelAsAppOptions} ModelAsAppOptions */
55
81
  export * from './domain/index.js'
package/src/inspect.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './testing/SnapshotRunner.js'
2
2
  export * from './testing/verifySnapshot.js'
3
3
  export { SnapshotAuditor } from './domain/app/SnapshotAuditor.js'
4
+ export { IntentAuditor } from './domain/app/IntentAuditor.js'
5
+ export { JsIntentAuditor } from './domain/app/JsIntentAuditor.js'
6
+ export { PyIntentAuditor } from './domain/app/PyIntentAuditor.js'
@@ -0,0 +1,21 @@
1
+ export const format = {
2
+ currency: (value, currency = 'UAH', locale = 'uk-UA') => {
3
+ return new Intl.NumberFormat(locale, {
4
+ style: 'currency',
5
+ currency,
6
+ maximumFractionDigits: 0
7
+ }).format(value).replace(/,/g, ' ')
8
+ },
9
+ rate: (value, locale = 'uk-UA') => {
10
+ const rate = value < 1 ? value * 100 : value
11
+ return new Intl.NumberFormat(locale, {
12
+ style: 'percent',
13
+ maximumFractionDigits: 2
14
+ }).format(rate / 100)
15
+ },
16
+ number: (value, locale = 'uk-UA') => {
17
+ return new Intl.NumberFormat(locale, {
18
+ maximumFractionDigits: 2
19
+ }).format(value).replace(/,/g, ' ')
20
+ }
21
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Обробляє i18n та замінює змінні в тексті.
3
+ * @param {any} input - Вміст для обробки.
4
+ * @param {Function} [t] - Функція перекладу.
5
+ * @param {Object} [data] - Дані для підстановки.
6
+ * @returns {any}
7
+ */
8
+ export function processI18n(input, t, data = {}) {
9
+ // If input is an object with $t property
10
+ if (input && typeof input === 'object' && input.$t) {
11
+ if (t) input = t(input.$t)
12
+ else input = input.$t
13
+ }
14
+
15
+ // Process string content
16
+ if (typeof input === 'string') {
17
+ return input.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || '—')
18
+ }
19
+
20
+ // Process arrays recursively
21
+ if (Array.isArray(input)) {
22
+ return input.map((item) => processI18n(item, t, data))
23
+ }
24
+
25
+ // Return input as is for other types
26
+ return input
27
+ }
@@ -0,0 +1,79 @@
1
+ import { Data as BaseData } from '@nan0web/db'
2
+
3
+ class Data extends BaseData {
4
+ static OBJECT_DIVIDER = '.'
5
+ }
6
+ /**
7
+ * Resolves context-aware values in strings and objects.
8
+ * Supports:
9
+ * - References: `data:currencies`, `action:save`, `fn:calculate,100,USD,UAH`
10
+ * - Templates: `{{user.name}}`, `{{currencies[0]}}`
11
+ * - Scalars: numbers, booleans, etc. (passthrough)
12
+ *
13
+ * @param {Object} context - Application context (data, actions, functions)
14
+ * @param {*} value - Raw value to resolve
15
+ * @returns {*} Resolved value
16
+ */
17
+ export function resolveContext(context, value) {
18
+ if (typeof value !== 'string') return value
19
+
20
+ // Handle data:* reference
21
+ if (value.startsWith('data:')) {
22
+ const path = value.slice(5)
23
+ return Data.find(path, context.data) ?? []
24
+ }
25
+
26
+ // Handle action:* reference
27
+ if (value.startsWith('action:')) {
28
+ const name = value.slice(7)
29
+ return context.actions?.[name]
30
+ }
31
+
32
+ // Handle fn:* reference with arguments
33
+ if (value.startsWith('fn:')) {
34
+ const [fnName, ...rawArgs] = value.slice(3).split(',')
35
+ const fn = context.functions?.[fnName]
36
+ if (typeof fn !== 'function') return undefined
37
+
38
+ // Parse arguments (numbers, booleans, strings)
39
+ const args = rawArgs.map((arg) => {
40
+ try {
41
+ return JSON.parse(arg)
42
+ } catch {
43
+ return arg // Return as-is if parsing fails
44
+ }
45
+ })
46
+
47
+ return () => fn(...args)
48
+ }
49
+
50
+ // Handle template interpolation {{key}} using Data.find
51
+ return value.replace(/{{(.*?)}}/g, (_, path) => {
52
+ const result = Data.find(path, context.data)
53
+ return result !== undefined ? result : `{{${path}}}`
54
+ })
55
+ }
56
+
57
+ /**
58
+ * Resolves a context value or returns undefined if resolution fails.
59
+ * Used internally by renderers to safely resolve props.
60
+ *
61
+ * @param {Object} ctx - Context with data, actions and functions
62
+ * @param {string} value - String value to resolve
63
+ * @returns {*} Resolved value or undefined
64
+ */
65
+ export function resolveContextValue(ctx, value) {
66
+ if (typeof value !== 'string') return undefined
67
+ if (value.startsWith('data:')) {
68
+ return Data.find(value.slice(5), ctx.data) ?? []
69
+ }
70
+ if (value.startsWith('action:')) {
71
+ return ctx.actions?.[value.slice(7)]
72
+ }
73
+ if (value.startsWith('fn:')) {
74
+ const [fnName, ...args] = value.slice(3).split(',')
75
+ const fn = ctx.functions?.[fnName]
76
+ return typeof fn === 'function' ? fn : undefined
77
+ }
78
+ return undefined
79
+ }
@@ -1,10 +1,6 @@
1
+ export { SortableList };
1
2
  declare namespace _default {
2
- export { Welcome };
3
- export { Process };
4
3
  export { SortableList };
5
4
  }
6
5
  export default _default;
7
- import Welcome from './Welcome/index.js';
8
- import Process from './Process/index.js';
9
6
  import SortableList from './SortableList/index.js';
10
- export { Welcome, Process, SortableList };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @file Element — the model of a declarative UI block.
3
+ * Format: { Button: [...], $variant: 'info', $onClick: fn }
4
+ * Framework-agnostic.
5
+ */
6
+ export default class Element {
7
+ /** @type {Record<string, string | ((val: any, key: string) => Record<string, any>)>} */
8
+ static PROP_ALIASES: Record<string, string | ((val: any, key: string) => Record<string, any>)>;
9
+ /**
10
+ * Factory method to create or return an existing `Element`.
11
+ * @param {Object} input
12
+ * @returns {Element}
13
+ */
14
+ static from(input: any): Element;
15
+ /**
16
+ * Parses $-props.
17
+ * Converts for example:
18
+ * - $style="color:red;font-size:14px" → { style: { color: 'red', 'font-size': '14px' } }
19
+ * - $onClick=fn → { onClick: fn }
20
+ * - $ariaHidden=true → { 'aria-hidden': true }
21
+ *
22
+ * @param {string} key - The name of the prop
23
+ * @param {any} value - The value of the prop
24
+ * @returns {Object} - An object { propName: newVal }
25
+ */
26
+ static parseProp(key: string, value: any): any;
27
+ /**
28
+ * Parses an inline CSS style string.
29
+ * @param {string} styleStr
30
+ * @returns {Object}
31
+ */
32
+ static parseInlineStyle(styleStr: string): any;
33
+ /**
34
+ * Extracts $-props from the block.
35
+ * @param {Object} block
36
+ * @param {boolean} keep$ - Whether to keep the prefix `$`.
37
+ * @param {Function} [flatMap] - Function that transforms a key-value pair into a new key-value pair.
38
+ * @returns {Object}
39
+ */
40
+ static extractProps(block: any, keep$?: boolean, flatMap?: Function): any;
41
+ /**
42
+ * Extracts tags (non-prefixed keys) from the block.
43
+ * @param {Object} block
44
+ * @returns {Array<[string, any]>}
45
+ */
46
+ static extractTags(block: any): Array<[string, any]>;
47
+ /**
48
+ * Creates an `Element` from a declarative block.
49
+ * @param {Object} input - For example, { Button: ["Click"], $variant: "primary" }
50
+ */
51
+ constructor(input?: any);
52
+ /**
53
+ * The component type or HTML tag (e.g., "Button", "div")
54
+ * @type {string}
55
+ */
56
+ type: string;
57
+ /**
58
+ * The content of the element.
59
+ * Can be:
60
+ * - string → text
61
+ * - true → empty
62
+ * - array → nested blocks { Icon } or text nodes
63
+ * @type {string | true | any[]}
64
+ */
65
+ content: string | true | any[];
66
+ /**
67
+ * Props extracted from $-keys.
68
+ * For example, { $onClick: fn, $variant: 'info' } → { onClick: fn, variant: 'info' }
69
+ * @type {Object}
70
+ */
71
+ props: any;
72
+ /**
73
+ * Checks if the element contains nested elements (not only text).
74
+ * @returns {boolean}
75
+ */
76
+ hasChildren(): boolean;
77
+ /**
78
+ * Checks if the content contains text fragments.
79
+ * @returns {boolean}
80
+ */
81
+ hasText(): boolean;
82
+ /**
83
+ * Returns an array of child elements (Element instances).
84
+ * @returns {Element[]}
85
+ */
86
+ getChildElements(): Element[];
87
+ }
@@ -0,0 +1,14 @@
1
+ declare const _default: {
2
+ fontFamily: string;
3
+ atoms: Partial<typeof import("./atoms/index.js")>;
4
+ molecules: typeof import("./molecules/index.js");
5
+ organisms: typeof import("./organisms/index.js");
6
+ };
7
+ export default _default;
8
+ /**
9
+ * Base application theme.
10
+ * Should be extended by app-specific themes.
11
+ */
12
+ export type AppThemeConfig = import("./Theme.js").ThemeConfig & {
13
+ fontFamily?: string;
14
+ };