@birdapi/velinstyle 1.2.1 → 1.3.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 (156) hide show
  1. package/README.de.md +148 -11
  2. package/README.md +174 -16
  3. package/cli/__fixtures__/atelier-library/showcases/01-login/app.css +1 -0
  4. package/cli/__fixtures__/atelier-library/showcases/01-login/app.js +2 -0
  5. package/cli/__fixtures__/atelier-library/showcases/01-login/index.html +5 -0
  6. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.css +1 -0
  7. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.js +2 -0
  8. package/cli/__fixtures__/atelier-library/showcases/04-pricing/index.html +5 -0
  9. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.css +1 -0
  10. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.js +2 -0
  11. package/cli/__fixtures__/atelier-library/showcases/36-calendar/index.html +5 -0
  12. package/cli/atelier-catalog.json +1267 -0
  13. package/cli/atelier-formats.js +159 -0
  14. package/cli/atelier.js +382 -0
  15. package/cli/blueprints/empty-state.html +3 -5
  16. package/cli/cli-manifest.json +441 -161
  17. package/cli/docgen/extract-cli.js +2 -1
  18. package/cli/docs-generate.js +9 -0
  19. package/cli/experience.js +173 -0
  20. package/cli/index.js +1384 -361
  21. package/cli/motion.js +738 -0
  22. package/cli/production/component-graph.json +166 -0
  23. package/cli/production/explain.js +52 -0
  24. package/cli/production/extract.js +278 -0
  25. package/cli/production/graph.js +102 -0
  26. package/cli/production/report.js +168 -0
  27. package/cli/production/run.js +355 -0
  28. package/cli/production/trim-css.js +177 -0
  29. package/cli/production/trim-fonts.js +23 -0
  30. package/cli/production/trim-icons.js +38 -0
  31. package/cli/production/trim-js.js +48 -0
  32. package/cli/production/trim-motion.js +22 -0
  33. package/cli/production/trim-themes.js +50 -0
  34. package/cli/production/watch.js +38 -0
  35. package/cli/review.js +48 -1
  36. package/cli/scripts/write-atelier-fixtures.mjs +33 -0
  37. package/cli/security.js +190 -0
  38. package/cli/skills.js +53 -10
  39. package/cli/transparency.js +140 -41
  40. package/cli/workflow.js +32 -17
  41. package/components/index.js +3 -0
  42. package/components/runtime/component-loaders.js +3 -0
  43. package/components/velin-drawer.js +43 -4
  44. package/components/velin-empty-state.js +83 -0
  45. package/components/velin-modal.js +76 -15
  46. package/components/velin-otp-input.js +220 -0
  47. package/components/velin-password-strength.js +147 -0
  48. package/components/velin-sheet.js +49 -4
  49. package/components/velin-theme-toggle.js +15 -1
  50. package/core/a11y/component-contracts.json +391 -255
  51. package/core/attributes/registry.js +1 -1
  52. package/core/meta/knowledge/components.json +75 -3
  53. package/core/motion/analyze.js +38 -0
  54. package/core/motion/blueprint.js +51 -0
  55. package/core/motion/composer.js +114 -0
  56. package/core/motion/data/registry.json +352 -0
  57. package/core/motion/effects.js +17 -1
  58. package/core/motion/index.js +112 -9
  59. package/core/motion/optimize.js +44 -0
  60. package/core/motion/policy.js +77 -0
  61. package/core/motion/registry.js +131 -0
  62. package/core/motion/review.js +59 -0
  63. package/core/motion/scan.js +150 -0
  64. package/core/motion/timeline.js +68 -0
  65. package/core/motion/transitions.js +49 -0
  66. package/core/motion/triggers.js +168 -0
  67. package/core/security/checks/fs.js +92 -0
  68. package/core/security/detect.js +35 -0
  69. package/core/security/engine.js +116 -0
  70. package/core/security/index.js +18 -0
  71. package/core/security/registry.js +85 -0
  72. package/core/security/report/report.js +113 -0
  73. package/core/security/rules/ci/index.js +146 -0
  74. package/core/security/rules/consumer/index.js +174 -0
  75. package/core/security/rules/deps/index.js +182 -0
  76. package/core/security/rules/index.js +24 -0
  77. package/core/security/rules/publish/index.js +230 -0
  78. package/core/security/rules/repo/index.js +143 -0
  79. package/core/security/rules/secrets/index.js +133 -0
  80. package/core/security/score/dependency-health.js +53 -0
  81. package/core/security/score/release-health.js +66 -0
  82. package/core/security/score/score.js +102 -0
  83. package/core/security/timeline/timeline.js +76 -0
  84. package/dist/chunks/attributes-2ORR27KA.js +404 -0
  85. package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
  86. package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
  87. package/dist/chunks/chunk-MYKUI364.js +116 -0
  88. package/dist/chunks/chunk-NEVBPD5T.js +116 -0
  89. package/dist/chunks/chunk-Y6HN7HWX.js +116 -0
  90. package/dist/chunks/runtime-entry.js +1 -1
  91. package/dist/chunks/velin-drawer-A7Q7EBOS.js +162 -0
  92. package/dist/chunks/velin-empty-state-3NTUH2RF.js +81 -0
  93. package/dist/chunks/velin-modal-3MV4FYBK.js +231 -0
  94. package/dist/chunks/velin-otp-input-PSK42MM6.js +207 -0
  95. package/dist/chunks/velin-password-strength-TAXMLSED.js +136 -0
  96. package/dist/chunks/velin-sheet-N2VVYXFP.js +157 -0
  97. package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
  98. package/dist/llms.txt +4 -4
  99. package/dist/search-index.json +232 -5
  100. package/dist/velin-agent.json +433 -46
  101. package/dist/velinstyle-components.iife.js +3695 -2272
  102. package/dist/velinstyle-components.js +3698 -2271
  103. package/dist/velinstyle-components.min.js +262 -120
  104. package/dist/velinstyle.css +96 -13
  105. package/dist/velinstyle.d.ts +3 -0
  106. package/dist/velinstyle.min.css +1 -1
  107. package/package.json +19 -4
  108. package/packages/velinstyle-cli-core/package.json +11 -0
  109. package/packages/velinstyle-cli-core/src/config.js +84 -0
  110. package/packages/velinstyle-cli-core/src/contract.js +56 -0
  111. package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
  112. package/packages/velinstyle-cli-core/src/index.js +18 -0
  113. package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
  114. package/packages/velinstyle-cli-core/src/runner.js +129 -0
  115. package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
  116. package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
  117. package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
  118. package/packages/velinstyle-cli-registry/package.json +13 -0
  119. package/packages/velinstyle-cli-registry/src/index.js +88 -0
  120. package/packages/velinstyle-cli-renderer/package.json +8 -0
  121. package/packages/velinstyle-cli-renderer/src/index.js +286 -0
  122. package/packages/velinstyle-cli-theme/package.json +8 -0
  123. package/packages/velinstyle-cli-theme/src/index.js +84 -0
  124. package/packages/velinstyle-cli-ui/package.json +8 -0
  125. package/packages/velinstyle-cli-ui/src/index.js +34 -0
  126. package/packages/velinstyle-motion/README.md +81 -0
  127. package/packages/velinstyle-motion/package.json +37 -0
  128. package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
  129. package/packages/velinstyle-motion/src/ai/provider.js +59 -0
  130. package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
  131. package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
  132. package/packages/velinstyle-motion/src/config.js +257 -0
  133. package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
  134. package/packages/velinstyle-motion/src/index.js +304 -0
  135. package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
  136. package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
  137. package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
  138. package/packages/velinstyle-motion/src/local/effects.js +139 -0
  139. package/packages/velinstyle-motion/src/local/engine.js +96 -0
  140. package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
  141. package/packages/velinstyle-motion/src/registry/presets.js +25 -0
  142. package/packages/velinstyle-motion/src/registry/presets.json +85 -0
  143. package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
  144. package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
  145. package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
  146. package/packages/velinstyle-motion/src/validate-image.js +91 -0
  147. package/packages/velinstyle-skills/catalog.json +1 -1
  148. package/packages/velinstyle-skills/registry.json +1 -142
  149. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
  150. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
  151. package/src/components/data-table.css +19 -0
  152. package/src/components/empty-auth.css +33 -0
  153. package/src/components/table.css +16 -6
  154. package/src/tokens/motion.css +7 -0
  155. package/src/utilities/scroll-animation.css +66 -0
  156. package/src/velinstyle.css +1 -0
package/cli/skills.js CHANGED
@@ -28,7 +28,11 @@ export function loadSkillRegistry() {
28
28
  return loadRegistry(REGISTRY_PATH);
29
29
  }
30
30
 
31
- export function skillsList(argv = []) {
31
+ /**
32
+ * @param {string[]} argv
33
+ * @param {{ silent?: boolean }} [opts]
34
+ */
35
+ export function skillsList(argv = [], opts = {}) {
32
36
  const registry = loadSkillRegistry();
33
37
  const items = listSkills(registry, {
34
38
  capability: argValue(argv, '--capability') || undefined,
@@ -37,14 +41,16 @@ export function skillsList(argv = []) {
37
41
  origin: argValue(argv, '--origin') || undefined,
38
42
  category: argValue(argv, '--category') || undefined,
39
43
  });
44
+ if (opts.silent) return items;
40
45
  if (hasFlag(argv, '--json')) {
41
46
  console.log(JSON.stringify(items, null, 2));
42
- return;
47
+ return items;
43
48
  }
44
49
  for (const item of items) {
45
50
  console.log(`${item.id} [${item.category}] ${item.priority}/${item.status} capabilities=${(item.capabilities || []).join(',')}`);
46
51
  }
47
52
  console.log(`\n${items.length} skill(s)`);
53
+ return items;
48
54
  }
49
55
 
50
56
  export function skillsShow(skillId, argv = []) {
@@ -56,9 +62,10 @@ export function skillsShow(skillId, argv = []) {
56
62
  }
57
63
  if (hasFlag(argv, '--human')) {
58
64
  console.log(formatSkillHuman(skill));
59
- return;
65
+ return skill;
60
66
  }
61
67
  console.log(JSON.stringify(skill, null, 2));
68
+ return skill;
62
69
  }
63
70
 
64
71
  function formatSkillHuman(skill) {
@@ -103,9 +110,14 @@ function formatSkillHuman(skill) {
103
110
  return lines.filter(Boolean).join('\n');
104
111
  }
105
112
 
106
- export function skillsInstall(argv = []) {
113
+ /**
114
+ * @param {string[]} argv
115
+ * @param {{ silent?: boolean }} [opts]
116
+ */
117
+ export function skillsInstall(argv = [], opts = {}) {
107
118
  const selection = argv[0];
108
119
  if (!selection) {
120
+ if (opts.silent) return null;
109
121
  console.error('Usage: velinstyle skills install <skill-id|pack|bundle:id|project:id>');
110
122
  process.exit(1);
111
123
  }
@@ -118,18 +130,30 @@ export function skillsInstall(argv = []) {
118
130
  targetRoot: target,
119
131
  includeDependencies: !hasFlag(argv, '--no-deps'),
120
132
  });
133
+ if (opts.silent) {
134
+ const markerPath = join(target, 'registry-source.json');
135
+ writeFileSync(markerPath, JSON.stringify({ source: REGISTRY_PATH, installed: result.installed }, null, 2), 'utf-8');
136
+ return result;
137
+ }
121
138
  for (const installed of result.installed) console.log(`Installed ${installed}`);
122
139
  if (result.issues.length) {
123
140
  for (const issue of result.issues) console.warn(`Warning: ${issue}`);
124
141
  }
125
142
  const markerPath = join(target, 'registry-source.json');
126
143
  writeFileSync(markerPath, JSON.stringify({ source: REGISTRY_PATH, installed: result.installed }, null, 2), 'utf-8');
144
+ return result;
127
145
  }
128
146
 
129
- export function skillsRun(skillId, argv = []) {
147
+ /**
148
+ * @param {string} skillId
149
+ * @param {string[]} argv
150
+ * @param {{ silent?: boolean }} [opts]
151
+ */
152
+ export function skillsRun(skillId, argv = [], opts = {}) {
130
153
  const registry = loadSkillRegistry();
131
154
  const resolved = resolveSkillGraph(registry, skillId);
132
155
  if (resolved.issues.length) {
156
+ if (opts.silent) return { ok: false, issues: resolved.issues, steps: [] };
133
157
  for (const issue of resolved.issues) console.error(issue);
134
158
  process.exit(1);
135
159
  }
@@ -139,20 +163,27 @@ export function skillsRun(skillId, argv = []) {
139
163
  if (!skill) continue;
140
164
  steps.push({ id: skill.id, requiresCli: skill.requiresCli || [], onlyIf: skill.onlyIf || [] });
141
165
  }
166
+ const payload = { steps };
167
+ if (opts.silent) return payload;
142
168
  if (hasFlag(argv, '--json')) {
143
- console.log(JSON.stringify({ steps }, null, 2));
144
- return;
169
+ console.log(JSON.stringify(payload, null, 2));
170
+ return payload;
145
171
  }
146
172
  for (const step of steps) {
147
173
  console.log(`- ${step.id}`);
148
174
  if (step.requiresCli.length) console.log(` cli: ${step.requiresCli.join(', ')}`);
149
175
  if (step.onlyIf.length) console.log(` onlyIf: ${step.onlyIf.join(', ')}`);
150
176
  }
177
+ return payload;
151
178
  }
152
179
 
153
- export function skillsValidate() {
180
+ /**
181
+ * @param {{ silent?: boolean }} [opts]
182
+ */
183
+ export function skillsValidate(opts = {}) {
154
184
  const registry = loadSkillRegistry();
155
185
  const result = validateRegistry(registry);
186
+ if (opts.silent) return result;
156
187
  if (result.errors.length) {
157
188
  for (const error of result.errors) console.error(`Error: ${error}`);
158
189
  }
@@ -163,9 +194,14 @@ export function skillsValidate() {
163
194
  process.exit(result.ok ? 0 : 1);
164
195
  }
165
196
 
166
- export async function skillsDoctor(argv = []) {
197
+ /**
198
+ * @param {string[]} argv
199
+ * @param {{ silent?: boolean }} [opts]
200
+ */
201
+ export async function skillsDoctor(argv = [], opts = {}) {
167
202
  const { doctorSkillPaths } = await import('../scripts/check-skills-paths.mjs');
168
203
  const result = doctorSkillPaths(PKG_ROOT);
204
+ if (opts.silent) return result;
169
205
  if (hasFlag(argv, '--json')) {
170
206
  console.log(JSON.stringify(result, null, 2));
171
207
  process.exit(result.ok ? 0 : 1);
@@ -176,9 +212,14 @@ export async function skillsDoctor(argv = []) {
176
212
  process.exit(1);
177
213
  }
178
214
  console.log(`skills doctor OK (${result.checked} paths).`);
215
+ return result;
179
216
  }
180
217
 
181
- export function skillsIndex(kind) {
218
+ /**
219
+ * @param {string} kind
220
+ * @param {{ silent?: boolean }} [opts]
221
+ */
222
+ export function skillsIndex(kind, opts = {}) {
182
223
  const registry = loadSkillRegistry();
183
224
  const map = {
184
225
  packs: registry.packs || [],
@@ -188,7 +229,9 @@ export function skillsIndex(kind) {
188
229
  graphs: registry.workflowGraphs || [],
189
230
  };
190
231
  const out = map[kind] || [];
232
+ if (opts.silent) return out;
191
233
  console.log(JSON.stringify(out, null, 2));
234
+ return out;
192
235
  }
193
236
 
194
237
  export function skillsCommand(argv = []) {
@@ -44,10 +44,10 @@ function printScores(scores, C) {
44
44
 
45
45
  /**
46
46
  * @param {string[]} args process.argv slice after "transparency"
47
- * @param {{ C: object, getArg: Function, hasFlag: Function }} util
47
+ * @param {{ C: object, getArg: Function, hasFlag: Function, emitExperience?: Function }} util
48
48
  */
49
49
  export async function transparencyCmd(args, util) {
50
- const { C, getArg, hasFlag } = util;
50
+ const { C, getArg, hasFlag, emitExperience } = util;
51
51
  const sub = args[0] || 'doctor';
52
52
  const rest = args.slice(1);
53
53
  const target = rest.find((a) => !a.startsWith('-')) || '.';
@@ -55,32 +55,55 @@ export async function transparencyCmd(args, util) {
55
55
  const policy = loadPolicy(policyPath);
56
56
  const asJson = hasFlag('--json');
57
57
  const engine = createTransparencyEngine({ policy });
58
+ const present = emitExperience
59
+ ? async (payload) => emitExperience('transparency', payload)
60
+ : null;
58
61
 
59
- if (sub === 'scan') {
60
- return runDoctor(target, engine, { asJson, C });
61
- }
62
- if (sub === 'doctor') {
63
- return runDoctor(target, engine, { asJson, C });
64
- }
65
- if (sub === 'validate') {
66
- return runValidate(target, engine, { asJson, C });
67
- }
68
- if (sub === 'report') {
69
- return runReport(target, engine, { C, getArg });
70
- }
71
- if (sub === 'export') {
72
- return runExport(target, engine, { C, getArg });
73
- }
74
- if (sub === 'migrate') {
75
- return runMigrate(target, engine, { C, hasFlag, getArg });
76
- }
77
- if (sub === 'suggest') {
78
- return runMigrate(target, engine, { C, hasFlag, getArg, forceDry: true });
79
- }
80
- if (sub === 'apply') {
81
- return runMigrate(target, engine, { C, hasFlag, getArg, forceApply: true });
62
+ try {
63
+ if (sub === 'scan') {
64
+ return await runDoctor(target, engine, { asJson, C, present });
65
+ }
66
+ if (sub === 'doctor') {
67
+ return await runDoctor(target, engine, { asJson, C, present });
68
+ }
69
+ if (sub === 'validate') {
70
+ return await runValidate(target, engine, { asJson, C, present });
71
+ }
72
+ if (sub === 'report') {
73
+ return await runReport(target, engine, { C, getArg, present });
74
+ }
75
+ if (sub === 'export') {
76
+ return await runExport(target, engine, { C, getArg, present });
77
+ }
78
+ if (sub === 'migrate') {
79
+ return await runMigrate(target, engine, { C, hasFlag, getArg, present });
80
+ }
81
+ if (sub === 'suggest') {
82
+ return await runMigrate(target, engine, { C, hasFlag, getArg, present, forceDry: true });
83
+ }
84
+ if (sub === 'apply') {
85
+ return await runMigrate(target, engine, { C, hasFlag, getArg, present, forceApply: true });
86
+ }
87
+ } catch (e) {
88
+ const message = /** @type {Error} */ (e).message || String(e);
89
+ if (present) {
90
+ await present({ status: 'ERROR', message, exitCode: 1 });
91
+ return;
92
+ }
93
+ console.log(C.red(message));
94
+ process.exitCode = 1;
95
+ return;
82
96
  }
83
97
 
98
+ if (present) {
99
+ await present({
100
+ status: 'ERROR',
101
+ message: `Unknown transparency subcommand: ${sub}`,
102
+ suggestions: [{ text: 'velinstyle transparency doctor|validate|report|export|migrate|scan [path]' }],
103
+ exitCode: 1,
104
+ });
105
+ return;
106
+ }
84
107
  console.log(`Unknown transparency subcommand: ${sub}
85
108
  Usage:
86
109
  velinstyle transparency doctor|validate|report|export|migrate|scan [path]
@@ -88,9 +111,13 @@ Usage:
88
111
  process.exitCode = 1;
89
112
  }
90
113
 
91
- async function runDoctor(target, engine, { asJson, C }) {
114
+ async function runDoctor(target, engine, { asJson, C, present }) {
92
115
  const files = collectHtmlFiles(target);
93
116
  if (!files.length) {
117
+ if (present) {
118
+ await present({ status: 'WARNING', message: 'No HTML files found.', exitCode: 1 });
119
+ return;
120
+ }
94
121
  console.log(C.yellow('No HTML files found.'));
95
122
  process.exitCode = 1;
96
123
  return;
@@ -102,7 +129,7 @@ async function runDoctor(target, engine, { asJson, C }) {
102
129
  const report = await engine.doctor(html, { file });
103
130
  reports.push(report);
104
131
  if (!report.ok) failed += 1;
105
- if (!asJson) {
132
+ if (!asJson && !present) {
106
133
  console.log(C.bold(`\n── ${basename(file)} ──`));
107
134
  console.log(report.ok ? C.green('PASS') : C.red('FAIL'));
108
135
  console.log(`disclosures=${report.summary.disclosures} errors=${report.summary.errors} warnings=${report.summary.warnings}`);
@@ -116,6 +143,30 @@ async function runDoctor(target, engine, { asJson, C }) {
116
143
  }
117
144
  if (asJson) {
118
145
  console.log(JSON.stringify(files.length === 1 ? reports[0] : { reports }, null, 2));
146
+ } else if (present) {
147
+ const findings = reports.flatMap((r) =>
148
+ (r.findings || []).slice(0, 10).map((f) => [
149
+ basename(r.file || '') || 'file',
150
+ f.severity,
151
+ f.code,
152
+ String(f.message || '').slice(0, 40),
153
+ ]),
154
+ );
155
+ const lastScores = reports[reports.length - 1]?.scores || {};
156
+ await present({
157
+ subtitle: 'doctor',
158
+ status: failed ? 'ERROR' : 'PASS',
159
+ message: failed ? `transparency doctor: ${failed} file(s) failed` : 'transparency doctor: ok',
160
+ items: [
161
+ { label: 'Files', value: String(files.length) },
162
+ { label: 'Failed', value: String(failed) },
163
+ ...Object.entries(lastScores).map(([k, v]) => ({ label: k, value: `${v}%` })),
164
+ ],
165
+ table: findings.length
166
+ ? { columns: ['File', 'Sev', 'Code', 'Message'], rows: findings.slice(0, 25) }
167
+ : null,
168
+ exitCode: failed ? 1 : undefined,
169
+ });
119
170
  } else {
120
171
  console.log(failed ? C.red(`\ntransparency doctor: ${failed} file(s) failed`) : C.green('\ntransparency doctor: ok'));
121
172
  }
@@ -123,7 +174,7 @@ async function runDoctor(target, engine, { asJson, C }) {
123
174
  return reports;
124
175
  }
125
176
 
126
- async function runValidate(target, engine, { asJson, C }) {
177
+ async function runValidate(target, engine, { asJson, C, present }) {
127
178
  const files = collectHtmlFiles(target);
128
179
  const all = [];
129
180
  let failed = 0;
@@ -132,15 +183,28 @@ async function runValidate(target, engine, { asJson, C }) {
132
183
  const result = await engine.validate(html, { file });
133
184
  all.push({ file, ...result });
134
185
  if (!result.ok) failed += 1;
135
- if (!asJson) {
186
+ if (!asJson && !present) {
136
187
  console.log(`${basename(file)}: ${result.ok ? C.green('valid') : C.red('invalid')} (${result.findings.length} findings)`);
137
188
  }
138
189
  }
139
- if (asJson) console.log(JSON.stringify(all, null, 2));
190
+ if (asJson) {
191
+ console.log(JSON.stringify(all, null, 2));
192
+ } else if (present) {
193
+ await present({
194
+ subtitle: 'validate',
195
+ status: failed ? 'ERROR' : 'PASS',
196
+ message: failed ? `${failed} file(s) invalid` : 'All files valid',
197
+ table: {
198
+ columns: ['File', 'Status', 'Findings'],
199
+ rows: all.map((r) => [basename(r.file), r.ok ? 'valid' : 'invalid', String(r.findings?.length || 0)]),
200
+ },
201
+ exitCode: failed ? 1 : undefined,
202
+ });
203
+ }
140
204
  if (failed) process.exitCode = 1;
141
205
  }
142
206
 
143
- async function runReport(target, engine, { C, getArg }) {
207
+ async function runReport(target, engine, { C, getArg, present }) {
144
208
  const outDir = resolve(getArg('--out') || 'transparency-report');
145
209
  mkdirSync(outDir, { recursive: true });
146
210
  const files = collectHtmlFiles(target);
@@ -155,10 +219,21 @@ async function runReport(target, engine, { C, getArg }) {
155
219
  combined.files.push({ file, scores: artifacts.json.scores, ok: artifacts.json.ok });
156
220
  }
157
221
  writeFileSync(join(outDir, 'index.json'), JSON.stringify(combined, null, 2));
158
- console.log(C.green(`Wrote transparency reports to ${outDir}`));
222
+ if (present) {
223
+ await present({
224
+ subtitle: 'report',
225
+ message: `Wrote transparency reports to ${outDir}`,
226
+ items: [
227
+ { label: 'Files', value: String(files.length) },
228
+ { label: 'Out', value: outDir },
229
+ ],
230
+ });
231
+ } else {
232
+ console.log(C.green(`Wrote transparency reports to ${outDir}`));
233
+ }
159
234
  }
160
235
 
161
- async function runExport(target, engine, { C, getArg }) {
236
+ async function runExport(target, engine, { C, getArg, present }) {
162
237
  const format = getArg('--format') || 'json';
163
238
  const out = getArg('--output') || getArg('-o');
164
239
  const files = collectHtmlFiles(target);
@@ -171,32 +246,56 @@ async function runExport(target, engine, { C, getArg }) {
171
246
  const body = fresh.export(format);
172
247
  if (out) {
173
248
  writeFileSync(resolve(out), body);
174
- console.log(C.green(`Exported ${format} → ${out}`));
249
+ if (present) {
250
+ await present({
251
+ subtitle: 'export',
252
+ message: `Exported ${format} → ${out}`,
253
+ items: [{ label: 'Format', value: format }, { label: 'Out', value: out }],
254
+ });
255
+ } else {
256
+ console.log(C.green(`Exported ${format} → ${out}`));
257
+ }
175
258
  } else {
176
259
  console.log(body);
177
260
  }
178
261
  }
179
262
 
180
- async function runMigrate(target, engine, { C, hasFlag, getArg, forceDry, forceApply }) {
263
+ async function runMigrate(target, engine, { C, hasFlag, getArg, present, forceDry, forceApply }) {
181
264
  const apply = forceApply || hasFlag('--apply') || hasFlag('--write');
182
265
  const dryRun = forceDry || (!apply);
183
266
  const files = collectHtmlFiles(target);
184
267
  let total = 0;
268
+ const rows = [];
185
269
  for (const file of files) {
186
270
  const html = readFileSync(file, 'utf-8');
187
271
  const result = await engine.migrate(html, { file, apply: apply && !dryRun, dryRun });
188
272
  total += result.suggestions.length;
189
- console.log(C.bold(`\n── ${basename(file)} ──`));
190
- console.log(`${result.suggestions.length} suggestion(s)${apply && !dryRun ? `, applied ${result.applied}` : ' (dry-run)'}`);
191
- for (const s of result.suggestions.slice(0, 30)) {
192
- console.log(C.dim(` • ${s.id || s.field || s.kind}: ${s.reason}`));
273
+ if (!present) {
274
+ console.log(C.bold(`\n── ${basename(file)} ──`));
275
+ console.log(`${result.suggestions.length} suggestion(s)${apply && !dryRun ? `, applied ${result.applied}` : ' (dry-run)'}`);
276
+ for (const s of result.suggestions.slice(0, 30)) {
277
+ console.log(C.dim(` • ${s.id || s.field || s.kind}: ${s.reason}`));
278
+ }
279
+ }
280
+ for (const s of result.suggestions.slice(0, 15)) {
281
+ rows.push([basename(file), s.id || s.field || s.kind || '—', String(s.reason || '').slice(0, 40)]);
193
282
  }
194
283
  if (apply && !dryRun && result.applied) {
195
284
  writeFileSync(file, result.html);
196
- console.log(C.green(` wrote ${file}`));
285
+ if (!present) console.log(C.green(` wrote ${file}`));
197
286
  }
198
287
  }
199
- if (!total) console.log(C.green('\nNo migration suggestions.'));
288
+ if (present) {
289
+ await present({
290
+ subtitle: apply && !dryRun ? 'apply' : 'migrate',
291
+ message: total
292
+ ? `${total} suggestion(s)${apply && !dryRun ? ' applied' : ' (dry-run)'}`
293
+ : 'No migration suggestions',
294
+ table: rows.length ? { columns: ['File', 'Id', 'Reason'], rows: rows.slice(0, 30) } : null,
295
+ });
296
+ } else if (!total) {
297
+ console.log(C.green('\nNo migration suggestions.'));
298
+ }
200
299
  }
201
300
 
202
301
  /**
package/cli/workflow.js CHANGED
@@ -5,38 +5,53 @@ function hasFlag(argv, flag) {
5
5
  return argv.includes(flag);
6
6
  }
7
7
 
8
- export function workflowCommand(argv = []) {
9
- const workflowId = argv[0];
10
- if (!workflowId || workflowId === '--help' || workflowId === '-h') {
11
- console.log(`Usage: velinstyle workflow <graph-id|project:id> [--json]
12
-
13
- Examples:
14
- velinstyle workflow landingpage
15
- velinstyle workflow project:marketing-site --json
16
- `);
17
- process.exit(0);
18
- }
8
+ /**
9
+ * Resolve a workflow graph or project workflow without printing.
10
+ * @param {string} workflowId
11
+ * @returns {{ ok: true, data: object } | { ok: false, error: string, issues?: string[] }}
12
+ */
13
+ export function workflowResolve(workflowId) {
19
14
  const registry = loadRegistry(getRegistryPathForMeta());
20
15
  let resolved;
21
16
  if (workflowId.startsWith('project:')) {
22
17
  const projectId = workflowId.replace('project:', '');
23
18
  const project = (registry.projects || []).find((item) => item.id === projectId);
24
19
  if (!project) {
25
- console.error(`Unknown project: ${projectId}`);
26
- process.exit(1);
20
+ return { ok: false, error: `Unknown project: ${projectId}` };
27
21
  }
28
22
  resolved = resolveWorkflowGraph(registry, project.workflowGraph);
29
23
  } else {
30
24
  resolved = resolveWorkflowGraph(registry, workflowId);
31
25
  }
32
26
  if (resolved.issues.length) {
33
- for (const issue of resolved.issues) console.error(issue);
27
+ return { ok: false, error: 'Workflow resolve failed', issues: resolved.issues };
28
+ }
29
+ return { ok: true, data: resolved };
30
+ }
31
+
32
+ export function workflowCommand(argv = []) {
33
+ const workflowId = argv[0];
34
+ if (!workflowId || workflowId === '--help' || workflowId === '-h') {
35
+ console.log(`Usage: velinstyle workflow <graph-id|project:id> [--json]
36
+
37
+ Examples:
38
+ velinstyle workflow landingpage
39
+ velinstyle workflow project:marketing-site --json
40
+ `);
41
+ process.exit(0);
42
+ }
43
+ const resolved = workflowResolve(workflowId);
44
+ if (!resolved.ok) {
45
+ console.error(resolved.error);
46
+ if (resolved.issues) {
47
+ for (const issue of resolved.issues) console.error(issue);
48
+ }
34
49
  process.exit(1);
35
50
  }
36
51
  if (hasFlag(argv, '--json')) {
37
- console.log(JSON.stringify(resolved, null, 2));
52
+ console.log(JSON.stringify(resolved.data, null, 2));
38
53
  return;
39
54
  }
40
- console.log(`Workflow steps (${resolved.orderedSteps.length}):`);
41
- for (const step of resolved.orderedSteps) console.log(`- ${step}`);
55
+ console.log(`Workflow steps (${resolved.data.orderedSteps.length}):`);
56
+ for (const step of resolved.data.orderedSteps) console.log(`- ${step}`);
42
57
  }
@@ -38,6 +38,9 @@ export { default as VelinFormSummary } from './velin-form-summary.js';
38
38
  export { default as VelinCalendar } from './velin-calendar.js';
39
39
  export { default as VelinFileDropzone } from './velin-file-dropzone.js';
40
40
  export { default as VelinSearch } from './velin-search.js';
41
+ export { default as VelinOtpInput } from './velin-otp-input.js';
42
+ export { default as VelinPasswordStrength, scorePassword } from './velin-password-strength.js';
43
+ export { default as VelinEmptyState } from './velin-empty-state.js';
41
44
  export { bindDeclarativeSearch } from './velin-search.js';
42
45
  export { initReveal, initMotion, velinMotion } from './velin-reveal.js';
43
46
  export { initA11y, announce, getAnnouncer } from './a11y-entry.js';
@@ -19,6 +19,7 @@ export const COMPONENT_LOADERS = {
19
19
  'velin-drawer': () => import('../velin-drawer.js'),
20
20
  'velin-dropdown': () => import('../velin-dropdown.js'),
21
21
  'velin-email': () => import('../velin-email.js'),
22
+ 'velin-empty-state': () => import('../velin-empty-state.js'),
22
23
  'velin-file-dropzone': () => import('../velin-file-dropzone.js'),
23
24
  'velin-form-summary': () => import('../velin-form-summary.js'),
24
25
  'velin-icon': () => import('../velin-icon.js'),
@@ -26,6 +27,8 @@ export const COMPONENT_LOADERS = {
26
27
  'velin-live-dot': () => import('../velin-live-dot.js'),
27
28
  'velin-menubar': () => import('../velin-menubar.js'),
28
29
  'velin-modal': () => import('../velin-modal.js'),
30
+ 'velin-otp-input': () => import('../velin-otp-input.js'),
31
+ 'velin-password-strength': () => import('../velin-password-strength.js'),
29
32
  'velin-persist': () => import('../velin-persist.js'),
30
33
  'velin-popover': () => import('../velin-popover.js'),
31
34
  'velin-progress-ring': () => import('../velin-progress-ring.js'),
@@ -49,25 +49,28 @@ const styles = `
49
49
  `;
50
50
 
51
51
  class VelinDrawer extends HTMLElement {
52
- static get observedAttributes() { return ['open']; }
52
+ static get observedAttributes() { return ['open', 'title']; }
53
53
 
54
54
  constructor() {
55
55
  super();
56
56
  this.attachShadow({ mode: 'open', delegatesFocus: true });
57
57
  this._prev = null;
58
58
  this._onKey = this._onKey.bind(this);
59
+ this._onTitleSlot = this._onTitleSlot.bind(this);
59
60
  }
60
61
 
61
62
  connectedCallback() {
62
- const title = this.getAttribute('title') || '';
63
- const safeTitle = escapeHTML(title);
63
+ if (this.shadowRoot.querySelector('.drawer')) return;
64
64
  const titleId = 'velin-drawer-title';
65
65
  this.shadowRoot.innerHTML = `
66
66
  <style>${styles}</style>
67
67
  <div class="overlay" part="overlay"></div>
68
68
  <div class="drawer" role="dialog" aria-modal="true" aria-labelledby="${titleId}" part="drawer">
69
69
  <div class="header" part="header">
70
- <h2 class="title" id="${titleId}">${safeTitle}</h2>
70
+ <h2 class="title" id="${titleId}" part="title">
71
+ <slot name="title"></slot>
72
+ <span class="title-fallback"></span>
73
+ </h2>
71
74
  <button class="close-btn" aria-label="Close" part="close">&#215;</button>
72
75
  </div>
73
76
  <div class="body" part="body"><slot></slot></div>
@@ -75,15 +78,51 @@ class VelinDrawer extends HTMLElement {
75
78
  `;
76
79
  this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => this.close());
77
80
  this.shadowRoot.querySelector('.overlay').addEventListener('click', () => this.close());
81
+ this.shadowRoot.querySelector('slot[name="title"]').addEventListener('slotchange', this._onTitleSlot);
82
+ this._syncTitle();
78
83
  }
79
84
 
80
85
  attributeChangedCallback(name) {
81
86
  if (name === 'open') this.hasAttribute('open') ? this._open() : this._close();
87
+ if (name === 'title') this._syncTitle();
82
88
  }
83
89
 
84
90
  open() { this.setAttribute('open', ''); }
85
91
  close() { this.removeAttribute('open'); this.dispatchEvent(new CustomEvent('velin-close', { bubbles: true })); }
86
92
 
93
+ _syncTitle() {
94
+ const fallback = this.shadowRoot?.querySelector('.title-fallback');
95
+ if (!fallback) return;
96
+ fallback.textContent = this.getAttribute('title') || '';
97
+ }
98
+
99
+ _hasTitleSlot() {
100
+ const slot = this.shadowRoot?.querySelector('slot[name="title"]');
101
+ if (!slot) return false;
102
+ return slot.assignedNodes({ flatten: true }).some((n) => {
103
+ if (n.nodeType === Node.TEXT_NODE) return Boolean(n.textContent.trim());
104
+ return n.nodeType === Node.ELEMENT_NODE;
105
+ });
106
+ }
107
+
108
+ _onTitleSlot() {
109
+ const dialog = this.shadowRoot?.querySelector('[role="dialog"]');
110
+ const fallback = this.shadowRoot?.querySelector('.title-fallback');
111
+ if (!dialog || !fallback) return;
112
+ if (this._hasTitleSlot()) {
113
+ fallback.hidden = true;
114
+ dialog.removeAttribute('aria-labelledby');
115
+ const slot = this.shadowRoot.querySelector('slot[name="title"]');
116
+ const label = slot.assignedNodes({ flatten: true }).map((n) => n.textContent || '').join(' ').trim();
117
+ if (label) dialog.setAttribute('aria-label', label);
118
+ } else {
119
+ fallback.hidden = false;
120
+ dialog.removeAttribute('aria-label');
121
+ dialog.setAttribute('aria-labelledby', 'velin-drawer-title');
122
+ this._syncTitle();
123
+ }
124
+ }
125
+
87
126
  _open() {
88
127
  this._prev = saveFocus();
89
128
  setBackgroundInert(this);