@birdapi/velinstyle 1.2.2 → 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 (109) hide show
  1. package/README.de.md +18 -4
  2. package/README.md +42 -7
  3. package/cli/cli-manifest.json +441 -174
  4. package/cli/docgen/extract-cli.js +2 -1
  5. package/cli/docs-generate.js +9 -0
  6. package/cli/experience.js +173 -0
  7. package/cli/index.js +1294 -417
  8. package/cli/motion.js +738 -0
  9. package/cli/production/extract.js +46 -6
  10. package/cli/production/report.js +104 -14
  11. package/cli/production/run.js +44 -1
  12. package/cli/security.js +190 -0
  13. package/cli/skills.js +53 -10
  14. package/cli/transparency.js +140 -41
  15. package/cli/workflow.js +32 -17
  16. package/components/velin-theme-toggle.js +15 -1
  17. package/core/a11y/component-contracts.json +391 -274
  18. package/core/attributes/registry.js +1 -1
  19. package/core/motion/analyze.js +38 -0
  20. package/core/motion/blueprint.js +51 -0
  21. package/core/motion/composer.js +114 -0
  22. package/core/motion/data/registry.json +352 -0
  23. package/core/motion/effects.js +17 -1
  24. package/core/motion/index.js +112 -9
  25. package/core/motion/optimize.js +44 -0
  26. package/core/motion/policy.js +77 -0
  27. package/core/motion/registry.js +131 -0
  28. package/core/motion/review.js +59 -0
  29. package/core/motion/scan.js +150 -0
  30. package/core/motion/timeline.js +68 -0
  31. package/core/motion/transitions.js +49 -0
  32. package/core/motion/triggers.js +168 -0
  33. package/core/security/checks/fs.js +92 -0
  34. package/core/security/detect.js +35 -0
  35. package/core/security/engine.js +116 -0
  36. package/core/security/index.js +18 -0
  37. package/core/security/registry.js +85 -0
  38. package/core/security/report/report.js +113 -0
  39. package/core/security/rules/ci/index.js +146 -0
  40. package/core/security/rules/consumer/index.js +174 -0
  41. package/core/security/rules/deps/index.js +182 -0
  42. package/core/security/rules/index.js +24 -0
  43. package/core/security/rules/publish/index.js +230 -0
  44. package/core/security/rules/repo/index.js +143 -0
  45. package/core/security/rules/secrets/index.js +133 -0
  46. package/core/security/score/dependency-health.js +53 -0
  47. package/core/security/score/release-health.js +66 -0
  48. package/core/security/score/score.js +102 -0
  49. package/core/security/timeline/timeline.js +76 -0
  50. package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
  51. package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
  52. package/dist/chunks/chunk-MYKUI364.js +116 -0
  53. package/dist/chunks/chunk-NEVBPD5T.js +116 -0
  54. package/dist/chunks/runtime-entry.js +1 -1
  55. package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
  56. package/dist/llms.txt +2 -2
  57. package/dist/search-index.json +166 -2
  58. package/dist/velin-agent.json +156 -32
  59. package/dist/velinstyle-components.iife.js +862 -17
  60. package/dist/velinstyle-components.js +862 -17
  61. package/dist/velinstyle-components.min.js +126 -126
  62. package/dist/velinstyle.css +42 -7
  63. package/dist/velinstyle.min.css +1 -1
  64. package/package.json +158 -143
  65. package/packages/velinstyle-cli-core/package.json +11 -0
  66. package/packages/velinstyle-cli-core/src/config.js +84 -0
  67. package/packages/velinstyle-cli-core/src/contract.js +56 -0
  68. package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
  69. package/packages/velinstyle-cli-core/src/index.js +18 -0
  70. package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
  71. package/packages/velinstyle-cli-core/src/runner.js +129 -0
  72. package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
  73. package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
  74. package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
  75. package/packages/velinstyle-cli-registry/package.json +13 -0
  76. package/packages/velinstyle-cli-registry/src/index.js +88 -0
  77. package/packages/velinstyle-cli-renderer/package.json +8 -0
  78. package/packages/velinstyle-cli-renderer/src/index.js +286 -0
  79. package/packages/velinstyle-cli-theme/package.json +8 -0
  80. package/packages/velinstyle-cli-theme/src/index.js +84 -0
  81. package/packages/velinstyle-cli-ui/package.json +8 -0
  82. package/packages/velinstyle-cli-ui/src/index.js +34 -0
  83. package/packages/velinstyle-motion/README.md +81 -0
  84. package/packages/velinstyle-motion/package.json +37 -0
  85. package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
  86. package/packages/velinstyle-motion/src/ai/provider.js +59 -0
  87. package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
  88. package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
  89. package/packages/velinstyle-motion/src/config.js +257 -0
  90. package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
  91. package/packages/velinstyle-motion/src/index.js +304 -0
  92. package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
  93. package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
  94. package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
  95. package/packages/velinstyle-motion/src/local/effects.js +139 -0
  96. package/packages/velinstyle-motion/src/local/engine.js +96 -0
  97. package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
  98. package/packages/velinstyle-motion/src/registry/presets.js +25 -0
  99. package/packages/velinstyle-motion/src/registry/presets.json +85 -0
  100. package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
  101. package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
  102. package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
  103. package/packages/velinstyle-motion/src/validate-image.js +91 -0
  104. package/packages/velinstyle-skills/catalog.json +1 -1
  105. package/packages/velinstyle-skills/registry.json +1 -142
  106. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
  107. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
  108. package/src/tokens/motion.css +7 -0
  109. package/src/utilities/scroll-animation.css +66 -0
@@ -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
  }
@@ -17,9 +17,18 @@ const THEMES = [
17
17
  ];
18
18
 
19
19
  const BUILTIN_THEMES = new Set(['', 'dark']);
20
+ const DARK_THEME_SLUGS = new Set(['dark', 'midnight', 'neon']);
20
21
 
21
22
  const loadedThemeStylesheets = new Set();
22
23
 
24
+ function applyColorScheme(target, slug) {
25
+ const dark = DARK_THEME_SLUGS.has(slug);
26
+ if (target === document.documentElement || target === document.body) {
27
+ target.style.colorScheme = dark ? 'dark' : 'light';
28
+ }
29
+ target.setAttribute('data-velin-color-scheme', dark ? 'dark' : 'light');
30
+ }
31
+
23
32
  function ensureThemeStylesheet(slug, base) {
24
33
  if (!slug || BUILTIN_THEMES.has(slug)) return;
25
34
  if (loadedThemeStylesheets.has(slug)) return;
@@ -288,6 +297,7 @@ class VelinThemeToggle extends HTMLElement {
288
297
  this.setAttribute('theme', normalized === 'dark' ? 'dark' : normalized);
289
298
  ensureThemeStylesheet(normalized, this._themesBase);
290
299
  }
300
+ applyColorScheme(this._target, normalized);
291
301
  if (persist) {
292
302
  if (!normalized) localStorage.removeItem('velin-theme');
293
303
  else localStorage.setItem('velin-theme', normalized);
@@ -295,7 +305,11 @@ class VelinThemeToggle extends HTMLElement {
295
305
  this._highlightActive();
296
306
  this.dispatchEvent(new CustomEvent('velin-theme-change', {
297
307
  bubbles: true,
298
- detail: { theme: normalized || 'light', dark: normalized === 'dark', slug: normalized },
308
+ detail: {
309
+ theme: normalized || 'light',
310
+ dark: DARK_THEME_SLUGS.has(normalized),
311
+ slug: normalized,
312
+ },
299
313
  }));
300
314
  }
301
315