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
package/AGENTS.md ADDED
@@ -0,0 +1,868 @@
1
+ # Decantr — Framework Translation Layer
2
+
3
+ **This is a Decantr project.** Use Decantr APIs exclusively. Do not suggest installing or using React, Vue, Angular, Svelte, Next.js, Nuxt, Tailwind, Bootstrap, or any external UI/CSS framework. Decantr is a zero-dependency web framework with its own component library, routing, state management, atomic CSS engine, and theme system.
4
+
5
+ If a user references patterns from other frameworks (e.g., "add a ShadCN dialog" or "use useState"), translate the intent into the Decantr equivalent using the tables below. Do not reach for external packages when Decantr has a built-in solution.
6
+
7
+ ---
8
+
9
+ ## Quick Equivalence Cheat Sheet
10
+
11
+ This table resolves 80% of framework translation needs. Scan here first.
12
+
13
+ | Concept | React / Next.js | Vue 3 / Nuxt | Svelte / Kit | Angular | **Decantr** |
14
+ |---------|----------------|-------------|-------------|---------|-------------|
15
+ | Element creation | JSX `<div>` | `<template>` | `<div>` | template | `const { div } = tags` |
16
+ | Reactive state | `useState(init)` | `ref(init)` | `$state` | `signal(init)` | `createSignal(init)` → `[get, set]` |
17
+ | Derived state | `useMemo(fn, deps)` | `computed(fn)` | `$derived` | `computed(fn)` | `createMemo(fn)` |
18
+ | Side effects | `useEffect(fn, deps)` | `watchEffect(fn)` | `$effect` | `effect(fn)` | `createEffect(fn)` — auto-tracked |
19
+ | Store / context | Context + Redux/Zustand | Pinia / provide-inject | stores | Services + RxJS | `createStore(obj)` |
20
+ | Routing | react-router | vue-router | SvelteKit | @angular/router | `createRouter({ routes })` |
21
+ | Conditional render | `{cond ? <A/> : <B/>}` | `v-if` / `v-else` | `{#if}` | `@if` | `cond(pred, trueFn, falseFn)` |
22
+ | List render | `.map(item => <X key=.../>)` | `v-for` + `:key` | `{#each}` | `@for` | `list(items, keyFn, renderFn)` |
23
+ | CSS / styling | Tailwind / CSS-in-JS | scoped `<style>` | scoped `<style>` | ViewEncapsulation | `css('_flex _gap4 _p6 _bgmuted')` |
24
+ | Component library | ShadCN / MUI / Radix | Element Plus / PrimeVue | Skeleton / Melt | Angular Material | `decantr/components` (100+ components) |
25
+ | Build / deploy | Next.js / Vite | Nuxt / Vite | SvelteKit / Vite | Angular CLI | `decantr build` → `dist/` |
26
+ | Testing | Jest / Vitest | Vitest | Vitest | Karma / Jest | `decantr/test` (node:test based) |
27
+ | i18n | react-intl / i18next | vue-i18n | svelte-i18n | @ngx-translate | `createI18n({ locale, messages })` |
28
+ | Auth | NextAuth.js | @nuxtjs/auth | SvelteKit auth | @angular/fire/auth | `createAuth(config)` + `requireAuth(router)` |
29
+
30
+ ---
31
+
32
+ ## Common Pitfalls
33
+
34
+ ### Conditional rendering — use `cond()`, not ternaries
35
+
36
+ ```js
37
+ // WRONG — inline ternary as child produces "[object HTMLSpanElement]" when wrapped in a reactive function
38
+ div({}, () => show() ? span({}, 'text') : null)
39
+
40
+ // CORRECT — use cond() for conditional DOM elements
41
+ div({}, cond(() => show(), () => span({}, 'text')))
42
+ ```
43
+
44
+ Function children are for **reactive text only** (`String(fn())`). For conditional DOM elements, always use `cond(predicate, trueFn, falseFn)`.
45
+
46
+ ### Mount + Router — argument order matters
47
+
48
+ ```js
49
+ // WRONG — reversed arguments
50
+ mount(() => router.outlet(), document.getElementById('app'))
51
+
52
+ // CORRECT — target first, render function second
53
+ mount(document.getElementById('app'), () => router.outlet())
54
+ ```
55
+
56
+ `createRouter()` returns a plain object. Call `router.outlet()` to get the DOM element — do not pass the router object directly to `mount()`.
57
+
58
+ ---
59
+
60
+ ## Tier 1: Full Framework Mappings
61
+
62
+ ### React
63
+
64
+ | React API | Decantr Equivalent |
65
+ |-----------|-------------------|
66
+ | `React.createElement('div', props, ...children)` | `const { div } = tags; div(props, ...children)` |
67
+ | `<div className="x">` (JSX) | `div({ class: css('_flex _p4') }, ...)` |
68
+ | `<>{child1}{child2}</>` (Fragment) | Just pass multiple children: `div(child1, child2)` |
69
+ | `useState(initial)` | `createSignal(initial)` → `[getter, setter]`. Read: `getter()`. Write: `setter(val)` |
70
+ | `useReducer(reducer, init)` | `createSignal(init)` + dispatch function that calls `setter` |
71
+ | `useMemo(fn, [deps])` | `createMemo(fn)` — deps are auto-tracked |
72
+ | `useCallback(fn, [deps])` | Not needed — no VDOM reconciliation. Just use the function directly |
73
+ | `useEffect(fn, [deps])` | `createEffect(fn)` — deps are auto-tracked, no dependency array needed |
74
+ | `useEffect(() => () => cleanup, [])` | `onDestroy(() => cleanup)` |
75
+ | `useEffect(() => {...}, [])` (mount-only) | `onMount(() => {...})` |
76
+ | `useRef(null)` | Assign via `const el = div(...)` — elements are real DOM nodes |
77
+ | `useContext(MyContext)` | `createStore(obj)` or import shared signals directly |
78
+ | `createContext` + `Provider` | `createStore(obj)` — no provider wrapper needed |
79
+ | `useId()` | Not needed — no SSR hydration mismatch concerns |
80
+ | `forwardRef` | Not needed — components return real DOM elements |
81
+ | `React.lazy(() => import(...))` | Standard dynamic `import()` — no wrapper needed |
82
+ | `Suspense` | `cond(() => isLoading(), fallback, content)` |
83
+ | `createPortal(child, container)` | `document.body.appendChild(el)` — real DOM, no portals needed |
84
+ | `ReactDOM.createRoot(el).render(<App/>)` | `mount(document.getElementById('app'), () => App())` |
85
+ | `<BrowserRouter>` + `<Routes>` | `createRouter({ routes }); mount(el, () => router.outlet())` |
86
+ | `className={clsx('a', cond && 'b')}` | `class: css('_a', cond && '_b')` |
87
+ | `style={{ color: 'red' }}` | `class: css('_fgerror')` (use semantic color atom). For runtime-computed values only: `style: () => \`color:${dynamicColor()}\`` |
88
+ | `onClick={handler}` | `onclick: handler` (lowercase, native DOM) |
89
+ | `onChange={handler}` | `oninput: handler` (for inputs — native DOM event) |
90
+ | `<Link to="/about">` (React Router) | `link({ href: '/about' }, 'About')` |
91
+ | `useNavigate()` then `nav('/path')` | `navigate('/path')` |
92
+ | `navigate(-1)` / `navigate(1)` (React Router) | `back()` / `forward()` |
93
+ | `useNavigation().state` (React Router) | `isNavigating()` — reactive boolean for loading bars |
94
+ | `route.meta` (Vue Router) | `to.meta` — merged parent→child, set via `meta: {}` on route config |
95
+ | `basename` (React Router) / `base` (Vue Router) | `createRouter({ base: '/app' })` |
96
+ | `useLocation()` / `useParams()` | `useRoute()` → signal with `path`, `params`, `query`, `meta` |
97
+ | `useEffect` + `useLocation()` for analytics | `onNavigate((to, from) => { ... })` |
98
+ | `<Routes><Route path="/" element={...}/></Routes>` | `createRouter({ routes: [{ path: '/', component: Home }] })` |
99
+
100
+ ### Vue 3
101
+
102
+ | Vue 3 API | Decantr Equivalent |
103
+ |-----------|-------------------|
104
+ | `<template>` + SFC | `const { div, p } = tags; div(p('Hello'))` |
105
+ | `ref(value)` | `createSignal(value)` → `[get, set]`. Read: `get()` not `.value` |
106
+ | `reactive(obj)` | `createStore(obj)` — per-property proxy tracking |
107
+ | `computed(() => ...)` | `createMemo(() => ...)` |
108
+ | `watch(source, cb)` | `createEffect(() => { const v = source(); /* react */ })` |
109
+ | `watchEffect(fn)` | `createEffect(fn)` — identical concept |
110
+ | `provide(key, value)` / `inject(key)` | Import shared signals/stores directly — no DI needed |
111
+ | `v-if` / `v-else` | `cond(pred, trueFn, falseFn)` |
112
+ | `v-for="item in items" :key="item.id"` | `list(() => items(), i => i.id, i => renderItem(i))` |
113
+ | `v-show` | `el.style.display = cond ? '' : 'none'` or toggle CSS class |
114
+ | `v-model` | `Input({ value: getter, oninput: e => setter(e.target.value) })` |
115
+ | `defineProps(['title'])` | Function parameter: `function MyComp({ title }) {}` |
116
+ | `defineEmits(['update'])` | Callback prop: `function MyComp({ onUpdate }) {}` |
117
+ | `<slot>` / `<slot name="x">` | Children arguments: `function MyComp(props, ...children) {}` |
118
+ | `<Teleport to="body">` | `document.body.appendChild(el)` — real DOM |
119
+ | `onMounted(fn)` | `onMount(fn)` |
120
+ | `onBeforeUnmount(fn)` | `onDestroy(fn)` |
121
+ | `<RouterLink :to="path">` | `link({ href: path }, 'Text')` |
122
+ | `useRouter().push(path)` | `navigate(path)` |
123
+ | `useRoute()` | `useRoute()` — same name, returns signal |
124
+ | Pinia store | `createStore(obj)` or module-level signals |
125
+ | `<Transition>` / `<TransitionGroup>` | CSS transitions via theme system + `_trans` atoms |
126
+
127
+ ### Svelte
128
+
129
+ | Svelte API | Decantr Equivalent |
130
+ |------------|-------------------|
131
+ | `<div>` in `.svelte` file | `const { div } = tags; div(...)` |
132
+ | `let count = $state(0)` | `const [count, setCount] = createSignal(0)` |
133
+ | `$derived(expression)` | `createMemo(() => expression)` |
134
+ | `$effect(() => {...})` | `createEffect(() => {...})` |
135
+ | `$props()` | Function parameter: `function MyComp(props) {}` |
136
+ | `$bindable()` | Signal getter prop: `MyComp({ value: () => signal() })` |
137
+ | `{#if cond}...{:else}...{/if}` | `cond(pred, trueFn, falseFn)` |
138
+ | `{#each items as item (item.id)}` | `list(() => items(), i => i.id, i => render(i))` |
139
+ | `{#await promise}` | `createSignal` for loading/data/error states |
140
+ | `writable(value)` | `createSignal(value)` |
141
+ | `derived(store, fn)` | `createMemo(fn)` |
142
+ | `onMount(fn)` | `onMount(fn)` — same name |
143
+ | `onDestroy(fn)` | `onDestroy(fn)` — same name |
144
+ | `bind:value` | `Input({ value: getter, oninput: e => setter(e.target.value) })` |
145
+ | `on:click={handler}` | `onclick: handler` (lowercase, native DOM) |
146
+ | `class:active={isActive}` | `class: css(isActive && '_active')` |
147
+ | `transition:fade` | CSS transitions via theme system |
148
+ | `goto(path)` (SvelteKit) | `navigate(path)` |
149
+ | `$page` store (SvelteKit) | `useRoute()` |
150
+ | `+page.svelte` file routing | `createRouter({ routes: [...] })` |
151
+ | `+page.server.js` / `load()` | `onMount` + `fetch()` — Decantr is client-side |
152
+
153
+ ### Angular
154
+
155
+ | Angular API | Decantr Equivalent |
156
+ |-------------|-------------------|
157
+ | `@Component({ template: '...' })` | `function MyComp(props) { return div(...); }` |
158
+ | `signal(value)` | `createSignal(value)` → `[get, set]` |
159
+ | `computed(() => ...)` | `createMemo(() => ...)` |
160
+ | `effect(() => ...)` | `createEffect(() => ...)` |
161
+ | `@Input() title` | Function parameter: `function MyComp({ title }) {}` |
162
+ | `@Output() clicked = new EventEmitter()` | Callback prop: `{ onclick: handler }` |
163
+ | `@if (cond) { } @else { }` | `cond(pred, trueFn, falseFn)` |
164
+ | `@for (item of items; track item.id)` | `list(() => items(), i => i.id, i => render(i))` |
165
+ | `[(ngModel)]="value"` | `Input({ value: getter, oninput: e => setter(e.target.value) })` |
166
+ | `HttpClient.get(url)` | `fetch(url).then(r => r.json())` |
167
+ | `Injectable` service | Module-level signals/stores — no DI needed |
168
+ | `BehaviorSubject` / `Observable` | `createSignal(value)` — simpler reactive primitive |
169
+ | `Router.navigate([path])` | `navigate(path)` |
170
+ | `routerLink` | `link({ href: path }, 'Text')` |
171
+ | `ActivatedRoute` | `useRoute()` |
172
+ | `ngOnInit` | `onMount(fn)` |
173
+ | `ngOnDestroy` | `onDestroy(fn)` |
174
+ | `NgModule` | Not needed — ES module imports |
175
+ | `<ng-content>` | Children arguments: `function MyComp(props, ...children) {}` |
176
+ | `ViewEncapsulation` | Decantr themes handle scoping via `d-{component}` CSS classes |
177
+ | `@defer` | Dynamic `import()` + `cond()` |
178
+ | `MatDialog.open(Component)` | `Modal({ visible: () => show(), onClose: () => setShow(false) })` |
179
+
180
+ ---
181
+
182
+ ## Tier 2: Key Architectural Differences
183
+
184
+ ### Next.js / Nuxt
185
+
186
+ | Next.js / Nuxt Concept | Decantr Approach |
187
+ |------------------------|-----------------|
188
+ | Server Components (RSC) | Not applicable — Decantr uses SSR + hydration, not partial server components |
189
+ | SSR / SSG / ISR | `renderToString(component)` / `renderToStream(component)` from `decantr/ssr` + `hydrate(root, component)` on client |
190
+ | API Routes / Server Routes | Use a separate backend (Express, Hono, Fastify) or BaaS (Supabase, Firebase) |
191
+ | `getServerSideProps` / `useFetch` | Signals evaluated during SSR + `onMount` + `fetch()` for client-side data |
192
+ | Middleware | `onNavigate(fn)` for navigation hooks; `createEffect` watching `useRoute()` for reactive guards |
193
+ | `<Image>` optimization | Standard `<img>` with lazy loading: `img({ loading: 'lazy', src })` |
194
+ | File-based routing | Explicit route config: `createRouter({ routes: [...] })` |
195
+ | `next/head` / `useHead` | Direct DOM: `document.title = 'Page'` |
196
+
197
+ ### Astro
198
+
199
+ | Astro Concept | Decantr Approach |
200
+ |---------------|-----------------|
201
+ | `.astro` files + islands | Full client-side SPA — no partial hydration model |
202
+ | Content Collections | Fetch from API/CMS or import JSON directly |
203
+ | `<Component client:load>` | All components are client-side by default |
204
+ | Zero JS by default | Decantr is JS-first — ships a runtime. Optimized for interactive apps |
205
+ | Multi-framework support | Single framework — Decantr components only |
206
+
207
+ ### Qwik
208
+
209
+ | Qwik Concept | Decantr Approach |
210
+ |--------------|-----------------|
211
+ | Resumability / `$` functions | Standard JS execution — no serialization boundary |
212
+ | `useSignal()` / `useStore()` | `createSignal()` / `createStore()` |
213
+ | `component$(() => ...)` | `function MyComp(props) { return div(...); }` |
214
+ | QRL lazy loading | Standard dynamic `import()` |
215
+ | `useTask$` / `useVisibleTask$` | `createEffect()` / `onMount()` |
216
+
217
+ ### Solid.js
218
+
219
+ Solid.js is architecturally closest to Decantr. Key differences:
220
+
221
+ | Solid.js | Decantr |
222
+ |----------|---------|
223
+ | `createSignal(init)` | `createSignal(init)` — identical API |
224
+ | `createEffect(fn)` | `createEffect(fn)` — identical API |
225
+ | `createMemo(fn)` | `createMemo(fn)` — identical API |
226
+ | JSX `<div class="x">` | `const { div } = tags; div({ class: css('_x') })` |
227
+ | `<Show when={cond}>` | `cond(pred, trueFn, falseFn)` |
228
+ | `<For each={items}>` | `list(items, keyFn, renderFn)` |
229
+ | `createStore()` | `createStore()` — similar concept |
230
+ | `@solidjs/router` | `createRouter()` — built-in |
231
+
232
+ ---
233
+
234
+ ## Component Suite Mappings
235
+
236
+ ### ShadCN / ui
237
+
238
+ | ShadCN Component | Decantr Equivalent | Notes |
239
+ |-----------------|-------------------|-------|
240
+ | `<Button>` | `Button({ variant, size })` | Variants: default, outline, ghost, destructive, link |
241
+ | `<Card>` + `<CardHeader>` etc. | `Card(props, ...children)` | Compound: `Card.Header`, `Card.Body`, `Card.Footer` |
242
+ | `<Dialog>` | `Modal({ title, visible, onClose })` | Role dialog + aria-modal built-in |
243
+ | `<Sheet>` / `<Drawer>` | `Drawer({ visible, side, title, size, footer, closeOnOutside })` | Compound: `Drawer.Header`, `Drawer.Body`, `Drawer.Footer` |
244
+ | `<AlertDialog>` | `Modal()` + `Alert()` | Compose Modal with Alert content |
245
+ | `<DropdownMenu>` | `Dropdown({ trigger, items })` | |
246
+ | `<Select>` | `Select({ options, value })` | |
247
+ | `<Combobox>` / `<Command>` | `Combobox({ options, value, onfilter })` | Searchable select |
248
+ | `<Popover>` | `Popover({ trigger, position })` | |
249
+ | `<Tooltip>` | `Tooltip({ content, position })` | |
250
+ | `<Tabs>` | `Tabs({ tabs, active })` | |
251
+ | `<Accordion>` | `Accordion({ items, multiple })` | |
252
+ | `<Input>` | `Input({ type, value, error })` | |
253
+ | `<Textarea>` | `Textarea({ value, rows })` | |
254
+ | `<Checkbox>` | `Checkbox({ checked, label })` | |
255
+ | `<Switch>` | `Switch({ checked, label })` | |
256
+ | `<RadioGroup>` | `RadioGroup({ options, value })` | |
257
+ | `<Slider>` | `Slider({ value, min, max, step })` | |
258
+ | `<Progress>` | `Progress({ value, max, variant })` | |
259
+ | `<Badge>` | `Badge({ count, status, color })` | |
260
+ | `<Avatar>` | `Avatar({ src, alt, size })` | |
261
+ | `<Skeleton>` | `Skeleton({ variant, width, height })` | |
262
+ | `<Separator>` | `Separator({ vertical, label })` | |
263
+ | `<Breadcrumb>` | `Breadcrumb({ items, separator })` | |
264
+ | `<Table>` | `Table({ columns, data, striped })` | |
265
+ | `<Pagination>` | `Pagination({ total, perPage, current })` | |
266
+ | `<Sonner>` / `toast()` | `toast({ message, variant, duration })` | |
267
+ | `<Alert>` | `Alert({ variant, dismissible })` | |
268
+ | `<Spinner>` | `Spinner({ size, label })` | |
269
+ | `<Calendar>` | `DatePicker({ value, onChange })` | Use DatePicker in calendar mode |
270
+ | `<DatePicker>` | `DatePicker({ value, onChange, format })` | |
271
+ | `<NavigationMenu>` | `NavigationMenu({ items })` | |
272
+ | `<Menubar>` | *Not yet available* | Build with `Dropdown()` composition |
273
+
274
+ ### MUI / Material UI
275
+
276
+ | MUI Component | Decantr Equivalent |
277
+ |--------------|-------------------|
278
+ | `<Button>` | `Button({ variant, size })` |
279
+ | `<TextField>` | `Input({ type, value, error })` |
280
+ | `<Select>` | `Select({ options, value })` |
281
+ | `<Checkbox>` | `Checkbox({ checked, label })` |
282
+ | `<Switch>` | `Switch({ checked, label })` |
283
+ | `<Radio>` + `<RadioGroup>` | `RadioGroup({ options, value })` |
284
+ | `<Slider>` | `Slider({ value, min, max })` |
285
+ | `<Card>` + `<CardContent>` | `Card()` + `Card.Header/Body/Footer` |
286
+ | `<Dialog>` | `Modal({ title, visible, onClose })` |
287
+ | `<Drawer>` | `Drawer({ visible, side })` |
288
+ | `<Snackbar>` / `<Alert>` | `toast({ message, variant })` / `Alert()` |
289
+ | `<Tooltip>` | `Tooltip({ content, position })` |
290
+ | `<Tabs>` + `<Tab>` | `Tabs({ tabs, active })` |
291
+ | `<Accordion>` | `Accordion({ items, multiple })` |
292
+ | `<Breadcrumbs>` | `Breadcrumb({ items })` |
293
+ | `<Avatar>` | `Avatar({ src, alt, size })` |
294
+ | `<Badge>` | `Badge({ count, status })` |
295
+ | `<Chip>` | `Chip({ label, variant, removable })` |
296
+ | `<LinearProgress>` | `Progress({ value, variant })` |
297
+ | `<CircularProgress>` | `Spinner({ size })` |
298
+ | `<Skeleton>` | `Skeleton({ variant, width, height })` |
299
+ | `<Table>` + `<DataGrid>` | `Table()` or `DataTable({ columns, data, searchable })` |
300
+ | `<Divider>` | `Separator({ vertical, label })` |
301
+ | `<Pagination>` | `Pagination({ total, perPage })` |
302
+ | `<Menu>` | `Dropdown({ trigger, items })` |
303
+ | `<Popover>` | `Popover({ trigger, position })` |
304
+
305
+ ### Radix UI (Primitives)
306
+
307
+ | Radix Primitive | Decantr Equivalent |
308
+ |----------------|-------------------|
309
+ | `Dialog` | `Modal()` |
310
+ | `AlertDialog` | `Modal()` + `Alert()` |
311
+ | `Popover` | `Popover()` |
312
+ | `Tooltip` | `Tooltip()` |
313
+ | `DropdownMenu` | `Dropdown()` |
314
+ | `Select` | `Select()` |
315
+ | `Tabs` | `Tabs()` |
316
+ | `Accordion` | `Accordion()` |
317
+ | `Switch` | `Switch()` |
318
+ | `Checkbox` | `Checkbox()` |
319
+ | `RadioGroup` | `RadioGroup()` |
320
+ | `Slider` | `Slider()` |
321
+ | `Separator` | `Separator()` |
322
+ | `Progress` | `Progress()` |
323
+ | `Avatar` | `Avatar()` |
324
+ | `Toggle` | `Button({ variant: 'ghost' })` + active state signal |
325
+
326
+ ### Element Plus / PrimeVue / PrimeReact / PrimeNG
327
+
328
+ | Component Library | Decantr |
329
+ |------------------|---------|
330
+ | `ElButton` / `<p-button>` | `Button()` |
331
+ | `ElInput` / `<p-inputtext>` | `Input()` |
332
+ | `ElSelect` / `<p-dropdown>` | `Select()` |
333
+ | `ElDialog` / `<p-dialog>` | `Modal()` |
334
+ | `ElDrawer` / `<p-sidebar>` | `Drawer()` |
335
+ | `ElTable` / `<p-datatable>` | `Table()` or `DataTable()` |
336
+ | `ElTabs` / `<p-tabview>` | `Tabs()` |
337
+ | `ElCollapse` / `<p-accordion>` | `Accordion()` |
338
+ | `ElTooltip` / `<p-tooltip>` | `Tooltip()` |
339
+ | `ElPopover` / `<p-popover>` | `Popover()` |
340
+ | `ElDropdown` / `<p-menu>` | `Dropdown()` |
341
+ | `ElPagination` / `<p-paginator>` | `Pagination()` |
342
+ | `ElMessage` / Toast service | `toast()` |
343
+ | `ElAlert` / `<p-message>` | `Alert()` |
344
+ | `ElTag` / `<p-chip>` | `Chip()` |
345
+ | `ElAvatar` / `<p-avatar>` | `Avatar()` |
346
+ | `ElBadge` / `<p-badge>` | `Badge()` |
347
+ | `ElSwitch` / `<p-inputswitch>` | `Switch()` |
348
+ | `ElCheckbox` / `<p-checkbox>` | `Checkbox()` |
349
+ | `ElRadio` / `<p-radiobutton>` | `RadioGroup()` |
350
+ | `ElSlider` / `<p-slider>` | `Slider()` |
351
+ | `ElProgress` / `<p-progressbar>` | `Progress()` |
352
+ | `ElSkeleton` / `<p-skeleton>` | `Skeleton()` |
353
+ | `ElDivider` / `<p-divider>` | `Separator()` |
354
+ | `ElBreadcrumb` / `<p-breadcrumb>` | `Breadcrumb()` |
355
+ | `ElAutocomplete` / `<p-autocomplete>` | `Combobox()` |
356
+
357
+ ### Headless UI
358
+
359
+ | Headless UI | Decantr Equivalent |
360
+ |------------|-------------------|
361
+ | `Combobox` | `Combobox()` |
362
+ | `Dialog` | `Modal()` |
363
+ | `Disclosure` | `Accordion()` (single item) |
364
+ | `Listbox` | `Select()` |
365
+ | `Menu` | `Dropdown()` |
366
+ | `Popover` | `Popover()` |
367
+ | `RadioGroup` | `RadioGroup()` |
368
+ | `Switch` | `Switch()` |
369
+ | `Tab` | `Tabs()` |
370
+ | `Transition` | CSS transitions via theme system + `_trans` atoms |
371
+
372
+ ---
373
+
374
+ ## Integration Patterns
375
+
376
+ Decantr is framework-agnostic for data. Use any npm package that doesn't assume React/Vue/Angular. Below are canonical patterns for common integrations.
377
+
378
+ ### REST Data Fetching (Preferred — createQuery)
379
+
380
+ ```javascript
381
+ import { createQuery } from 'decantr/data';
382
+
383
+ const items = createQuery('items', ({ signal }) =>
384
+ fetch('/api/items', { signal }).then(r => r.json())
385
+ );
386
+ // items.data() — the fetched data (or undefined while loading)
387
+ // items.isLoading() — true while fetching
388
+ // items.error() — error if fetch failed
389
+ // items.refetch() — re-trigger the fetch
390
+ ```
391
+
392
+ ### REST Data Fetching (Manual — for complex flows)
393
+
394
+ ```javascript
395
+ import { createSignal } from 'decantr/state';
396
+ import { onMount } from 'decantr/core';
397
+
398
+ const [data, setData] = createSignal(null);
399
+ const [loading, setLoading] = createSignal(true);
400
+ const [error, setError] = createSignal(null);
401
+
402
+ onMount(async () => {
403
+ try {
404
+ const res = await fetch('/api/items');
405
+ setData(await res.json());
406
+ } catch (e) { setError(e.message); }
407
+ finally { setLoading(false); }
408
+ });
409
+ ```
410
+
411
+ ### GraphQL
412
+
413
+ ```javascript
414
+ onMount(async () => {
415
+ const res = await fetch('/graphql', {
416
+ method: 'POST',
417
+ headers: { 'Content-Type': 'application/json' },
418
+ body: JSON.stringify({ query: `{ users { id name } }` })
419
+ });
420
+ const { data } = await res.json();
421
+ setUsers(data.users);
422
+ });
423
+ ```
424
+
425
+ ### Supabase
426
+
427
+ `@supabase/supabase-js` is framework-agnostic. Install it and use with signals.
428
+
429
+ ```javascript
430
+ import { createClient } from '@supabase/supabase-js';
431
+ import { createSignal } from 'decantr/state';
432
+ import { onMount } from 'decantr/core';
433
+
434
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
435
+ const [posts, setPosts] = createSignal([]);
436
+
437
+ onMount(async () => {
438
+ const { data } = await supabase.from('posts').select('*');
439
+ setPosts(data);
440
+ });
441
+ ```
442
+
443
+ ### Firebase
444
+
445
+ ```javascript
446
+ import { initializeApp } from 'firebase/app';
447
+ import { getFirestore, collection, getDocs } from 'firebase/firestore';
448
+ import { createSignal } from 'decantr/state';
449
+ import { onMount } from 'decantr/core';
450
+
451
+ const app = initializeApp(firebaseConfig);
452
+ const db = getFirestore(app);
453
+ const [items, setItems] = createSignal([]);
454
+
455
+ onMount(async () => {
456
+ const snap = await getDocs(collection(db, 'items'));
457
+ setItems(snap.docs.map(d => ({ id: d.id, ...d.data() })));
458
+ });
459
+ ```
460
+
461
+ ### Authentication Flow
462
+
463
+ ```javascript
464
+ import { createSignal, createEffect } from 'decantr/state';
465
+ import { navigate } from 'decantr/router';
466
+ import { createForm, validators, useFormField } from 'decantr/form';
467
+ import { Button, Input, Card } from 'decantr/components';
468
+ import { tags } from 'decantr/tags';
469
+ import { css } from 'decantr/css';
470
+
471
+ const [user, setUser] = createSignal(null);
472
+ const [loading, setLoading] = createSignal(true);
473
+
474
+ // Redirect unauthenticated users
475
+ createEffect(() => {
476
+ if (!loading() && !user()) navigate('/login');
477
+ });
478
+
479
+ // Login page using form system + components
480
+ function LoginPage() {
481
+ const { div, h2 } = tags;
482
+ const form = createForm({
483
+ fields: { email: '', password: '' },
484
+ validators: { email: [validators.required, validators.email], password: [validators.required] },
485
+ onSubmit: async ({ email, password }) => {
486
+ const res = await fetch('/api/auth/login', {
487
+ method: 'POST',
488
+ headers: { 'Content-Type': 'application/json' },
489
+ body: JSON.stringify({ email, password })
490
+ });
491
+ if (res.ok) setUser(await res.json());
492
+ }
493
+ });
494
+ return Card(
495
+ Card.Header(h2({ class: css('_heading4') }, 'Login')),
496
+ Card.Body(
497
+ Input({ ...useFormField(form, 'email').bind(), type: 'email', placeholder: 'Email' }),
498
+ Input({ ...useFormField(form, 'password').bind(), type: 'password', placeholder: 'Password' }),
499
+ Button({ onclick: () => form.submit() }, 'Sign In')
500
+ )
501
+ );
502
+ }
503
+ ```
504
+
505
+ ### Real-Time / WebSocket
506
+
507
+ ```javascript
508
+ import { createSignal } from 'decantr/state';
509
+ import { onMount, onDestroy } from 'decantr/core';
510
+
511
+ const [messages, setMessages] = createSignal([]);
512
+ let ws;
513
+
514
+ onMount(() => {
515
+ ws = new WebSocket('wss://api.example.com/ws');
516
+ ws.onmessage = (e) => {
517
+ setMessages(prev => [...prev, JSON.parse(e.data)]);
518
+ };
519
+ });
520
+
521
+ onDestroy(() => ws?.close());
522
+ ```
523
+
524
+ ### Stripe Payments
525
+
526
+ ```javascript
527
+ import { onMount } from 'decantr/core';
528
+ import { tags } from 'decantr/tags';
529
+
530
+ const { div } = tags;
531
+ const container = div({ id: 'card-element' });
532
+
533
+ onMount(async () => {
534
+ const stripe = Stripe('pk_live_...');
535
+ const elements = stripe.elements();
536
+ elements.create('card').mount('#card-element');
537
+ });
538
+ ```
539
+
540
+ ### Form Validation
541
+
542
+ ```javascript
543
+ import { createSignal, createMemo } from 'decantr/state';
544
+ import { Input, Button } from 'decantr/components';
545
+
546
+ const [email, setEmail] = createSignal('');
547
+ const [submitted, setSubmitted] = createSignal(false);
548
+
549
+ const emailError = createMemo(() => {
550
+ if (!submitted()) return null;
551
+ if (!email()) return 'Email is required';
552
+ if (!email().includes('@')) return 'Invalid email';
553
+ return null;
554
+ });
555
+
556
+ Input({ value: email, error: emailError, oninput: e => setEmail(e.target.value) });
557
+ Button({ onclick: () => setSubmitted(true) }, 'Submit');
558
+ ```
559
+
560
+ ### File Upload
561
+
562
+ ```javascript
563
+ import { createSignal } from 'decantr/state';
564
+
565
+ const [progress, setProgress] = createSignal(0);
566
+
567
+ async function upload(file) {
568
+ const form = new FormData();
569
+ form.append('file', file);
570
+ const xhr = new XMLHttpRequest();
571
+ xhr.upload.onprogress = (e) => setProgress(Math.round(e.loaded / e.total * 100));
572
+ xhr.open('POST', '/api/upload');
573
+ xhr.send(form);
574
+ }
575
+ ```
576
+
577
+ ### Local Storage Persistence
578
+
579
+ ```javascript
580
+ import { useLocalStorage } from 'decantr/state';
581
+
582
+ // Automatically persists to localStorage and syncs across tabs
583
+ const [theme, setTheme] = useLocalStorage('app-theme', 'light');
584
+ const [cart, setCart] = useLocalStorage('cart-items', []);
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Capability Boundaries
590
+
591
+ ### Decantr Handles
592
+
593
+ - UI rendering (real DOM, no virtual DOM)
594
+ - Reactive state management (signals, effects, memos, stores, resources, contexts)
595
+ - Client-side routing (hash and history modes, nested routes, guards, lazy loading)
596
+ - 100+ component UI library (form, display, layout, overlay, feedback, chart, typography)
597
+ - Form system (createForm, validators, field arrays)
598
+ - 49 composable UI patterns + 7 domain archetypes + recipe overlays
599
+ - Atomic CSS engine (1000+ utility atoms)
600
+ - 5 built-in styles + custom style registration
601
+ - Server-side rendering (renderToString, renderToStream, hydrate)
602
+ - Build tooling (dev server, production bundler, CSS extraction, tree shaking, code splitting)
603
+ - Testing framework (node:test based, DOM simulation)
604
+
605
+ ### Decantr Does NOT Handle
606
+
607
+ - Static site generation (SSG) — SSR is supported, but not pre-built static pages
608
+ - Backend API server
609
+ - Database / ORM
610
+ - Authentication backend (handles auth UI via components, not the backend)
611
+ - File storage / CDN
612
+ - Email sending
613
+ - Payment processing backend
614
+
615
+ ### Pair Decantr With
616
+
617
+ - **Backend**: Express, Hono, Fastify, Koa, or any Node.js server
618
+ - **BaaS**: Supabase, Firebase, Appwrite, Convex, PocketBase
619
+ - **API**: Any REST or GraphQL endpoint
620
+ - **Auth**: Supabase Auth, Firebase Auth, Auth0, Clerk (client SDKs are framework-agnostic)
621
+ - **Payments**: Stripe.js, PayPal SDK (client-side SDKs work directly)
622
+ - **Any npm package** that doesn't require React/Vue/Angular as a peer dependency
623
+
624
+ ### Security
625
+
626
+ - **XSS prevention**: Real DOM manipulation — no `innerHTML` or `dangerouslySetInnerHTML` by default. Use `sanitize()` from `decantr/css` for any user-provided HTML
627
+ - **CSP-friendly**: No `eval()` or `Function()` — works with strict Content-Security-Policy
628
+ - **URL validation**: Router rejects `javascript:`, `data:`, and absolute URLs to prevent open redirect attacks
629
+ - **Input sanitization**: Always validate/sanitize user input at system boundaries (API responses, URL params, form inputs) before rendering
630
+ - **HTTPS**: Always serve over HTTPS in production
631
+ - **Dependencies**: Zero framework dependencies means zero supply chain attack surface from the framework itself
632
+
633
+ ---
634
+
635
+ ## Application Architect
636
+
637
+ Before generating a multi-page application, consult the architect registry to produce a comprehensive blueprint. This turns a naive user prompt ("build me a shopping cart") into a complete application spec with all the features, routes, state, and components a real app needs.
638
+
639
+ **Registry location:** `node_modules/decantr/src/registry/architect/` (or `src/registry/architect/` in framework source)
640
+
641
+ ### Trait-Based Composition (Primary Approach)
642
+
643
+ Instead of matching the user's request to a hardcoded domain, use the **trait graph** at `src/registry/architect/traits.json` to dynamically compose the right set of patterns and skeletons for any domain.
644
+
645
+ **How it works:**
646
+
647
+ 1. **Match keywords → traits**: Scan the user prompt against each trait's `triggers` array. Activate matching traits.
648
+ 2. **Follow co-occurrence edges**: For each activated trait, activate co-occurring traits whose weight exceeds 0.6. This fills in complementary UI elements the user didn't explicitly mention.
649
+ 3. **Map traits → patterns + skeletons**: Each trait maps to specific patterns (e.g., `widget-grid` → `kpi-grid`) and optionally a skeleton (e.g., `sidebar-nav` → `sidebar-main`).
650
+ 4. **Check composites**: If the activated trait set closely matches a pre-built composite (dashboard, landing, ecommerce, etc.), use it as a starting point. Composites also suggest a vintage (style + mode).
651
+ 5. **Compose essence**: Build the essence's `structure` array from the resolved patterns and skeletons.
652
+
653
+ **Example:** User says "build me a mortgage pipeline dashboard"
654
+ - Keyword matches: "pipeline" → `pipeline-view`, "dashboard" → `widget-grid`, `sidebar-nav`
655
+ - Co-occurrence: `widget-grid` → `chart-area` (0.85), `data-table-view` (0.75); `pipeline-view` → `data-table-view` (0.7)
656
+ - Composite match: closest is `financial` composite
657
+ - Result: sidebar-main skeleton with pages containing kpi-grid, chart-grid, pipeline-tracker, data-table, filter-bar
658
+
659
+ **Pre-built archetypes become examples, not constraints.** The trait graph is the reasoning scaffold; archetypes and composites are common trait combinations pre-packaged for convenience. Use them as shortcuts when the match is confident, but always prefer composing from traits when the user's request doesn't fit a clean archetype match.
660
+
661
+ ### Legacy Domain Classification (Fallback)
662
+
663
+ **Current status:** Only `ecommerce` has a full architect domain file (`architect/domains/ecommerce.json`). The other 6 domains (saas-dashboard, portfolio, content-site, docs-explorer, financial-dashboard, recipe-community) have archetype blueprints in `src/registry/archetypes/` but no architect trigger/feature files yet. If no architect trigger file exists for the domain, use the archetype blueprint as the feature source. Still run SETTLE (5-layer decomposition), CLARIFY (write essence), and DECANT (resolve blends). The architect algorithm is an enhancement, not a prerequisite.
664
+
665
+ ### When to Use
666
+
667
+ Use Architect when the user's request implies **2+ routes with distinct page layouts** OR references a **domain keyword** from any architect trigger file. Single-page requests (one component, one form, one widget) skip Architect but STILL follow POUR→SETTLE→CLARIFY for the page they're adding to.
668
+
669
+ ### Step-by-Step Algorithm
670
+
671
+ Follow these 5 steps in order. Each step produces input for the next.
672
+
673
+ #### Step 1: Domain Classification (Weighted Keyword Matching)
674
+
675
+ 1. Read `src/registry/architect/index.json` to get available domains
676
+ 2. Normalize the user prompt to lowercase tokens
677
+ 3. For each domain, load its file and score against `triggers`:
678
+ - Each `primary` keyword match: **+3.0 points**
679
+ - Each `secondary` keyword match: **+1.0 points**
680
+ - Each `negative` keyword match: **−5.0 points**
681
+ - If 2+ primary hits: multiply total score by **1.5×**
682
+ - If 0 primary hits: multiply total score by **0.4×**
683
+ 4. Decision:
684
+ - Top score < 2.0 → domain unclear, treat as "general" (skip architect)
685
+ - Top two scores within 30% of each other → ambiguous, consider both domains
686
+ - Otherwise → confident single domain match
687
+
688
+ #### Step 2: Feature Graph Activation (DAG Forward-Chaining)
689
+
690
+ Given the matched domain file:
691
+
692
+ 1. **Explicit activation**: Scan the user prompt for keywords matching feature `label` or `desc`. Set confidence = 1.0 for matches.
693
+ 2. **Forward propagation via `implies` edges**: For each activated feature, activate its `implies` targets. Confidence decays **0.85× per hop**. Stop propagating when confidence drops below **0.3**.
694
+ 3. **Backward dependency resolution**: For every activated feature, ensure all `requires` features are also active (confidence = 1.0). Repeat until stable.
695
+ 4. **Auto-include tier=core**: Activate all features with `"t": "core"` regardless of prompt mentions.
696
+ 5. **Add cross-cutting concerns**: Load `cross-cutting.json` and apply all concerns listed in the domain's `cross_cutting` array.
697
+
698
+ #### Step 3: Completeness Scoring
699
+
700
+ Calculate a composite score from activated features vs. total available:
701
+
702
+ ```
703
+ composite = (
704
+ core_coverage × 0.60 + // % of core features activated
705
+ should_coverage × 0.25 + // % of should features activated
706
+ nice_coverage × 0.10 + // % of nice features activated
707
+ cross_coverage × 0.05 // % of cross-cutting concerns addressed
708
+ )
709
+ ```
710
+
711
+ Grade: **A** (≥0.9) | **B** (≥0.75) | **C** (≥0.5) | **D** (<0.5)
712
+
713
+ Identify gaps — features NOT activated — categorized as:
714
+ - **Critical gaps**: core features missing (should not happen after Step 2)
715
+ - **Recommended gaps**: should-tier features not activated
716
+ - **Optional gaps**: nice-tier features not activated
717
+
718
+ #### Step 4: Question Generation (max 5 per round)
719
+
720
+ Collect question candidates from 3 sources:
721
+
722
+ 1. **Feature-embedded questions** (from activated features' `questions` arrays) — priority: `weight × 2.0`
723
+ 2. **Critical gap questions** ("Your app needs X. Include it?") — priority: `weight × 3.0`
724
+ 3. **Recommended gap questions** ("Would you like X?") — priority: `weight × 1.5`
725
+
726
+ De-duplicate by affected feature. Take top 5 by priority. Present to user.
727
+
728
+ **Stop criteria**: Stop asking if completeness ≥ 0.75 AND no critical gaps. Max 2 rounds of questions.
729
+
730
+ #### Step 5: Blueprint Generation
731
+
732
+ Produce a structured blueprint JSON with these sections:
733
+
734
+ ```json
735
+ {
736
+ "domain": "<domain-id>",
737
+ "theme": "<suggested-theme>",
738
+ "score": { "composite": 0.87, "grade": "B" },
739
+ "routes": [
740
+ { "path": "/", "component": "HomePage" },
741
+ { "path": "/products", "component": "ProductListPage" }
742
+ ],
743
+ "files": [
744
+ { "path": "src/app.js", "type": "entry", "desc": "Router + layout + theme init" },
745
+ { "path": "src/state/store.js", "type": "state", "signals": ["..."], "stores": ["..."], "memos": ["..."] },
746
+ { "path": "src/pages/products.js", "type": "page", "components": ["Card", "Badge"], "features": ["product-catalog"] }
747
+ ],
748
+ "cross_cutting": {
749
+ "loading": { "applies": ["products", "cart", "checkout"] },
750
+ "errors": { "components": ["Alert", "toast"] },
751
+ "empty": { "applies": ["product-list", "cart", "search-results"] },
752
+ "a11y": true,
753
+ "responsive": true
754
+ }
755
+ }
756
+ ```
757
+
758
+ **Blueprint rules:**
759
+ - Every activated feature's `routes` → merged into `routes` array
760
+ - Every activated feature's `state` → merged into a single state file
761
+ - Every activated feature's `components` → listed in the relevant page file
762
+ - Every activated feature's `ux` hints → used when generating the page code
763
+ - `cross_cutting` concerns → applied to every relevant page
764
+ - Only reference components that exist in the Decantr component registry
765
+
766
+ ### Multi-Domain Resolution
767
+
768
+ When the domain classification (Step 1) identifies multiple applicable domains, follow this extended algorithm:
769
+
770
+ #### Trigger Conditions
771
+ - Top-2 domain scores are within 30% of each other
772
+ - User explicitly names multiple domains (e.g., "brand site with docs and explorer")
773
+ - User describes pages that map to different archetypes
774
+
775
+ #### Per-Section Processing
776
+ 1. **Classify sections**: Map each user-described section to its best-fit archetype
777
+ 2. **Independent SETTLE**: Run the 5-layer decomposition per section — each gets its own terroir, vintage, structure, and tannins
778
+ 3. **Shared tannin extraction**: Identify tannins that appear in 2+ sections (auth, analytics) → move to `shared_tannins`
779
+ 4. **Merge router tree**: Combine section routes under section path prefixes into a unified router config
780
+ 5. **Write sectioned essence**: Use the sectioned format with one entry per domain section
781
+
782
+ #### General Fallback
783
+ When no domain scores above 2.0 (no confident match):
784
+ 1. Ask the user to describe their main pages/sections
785
+ 2. Pattern-match each described section against archetype pages
786
+ 3. If a section matches an archetype's page set → assign that terroir
787
+ 4. If no match → treat as custom section with manual structure definition
788
+ 5. Still write a sectioned essence — even custom sections benefit from the Blend system
789
+
790
+ #### Per-Section Style Switching
791
+ For sectioned essences with different vintages per section, the generated `app.js` should include:
792
+
793
+ ```js
794
+ // Generated during SERVE for sectioned essence
795
+ router.beforeEach((to) => {
796
+ const section = sections.find(s => to.path.startsWith(s.path));
797
+ if (section?.vintage?.style) setStyle(section.vintage.style);
798
+ if (section?.vintage?.mode) setMode(section.vintage.mode);
799
+ });
800
+ ```
801
+
802
+ This is a **generated code pattern**, not framework code — the LLM produces it during SERVE.
803
+
804
+ When the architect detects multiple plausible domains, use a sectioned essence:
805
+
806
+ - **Top-2 scores within 30%**: Create a sectioned essence with one section per domain. Each section gets its own terroir, vintage, structure, and tannins. Shared tannins (auth, analytics) are extracted to `shared_tannins`.
807
+ - **User explicitly names multiple domains**: Same approach — sectioned essence with one section per domain, regardless of scoring.
808
+ - **Per-section SETTLE**: Run the 5-layer decomposition (Terroir, Vintage, Character, Structure, Tannins) independently for each section. Each section may have a different archetype, different style, and different page layouts.
809
+ - **Merge into unified router**: Combine all section structures into a single router tree. Each section's pages are nested under that section's `path` prefix. Shared tannins are wired once at the app level, not per-section.
810
+ - **"General" fallback**: When no domain scores above 2.0, do not guess. Ask the user to describe their pages in concrete terms — then pattern-match each page against archetypes individually. If pages map to multiple archetypes, use sectioned essence. If all pages fit one archetype, use simple essence.
811
+
812
+ ### Example Trace
813
+
814
+ User prompt: **"build me a shopping cart"**
815
+
816
+ 1. **Domain**: "cart" = primary hit (+3.0), score = 4.5 → **ecommerce** (confident)
817
+ 2. **Activation**: cart (explicit, 1.0) → implies checkout (0.85) → implies auth (0.72), order-confirmation (0.72), payment-integration (0.72) → auth implies user-profile (0.61). All core features auto-included: product-catalog, product-detail, cart, checkout, auth, search, order-confirmation, navbar.
818
+ 3. **Score**: core 100%, should ~60%, nice 0% → composite ≈ 0.78, grade **B**
819
+ 4. **Questions**: "Allow guest checkout or require account?" (from checkout), "Which payment provider?" (from payment-integration), "Would you like product reviews?" (nice gap), "Would you like wishlists?" (nice gap)
820
+ 5. **Blueprint**: 8 routes, 12 files, 15+ components, full cross-cutting coverage
821
+
822
+ ---
823
+
824
+ ## v0.5.0 Breaking Changes — Migration Guide
825
+
826
+ ### `createFormField()` wrapper class rename
827
+
828
+ The `createFormField()` wrapper class changed from `d-field` → `d-form-field`. This was necessary because `.d-field` is now the visual styling class for field containers (border, background, focus ring).
829
+
830
+ ```javascript
831
+ // Before (v0.4.x)
832
+ document.querySelector('.d-field') // could be either wrapper or visual
833
+ document.querySelector('.d-field-label') // form label
834
+
835
+ // After (v0.5.0)
836
+ document.querySelector('.d-form-field') // structural wrapper (label + control + help + error)
837
+ document.querySelector('.d-form-field-label') // form label
838
+ document.querySelector('.d-field') // visual styling (border, bg, focus)
839
+ ```
840
+
841
+ ### `createFormField()` return value
842
+
843
+ ```javascript
844
+ // Before: returns HTMLElement
845
+ const wrapper = createFormField(input, { label: 'Name' });
846
+
847
+ // After: returns { wrapper, setError, setSuccess, destroy }
848
+ const { wrapper, setError, setSuccess } = createFormField(input, { label: 'Name' });
849
+ ```
850
+
851
+ ### New unified field component API
852
+
853
+ All 14 field components now accept these additional props:
854
+
855
+ | Prop | Type | Description |
856
+ |------|------|-------------|
857
+ | `variant` | `'outlined'\|'filled'\|'ghost'` | Visual variant (default: outlined) |
858
+ | `success` | `boolean\|string\|Function` | Success state |
859
+ | `loading` | `boolean\|Function` | Loading state |
860
+ | `label` | `string` | Wraps component with `createFormField` |
861
+ | `help` | `string` | Help text below field |
862
+ | `required` | `boolean` | Required indicator in label |
863
+ | `aria-label` | `string` | Accessible name when no label |
864
+
865
+ ### ColorPicker: HSV → OKLCH
866
+
867
+ The color picker's saturation panel now uses OKLCH (perceptually uniform). The X axis maps to Chroma (0–0.4) and Y axis to Lightness (0–1). The API is unchanged: hex in, hex out. Colors may appear slightly different due to the perceptual uniformity of OKLCH.
868
+