decantr 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,41 @@
1
+ import { tags } from 'decantr/tags';
2
+ import { css } from 'decantr/css';
3
+ import { injectExplorerCSS } from '../styles.js';
4
+ injectExplorerCSS();
5
+
6
+ const { div, table, thead, tbody, tr, th, td, span, code } = tags;
7
+
8
+ /**
9
+ * Auto-generated props/API table from registry JSON.
10
+ * @param {{ props: Record<string, { type: string, default?: any, description?: string, enum?: string[] }> }} opts
11
+ */
12
+ export function SpecTable({ props }) {
13
+ if (!props || Object.keys(props).length === 0) {
14
+ return div({ class: css('_fgmutedfg _body') }, 'No props defined in registry.');
15
+ }
16
+
17
+ const rows = Object.entries(props).map(([name, meta]) => {
18
+ const typeStr = meta.enum ? meta.enum.join(' | ') : (meta.type || 'any');
19
+ const defaultStr = meta.default !== undefined ? String(meta.default) : '—';
20
+ return tr({},
21
+ td({ class: 'de-spec-name' }, code({}, name)),
22
+ td({ class: 'de-spec-type' }, code({}, typeStr)),
23
+ td({ class: 'de-spec-default' }, defaultStr),
24
+ td({ class: 'de-spec-desc' }, meta.description || '—')
25
+ );
26
+ });
27
+
28
+ return div({ class: 'de-spec-table-wrap' },
29
+ table({ class: 'de-spec-table' },
30
+ thead({},
31
+ tr({},
32
+ th({}, 'Prop'),
33
+ th({}, 'Type'),
34
+ th({}, 'Default'),
35
+ th({}, 'Description')
36
+ )
37
+ ),
38
+ tbody({}, ...rows)
39
+ )
40
+ );
41
+ }
@@ -0,0 +1,87 @@
1
+ import { tags } from 'decantr/tags';
2
+ import { css } from 'decantr/css';
3
+ import { Chip } from 'decantr/components';
4
+
5
+ const { div, span, h4 } = tags;
6
+
7
+ // Reverse indexes built on init
8
+ let componentToPatterns = {};
9
+ let patternToArchetypes = {};
10
+ let initialized = false;
11
+
12
+ /**
13
+ * Build reverse indexes from registry data.
14
+ * Call once after registry is loaded.
15
+ */
16
+ export function initUsageIndex(patterns, archetypes) {
17
+ componentToPatterns = {};
18
+ patternToArchetypes = {};
19
+
20
+ // Component → Patterns
21
+ for (const [patternId, pattern] of Object.entries(patterns)) {
22
+ for (const comp of (pattern.components || [])) {
23
+ if (!componentToPatterns[comp]) componentToPatterns[comp] = [];
24
+ componentToPatterns[comp].push({ id: patternId, name: pattern.name || patternId });
25
+ }
26
+ }
27
+
28
+ // Pattern → Archetypes
29
+ for (const [archId, arch] of Object.entries(archetypes)) {
30
+ for (const page of (arch.pages || [])) {
31
+ for (const pat of (page.patterns || [])) {
32
+ if (!patternToArchetypes[pat]) patternToArchetypes[pat] = [];
33
+ patternToArchetypes[pat].push({ archetype: archId, page: page.id });
34
+ }
35
+ }
36
+ }
37
+
38
+ initialized = true;
39
+ }
40
+
41
+ /**
42
+ * Render "Used in patterns" chips for a component.
43
+ */
44
+ export function ComponentUsageLinks(componentName, navigateTo) {
45
+ const patterns = componentToPatterns[componentName] || [];
46
+ if (patterns.length === 0) {
47
+ return span({ class: css('_fgmutedfg _caption') }, 'Not referenced by any patterns.');
48
+ }
49
+
50
+ return div({ class: css('_flex _col _gap2') },
51
+ h4({ class: css('_heading6') }, `Used in ${patterns.length} pattern${patterns.length > 1 ? 's' : ''}`),
52
+ div({ class: css('_flex _gap2 _wrap') },
53
+ ...patterns.map(pat =>
54
+ Chip({
55
+ label: pat.name,
56
+ variant: 'outline',
57
+ size: 'sm',
58
+ onclick: () => navigateTo(`/patterns/${pat.id}`)
59
+ })
60
+ )
61
+ )
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Render "Used in archetypes" chips for a pattern.
67
+ */
68
+ export function PatternUsageLinks(patternId, navigateTo) {
69
+ const archs = patternToArchetypes[patternId] || [];
70
+ if (archs.length === 0) {
71
+ return span({ class: css('_fgmutedfg _caption') }, 'Not referenced by any archetypes.');
72
+ }
73
+
74
+ return div({ class: css('_flex _col _gap2') },
75
+ h4({ class: css('_heading6') }, `Used in ${archs.length} archetype page${archs.length > 1 ? 's' : ''}`),
76
+ div({ class: css('_flex _gap2 _wrap') },
77
+ ...archs.map(ref =>
78
+ Chip({
79
+ label: `${ref.archetype} / ${ref.page}`,
80
+ variant: 'outline',
81
+ size: 'sm',
82
+ onclick: () => navigateTo(`/archetypes/${ref.archetype}`)
83
+ })
84
+ )
85
+ )
86
+ );
87
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared signal for "Apply to Workbench" shell config.
3
+ * Separate module to avoid circular imports between app.js and explorer/shells.js.
4
+ */
5
+ import { createSignal } from 'decantr/state';
6
+
7
+ const storedConfig = typeof localStorage !== 'undefined' ? localStorage.getItem('de-shell-config') : null;
8
+ const [activeShellConfig, setActiveShellConfig] = createSignal(storedConfig ? JSON.parse(storedConfig) : null);
9
+
10
+ export { activeShellConfig, setActiveShellConfig };
@@ -0,0 +1,551 @@
1
+ import { css } from 'decantr/css';
2
+ import { tags } from 'decantr/tags';
3
+ import { createSignal, createEffect } from 'decantr/state';
4
+ import { Tabs, Separator, Chip, Button, Slider, Switch } from 'decantr/components';
5
+ import { resolveShellConfig, buildGridTemplate } from 'decantr/components/shell.js';
6
+ import { activeShellConfig, setActiveShellConfig } from './shell-config.js';
7
+ import { injectExplorerCSS } from './styles.js';
8
+ injectExplorerCSS();
9
+
10
+ const { div, h2, h3, h4, p, span, code, pre, section, button } = tags;
11
+
12
+ // ─── Skeleton data (loaded from registry) ───────────────────────
13
+
14
+ let skeletonsData = {};
15
+ let skeletonsLoaded = false;
16
+
17
+ async function loadSkeletons() {
18
+ if (skeletonsLoaded) return skeletonsData;
19
+ try {
20
+ const resp = await fetch('/__decantr/registry/skeletons.json');
21
+ const data = await resp.json();
22
+ skeletonsData = data.skeletons || {};
23
+ skeletonsLoaded = true;
24
+ } catch {
25
+ skeletonsLoaded = true;
26
+ }
27
+ return skeletonsData;
28
+ }
29
+
30
+ // ─── CSS Miniature Thumbnail ────────────────────────────────────
31
+
32
+ const REGION_COLORS = {
33
+ header: 'var(--d-surface-1)',
34
+ nav: 'var(--d-surface-1)',
35
+ body: 'var(--d-surface-0)',
36
+ footer: 'var(--d-surface-1)',
37
+ aside: 'var(--d-surface-1)'
38
+ };
39
+
40
+ const REGION_LABELS = {
41
+ header: 'Header',
42
+ nav: 'Nav',
43
+ body: 'Body',
44
+ footer: 'Footer',
45
+ aside: 'Aside'
46
+ };
47
+
48
+ /**
49
+ * Proportional grid template for thumbnail previews.
50
+ * Uses fr units instead of absolute px so the layout scales to any container.
51
+ */
52
+ function buildThumbnailTemplate(cfg) {
53
+ const { grid } = cfg;
54
+ const areas = grid.areas.map(row => `"${row.join(' ')}"`).join(' ');
55
+
56
+ // Columns: nav/aside → 1fr, body/header → 3fr
57
+ const firstRow = grid.areas[0];
58
+ const colDefs = [];
59
+ for (let c = 0; c < firstRow.length; c++) {
60
+ const regionSet = new Set();
61
+ for (const row of grid.areas) regionSet.add(row[c]);
62
+ colDefs.push((regionSet.has('nav') && regionSet.size === 1) || (regionSet.has('aside') && regionSet.size === 1) ? '1fr' : '3fr');
63
+ }
64
+
65
+ // Rows: header/footer → 1fr, body → 3fr
66
+ const rowDefs = [];
67
+ for (let r = 0; r < grid.areas.length; r++) {
68
+ const regionSet = new Set(grid.areas[r]);
69
+ rowDefs.push((regionSet.has('body') && !regionSet.has('header') && !regionSet.has('footer')) ? '3fr' : '1fr');
70
+ }
71
+
72
+ return { areas, columns: colDefs.join(' '), rows: rowDefs.join(' ') };
73
+ }
74
+
75
+ function ShellThumbnail(presetId) {
76
+ let cfg;
77
+ try { cfg = resolveShellConfig(presetId); }
78
+ catch { return div({ class: css('_p4 _fgmutedfg _caption') }, 'Invalid'); }
79
+
80
+ const tpl = buildThumbnailTemplate(cfg);
81
+
82
+ // Dynamic grid template — runtime value from config resolution
83
+ const thumb = div({
84
+ class: css('_grid _border _bcborder _radius _overflow[hidden] _minh[140px]'),
85
+ style: () => `grid-template-areas:${tpl.areas};grid-template-columns:${tpl.columns};grid-template-rows:${tpl.rows}`
86
+ });
87
+
88
+ // Add region cells
89
+ const rendered = new Set();
90
+ for (const row of cfg.grid.areas) {
91
+ for (const cell of row) {
92
+ if (rendered.has(cell)) continue;
93
+ rendered.add(cell);
94
+ // grid-area + background are dynamic (cell-dependent runtime values)
95
+ thumb.appendChild(div({
96
+ class: css('_flex _center _border _bcborder _textxs _fgmuted _uppercase _ls[0.05em]'),
97
+ style: () => `grid-area:${cell};background:${REGION_COLORS[cell] || 'var(--d-bg)'}`
98
+ }, REGION_LABELS[cell] || cell));
99
+ }
100
+ }
101
+
102
+ return thumb;
103
+ }
104
+
105
+ // ─── Live Preview ───────────────────────────────────────────────
106
+
107
+ function ShellPreview(presetId, configSignals) {
108
+ const container = div({ class: css('_border _bcborder _radius _overflow[hidden] _minh[300px]') });
109
+
110
+ createEffect(() => {
111
+ const navPos = configSignals.navPosition();
112
+ const navMode = configSignals.navMode();
113
+ const showHeader = configSignals.showHeader();
114
+ const showFooter = configSignals.showFooter();
115
+ const showAside = configSignals.showAside();
116
+ const sidebarW = configSignals.sidebarWidth() + 'px';
117
+ const headerH = configSignals.headerHeight() + 'px';
118
+ const asideW = configSignals.asideWidth() + 'px';
119
+
120
+ const isTop = navPos === 'top';
121
+ const hasSidebar = !isTop && navMode !== 'hidden';
122
+
123
+ // Build areas
124
+ const areas = [];
125
+
126
+ if (showHeader) {
127
+ const row = [];
128
+ if (hasSidebar && navPos === 'left') row.push('nav');
129
+ row.push('header');
130
+ if (hasSidebar && navPos === 'right') row.push('nav');
131
+ if (showAside) row.push('aside');
132
+ areas.push(row);
133
+ }
134
+
135
+ // Body row
136
+ const bodyRow = [];
137
+ if (hasSidebar && navPos === 'left') bodyRow.push('nav');
138
+ bodyRow.push('body');
139
+ if (hasSidebar && navPos === 'right') bodyRow.push('nav');
140
+ if (showAside) bodyRow.push('aside');
141
+ areas.push(bodyRow);
142
+
143
+ if (showFooter) {
144
+ const footerRow = [];
145
+ if (hasSidebar && navPos === 'left') footerRow.push('nav');
146
+ footerRow.push('footer');
147
+ if (hasSidebar && navPos === 'right') footerRow.push('nav');
148
+ if (showAside) footerRow.push('aside');
149
+ areas.push(footerRow);
150
+ }
151
+
152
+ // Build template strings
153
+ const areasStr = areas.map(r => `"${r.join(' ')}"`).join(' ');
154
+
155
+ const cols = [];
156
+ if (hasSidebar && navPos === 'left') cols.push(navMode === 'rail' ? 'var(--de-shell-rail-w, 64px)' : sidebarW);
157
+ cols.push('1fr');
158
+ if (hasSidebar && navPos === 'right') cols.push(navMode === 'rail' ? 'var(--de-shell-rail-w, 64px)' : sidebarW);
159
+ if (showAside) cols.push(asideW);
160
+
161
+ const rowDefs = [];
162
+ if (showHeader) rowDefs.push(headerH);
163
+ rowDefs.push('1fr');
164
+ if (showFooter) rowDefs.push('auto');
165
+
166
+ container.innerHTML = '';
167
+ // Dynamic grid template — computed from reactive signals
168
+ const grid = div({
169
+ class: css('_grid _h[300px]'),
170
+ style: () => `grid-template-areas:${areasStr};grid-template-columns:${cols.join(' ')};grid-template-rows:${rowDefs.join(' ')};transition:all var(--d-duration-fast) var(--d-easing-standard)`
171
+ });
172
+
173
+ // Render regions
174
+ const renderedRegions = new Set();
175
+ for (const row of areas) {
176
+ for (const cell of row) {
177
+ if (renderedRegions.has(cell)) continue;
178
+ renderedRegions.add(cell);
179
+
180
+ let content = REGION_LABELS[cell] || cell;
181
+ if (cell === 'header' && isTop) content = 'Header + Nav';
182
+ if (cell === 'body') content = 'Main Content';
183
+
184
+ // grid-area + background are dynamic per-cell
185
+ grid.appendChild(div({
186
+ class: css('_flex _center _border _bcborder _p2 _fgmutedfg _textsm'),
187
+ style: () => `grid-area:${cell};background:${REGION_COLORS[cell] || 'var(--d-bg)'}`
188
+ }, content));
189
+ }
190
+ }
191
+
192
+ container.appendChild(grid);
193
+ });
194
+
195
+ return container;
196
+ }
197
+
198
+ // ─── Configurator Controls ──────────────────────────────────────
199
+
200
+ function ShellConfigurator(presetId, configSignals) {
201
+ const { navPosition, setNavPosition, navMode, setNavMode,
202
+ showHeader, setShowHeader, showFooter, setShowFooter,
203
+ showAside, setShowAside, sidebarWidth, setSidebarWidth,
204
+ headerHeight, setHeaderHeight, asideWidth, setAsideWidth } = configSignals;
205
+
206
+ function segmentedControl(label, options, getter, setter) {
207
+ return div({ class: css('_flex _aic _jcsb _gap4 _py2') },
208
+ span({ class: css('_caption _fgmutedfg') }, label),
209
+ div({ class: css('_flex _gap1') },
210
+ ...options.map(opt =>
211
+ button({
212
+ class: () => css('_px3 _py1 _radius _caption _cursor[pointer] _border _bcborder _trans') +
213
+ (getter() === opt.value ? ' ' + css('_bgprimary/20 _fgprimary _bcprimary/40') : ' ' + css('_bgmuted/10 _fgmutedfg')),
214
+ onclick: () => setter(opt.value)
215
+ }, opt.label)
216
+ )
217
+ )
218
+ );
219
+ }
220
+
221
+ function toggleControl(label, getter, setter) {
222
+ return div({ class: css('_flex _aic _jcsb _py2') },
223
+ span({ class: css('_caption _fgmutedfg') }, label),
224
+ Switch({ checked: getter, onchange: setter, size: 'sm' })
225
+ );
226
+ }
227
+
228
+ function sliderControl(label, getter, setter, min, max, unit = 'px') {
229
+ return div({ class: css('_flex _col _gap1 _py2') },
230
+ div({ class: css('_flex _aic _jcsb') },
231
+ span({ class: css('_caption _fgmutedfg') }, label),
232
+ span({ class: css('_caption _fgmutedfg _fontmono') }, () => getter() + unit)
233
+ ),
234
+ Slider({ value: getter, onchange: setter, min, max, step: 4 })
235
+ );
236
+ }
237
+
238
+ const isTopNav = () => navPosition() === 'top';
239
+
240
+ return div({ class: css('_flex _col _gap2 _p4 _border _bcborder _radius') },
241
+ h4({ class: css('_heading6 _mb2') }, 'Configuration'),
242
+
243
+ segmentedControl('Nav Position', [
244
+ { value: 'left', label: 'Left' },
245
+ { value: 'right', label: 'Right' },
246
+ { value: 'top', label: 'Top' }
247
+ ], navPosition, setNavPosition),
248
+
249
+ segmentedControl('Nav Mode', [
250
+ { value: 'full', label: 'Full' },
251
+ { value: 'rail', label: 'Rail' },
252
+ { value: 'hidden', label: 'Hidden' }
253
+ ], navMode, (v) => { if (!isTopNav()) setNavMode(v); }),
254
+
255
+ Separator({}),
256
+
257
+ toggleControl('Header', showHeader, setShowHeader),
258
+ toggleControl('Footer', showFooter, setShowFooter),
259
+ toggleControl('Aside Panel', showAside, setShowAside),
260
+
261
+ Separator({}),
262
+
263
+ sliderControl('Sidebar Width', sidebarWidth, setSidebarWidth, 48, 400),
264
+ sliderControl('Header Height', headerHeight, setHeaderHeight, 36, 80),
265
+ sliderControl('Aside Width', asideWidth, setAsideWidth, 200, 480),
266
+
267
+ Separator({}),
268
+
269
+ // Apply to Workbench toggle
270
+ div({ class: css('_flex _aic _jcsb _py2') },
271
+ span({ class: css('_caption _fgprimary _bold') }, 'Apply to Workbench'),
272
+ Switch({
273
+ checked: () => !!activeShellConfig(),
274
+ onchange: (checked) => {
275
+ if (checked) {
276
+ setActiveShellConfig({
277
+ presetId,
278
+ navPosition: navPosition(),
279
+ navMode: navMode(),
280
+ showHeader: showHeader(),
281
+ showFooter: showFooter(),
282
+ showAside: showAside(),
283
+ sidebarWidth: sidebarWidth(),
284
+ headerHeight: headerHeight(),
285
+ asideWidth: asideWidth()
286
+ });
287
+ } else {
288
+ setActiveShellConfig(null);
289
+ }
290
+ },
291
+ size: 'sm'
292
+ })
293
+ )
294
+ );
295
+ }
296
+
297
+ // ─── Code Tab ───────────────────────────────────────────────────
298
+
299
+ function ShellCodeTab(presetId, configSignals) {
300
+ const codeContainer = div({ class: css('_flex _col _gap3') });
301
+
302
+ createEffect(() => {
303
+ const navPos = configSignals.navPosition();
304
+ const navMode = configSignals.navMode();
305
+ const showHeader = configSignals.showHeader();
306
+ const showFooter = configSignals.showFooter();
307
+ const showAside = configSignals.showAside();
308
+
309
+ const subComponents = [];
310
+ if (showHeader) subComponents.push(" Shell.Header({}, 'Header content')");
311
+ if (navPos !== 'top' && navMode !== 'hidden') subComponents.push(" Shell.Nav({}, navContent)");
312
+ subComponents.push(" Shell.Body({}, mainContent)");
313
+ if (showFooter) subComponents.push(" Shell.Footer({}, 'Footer')");
314
+ if (showAside) subComponents.push(" Shell.Aside({}, asideContent)");
315
+
316
+ const codeStr = `import { Shell } from 'decantr/components';
317
+ import { createSignal } from 'decantr/state';
318
+
319
+ const [navState, setNavState] = createSignal('${navMode === 'rail' ? 'rail' : 'expanded'}');
320
+
321
+ Shell({
322
+ config: '${presetId}',
323
+ navState,
324
+ onNavStateChange: setNavState
325
+ },
326
+ ${subComponents.join(',\n')}
327
+ )`;
328
+
329
+ codeContainer.innerHTML = '';
330
+
331
+ const copyBtn = Button({
332
+ variant: 'outline', size: 'sm',
333
+ onclick: () => {
334
+ navigator.clipboard?.writeText(codeStr);
335
+ copyBtn.textContent = 'Copied!';
336
+ setTimeout(() => { copyBtn.textContent = 'Copy Code'; }, 1500);
337
+ }
338
+ }, 'Copy Code');
339
+
340
+ codeContainer.appendChild(
341
+ div({ class: css('_flex _jce _mb2') }, copyBtn)
342
+ );
343
+ codeContainer.appendChild(
344
+ pre({ class: css('_p4 _bgmuted/10 _radius _overflow[auto] _caption _fontmono _border _bcborder _whitespace[pre-wrap] _wb[break-word]') }, codeStr)
345
+ );
346
+ });
347
+
348
+ return codeContainer;
349
+ }
350
+
351
+ // ─── Shell Detail (3 tabs) ──────────────────────────────────────
352
+
353
+ export function ShellDetail(presetId) {
354
+ const container = div({ class: css('_flex _col _gap4') },
355
+ p({ class: css('_body _fgmutedfg') }, 'Loading shell layout...')
356
+ );
357
+
358
+ // Config signals
359
+ const [navPosition, setNavPosition] = createSignal('left');
360
+ const [navMode, setNavMode] = createSignal('full');
361
+ const [showHeader, setShowHeader] = createSignal(true);
362
+ const [showFooter, setShowFooter] = createSignal(false);
363
+ const [showAside, setShowAside] = createSignal(false);
364
+ const [sidebarWidth, setSidebarWidth] = createSignal(240);
365
+ const [headerHeight, setHeaderHeight] = createSignal(52);
366
+ const [asideWidth, setAsideWidth] = createSignal(280);
367
+
368
+ const configSignals = {
369
+ navPosition, setNavPosition, navMode, setNavMode,
370
+ showHeader, setShowHeader, showFooter, setShowFooter,
371
+ showAside, setShowAside, sidebarWidth, setSidebarWidth,
372
+ headerHeight, setHeaderHeight, asideWidth, setAsideWidth
373
+ };
374
+
375
+ loadSkeletons().then(skeletons => {
376
+ container.innerHTML = '';
377
+ const skeleton = skeletons[presetId];
378
+
379
+ if (!skeleton) {
380
+ container.appendChild(p({ class: css('_fgmutedfg _body') }, `Shell layout "${presetId}" not found.`));
381
+ return;
382
+ }
383
+
384
+ // Initialize config signals from skeleton data
385
+ const cfg = skeleton.config;
386
+ if (cfg) {
387
+ if (cfg.nav?.position) setNavPosition(cfg.nav.position);
388
+ if (cfg.nav?.defaultState === 'hidden') setNavMode('hidden');
389
+ if (cfg.regions?.includes('footer')) setShowFooter(true);
390
+ if (cfg.regions?.includes('aside')) setShowAside(true);
391
+ if (cfg.header?.height) setHeaderHeight(parseInt(cfg.header.height) || 52);
392
+ if (cfg.nav?.width) setSidebarWidth(parseInt(cfg.nav.width) || 240);
393
+ if (cfg.aside?.width) setAsideWidth(parseInt(cfg.aside.width) || 280);
394
+ }
395
+
396
+ // Title
397
+ container.appendChild(
398
+ div({ class: css('_flex _col _gap1 _mb3') },
399
+ h2({ class: css('_heading4') }, skeleton.name || presetId),
400
+ p({ class: css('_body _fgmutedfg') }, skeleton.description || 'Configurable shell layout.')
401
+ )
402
+ );
403
+
404
+ // Tabs
405
+ container.appendChild(Tabs({
406
+ tabs: [
407
+ {
408
+ id: 'features',
409
+ label: 'Features',
410
+ content: () => featuresTab(skeleton, presetId)
411
+ },
412
+ {
413
+ id: 'configuration',
414
+ label: 'Configuration',
415
+ content: () => div({ class: css('_flex _col _gap4') },
416
+ ShellPreview(presetId, configSignals),
417
+ ShellConfigurator(presetId, configSignals)
418
+ )
419
+ },
420
+ {
421
+ id: 'code',
422
+ label: 'Code',
423
+ content: () => ShellCodeTab(presetId, configSignals)
424
+ }
425
+ ]
426
+ }));
427
+ });
428
+
429
+ return container;
430
+ }
431
+
432
+ function featuresTab(skeleton, presetId) {
433
+ const sections = [];
434
+
435
+ // Thumbnail preview
436
+ sections.push(
437
+ div({ class: css('_flex _col _gap3') },
438
+ h4({ class: css('_heading5') }, 'Shell Layout'),
439
+ ShellThumbnail(presetId)
440
+ )
441
+ );
442
+
443
+ // Layout type
444
+ sections.push(
445
+ div({ class: css('_flex _col _gap2') },
446
+ h4({ class: css('_heading6') }, 'Details'),
447
+ div({ class: css('_flex _gap2 _wrap') },
448
+ Chip({ label: `Layout: ${skeleton.layout || 'grid'}`, size: 'sm' }),
449
+ Chip({ label: `Atoms: ${skeleton.atoms || ''}`, variant: 'outline', size: 'sm' })
450
+ )
451
+ )
452
+ );
453
+
454
+ // Config regions
455
+ const cfg = skeleton.config;
456
+ if (cfg) {
457
+ sections.push(Separator({}));
458
+ sections.push(
459
+ div({ class: css('_flex _col _gap2') },
460
+ h4({ class: css('_heading6') }, 'Regions'),
461
+ div({ class: css('_flex _gap2 _wrap') },
462
+ ...cfg.regions.map(r => Chip({ label: r, variant: 'outline', size: 'sm' }))
463
+ )
464
+ )
465
+ );
466
+
467
+ if (cfg.nav) {
468
+ sections.push(
469
+ div({ class: css('_flex _col _gap1') },
470
+ h4({ class: css('_heading6') }, 'Nav Config'),
471
+ code({ class: css('_caption _fgmutedfg') }, `Position: ${cfg.nav.position || 'left'}`),
472
+ cfg.nav.width ? code({ class: css('_caption _fgmutedfg') }, `Width: ${cfg.nav.width}`) : null,
473
+ cfg.nav.collapseTo ? code({ class: css('_caption _fgmutedfg') }, `Collapse to: ${cfg.nav.collapseTo}`) : null,
474
+ cfg.nav.collapseBelow ? code({ class: css('_caption _fgmutedfg') }, `Auto-collapse below: ${cfg.nav.collapseBelow}`) : null
475
+ )
476
+ );
477
+ }
478
+ }
479
+
480
+ // Configurable options
481
+ const configurable = skeleton.configurable;
482
+ if (configurable) {
483
+ sections.push(Separator({}));
484
+ sections.push(
485
+ div({ class: css('_flex _col _gap2') },
486
+ h4({ class: css('_heading6') }, 'Configurable'),
487
+ configurable.nav?.positions ?
488
+ div({}, span({ class: css('_caption _fgmutedfg') }, `Nav positions: ${configurable.nav.positions.join(', ')}`)) : null,
489
+ configurable.nav?.modes ?
490
+ div({}, span({ class: css('_caption _fgmutedfg') }, `Nav modes: ${configurable.nav.modes.join(', ')}`)) : null,
491
+ configurable.footer?.toggleable ?
492
+ div({}, span({ class: css('_caption _fgmutedfg') }, 'Footer: toggleable')) : null,
493
+ configurable.aside?.toggleable ?
494
+ div({}, span({ class: css('_caption _fgmutedfg') }, 'Aside: toggleable')) : null
495
+ )
496
+ );
497
+ }
498
+
499
+ return div({ class: css('_flex _col _gap4') }, ...sections);
500
+ }
501
+
502
+ // ─── Shell List View (gallery) ──────────────────────────────────
503
+
504
+ export function ShellListView(navigateTo) {
505
+ const container = div({ class: css('_flex _col _gap4') },
506
+ p({ class: css('_body _fgmutedfg') }, 'Loading shell layouts...')
507
+ );
508
+
509
+ loadSkeletons().then(skeletons => {
510
+ container.innerHTML = '';
511
+ const entries = Object.entries(skeletons);
512
+ container.appendChild(h2({ class: css('_heading4 _mb2') }, 'Shells'));
513
+ container.appendChild(p({ class: css('_body _fgmutedfg _mb3') }, `${entries.length} configurable shell layouts.`));
514
+
515
+ const grid = div({ class: 'de-card-grid' });
516
+ for (const [id, skel] of entries) {
517
+ grid.appendChild(div({
518
+ class: 'de-card-item',
519
+ onclick: () => navigateTo(`/shells/${id}`)
520
+ },
521
+ ShellThumbnail(id),
522
+ div({ class: css('_flex _col _gap2') },
523
+ h3({ class: css('_heading6') }, skel.name || id),
524
+ p({ class: css('_caption _fgmutedfg') }, skel.description || ''),
525
+ skel.config ? div({ class: css('_flex _gap1 _wrap') },
526
+ ...(skel.config.regions || []).map(r =>
527
+ Chip({ label: r, variant: 'outline', size: 'sm' })
528
+ )
529
+ ) : null
530
+ )
531
+ ));
532
+ }
533
+ container.appendChild(grid);
534
+ });
535
+
536
+ return container;
537
+ }
538
+
539
+ // ─── Sidebar items loader ───────────────────────────────────────
540
+
541
+ export async function loadShellItems() {
542
+ try {
543
+ const resp = await fetch('/__decantr/registry/skeletons.json');
544
+ const data = await resp.json();
545
+ return Object.entries(data.skeletons || {}).map(([id, skel]) => ({
546
+ id, label: skel.name || id.split('-').map(w => w[0].toUpperCase() + w.slice(1)).join(' ')
547
+ }));
548
+ } catch {
549
+ return [];
550
+ }
551
+ }