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.
- package/AGENTS.md +868 -0
- package/CHANGELOG.md +255 -0
- package/CLAUDE.md +178 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/cli/art.js +127 -0
- package/cli/commands/a11y.js +61 -0
- package/cli/commands/audit.js +225 -0
- package/cli/commands/build.js +38 -0
- package/cli/commands/dev.js +18 -0
- package/cli/commands/doctor.js +197 -0
- package/cli/commands/figma-sync.js +48 -0
- package/cli/commands/figma-tokens.js +55 -0
- package/cli/commands/generate.js +26 -0
- package/cli/commands/init.js +116 -0
- package/cli/commands/lint.js +209 -0
- package/cli/commands/mcp.js +530 -0
- package/cli/commands/migrate.js +175 -0
- package/cli/commands/test.js +38 -0
- package/cli/commands/validate.js +354 -0
- package/cli/index.js +113 -0
- package/package.json +95 -0
- package/reference/atoms.md +517 -0
- package/reference/behaviors.md +384 -0
- package/reference/build-tooling.md +275 -0
- package/reference/color-guidelines.md +965 -0
- package/reference/component-lifecycle.md +137 -0
- package/reference/compound-spacing.md +95 -0
- package/reference/decantation-process.md +499 -0
- package/reference/dev-server-routes.md +93 -0
- package/reference/form-system.md +253 -0
- package/reference/i18n.md +336 -0
- package/reference/icons.md +576 -0
- package/reference/llm-primer.md +953 -0
- package/reference/plugins.md +252 -0
- package/reference/registry-consumption.md +76 -0
- package/reference/router.md +217 -0
- package/reference/shells.md +116 -0
- package/reference/spatial-guidelines.md +541 -0
- package/reference/ssr.md +234 -0
- package/reference/state-data.md +215 -0
- package/reference/state-patterns.md +166 -0
- package/reference/state.md +194 -0
- package/reference/style-system.md +110 -0
- package/reference/tokens.md +460 -0
- package/src/app.js +19 -0
- package/src/chart/_animate.js +266 -0
- package/src/chart/_base.js +109 -0
- package/src/chart/_data.js +209 -0
- package/src/chart/_format.js +106 -0
- package/src/chart/_interact.js +364 -0
- package/src/chart/_palette.js +105 -0
- package/src/chart/_renderer.js +52 -0
- package/src/chart/_scene.js +262 -0
- package/src/chart/_shared.js +371 -0
- package/src/chart/index.js +637 -0
- package/src/chart/layouts/_layout-base.js +328 -0
- package/src/chart/layouts/cartesian.js +148 -0
- package/src/chart/layouts/hierarchy.js +562 -0
- package/src/chart/layouts/polar.js +101 -0
- package/src/chart/renderers/canvas.js +179 -0
- package/src/chart/renderers/svg.js +256 -0
- package/src/chart/renderers/webgpu.js +715 -0
- package/src/chart/types/_type-base.js +26 -0
- package/src/chart/types/area.js +134 -0
- package/src/chart/types/bar.js +173 -0
- package/src/chart/types/box-plot.js +125 -0
- package/src/chart/types/bubble.js +63 -0
- package/src/chart/types/candlestick.js +115 -0
- package/src/chart/types/chord.js +85 -0
- package/src/chart/types/combination.js +108 -0
- package/src/chart/types/funnel.js +68 -0
- package/src/chart/types/gauge.js +163 -0
- package/src/chart/types/heatmap.js +98 -0
- package/src/chart/types/histogram.js +71 -0
- package/src/chart/types/line.js +111 -0
- package/src/chart/types/org-chart.js +93 -0
- package/src/chart/types/pie.js +81 -0
- package/src/chart/types/radar.js +96 -0
- package/src/chart/types/radial.js +68 -0
- package/src/chart/types/range-area.js +55 -0
- package/src/chart/types/range-bar.js +61 -0
- package/src/chart/types/sankey.js +73 -0
- package/src/chart/types/scatter.js +66 -0
- package/src/chart/types/sparkline.js +81 -0
- package/src/chart/types/sunburst.js +69 -0
- package/src/chart/types/swimlane.js +88 -0
- package/src/chart/types/treemap.js +62 -0
- package/src/chart/types/waterfall.js +100 -0
- package/src/components/_base.js +1658 -0
- package/src/components/_behaviors.js +1140 -0
- package/src/components/_primitives.js +534 -0
- package/src/components/_qr-encoder.js +539 -0
- package/src/components/accordion.js +207 -0
- package/src/components/affix.js +62 -0
- package/src/components/alert-dialog.js +75 -0
- package/src/components/alert.js +47 -0
- package/src/components/aspect-ratio.js +24 -0
- package/src/components/avatar-group.js +55 -0
- package/src/components/avatar.js +38 -0
- package/src/components/back-top.js +75 -0
- package/src/components/badge.js +74 -0
- package/src/components/banner.js +68 -0
- package/src/components/breadcrumb.js +162 -0
- package/src/components/button.js +115 -0
- package/src/components/calendar.js +131 -0
- package/src/components/card.js +192 -0
- package/src/components/carousel.js +98 -0
- package/src/components/cascader.js +261 -0
- package/src/components/checkbox.js +80 -0
- package/src/components/chip.js +81 -0
- package/src/components/code-block.js +82 -0
- package/src/components/collapsible.js +50 -0
- package/src/components/color-palette.js +438 -0
- package/src/components/color-picker.js +314 -0
- package/src/components/combobox.js +181 -0
- package/src/components/command.js +174 -0
- package/src/components/comment.js +206 -0
- package/src/components/context-menu.js +76 -0
- package/src/components/data-table.js +724 -0
- package/src/components/date-picker.js +217 -0
- package/src/components/date-range-picker.js +244 -0
- package/src/components/datetime-picker.js +271 -0
- package/src/components/descriptions.js +68 -0
- package/src/components/drawer.js +179 -0
- package/src/components/dropdown.js +88 -0
- package/src/components/empty.js +41 -0
- package/src/components/float-button.js +90 -0
- package/src/components/form.js +106 -0
- package/src/components/hover-card.js +49 -0
- package/src/components/icon.js +87 -0
- package/src/components/image.js +97 -0
- package/src/components/index.js +117 -0
- package/src/components/input-group.js +75 -0
- package/src/components/input-number.js +155 -0
- package/src/components/input-otp.js +178 -0
- package/src/components/input.js +91 -0
- package/src/components/kbd.js +36 -0
- package/src/components/label.js +25 -0
- package/src/components/list.js +118 -0
- package/src/components/masked-input.js +236 -0
- package/src/components/mentions.js +165 -0
- package/src/components/menu.js +259 -0
- package/src/components/message.js +80 -0
- package/src/components/modal.js +147 -0
- package/src/components/navigation-menu.js +166 -0
- package/src/components/notification.js +84 -0
- package/src/components/pagination.js +104 -0
- package/src/components/placeholder.js +132 -0
- package/src/components/popconfirm.js +70 -0
- package/src/components/popover.js +58 -0
- package/src/components/progress.js +61 -0
- package/src/components/qrcode.js +251 -0
- package/src/components/radiogroup.js +120 -0
- package/src/components/range-slider.js +176 -0
- package/src/components/rate.js +186 -0
- package/src/components/resizable.js +83 -0
- package/src/components/result.js +57 -0
- package/src/components/scroll-area.js +43 -0
- package/src/components/segmented.js +97 -0
- package/src/components/select.js +165 -0
- package/src/components/separator.js +31 -0
- package/src/components/shell.js +407 -0
- package/src/components/skeleton.js +39 -0
- package/src/components/slider.js +141 -0
- package/src/components/sortable-list.js +176 -0
- package/src/components/space.js +42 -0
- package/src/components/spinner.js +112 -0
- package/src/components/splitter.js +147 -0
- package/src/components/statistic.js +136 -0
- package/src/components/steps.js +99 -0
- package/src/components/switch.js +95 -0
- package/src/components/table.js +44 -0
- package/src/components/tabs.js +216 -0
- package/src/components/tag.js +115 -0
- package/src/components/textarea.js +82 -0
- package/src/components/time-picker.js +153 -0
- package/src/components/time-range-picker.js +170 -0
- package/src/components/timeline.js +226 -0
- package/src/components/toast.js +71 -0
- package/src/components/toggle.js +213 -0
- package/src/components/tooltip.js +57 -0
- package/src/components/tour.js +159 -0
- package/src/components/transfer.js +163 -0
- package/src/components/tree-select.js +274 -0
- package/src/components/tree.js +141 -0
- package/src/components/typography.js +136 -0
- package/src/components/upload.js +118 -0
- package/src/components/visually-hidden.js +20 -0
- package/src/components/watermark.js +124 -0
- package/src/core/index.js +539 -0
- package/src/core/lifecycle.js +69 -0
- package/src/css/atoms.js +651 -0
- package/src/css/components.js +940 -0
- package/src/css/derive.js +1296 -0
- package/src/css/index.js +265 -0
- package/src/css/runtime.js +268 -0
- package/src/css/styles/addons/bioluminescent.js +93 -0
- package/src/css/styles/addons/clay.js +70 -0
- package/src/css/styles/addons/clean.js +57 -0
- package/src/css/styles/addons/command-center.js +143 -0
- package/src/css/styles/addons/dopamine.js +83 -0
- package/src/css/styles/addons/editorial.js +80 -0
- package/src/css/styles/addons/glassmorphism.js +99 -0
- package/src/css/styles/addons/liquid-glass.js +105 -0
- package/src/css/styles/addons/prismatic.js +100 -0
- package/src/css/styles/addons/retro.js +63 -0
- package/src/css/styles/auradecantism.js +96 -0
- package/src/css/theme-registry.js +444 -0
- package/src/data/entity.js +281 -0
- package/src/data/index.js +13 -0
- package/src/data/persist.js +225 -0
- package/src/data/query.js +839 -0
- package/src/data/realtime.js +299 -0
- package/src/data/url.js +177 -0
- package/src/data/worker.js +134 -0
- package/src/explorer/archetypes.js +243 -0
- package/src/explorer/atoms.js +228 -0
- package/src/explorer/charts.js +497 -0
- package/src/explorer/components.js +129 -0
- package/src/explorer/foundations.js +949 -0
- package/src/explorer/icons.js +178 -0
- package/src/explorer/patterns.js +247 -0
- package/src/explorer/recipes.js +194 -0
- package/src/explorer/shared/pattern-examples.js +1337 -0
- package/src/explorer/shared/showcase-renderer.js +958 -0
- package/src/explorer/shared/spec-table.js +41 -0
- package/src/explorer/shared/usage-links.js +87 -0
- package/src/explorer/shell-config.js +10 -0
- package/src/explorer/shells.js +551 -0
- package/src/explorer/styles.js +161 -0
- package/src/explorer/tokens.js +262 -0
- package/src/explorer/tools.js +525 -0
- package/src/form/index.js +804 -0
- package/src/i18n/index.js +251 -0
- package/src/icons/essential.js +479 -0
- package/src/icons/index.js +53 -0
- package/src/plugins/index.js +282 -0
- package/src/registry/archetypes/content-site.json +71 -0
- package/src/registry/archetypes/docs-explorer.json +23 -0
- package/src/registry/archetypes/ecommerce.json +104 -0
- package/src/registry/archetypes/financial-dashboard.json +77 -0
- package/src/registry/archetypes/index.json +41 -0
- package/src/registry/archetypes/portfolio.json +82 -0
- package/src/registry/archetypes/recipe-community.json +159 -0
- package/src/registry/archetypes/saas-dashboard.json +86 -0
- package/src/registry/architect/cross-cutting.json +45 -0
- package/src/registry/architect/domains/ecommerce.json +294 -0
- package/src/registry/architect/domains/financial-services.json +302 -0
- package/src/registry/architect/index.json +26 -0
- package/src/registry/architect/traits.json +379 -0
- package/src/registry/atoms.json +16 -0
- package/src/registry/chart-showcase.json +160 -0
- package/src/registry/chart.json +136 -0
- package/src/registry/components.json +8616 -0
- package/src/registry/core.json +216 -0
- package/src/registry/css.json +319 -0
- package/src/registry/data.json +135 -0
- package/src/registry/foundations.json +11 -0
- package/src/registry/icons.json +463 -0
- package/src/registry/index.json +101 -0
- package/src/registry/patterns/activity-feed.json +37 -0
- package/src/registry/patterns/article-content.json +27 -0
- package/src/registry/patterns/auth-form.json +37 -0
- package/src/registry/patterns/author-card.json +20 -0
- package/src/registry/patterns/card-grid.json +127 -0
- package/src/registry/patterns/category-nav.json +26 -0
- package/src/registry/patterns/chart-grid.json +36 -0
- package/src/registry/patterns/chat-interface.json +37 -0
- package/src/registry/patterns/checklist-card.json +55 -0
- package/src/registry/patterns/comparison-panel.json +27 -0
- package/src/registry/patterns/component-showcase.json +24 -0
- package/src/registry/patterns/contact-form.json +31 -0
- package/src/registry/patterns/cta-section.json +20 -0
- package/src/registry/patterns/data-table.json +37 -0
- package/src/registry/patterns/detail-header.json +83 -0
- package/src/registry/patterns/detail-panel.json +27 -0
- package/src/registry/patterns/explorer-shell.json +22 -0
- package/src/registry/patterns/filter-bar.json +33 -0
- package/src/registry/patterns/filter-sidebar.json +27 -0
- package/src/registry/patterns/form-sections.json +110 -0
- package/src/registry/patterns/goal-tracker.json +27 -0
- package/src/registry/patterns/hero.json +107 -0
- package/src/registry/patterns/index.json +47 -0
- package/src/registry/patterns/kpi-grid.json +36 -0
- package/src/registry/patterns/media-gallery.json +20 -0
- package/src/registry/patterns/order-history.json +20 -0
- package/src/registry/patterns/pagination.json +19 -0
- package/src/registry/patterns/photo-to-recipe.json +36 -0
- package/src/registry/patterns/pipeline-tracker.json +28 -0
- package/src/registry/patterns/post-list.json +27 -0
- package/src/registry/patterns/pricing-table.json +32 -0
- package/src/registry/patterns/scorecard.json +28 -0
- package/src/registry/patterns/search-bar.json +20 -0
- package/src/registry/patterns/specimen-grid.json +19 -0
- package/src/registry/patterns/stat-card.json +55 -0
- package/src/registry/patterns/stats-bar.json +55 -0
- package/src/registry/patterns/steps-card.json +55 -0
- package/src/registry/patterns/table-of-contents.json +19 -0
- package/src/registry/patterns/testimonials.json +21 -0
- package/src/registry/patterns/timeline.json +27 -0
- package/src/registry/patterns/token-inspector.json +21 -0
- package/src/registry/patterns/wizard.json +27 -0
- package/src/registry/recipe-auradecantism.json +69 -0
- package/src/registry/recipe-clean.json +65 -0
- package/src/registry/recipe-command-center.json +78 -0
- package/src/registry/router.json +73 -0
- package/src/registry/schema/README.md +197 -0
- package/src/registry/skeletons.json +259 -0
- package/src/registry/state.json +137 -0
- package/src/registry/tokens.json +40 -0
- package/src/router/hash.js +17 -0
- package/src/router/history.js +18 -0
- package/src/router/index.js +598 -0
- package/src/ssr/index.js +922 -0
- package/src/state/arrays.js +181 -0
- package/src/state/devtools.js +647 -0
- package/src/state/index.js +498 -0
- package/src/state/middleware.js +288 -0
- package/src/state/scheduler.js +206 -0
- package/src/state/store.js +300 -0
- package/src/tags/index.js +19 -0
- package/src/tannins/auth.js +396 -0
- package/src/test/dom.js +352 -0
- package/src/test/index.js +62 -0
- package/src/test/state.js +306 -0
- package/tools/a11y-audit.js +487 -0
- package/tools/analyzer.js +315 -0
- package/tools/audit.js +706 -0
- package/tools/builder.js +1422 -0
- package/tools/css-extract.js +188 -0
- package/tools/dev-server.js +316 -0
- package/tools/dts-gen.js +1260 -0
- package/tools/figma-components.js +329 -0
- package/tools/figma-patterns.js +516 -0
- package/tools/figma-plugin/code.js +453 -0
- package/tools/figma-plugin/manifest.json +14 -0
- package/tools/figma-plugin/ui.html +268 -0
- package/tools/figma-render.js +293 -0
- package/tools/figma-tokens.js +712 -0
- package/tools/figma-upload.js +318 -0
- package/tools/generate.js +738 -0
- package/tools/icons.js +133 -0
- package/tools/init-templates.js +265 -0
- package/tools/install-hooks.sh +5 -0
- package/tools/migrations/0.5.0.js +53 -0
- package/tools/migrations/0.6.0.js +95 -0
- package/tools/minify.js +170 -0
- package/tools/pre-commit +4 -0
- package/tools/registry.js +662 -0
- package/tools/reset-playground.js +61 -0
- package/tools/starter-templates/content-site/app.js +49 -0
- package/tools/starter-templates/content-site/essence.js +19 -0
- package/tools/starter-templates/content-site/pages.js +31 -0
- package/tools/starter-templates/ecommerce/app.js +50 -0
- package/tools/starter-templates/ecommerce/essence.js +19 -0
- package/tools/starter-templates/ecommerce/pages.js +31 -0
- package/tools/starter-templates/landing-page/app.js +38 -0
- package/tools/starter-templates/landing-page/essence.js +18 -0
- package/tools/starter-templates/landing-page/pages.js +21 -0
- package/tools/starter-templates/portfolio/app.js +45 -0
- package/tools/starter-templates/portfolio/essence.js +19 -0
- package/tools/starter-templates/portfolio/pages.js +33 -0
- package/tools/starter-templates/saas-dashboard/app.js +70 -0
- package/tools/starter-templates/saas-dashboard/essence.js +19 -0
- package/tools/starter-templates/saas-dashboard/pages.js +31 -0
- package/tools/verify-pack.js +203 -0
- package/types/chart.d.ts +77 -0
- package/types/components.d.ts +587 -0
- package/types/core.d.ts +89 -0
- package/types/css.d.ts +149 -0
- package/types/data.d.ts +238 -0
- package/types/form.d.ts +164 -0
- package/types/i18n.d.ts +51 -0
- package/types/icons.d.ts +27 -0
- package/types/index.d.ts +13 -0
- package/types/router.d.ts +116 -0
- package/types/ssr.d.ts +102 -0
- package/types/state.d.ts +83 -0
- package/types/tags.d.ts +62 -0
- package/types/tannins.d.ts +63 -0
- 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
|
+
|