decantr 0.9.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 (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,38 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { glob } from 'node:fs';
3
+ import { join } from 'node:path';
4
+
5
+ export async function run() {
6
+ const cwd = process.cwd();
7
+ const args = process.argv.slice(3);
8
+ const watch = args.includes('--watch');
9
+
10
+ const testArgs = ['--test'];
11
+ if (watch) testArgs.push('--watch');
12
+
13
+ // Find test files
14
+ const testFiles = [];
15
+ const pattern = join(cwd, 'test', '**', '*.test.js');
16
+
17
+ // Use node:fs glob (Node 22+)
18
+ const { glob: fsGlob } = await import('node:fs/promises');
19
+ for await (const file of fsGlob(pattern)) {
20
+ testFiles.push(file);
21
+ }
22
+
23
+ if (testFiles.length === 0) {
24
+ console.log('No test files found in test/**/*.test.js');
25
+ return;
26
+ }
27
+
28
+ testArgs.push(...testFiles);
29
+
30
+ const child = spawn('node', testArgs, {
31
+ cwd,
32
+ stdio: 'inherit'
33
+ });
34
+
35
+ child.on('exit', (code) => {
36
+ process.exit(code || 0);
37
+ });
38
+ }
@@ -0,0 +1,354 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+
7
+ export async function run() {
8
+ const cwd = process.cwd();
9
+ const essencePath = join(cwd, 'decantr.essence.json');
10
+ const configPath = join(cwd, 'decantr.config.json');
11
+ const registryRoot = join(__dirname, '..', '..', 'src', 'registry');
12
+
13
+ // Read essence
14
+ let essence;
15
+ try {
16
+ essence = JSON.parse(await readFile(essencePath, 'utf-8'));
17
+ } catch (e) {
18
+ console.error(' ✗ decantr.essence.json not found or invalid JSON');
19
+ console.error(' Run the CLARIFY stage to create your project essence.');
20
+ process.exitCode = 1;
21
+ return;
22
+ }
23
+
24
+ const errors = [];
25
+ const warnings = [];
26
+
27
+ // Detect schema form
28
+ const isSectioned = Array.isArray(essence.sections);
29
+ const isSimple = typeof essence.terroir === 'string' || essence.terroir === null;
30
+
31
+ if (!isSectioned && !isSimple) {
32
+ errors.push('Essence must have either "terroir" (simple) or "sections" (sectioned)');
33
+ }
34
+
35
+ // Known archetypes
36
+ const KNOWN_ARCHETYPES = ['ecommerce', 'saas-dashboard', 'portfolio', 'content-site', 'docs-explorer', 'financial-dashboard', 'recipe-community'];
37
+
38
+ // Known styles
39
+ const KNOWN_STYLES = ['auradecantism', 'clean', 'retro', 'glassmorphism', 'command-center'];
40
+
41
+ // Validate vintage
42
+ function validateVintage(vintage, prefix) {
43
+ if (!vintage) return;
44
+ if (vintage.style && !KNOWN_STYLES.includes(vintage.style)) {
45
+ warnings.push(`${prefix}vintage.style "${vintage.style}" is not a built-in style (may be registered via registerStyle()). Built-in: ${KNOWN_STYLES.join(', ')}`);
46
+ }
47
+ if (vintage.mode && !['light', 'dark', 'auto'].includes(vintage.mode)) {
48
+ errors.push(`${prefix}vintage.mode must be light|dark|auto, got "${vintage.mode}"`);
49
+ }
50
+ }
51
+
52
+ // Valid responsive breakpoints for blend items
53
+ const VALID_BREAKPOINTS = ['sm', 'md', 'lg', 'xl', '2xl'];
54
+
55
+ // Validate blend item depth (Gap 2.3)
56
+ function validateBlendItems(blend, pageId, prefix) {
57
+ if (!Array.isArray(blend)) return;
58
+ for (let i = 0; i < blend.length; i++) {
59
+ const item = blend[i];
60
+ if (typeof item === 'string') continue;
61
+ if (item && typeof item === 'object' && !item.cols && item.pattern) continue; // v2 preset ref
62
+
63
+ if (item && typeof item === 'object' && item.cols) {
64
+ // Validate breakpoint
65
+ if (item.at != null) {
66
+ if (!VALID_BREAKPOINTS.includes(item.at)) {
67
+ errors.push(`${prefix}Page "${pageId}": blend row ${i} has invalid breakpoint "${item.at}". Valid: ${VALID_BREAKPOINTS.join(', ')}`);
68
+ }
69
+ }
70
+
71
+ // Validate span values
72
+ if (item.span != null) {
73
+ if (typeof item.span !== 'object' || Array.isArray(item.span)) {
74
+ errors.push(`${prefix}Page "${pageId}": blend row ${i} "span" must be an object mapping column IDs to weights`);
75
+ } else {
76
+ for (const [key, value] of Object.entries(item.span)) {
77
+ // Validate span values are positive integers
78
+ if (!Number.isInteger(value) || value < 1) {
79
+ errors.push(`${prefix}Page "${pageId}": blend row ${i} span["${key}"] must be a positive integer, got ${JSON.stringify(value)}`);
80
+ }
81
+ // Validate span keys match cols entries
82
+ if (!item.cols.includes(key)) {
83
+ errors.push(`${prefix}Page "${pageId}": blend row ${i} span key "${key}" does not match any cols entry. Cols: ${item.cols.join(', ')}`);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ // Validate structure entries
93
+ function validateStructure(structure, prefix) {
94
+ if (!Array.isArray(structure)) return;
95
+ for (const page of structure) {
96
+ if (!page.id) errors.push(`${prefix}structure entry missing "id"`);
97
+ if (!page.skeleton) warnings.push(`${prefix}structure entry "${page.id || '?'}" missing "skeleton"`);
98
+ if (!page.blend && !page.patterns) warnings.push(`${prefix}structure entry "${page.id || '?'}" missing "blend"`);
99
+ // Validate blend item depth
100
+ if (page.blend) validateBlendItems(page.blend, page.id || '?', prefix);
101
+ }
102
+ }
103
+
104
+ if (isSectioned) {
105
+ // Sectioned validation
106
+ const sectionPaths = new Set();
107
+ for (const section of essence.sections) {
108
+ if (!section.id) errors.push('Section missing "id"');
109
+ if (!section.path) errors.push(`Section "${section.id || '?'}" missing "path"`);
110
+ if (section.path && sectionPaths.has(section.path)) {
111
+ errors.push(`Duplicate section path: "${section.path}"`);
112
+ }
113
+ if (section.path) sectionPaths.add(section.path);
114
+
115
+ if (section.terroir && !KNOWN_ARCHETYPES.includes(section.terroir)) {
116
+ errors.push(`Section "${section.id}": terroir "${section.terroir}" is not a known archetype`);
117
+ }
118
+
119
+ validateVintage(section.vintage, `Section "${section.id || '?'}": `);
120
+ validateStructure(section.structure, `Section "${section.id || '?'}": `);
121
+ }
122
+
123
+ // Check shared_tannins not duplicated in sections
124
+ if (Array.isArray(essence.shared_tannins)) {
125
+ for (const section of essence.sections) {
126
+ if (Array.isArray(section.tannins)) {
127
+ for (const t of section.tannins) {
128
+ if (essence.shared_tannins.includes(t)) {
129
+ warnings.push(`Tannin "${t}" is in both shared_tannins and section "${section.id}"`);
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ } else {
136
+ // Simple validation
137
+ if (essence.terroir && !KNOWN_ARCHETYPES.includes(essence.terroir)) {
138
+ errors.push(`terroir "${essence.terroir}" is not a known archetype. Known: ${KNOWN_ARCHETYPES.join(', ')}`);
139
+ }
140
+ validateVintage(essence.vintage, '');
141
+ validateStructure(essence.structure, '');
142
+ }
143
+
144
+ // Vessel validation
145
+ if (essence.vessel) {
146
+ if (essence.vessel.type && !['spa', 'mpa'].includes(essence.vessel.type)) {
147
+ warnings.push(`vessel.type "${essence.vessel.type}" is unusual (expected spa|mpa)`);
148
+ }
149
+ if (essence.vessel.routing && !['hash', 'history'].includes(essence.vessel.routing)) {
150
+ errors.push(`vessel.routing must be hash|history, got "${essence.vessel.routing}"`);
151
+ }
152
+ }
153
+
154
+ // Version field validation (Gap 2.5)
155
+ if (essence.version != null) {
156
+ if (typeof essence.version !== 'string') {
157
+ errors.push('version must be a string (semver format, e.g. "1.0.0")');
158
+ } else if (!/^\d+\.\d+\.\d+$/.test(essence.version)) {
159
+ warnings.push(`version "${essence.version}" is not standard semver (expected "X.Y.Z")`);
160
+ }
161
+ } else {
162
+ warnings.push('No "version" field in essence. Consider adding a semver version (e.g. "1.0.0") to track essence evolution.');
163
+ }
164
+
165
+ // Cork validation
166
+ if (essence.cork && typeof essence.cork !== 'object') {
167
+ errors.push('cork must be an object');
168
+ }
169
+
170
+ // Cross-reference with config
171
+ try {
172
+ const config = JSON.parse(await readFile(configPath, 'utf-8'));
173
+ const essenceStyle = isSectioned ? null : essence.vintage?.style;
174
+ const essenceMode = isSectioned ? null : essence.vintage?.mode;
175
+
176
+ if (essenceStyle && config.style && essenceStyle !== config.style) {
177
+ warnings.push(`Style mismatch: essence="${essenceStyle}", config="${config.style}". Essence is authoritative.`);
178
+ }
179
+ if (essenceMode && config.mode && essenceMode !== config.mode) {
180
+ warnings.push(`Mode mismatch: essence="${essenceMode}", config="${config.mode}". Essence is authoritative.`);
181
+ }
182
+
183
+ // Validate icons config
184
+ if (config.icons != null) {
185
+ if (typeof config.icons !== 'object' || Array.isArray(config.icons)) {
186
+ errors.push('icons must be an object');
187
+ } else if (config.icons.library != null && typeof config.icons.library !== 'string') {
188
+ errors.push('icons.library must be a string');
189
+ } else if (config.icons.library) {
190
+ const KNOWN_LIBS = ['lucide', 'heroicons', 'material', 'fontawesome', 'phosphor', 'tabler', 'feather'];
191
+ if (!KNOWN_LIBS.includes(config.icons.library)) {
192
+ warnings.push(`icons.library "${config.icons.library}" is not a recognized library (known: ${KNOWN_LIBS.join(', ')}). LLM code generation will use it as-is.`);
193
+ }
194
+ }
195
+ }
196
+ } catch {
197
+ // No config file — that's fine
198
+ }
199
+
200
+ // ─── Archetype inheritance validation ─────────────────────────
201
+ // If an archetype uses `extends`, verify the chain is valid
202
+ try {
203
+ const archetypeIdx = JSON.parse(await readFile(join(registryRoot, 'archetypes', 'index.json'), 'utf-8'));
204
+ const knownArchetypeIds = new Set(Object.keys(archetypeIdx.archetypes || {}));
205
+
206
+ // Load all archetypes to check extends chains
207
+ const archetypeData = {};
208
+ for (const [aid, meta] of Object.entries(archetypeIdx.archetypes || {})) {
209
+ try {
210
+ archetypeData[aid] = JSON.parse(await readFile(join(registryRoot, 'archetypes', meta.file), 'utf-8'));
211
+ } catch { /* skip unreadable */ }
212
+ }
213
+
214
+ for (const [aid, arch] of Object.entries(archetypeData)) {
215
+ if (!arch.extends) continue;
216
+
217
+ // Check that extends references an existing archetype
218
+ if (!knownArchetypeIds.has(arch.extends)) {
219
+ errors.push(`Archetype "${aid}": extends "${arch.extends}" is not a known archetype. Known: ${[...knownArchetypeIds].join(', ')}`);
220
+ continue;
221
+ }
222
+
223
+ // Check for circular inheritance by following the chain
224
+ const visited = new Set();
225
+ let current = aid;
226
+ let depth = 0;
227
+ while (current && depth < 10) {
228
+ if (visited.has(current)) {
229
+ errors.push(`Archetype "${aid}": circular inheritance detected: ${[...visited, current].join(' → ')}`);
230
+ break;
231
+ }
232
+ visited.add(current);
233
+ const currentArch = archetypeData[current];
234
+ current = currentArch?.extends || null;
235
+ depth++;
236
+ }
237
+ if (depth >= 10) {
238
+ errors.push(`Archetype "${aid}": inheritance chain exceeds depth limit (10)`);
239
+ }
240
+ }
241
+ } catch {
242
+ // Archetype registry not accessible — skip inheritance validation
243
+ }
244
+
245
+ // ─── Generate prerequisites ───────────────────────────────────
246
+
247
+ // Collect all structures for validation
248
+ const allStructures = isSectioned
249
+ ? essence.sections.flatMap(s => s.structure || [])
250
+ : (essence.structure || []);
251
+
252
+ // Warn on empty structure
253
+ if (allStructures.length === 0) {
254
+ warnings.push('No pages in structure — `decantr generate` will have nothing to produce');
255
+ }
256
+
257
+ // Check patterns in blend arrays exist in registry (supports v2 preset references)
258
+ try {
259
+ const patternFiles = await readdir(join(registryRoot, 'patterns'));
260
+ const knownPatterns = new Set(patternFiles.filter(f => f.endsWith('.json') && f !== 'index.json').map(f => f.replace('.json', '')));
261
+
262
+ // Load pattern index for preset validation
263
+ let patternIndex = {};
264
+ try {
265
+ const indexData = JSON.parse(await readFile(join(registryRoot, 'patterns', 'index.json'), 'utf-8'));
266
+ patternIndex = indexData.patterns || {};
267
+ } catch { /* skip */ }
268
+
269
+ function validateBlendRef(ref, pageId) {
270
+ if (typeof ref === 'string') {
271
+ if (!knownPatterns.has(ref)) {
272
+ warnings.push(`Page "${pageId}": pattern "${ref}" in blend not found in registry`);
273
+ }
274
+ } else if (ref && ref.pattern) {
275
+ // v2 preset reference: { pattern, preset, as }
276
+ if (!knownPatterns.has(ref.pattern)) {
277
+ warnings.push(`Page "${pageId}": pattern "${ref.pattern}" in blend not found in registry`);
278
+ } else if (ref.preset && patternIndex[ref.pattern]?.presets) {
279
+ if (!patternIndex[ref.pattern].presets.includes(ref.preset)) {
280
+ warnings.push(`Page "${pageId}": preset "${ref.preset}" not found on pattern "${ref.pattern}". Known presets: ${patternIndex[ref.pattern].presets.join(', ')}`);
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ for (const page of allStructures) {
287
+ const blend = page.blend || page.patterns || [];
288
+ for (const item of blend) {
289
+ if (typeof item === 'string') {
290
+ validateBlendRef(item, page.id);
291
+ } else if (item.pattern) {
292
+ // v2 preset reference at top level
293
+ validateBlendRef(item, page.id);
294
+ } else if (item.cols) {
295
+ for (const col of item.cols) {
296
+ validateBlendRef(col, page.id);
297
+ }
298
+ }
299
+ }
300
+ }
301
+ } catch {
302
+ // Registry not accessible — skip pattern validation
303
+ }
304
+
305
+ // Check skeletons referenced exist
306
+ try {
307
+ const skeletonsData = JSON.parse(await readFile(join(registryRoot, 'skeletons.json'), 'utf-8'));
308
+ const knownSkeletons = new Set(Object.keys(skeletonsData.skeletons || {}));
309
+
310
+ for (const page of allStructures) {
311
+ if (page.skeleton && !knownSkeletons.has(page.skeleton)) {
312
+ warnings.push(`Page "${page.id}": skeleton "${page.skeleton}" not found in skeletons.json`);
313
+ }
314
+ }
315
+ } catch {
316
+ // skeletons.json not accessible — skip
317
+ }
318
+
319
+ // Check recipe referenced in vintage exists
320
+ const allVintages = isSectioned
321
+ ? essence.sections.map(s => s.vintage).filter(Boolean)
322
+ : (essence.vintage ? [essence.vintage] : []);
323
+
324
+ try {
325
+ const registryFiles = await readdir(registryRoot);
326
+ const knownRecipes = new Set(
327
+ registryFiles.filter(f => f.startsWith('recipe-') && f.endsWith('.json')).map(f => f.replace('recipe-', '').replace('.json', ''))
328
+ );
329
+
330
+ for (const vintage of allVintages) {
331
+ if (vintage.recipe && !knownRecipes.has(vintage.recipe)) {
332
+ warnings.push(`Recipe "${vintage.recipe}" referenced in vintage not found in registry. Known: ${[...knownRecipes].join(', ')}`);
333
+ }
334
+ }
335
+ } catch {
336
+ // Registry not accessible — skip
337
+ }
338
+
339
+ // Report
340
+ if (errors.length === 0 && warnings.length === 0) {
341
+ const mode = isSectioned ? `sectioned (${essence.sections.length} sections)` : `simple (${essence.terroir || 'unset'})`;
342
+ console.log(` ✓ Essence valid — ${mode}`);
343
+ } else {
344
+ if (errors.length > 0) {
345
+ console.error(`\n ${errors.length} error(s):`);
346
+ for (const e of errors) console.error(` ✗ ${e}`);
347
+ }
348
+ if (warnings.length > 0) {
349
+ console.log(`\n ${warnings.length} warning(s):`);
350
+ for (const w of warnings) console.log(` ⚠ ${w}`);
351
+ }
352
+ if (errors.length > 0) process.exitCode = 1;
353
+ }
354
+ }
package/cli/index.js ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from 'node:util';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { loadPlugins, runHook } from '../src/plugins/index.js';
7
+
8
+ const { positionals } = parseArgs({ allowPositionals: true, strict: false });
9
+ const command = positionals[0];
10
+
11
+ // Load project config and plugins for commands that benefit from them
12
+ const cwd = process.cwd();
13
+ let _config = null;
14
+
15
+ async function getConfig() {
16
+ if (_config) return _config;
17
+ try {
18
+ const raw = await readFile(join(cwd, 'decantr.config.json'), 'utf-8');
19
+ _config = JSON.parse(raw);
20
+ } catch {
21
+ _config = {};
22
+ }
23
+ // Load plugins from config
24
+ await loadPlugins(_config, { cwd });
25
+ return _config;
26
+ }
27
+
28
+ switch (command) {
29
+ case 'init':
30
+ await import('./commands/init.js').then(m => m.run());
31
+ break;
32
+ case 'dev':
33
+ await getConfig();
34
+ await runHook('onDev', { cwd, command });
35
+ await import('./commands/dev.js').then(m => m.run());
36
+ break;
37
+ case 'build':
38
+ await getConfig();
39
+ await runHook('onBuild', { cwd, command });
40
+ await import('./commands/build.js').then(m => m.run());
41
+ break;
42
+ case 'test':
43
+ await import('./commands/test.js').then(m => m.run());
44
+ break;
45
+ case 'validate':
46
+ await import('./commands/validate.js').then(m => m.run());
47
+ break;
48
+ case 'lint':
49
+ await import('./commands/lint.js').then(m => m.run());
50
+ break;
51
+ case 'a11y':
52
+ await import('./commands/a11y.js').then(m => m.run());
53
+ break;
54
+ case 'audit':
55
+ if (process.argv.includes('--a11y')) {
56
+ await import('./commands/a11y.js').then(m => m.run());
57
+ } else {
58
+ await import('./commands/audit.js').then(m => m.run());
59
+ }
60
+ break;
61
+ case 'generate':
62
+ await getConfig();
63
+ await runHook('onGenerate', { cwd, command });
64
+ await import('./commands/generate.js').then(m => m.run());
65
+ break;
66
+ case 'mcp':
67
+ await import('./commands/mcp.js').then(m => m.run());
68
+ break;
69
+ case 'figma:tokens':
70
+ await import('./commands/figma-tokens.js').then(m => m.run());
71
+ break;
72
+ case 'figma:sync':
73
+ await import('./commands/figma-sync.js').then(m => m.run());
74
+ break;
75
+ case 'migrate':
76
+ await import('./commands/migrate.js').then(m => m.run());
77
+ break;
78
+ case 'doctor':
79
+ await import('./commands/doctor.js').then(m => m.run());
80
+ break;
81
+ default: {
82
+ const { art } = await import('./art.js');
83
+ console.log(art());
84
+ console.log(`
85
+ \x1b[1mdecantr\x1b[0m v0.9.0 \x1b[2m— AI-first web framework\x1b[0m
86
+
87
+ \x1b[1mCommands:\x1b[0m
88
+ init Create a new decantr project
89
+ dev Start development server
90
+ build Build for production
91
+ test Run tests
92
+ validate Validate decantr.essence.json
93
+ lint Code quality gates (atoms, essence drift, inline styles)
94
+ generate Generate code from decantr.essence.json
95
+ a11y Accessibility audit (8 WCAG rules, static analysis)
96
+ audit Audit project: framework coverage, quality, bundle size
97
+ mcp Start MCP server (stdio transport) for AI tool integration
98
+ migrate Migrate decantr.essence.json between versions
99
+ figma:tokens Export design tokens in W3C DTCG / Figma format
100
+ doctor Check project health and environment
101
+ figma:sync Push tokens to Figma file via REST API
102
+
103
+ \x1b[1mUsage:\x1b[0m
104
+ npx decantr init
105
+ npx decantr dev
106
+ npx decantr build
107
+ npx decantr test [--watch]
108
+ npx decantr generate [--force] [--dry-run] [--page <id>]
109
+ npx decantr migrate [--dry-run] [--target=<version>]
110
+ `);
111
+ break;
112
+ }
113
+ }
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "decantr",
3
+ "version": "0.9.0",
4
+ "type": "module",
5
+ "description": "AI-first web framework with signal-based reactivity, atomic CSS, and a structured registry of 100+ components.",
6
+ "homepage": "https://decantr.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/decantr-ai/decantr"
10
+ },
11
+ "author": "David Aimi",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "web-framework",
15
+ "ai-first",
16
+ "mcp",
17
+ "mcp-server",
18
+ "components",
19
+ "design-system",
20
+ "atomic-css"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20.0.0"
24
+ },
25
+ "files": [
26
+ "src/",
27
+ "cli/",
28
+ "tools/",
29
+ "types/",
30
+ "reference/",
31
+ "AGENTS.md",
32
+ "CLAUDE.md",
33
+ "CHANGELOG.md",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "mcpName": "decantr.ai/decantr",
41
+ "bin": {
42
+ "decantr": "./cli/index.js"
43
+ },
44
+ "scripts": {
45
+ "dev": "decantr dev",
46
+ "build": "decantr build",
47
+ "test": "decantr test"
48
+ },
49
+ "exports": {
50
+ "./core": "./src/core/index.js",
51
+ "./tags": "./src/tags/index.js",
52
+ "./css": "./src/css/index.js",
53
+ "./state": "./src/state/index.js",
54
+ "./state/store": "./src/state/store.js",
55
+ "./state/arrays": "./src/state/arrays.js",
56
+ "./state/devtools": "./src/state/devtools.js",
57
+ "./state/middleware": "./src/state/middleware.js",
58
+ "./data": "./src/data/index.js",
59
+ "./router": "./src/router/index.js",
60
+ "./components": "./src/components/index.js",
61
+ "./form": "./src/form/index.js",
62
+ "./chart": "./src/chart/index.js",
63
+ "./test": "./src/test/index.js",
64
+ "./plugins": "./src/plugins/index.js",
65
+ "./i18n": "./src/i18n/index.js",
66
+ "./ssr": "./src/ssr/index.js",
67
+ "./tannins/auth": "./src/tannins/auth.js",
68
+ "./styles/clean": "./src/css/styles/addons/clean.js",
69
+ "./styles/retro": "./src/css/styles/addons/retro.js",
70
+ "./styles/glassmorphism": "./src/css/styles/addons/glassmorphism.js",
71
+ "./styles/command-center": "./src/css/styles/addons/command-center.js",
72
+ "./styles/clay": "./src/css/styles/addons/clay.js",
73
+ "./styles/liquid-glass": "./src/css/styles/addons/liquid-glass.js",
74
+ "./styles/dopamine": "./src/css/styles/addons/dopamine.js",
75
+ "./styles/prismatic": "./src/css/styles/addons/prismatic.js",
76
+ "./styles/bioluminescent": "./src/css/styles/addons/bioluminescent.js",
77
+ "./styles/editorial": "./src/css/styles/addons/editorial.js",
78
+ "./explorer/patterns.js": "./src/explorer/patterns.js",
79
+ "./explorer/icons.js": "./src/explorer/icons.js",
80
+ "./explorer/tokens.js": "./src/explorer/tokens.js",
81
+ "./explorer/foundations.js": "./src/explorer/foundations.js",
82
+ "./explorer/archetypes.js": "./src/explorer/archetypes.js",
83
+ "./explorer/charts.js": "./src/explorer/charts.js",
84
+ "./explorer/shells.js": "./src/explorer/shells.js",
85
+ "./explorer/recipes.js": "./src/explorer/recipes.js",
86
+ "./explorer/tools.js": "./src/explorer/tools.js",
87
+ "./explorer/components.js": "./src/explorer/components.js",
88
+ "./explorer/atoms.js": "./src/explorer/atoms.js",
89
+ "./explorer/styles.js": "./src/explorer/styles.js"
90
+ },
91
+ "sideEffects": false,
92
+ "dependencies": {
93
+ "@modelcontextprotocol/sdk": "^1.27.1"
94
+ }
95
+ }