microui-wc 0.1.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 (609) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +33 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
  4. package/.github/workflows/ci.yml +42 -0
  5. package/.github/workflows/deploy-pages.yml +112 -0
  6. package/AGENTS.md +2366 -0
  7. package/CHANGELOG.md +47 -0
  8. package/CODE_OF_CONDUCT.md +59 -0
  9. package/CONTRIBUTING.md +156 -0
  10. package/LICENSE +190 -0
  11. package/README.md +254 -0
  12. package/SECURITY.md +58 -0
  13. package/app/.generated/routes/alerts.js +8 -0
  14. package/app/.generated/routes/avatars.js +8 -0
  15. package/app/.generated/routes/badges.js +8 -0
  16. package/app/.generated/routes/buttons.js +10 -0
  17. package/app/.generated/routes/cards.js +10 -0
  18. package/app/.generated/routes/checkboxes.js +9 -0
  19. package/app/.generated/routes/chips.js +8 -0
  20. package/app/.generated/routes/dropdowns.js +9 -0
  21. package/app/.generated/routes/home.js +7 -0
  22. package/app/.generated/routes/icons.js +9 -0
  23. package/app/.generated/routes/inputs.js +10 -0
  24. package/app/.generated/routes/installation.js +7 -0
  25. package/app/.generated/routes/layout.js +9 -0
  26. package/app/.generated/routes/modals.js +9 -0
  27. package/app/.generated/routes/navbar.js +7 -0
  28. package/app/.generated/routes/progress.js +9 -0
  29. package/app/.generated/routes/radios.js +9 -0
  30. package/app/.generated/routes/switches.js +9 -0
  31. package/app/.generated/routes/tabs.js +8 -0
  32. package/app/.generated/routes/toasts.js +9 -0
  33. package/app/index.html +67 -0
  34. package/app/pages/alerts.html +23 -0
  35. package/app/pages/avatars.html +22 -0
  36. package/app/pages/badges.html +22 -0
  37. package/app/pages/buttons.html +71 -0
  38. package/app/pages/cards.html +54 -0
  39. package/app/pages/checkboxes.html +39 -0
  40. package/app/pages/chips.html +23 -0
  41. package/app/pages/dropdowns.html +41 -0
  42. package/app/pages/home.html +59 -0
  43. package/app/pages/icons.html +29 -0
  44. package/app/pages/inputs.html +66 -0
  45. package/app/pages/installation.html +34 -0
  46. package/app/pages/layout.html +30 -0
  47. package/app/pages/modals.html +21 -0
  48. package/app/pages/navbar.html +22 -0
  49. package/app/pages/progress.html +35 -0
  50. package/app/pages/radios.html +40 -0
  51. package/app/pages/switches.html +39 -0
  52. package/app/pages/tabs.html +30 -0
  53. package/app/pages/toasts.html +22 -0
  54. package/app-dist/index.html +67 -0
  55. package/app-dist/pages/alerts.html +23 -0
  56. package/app-dist/pages/avatars.html +22 -0
  57. package/app-dist/pages/badges.html +22 -0
  58. package/app-dist/pages/buttons.html +71 -0
  59. package/app-dist/pages/cards.html +54 -0
  60. package/app-dist/pages/checkboxes.html +39 -0
  61. package/app-dist/pages/chips.html +23 -0
  62. package/app-dist/pages/dropdowns.html +41 -0
  63. package/app-dist/pages/home.html +59 -0
  64. package/app-dist/pages/icons.html +29 -0
  65. package/app-dist/pages/inputs.html +66 -0
  66. package/app-dist/pages/installation.html +34 -0
  67. package/app-dist/pages/layout.html +30 -0
  68. package/app-dist/pages/modals.html +21 -0
  69. package/app-dist/pages/navbar.html +22 -0
  70. package/app-dist/pages/progress.html +35 -0
  71. package/app-dist/pages/radios.html +40 -0
  72. package/app-dist/pages/switches.html +39 -0
  73. package/app-dist/pages/tabs.html +30 -0
  74. package/app-dist/pages/toasts.html +22 -0
  75. package/app-dist/pages.json +217 -0
  76. package/app-dist/routes/alerts.js +5 -0
  77. package/app-dist/routes/avatars.js +1 -0
  78. package/app-dist/routes/badges.js +1 -0
  79. package/app-dist/routes/buttons.js +1 -0
  80. package/app-dist/routes/cards.js +1 -0
  81. package/app-dist/routes/checkboxes.js +9 -0
  82. package/app-dist/routes/chips.js +4 -0
  83. package/app-dist/routes/chunk-019e5e2f.js +5 -0
  84. package/app-dist/routes/chunk-0m4j19yd.js +2 -0
  85. package/app-dist/routes/chunk-0tmmp5q0.js +1 -0
  86. package/app-dist/routes/chunk-10xn709r.js +1 -0
  87. package/app-dist/routes/chunk-15m2qcda.js +2 -0
  88. package/app-dist/routes/chunk-1bh8g23n.js +1 -0
  89. package/app-dist/routes/chunk-1vg0v937.js +1 -0
  90. package/app-dist/routes/chunk-1zvcgy3j.js +1 -0
  91. package/app-dist/routes/chunk-2afb0861.js +1 -0
  92. package/app-dist/routes/chunk-2c6ttpzt.js +5 -0
  93. package/app-dist/routes/chunk-3dy30fhs.js +1 -0
  94. package/app-dist/routes/chunk-426dnces.js +13 -0
  95. package/app-dist/routes/chunk-44kgxery.js +1 -0
  96. package/app-dist/routes/chunk-47fdnejd.js +33 -0
  97. package/app-dist/routes/chunk-49a6t2vq.js +1 -0
  98. package/app-dist/routes/chunk-4fe1rm5b.js +1 -0
  99. package/app-dist/routes/chunk-4ggmvkta.js +33 -0
  100. package/app-dist/routes/chunk-4vkz81q7.js +33 -0
  101. package/app-dist/routes/chunk-4w4tmj8f.js +31 -0
  102. package/app-dist/routes/chunk-532s62kr.js +31 -0
  103. package/app-dist/routes/chunk-5hm3bssy.js +33 -0
  104. package/app-dist/routes/chunk-5vrh24hc.js +1 -0
  105. package/app-dist/routes/chunk-61pcg25a.js +1 -0
  106. package/app-dist/routes/chunk-6nfhygvf.js +1 -0
  107. package/app-dist/routes/chunk-700e7je6.js +33 -0
  108. package/app-dist/routes/chunk-7fsn17kg.js +1 -0
  109. package/app-dist/routes/chunk-7k789b32.js +1 -0
  110. package/app-dist/routes/chunk-7r46q0ys.js +36 -0
  111. package/app-dist/routes/chunk-86fmc1fr.js +5 -0
  112. package/app-dist/routes/chunk-8qth37vw.js +1 -0
  113. package/app-dist/routes/chunk-924wv8n0.js +1 -0
  114. package/app-dist/routes/chunk-9mbhgxk9.js +1 -0
  115. package/app-dist/routes/chunk-a216hyd9.js +1 -0
  116. package/app-dist/routes/chunk-akzxykh9.js +33 -0
  117. package/app-dist/routes/chunk-b3dcvy8c.js +1 -0
  118. package/app-dist/routes/chunk-b74zahz5.js +31 -0
  119. package/app-dist/routes/chunk-bftj53p2.js +5 -0
  120. package/app-dist/routes/chunk-c01hnz3e.js +1 -0
  121. package/app-dist/routes/chunk-d8pvv5km.js +1 -0
  122. package/app-dist/routes/chunk-dev0aezr.js +2 -0
  123. package/app-dist/routes/chunk-dh6vnv0e.js +1 -0
  124. package/app-dist/routes/chunk-dn2cbpva.js +36 -0
  125. package/app-dist/routes/chunk-dvn0my90.js +1 -0
  126. package/app-dist/routes/chunk-dvq8mnve.js +36 -0
  127. package/app-dist/routes/chunk-e8c2gc4d.js +5 -0
  128. package/app-dist/routes/chunk-ejf9ak2x.js +1 -0
  129. package/app-dist/routes/chunk-f083m55s.js +1 -0
  130. package/app-dist/routes/chunk-fnrj28s1.js +31 -0
  131. package/app-dist/routes/chunk-fvg3yjdp.js +31 -0
  132. package/app-dist/routes/chunk-g7k381n1.js +1 -0
  133. package/app-dist/routes/chunk-h01kq2ae.js +13 -0
  134. package/app-dist/routes/chunk-h4dk761v.js +5 -0
  135. package/app-dist/routes/chunk-hmx91z2x.js +5 -0
  136. package/app-dist/routes/chunk-hxbg4m42.js +36 -0
  137. package/app-dist/routes/chunk-jbjnfp2b.js +2 -0
  138. package/app-dist/routes/chunk-jxtz5vv6.js +36 -0
  139. package/app-dist/routes/chunk-jxzcs0ey.js +36 -0
  140. package/app-dist/routes/chunk-kt7wwhcx.js +1 -0
  141. package/app-dist/routes/chunk-kzptszyc.js +33 -0
  142. package/app-dist/routes/chunk-mhgca4w4.js +2 -0
  143. package/app-dist/routes/chunk-mhswxa20.js +1 -0
  144. package/app-dist/routes/chunk-n8zfeex6.js +1 -0
  145. package/app-dist/routes/chunk-pee47b2r.js +1 -0
  146. package/app-dist/routes/chunk-pesmw829.js +1 -0
  147. package/app-dist/routes/chunk-pgc4c6f3.js +36 -0
  148. package/app-dist/routes/chunk-q8egegm1.js +1 -0
  149. package/app-dist/routes/chunk-q9mn2qyq.js +36 -0
  150. package/app-dist/routes/chunk-qh0rtaf3.js +5 -0
  151. package/app-dist/routes/chunk-qqhmk6ye.js +2 -0
  152. package/app-dist/routes/chunk-qrxygmf7.js +33 -0
  153. package/app-dist/routes/chunk-r46yzksx.js +36 -0
  154. package/app-dist/routes/chunk-rgpbw2w0.js +5 -0
  155. package/app-dist/routes/chunk-rnpzv3d8.js +2 -0
  156. package/app-dist/routes/chunk-s5v8cv05.js +2 -0
  157. package/app-dist/routes/chunk-sbwn5bpc.js +1 -0
  158. package/app-dist/routes/chunk-sqbg8jbt.js +33 -0
  159. package/app-dist/routes/chunk-sv8dqnf7.js +1 -0
  160. package/app-dist/routes/chunk-t67sw3za.js +1 -0
  161. package/app-dist/routes/chunk-tjdpqwdf.js +31 -0
  162. package/app-dist/routes/chunk-tq2mfghg.js +1 -0
  163. package/app-dist/routes/chunk-ttn10vt6.js +1 -0
  164. package/app-dist/routes/chunk-v2hzpjxr.js +1 -0
  165. package/app-dist/routes/chunk-wfjjkw9y.js +1 -0
  166. package/app-dist/routes/chunk-wt8cxzmf.js +31 -0
  167. package/app-dist/routes/chunk-x45d372k.js +5 -0
  168. package/app-dist/routes/chunk-y3wsazkt.js +1 -0
  169. package/app-dist/routes/chunk-y7pmgc7t.js +33 -0
  170. package/app-dist/routes/chunk-zefdt2q3.js +31 -0
  171. package/app-dist/routes/dropdowns.js +6 -0
  172. package/app-dist/routes/home.js +1 -0
  173. package/app-dist/routes/icons.js +1 -0
  174. package/app-dist/routes/inputs.js +12 -0
  175. package/app-dist/routes/installation.js +1 -0
  176. package/app-dist/routes/layout.js +1 -0
  177. package/app-dist/routes/modals.js +7 -0
  178. package/app-dist/routes/navbar.js +1 -0
  179. package/app-dist/routes/progress.js +1 -0
  180. package/app-dist/routes/radios.js +6 -0
  181. package/app-dist/routes/switches.js +6 -0
  182. package/app-dist/routes/tabs.js +1 -0
  183. package/app-dist/routes/toasts.js +16 -0
  184. package/assets/fonts/material-symbols-mini.woff2 +0 -0
  185. package/assets/fonts/material-symbols.woff2 +0 -0
  186. package/assets/fonts/roboto-400.woff2 +0 -0
  187. package/assets/fonts/roboto-500.woff2 +0 -0
  188. package/assets/fonts/roboto-700.woff2 +0 -0
  189. package/assets/logo-banner-400.jpg +0 -0
  190. package/assets/logo-banner-400.webp +0 -0
  191. package/assets/logo-banner-800.webp +0 -0
  192. package/assets/logo-banner.jpg +0 -0
  193. package/assets/logo-icon-64.jpg +0 -0
  194. package/assets/logo-icon-64.webp +0 -0
  195. package/assets/logo-icon.jpg +0 -0
  196. package/assets/logo-square.jpg +0 -0
  197. package/bun.lock +312 -0
  198. package/bunfig.toml +4 -0
  199. package/custom-elements.json +1916 -0
  200. package/demo/api/sample-data.json +38 -0
  201. package/demo/content/alerts.html +115 -0
  202. package/demo/content/avatars.html +70 -0
  203. package/demo/content/badges.html +65 -0
  204. package/demo/content/buttons.html +188 -0
  205. package/demo/content/callouts.html +91 -0
  206. package/demo/content/cards.html +121 -0
  207. package/demo/content/checkboxes.html +178 -0
  208. package/demo/content/chips.html +67 -0
  209. package/demo/content/codeblocks.html +101 -0
  210. package/demo/content/confirms.html +115 -0
  211. package/demo/content/datatables.html +149 -0
  212. package/demo/content/dividers.html +119 -0
  213. package/demo/content/dropdowns.html +89 -0
  214. package/demo/content/enterprise.html +252 -0
  215. package/demo/content/home.html +149 -0
  216. package/demo/content/icons.html +89 -0
  217. package/demo/content/inputs.html +135 -0
  218. package/demo/content/installation.html +16 -0
  219. package/demo/content/layout.html +136 -0
  220. package/demo/content/modals.html +141 -0
  221. package/demo/content/navbar.html +70 -0
  222. package/demo/content/progress.html +119 -0
  223. package/demo/content/radios.html +88 -0
  224. package/demo/content/skeletons.html +109 -0
  225. package/demo/content/spinners.html +96 -0
  226. package/demo/content/switches.html +84 -0
  227. package/demo/content/tables.html +124 -0
  228. package/demo/content/tabs.html +85 -0
  229. package/demo/content/toasts.html +116 -0
  230. package/demo/content/tooltips.html +107 -0
  231. package/demo/content/virtual-lists.html +233 -0
  232. package/demo/favicon.ico +0 -0
  233. package/demo/favicon.png +0 -0
  234. package/demo/full.html +52 -0
  235. package/demo/iife.html +46 -0
  236. package/demo/manifest.json +34 -0
  237. package/demo/pages/datatable-demo.html +237 -0
  238. package/demo/pages/prompt-ui-demo.html +218 -0
  239. package/demo/pages/responsive-demo.html +122 -0
  240. package/demo/pages/schema-form-demo.html +270 -0
  241. package/demo/robots.txt +6 -0
  242. package/demo/shell.html +712 -0
  243. package/demo/sw.js +387 -0
  244. package/dist/AGENTS.md +2366 -0
  245. package/dist/README.md +254 -0
  246. package/dist/chunks/advanced.js +174 -0
  247. package/dist/chunks/chunk-1nhr1wrq.js +14 -0
  248. package/dist/chunks/chunk-hssyjbr0.js +2 -0
  249. package/dist/chunks/chunk-k8etzx0z.js +2 -0
  250. package/dist/chunks/chunk-rr1et8fg.js +2 -0
  251. package/dist/chunks/chunk-sjcx4fd5.js +6 -0
  252. package/dist/chunks/chunk-v1c777xh.js +5 -0
  253. package/dist/chunks/chunk-w5k5vwjd.js +13 -0
  254. package/dist/chunks/core.js +10 -0
  255. package/dist/chunks/display.js +17 -0
  256. package/dist/chunks/feedback.js +15 -0
  257. package/dist/chunks/forms.js +48 -0
  258. package/dist/chunks/layout.js +9 -0
  259. package/dist/components/chunk-4tezav8r.js +2 -0
  260. package/dist/components/chunk-fqyb2pms.js +2 -0
  261. package/dist/components/chunk-h7cdbhxw.js +13 -0
  262. package/dist/components/chunk-mzd8jwrs.js +2 -0
  263. package/dist/components/chunk-qwmxyn8e.js +2 -0
  264. package/dist/components/chunk-redtk47a.js +14 -0
  265. package/dist/components/mu-alert.js +5 -0
  266. package/dist/components/mu-api-table.js +33 -0
  267. package/dist/components/mu-avatar.js +1 -0
  268. package/dist/components/mu-badge.js +1 -0
  269. package/dist/components/mu-bottom-nav.js +1 -0
  270. package/dist/components/mu-button.js +1 -0
  271. package/dist/components/mu-callout.js +1 -0
  272. package/dist/components/mu-card.js +1 -0
  273. package/dist/components/mu-checkbox.js +9 -0
  274. package/dist/components/mu-chip.js +4 -0
  275. package/dist/components/mu-code.js +48 -0
  276. package/dist/components/mu-confirm.js +10 -0
  277. package/dist/components/mu-container.js +1 -0
  278. package/dist/components/mu-datatable.js +96 -0
  279. package/dist/components/mu-divider.js +1 -0
  280. package/dist/components/mu-doc-page.js +26 -0
  281. package/dist/components/mu-drawer-item.js +9 -0
  282. package/dist/components/mu-drawer.js +1 -0
  283. package/dist/components/mu-dropdown.js +6 -0
  284. package/dist/components/mu-error-boundary.js +10 -0
  285. package/dist/components/mu-example.js +38 -0
  286. package/dist/components/mu-fetch.js +1 -0
  287. package/dist/components/mu-form.js +1 -0
  288. package/dist/components/mu-grid.js +1 -0
  289. package/dist/components/mu-icon.js +5 -0
  290. package/dist/components/mu-input.js +12 -0
  291. package/dist/components/mu-layout.js +1 -0
  292. package/dist/components/mu-lazy.js +1 -0
  293. package/dist/components/mu-modal.js +7 -0
  294. package/dist/components/mu-navbar.js +1 -0
  295. package/dist/components/mu-page.js +1 -0
  296. package/dist/components/mu-progress.js +1 -0
  297. package/dist/components/mu-prompt-ui.js +20 -0
  298. package/dist/components/mu-radio.js +6 -0
  299. package/dist/components/mu-repeat.js +1 -0
  300. package/dist/components/mu-router.js +6 -0
  301. package/dist/components/mu-schema-form.js +76 -0
  302. package/dist/components/mu-sidebar.js +1 -0
  303. package/dist/components/mu-skeleton.js +13 -0
  304. package/dist/components/mu-spinner.js +1 -0
  305. package/dist/components/mu-stack.js +1 -0
  306. package/dist/components/mu-switch.js +6 -0
  307. package/dist/components/mu-table.js +1 -0
  308. package/dist/components/mu-tabs.js +1 -0
  309. package/dist/components/mu-textarea.js +11 -0
  310. package/dist/components/mu-theme-toggle.js +5 -0
  311. package/dist/components/mu-toast.js +4 -0
  312. package/dist/components/mu-tooltip.js +10 -0
  313. package/dist/components/mu-virtual-list.js +33 -0
  314. package/dist/components.css +1 -0
  315. package/dist/microui.css +1 -0
  316. package/dist/microui.d.ts +234 -0
  317. package/dist/microui.esm.js +549 -0
  318. package/dist/microui.esm.js.map +79 -0
  319. package/dist/microui.min.js +549 -0
  320. package/dist/microui.min.js.map +79 -0
  321. package/dist/routes/alerts.js +1 -0
  322. package/dist/routes/avatars.js +1 -0
  323. package/dist/routes/badges.js +1 -0
  324. package/dist/routes/buttons.js +1 -0
  325. package/dist/routes/callouts.js +1 -0
  326. package/dist/routes/cards.js +1 -0
  327. package/dist/routes/checkboxes.js +9 -0
  328. package/dist/routes/chips.js +4 -0
  329. package/dist/routes/chunk-19wgcncm.js +2 -0
  330. package/dist/routes/chunk-1khyr3v1.js +33 -0
  331. package/dist/routes/chunk-4rhxe97g.js +1 -0
  332. package/dist/routes/chunk-5qah04bh.js +2 -0
  333. package/dist/routes/chunk-7gfxy70n.js +5 -0
  334. package/dist/routes/chunk-e86zbeta.js +1 -0
  335. package/dist/routes/chunk-fagt36h6.js +2 -0
  336. package/dist/routes/chunk-fed7zr7m.js +1 -0
  337. package/dist/routes/chunk-hwj7pfwp.js +1 -0
  338. package/dist/routes/chunk-mhvcs2f8.js +5 -0
  339. package/dist/routes/chunk-nv3bddmj.js +13 -0
  340. package/dist/routes/chunk-q3f2aeqe.js +7 -0
  341. package/dist/routes/chunk-qxxa8trk.js +1 -0
  342. package/dist/routes/chunk-rw15y9zh.js +1 -0
  343. package/dist/routes/chunk-sfb7x11v.js +5 -0
  344. package/dist/routes/chunk-swyhghrm.js +48 -0
  345. package/dist/routes/chunk-sxddjs2d.js +2 -0
  346. package/dist/routes/chunk-vby0zg5w.js +17 -0
  347. package/dist/routes/chunk-w6zqjqqs.js +9 -0
  348. package/dist/routes/chunk-z960rexd.js +38 -0
  349. package/dist/routes/codeblocks.js +1 -0
  350. package/dist/routes/confirms.js +10 -0
  351. package/dist/routes/datatables.js +96 -0
  352. package/dist/routes/dividers.js +1 -0
  353. package/dist/routes/dropdowns.js +6 -0
  354. package/dist/routes/enterprise.js +15 -0
  355. package/dist/routes/home.js +1 -0
  356. package/dist/routes/icons.js +1 -0
  357. package/dist/routes/inputs.js +22 -0
  358. package/dist/routes/installation.js +1 -0
  359. package/dist/routes/layout.js +1 -0
  360. package/dist/routes/modals.js +1 -0
  361. package/dist/routes/navbar.js +1 -0
  362. package/dist/routes/page-components.json +316 -0
  363. package/dist/routes/progress.js +1 -0
  364. package/dist/routes/radios.js +6 -0
  365. package/dist/routes/route-deps.json +156 -0
  366. package/dist/routes/shell-critical.js +1 -0
  367. package/dist/routes/shell-deferred.js +1 -0
  368. package/dist/routes/shell.js +20 -0
  369. package/dist/routes/skeletons.js +13 -0
  370. package/dist/routes/spinners.js +1 -0
  371. package/dist/routes/src/chunks/core.js +36 -0
  372. package/dist/routes/switches.js +6 -0
  373. package/dist/routes/tables.js +1 -0
  374. package/dist/routes/tabs.js +1 -0
  375. package/dist/routes/toasts.js +1 -0
  376. package/dist/routes/tooltips.js +10 -0
  377. package/dist/routes/virtual-lists.js +33 -0
  378. package/dist/styles/common.css +1 -0
  379. package/dist/styles/components/animations.css +1 -0
  380. package/dist/styles/components/avatar.css +1 -0
  381. package/dist/styles/components/badge.css +1 -0
  382. package/dist/styles/components/bottom-nav.css +1 -0
  383. package/dist/styles/components/button.css +1 -0
  384. package/dist/styles/components/card.css +1 -0
  385. package/dist/styles/components/checkbox.css +1 -0
  386. package/dist/styles/components/chip.css +1 -0
  387. package/dist/styles/components/datatable.css +1 -0
  388. package/dist/styles/components/divider.css +1 -0
  389. package/dist/styles/components/drawer-item.css +1 -0
  390. package/dist/styles/components/drawer.css +1 -0
  391. package/dist/styles/components/grid.css +1 -0
  392. package/dist/styles/components/icon.css +1 -0
  393. package/dist/styles/components/input.css +1 -0
  394. package/dist/styles/components/layout.css +1 -0
  395. package/dist/styles/components/navbar.css +1 -0
  396. package/dist/styles/components/overlays.css +1 -0
  397. package/dist/styles/components/progress.css +1 -0
  398. package/dist/styles/components/prompt-ui.css +1 -0
  399. package/dist/styles/components/radio.css +1 -0
  400. package/dist/styles/components/schema-form.css +1 -0
  401. package/dist/styles/components/switch.css +1 -0
  402. package/dist/styles/components/tabs.css +1 -0
  403. package/dist/styles/components/tooltip.css +1 -0
  404. package/dist/styles/components/virtual-list.css +1 -0
  405. package/dist/tokens.css +1 -0
  406. package/docs/api-reference.md +175 -0
  407. package/docs/component-schema.md +231 -0
  408. package/docs/components.md +269 -0
  409. package/docs/design-system.md +183 -0
  410. package/docs/getting-started.md +198 -0
  411. package/docs/message-protocol.md +262 -0
  412. package/docs/utility-classes.md +205 -0
  413. package/lighthouse-audit.mjs +113 -0
  414. package/package.json +45 -0
  415. package/scripts/analyze-components.js +105 -0
  416. package/scripts/build-app.js +193 -0
  417. package/scripts/build-framework.js +444 -0
  418. package/scripts/build-utils.js +101 -0
  419. package/scripts/test-isolated.js +151 -0
  420. package/server.js +256 -0
  421. package/src/chunks/advanced.js +27 -0
  422. package/src/chunks/core.js +61 -0
  423. package/src/chunks/display.js +25 -0
  424. package/src/chunks/feedback.js +15 -0
  425. package/src/chunks/forms.js +25 -0
  426. package/src/chunks/layout.js +27 -0
  427. package/src/components/mu-alert.js +96 -0
  428. package/src/components/mu-api-table.js +167 -0
  429. package/src/components/mu-avatar.js +94 -0
  430. package/src/components/mu-badge.js +32 -0
  431. package/src/components/mu-bottom-nav.js +115 -0
  432. package/src/components/mu-button.js +61 -0
  433. package/src/components/mu-callout.js +71 -0
  434. package/src/components/mu-card.js +36 -0
  435. package/src/components/mu-checkbox.js +186 -0
  436. package/src/components/mu-chip.js +125 -0
  437. package/src/components/mu-code.js +534 -0
  438. package/src/components/mu-confirm.js +268 -0
  439. package/src/components/mu-container.js +53 -0
  440. package/src/components/mu-datatable.js +517 -0
  441. package/src/components/mu-divider.js +40 -0
  442. package/src/components/mu-doc-page.js +100 -0
  443. package/src/components/mu-drawer-item.js +158 -0
  444. package/src/components/mu-drawer.js +305 -0
  445. package/src/components/mu-dropdown.js +239 -0
  446. package/src/components/mu-error-boundary.js +191 -0
  447. package/src/components/mu-example.js +335 -0
  448. package/src/components/mu-fetch.js +256 -0
  449. package/src/components/mu-form.js +133 -0
  450. package/src/components/mu-grid.js +63 -0
  451. package/src/components/mu-icon.js +211 -0
  452. package/src/components/mu-input.js +142 -0
  453. package/src/components/mu-layout.js +129 -0
  454. package/src/components/mu-lazy.js +94 -0
  455. package/src/components/mu-modal.js +160 -0
  456. package/src/components/mu-navbar.js +71 -0
  457. package/src/components/mu-page.js +77 -0
  458. package/src/components/mu-progress.js +54 -0
  459. package/src/components/mu-prompt-ui.js +382 -0
  460. package/src/components/mu-radio.js +200 -0
  461. package/src/components/mu-repeat.js +135 -0
  462. package/src/components/mu-router.js +169 -0
  463. package/src/components/mu-schema-form.js +441 -0
  464. package/src/components/mu-sidebar.js +81 -0
  465. package/src/components/mu-skeleton.js +69 -0
  466. package/src/components/mu-spinner.js +30 -0
  467. package/src/components/mu-stack.js +59 -0
  468. package/src/components/mu-switch.js +150 -0
  469. package/src/components/mu-table.js +80 -0
  470. package/src/components/mu-tabs.js +112 -0
  471. package/src/components/mu-textarea.js +96 -0
  472. package/src/components/mu-theme-toggle.js +52 -0
  473. package/src/components/mu-toast.js +151 -0
  474. package/src/components/mu-tooltip.js +182 -0
  475. package/src/components/mu-virtual-list.js +184 -0
  476. package/src/core/MuElement.js +562 -0
  477. package/src/core/agent-api.js +771 -0
  478. package/src/core/breakpoints.js +195 -0
  479. package/src/core/bus.js +378 -0
  480. package/src/core/component-schema.js +287 -0
  481. package/src/core/feature-registry.js +241 -0
  482. package/src/core/form-state.js +252 -0
  483. package/src/core/http.js +104 -0
  484. package/src/core/keyboard.js +105 -0
  485. package/src/core/layers.js +71 -0
  486. package/src/core/render.js +201 -0
  487. package/src/core/ripple.js +158 -0
  488. package/src/core/router.js +100 -0
  489. package/src/core/scheduler.js +109 -0
  490. package/src/core/signals.js +164 -0
  491. package/src/core/store.js +268 -0
  492. package/src/core/theme.js +68 -0
  493. package/src/core/transitions.js +72 -0
  494. package/src/core/utils.js +30 -0
  495. package/src/index.d.ts +234 -0
  496. package/src/index.js +308 -0
  497. package/src/styles/animations.css +252 -0
  498. package/src/styles/common.css +82 -0
  499. package/src/styles/components/animations.css +129 -0
  500. package/src/styles/components/avatar.css +83 -0
  501. package/src/styles/components/badge.css +80 -0
  502. package/src/styles/components/bottom-nav.css +37 -0
  503. package/src/styles/components/button.css +348 -0
  504. package/src/styles/components/card.css +138 -0
  505. package/src/styles/components/checkbox.css +201 -0
  506. package/src/styles/components/chip.css +93 -0
  507. package/src/styles/components/datatable.css +180 -0
  508. package/src/styles/components/divider.css +49 -0
  509. package/src/styles/components/drawer-item.css +123 -0
  510. package/src/styles/components/drawer.css +273 -0
  511. package/src/styles/components/grid.css +189 -0
  512. package/src/styles/components/icon.css +40 -0
  513. package/src/styles/components/input.css +203 -0
  514. package/src/styles/components/layout.css +121 -0
  515. package/src/styles/components/navbar.css +91 -0
  516. package/src/styles/components/overlays.css +329 -0
  517. package/src/styles/components/progress.css +79 -0
  518. package/src/styles/components/prompt-ui.css +286 -0
  519. package/src/styles/components/radio.css +17 -0
  520. package/src/styles/components/schema-form.css +85 -0
  521. package/src/styles/components/switch.css +69 -0
  522. package/src/styles/components/tabs.css +145 -0
  523. package/src/styles/components/tooltip.css +93 -0
  524. package/src/styles/components/virtual-list.css +36 -0
  525. package/src/styles/components.css +3677 -0
  526. package/src/styles/routes/home.css +97 -0
  527. package/src/styles/tokens.css +675 -0
  528. package/tests/agents/agent-integration.test.js +76 -0
  529. package/tests/benchmark.html +296 -0
  530. package/tests/build/scan-components.test.js +173 -0
  531. package/tests/components/all-components.test.js +245 -0
  532. package/tests/components/all-missing-components.test.js +574 -0
  533. package/tests/components/mu-alert.test.js +113 -0
  534. package/tests/components/mu-avatar.test.js +148 -0
  535. package/tests/components/mu-badge.test.js +92 -0
  536. package/tests/components/mu-button.test.js +112 -0
  537. package/tests/components/mu-card.test.js +89 -0
  538. package/tests/components/mu-checkbox.test.js +158 -0
  539. package/tests/components/mu-chip.test.js +118 -0
  540. package/tests/components/mu-container.test.js +120 -0
  541. package/tests/components/mu-divider.test.js +98 -0
  542. package/tests/components/mu-drawer-item.test.js +199 -0
  543. package/tests/components/mu-drawer.test.js +96 -0
  544. package/tests/components/mu-dropdown.test.js +125 -0
  545. package/tests/components/mu-form.test.js +138 -0
  546. package/tests/components/mu-grid.test.js +135 -0
  547. package/tests/components/mu-icon.test.js +110 -0
  548. package/tests/components/mu-input.test.js +131 -0
  549. package/tests/components/mu-lazy.test.js +103 -0
  550. package/tests/components/mu-modal.test.js +275 -0
  551. package/tests/components/mu-navbar.test.js +101 -0
  552. package/tests/components/mu-progress.test.js +115 -0
  553. package/tests/components/mu-radio.test.js +114 -0
  554. package/tests/components/mu-repeat.test.js +106 -0
  555. package/tests/components/mu-sidebar.test.js +126 -0
  556. package/tests/components/mu-skeleton.test.js +162 -0
  557. package/tests/components/mu-stack.test.js +143 -0
  558. package/tests/components/mu-switch.test.js +292 -0
  559. package/tests/components/mu-table.test.js +124 -0
  560. package/tests/components/mu-tabs.test.js +104 -0
  561. package/tests/components/mu-textarea.test.js +115 -0
  562. package/tests/components/mu-toast.test.js +321 -0
  563. package/tests/components/mu-tooltip.test.js +133 -0
  564. package/tests/components/mu-virtual-list.test.js +109 -0
  565. package/tests/core/MuElement.test.js +120 -0
  566. package/tests/core/agent-api.test.js +125 -0
  567. package/tests/core/all-core-modules.test.js +442 -0
  568. package/tests/core/bus.test.js +364 -0
  569. package/tests/core/component-schema.test.js +160 -0
  570. package/tests/core/feature-registry.test.js +198 -0
  571. package/tests/core/form-state.test.js +167 -0
  572. package/tests/core/http.test.js +119 -0
  573. package/tests/core/keyboard.test.js +319 -0
  574. package/tests/core/layers.test.js +129 -0
  575. package/tests/core/namespaced-stores.test.js +114 -0
  576. package/tests/core/render.test.js +121 -0
  577. package/tests/core/ripple.test.js +131 -0
  578. package/tests/core/router.test.js +89 -0
  579. package/tests/core/scheduler.test.js +121 -0
  580. package/tests/core/signals.test.js +128 -0
  581. package/tests/core/store.test.js +171 -0
  582. package/tests/core/transitions.test.js +82 -0
  583. package/tests/e2e/accessibility-harness.html +58 -0
  584. package/tests/e2e/accessibility.test.js +401 -0
  585. package/tests/e2e/agent-features.test.js +372 -0
  586. package/tests/e2e/card-spacing.test.js +287 -0
  587. package/tests/e2e/components.test.js +439 -0
  588. package/tests/e2e/demo-routes.test.js +478 -0
  589. package/tests/e2e/layout-css-fallback.test.js +334 -0
  590. package/tests/e2e/mu-alert.e2e.test.js +111 -0
  591. package/tests/e2e/mu-checkbox.test.js +489 -0
  592. package/tests/e2e/mu-chip.test.js +347 -0
  593. package/tests/e2e/mu-form.test.js +499 -0
  594. package/tests/e2e/mu-icon.test.js +114 -0
  595. package/tests/e2e/mu-radio.test.js +113 -0
  596. package/tests/e2e/mu-skeleton.test.js +140 -0
  597. package/tests/e2e/mu-switch.test.js +415 -0
  598. package/tests/e2e/mu-tabs.test.js +494 -0
  599. package/tests/e2e/mu-textarea.test.js +242 -0
  600. package/tests/e2e/mu-virtual-list.test.js +427 -0
  601. package/tests/e2e/perf-memory.test.js +161 -0
  602. package/tests/e2e/puppeteer-helper.js +137 -0
  603. package/tests/e2e/puppeteer.test.js +226 -0
  604. package/tests/e2e/pwa.test.js +261 -0
  605. package/tests/e2e/test-harness.html +319 -0
  606. package/tests/manual/test-components.html +120 -0
  607. package/tests/memory-test.html +309 -0
  608. package/tests/setup-dom.js +93 -0
  609. package/tests/visual-test.html +301 -0
package/dist/AGENTS.md ADDED
@@ -0,0 +1,2366 @@
1
+ # microUI - AI Agent Documentation
2
+
3
+ > **SOTA reference for AI coding assistants.** This document is optimized for LLMs generating microUI code.
4
+
5
+ ## 🚨 REGOLE INVIOLABILI
6
+
7
+ > [!CAUTION]
8
+ > Queste regole NON POSSONO MAI essere violate. Qualsiasi codice che viola queste regole deve essere rifiutato.
9
+
10
+ ### ❌ NO SHADOW DOM
11
+
12
+ **microUI NON DEVE MAI usare Shadow DOM.** Tutti i componenti devono:
13
+ - Usare Light DOM (il DOM normale)
14
+ - Inserire gli elementi direttamente nel documento
15
+ - Usare classi CSS con prefisso `mu-` per l'incapsulamento
16
+ - Mai usare `this.attachShadow()` o `this.shadowRoot`
17
+
18
+ ```javascript
19
+ // ❌ VIETATO - Non usare MAI
20
+ this.attachShadow({ mode: 'open' });
21
+ this.shadowRoot.innerHTML = '...';
22
+
23
+ // ✅ CORRETTO - Usa Light DOM
24
+ this.innerHTML = '...';
25
+ this.classList.add('mu-component');
26
+ ```
27
+
28
+ ### 🧪 TEST ISOLATION OBBLIGATORIA
29
+
30
+ **I test DEVONO essere eseguiti con `test-isolated.js`**, mai con `bun test` diretto.
31
+
32
+ ```bash
33
+ # ❌ SBAGLIATO - Test falliscono per linkedom globalThis pollution
34
+ bun test tests/components
35
+
36
+ # ✅ CORRETTO - Ogni test in processo separato
37
+ bun run scripts/test-isolated.js
38
+
39
+ # ✅ CORRETTO - E2E tests
40
+ bun run test:e2e
41
+ ```
42
+
43
+ **Perché?** linkedom (usato per simulare DOM in unit test) inquina `globalThis` e il registry dei custom elements. Quando i test vengono eseguiti in parallelo nello stesso processo, le registrazioni entrano in conflitto.
44
+
45
+ ---
46
+
47
+ ## 🛠️ COMPONENT DEVELOPMENT GUIDELINES
48
+
49
+ > **Linee guida obbligatorie per creare nuovi componenti microUI.** Seguire queste regole previene memory leak, crash e garantisce consistenza con il framework.
50
+
51
+ ### 📋 Template Componente Completo
52
+
53
+ ```javascript
54
+ /**
55
+ * mu-example.js - Description of what this component does
56
+ *
57
+ * @example
58
+ * <mu-example label="Test" value="123"></mu-example>
59
+ */
60
+
61
+ import { MuElement, define } from '../core/MuElement.js';
62
+ import { escapeHTML } from '../core/utils.js'; // Per contenuti user-facing
63
+
64
+ export class MuExample extends MuElement {
65
+ static get observedAttributes() {
66
+ return ['label', 'value', 'disabled'];
67
+ }
68
+
69
+ static baseClass = 'mu-example';
70
+ static cssFile = 'example';
71
+
72
+ constructor() {
73
+ super();
74
+ this._internalState = null;
75
+ }
76
+
77
+ // ========================
78
+ // LIFECYCLE (OBBLIGATORIO)
79
+ // ========================
80
+
81
+ connectedCallback() {
82
+ super.connectedCallback(); // ⚠️ OBBLIGATORIO - Inizializza AbortController
83
+ this.render();
84
+ }
85
+
86
+ disconnectedCallback() {
87
+ super.disconnectedCallback(); // ⚠️ OBBLIGATORIO se usi timer/subscriptions
88
+ // Cleanup subscriptions manuali (es. breakpoints.subscribe)
89
+ }
90
+
91
+ attributeChangedCallback(name, oldVal, newVal) {
92
+ if (this.isConnected && oldVal !== newVal) {
93
+ this.render();
94
+ }
95
+ }
96
+
97
+ // ========================
98
+ // PROPERTIES
99
+ // ========================
100
+
101
+ get label() {
102
+ return this.getAttribute('label') || '';
103
+ }
104
+
105
+ get value() {
106
+ return this.getAttribute('value') || '';
107
+ }
108
+
109
+ get disabled() {
110
+ return this.hasAttribute('disabled');
111
+ }
112
+
113
+ // ========================
114
+ // RENDER (XSS-Safe)
115
+ // ========================
116
+
117
+ render() {
118
+ // ✅ SEMPRE escapare contenuti user-facing
119
+ this.innerHTML = `
120
+ <div class="mu-example">
121
+ <label>${escapeHTML(this.label)}</label>
122
+ <input
123
+ type="text"
124
+ value="${escapeHTML(this.value)}"
125
+ placeholder="${escapeHTML(this.getAttribute('placeholder') || '')}"
126
+ ${this.disabled ? 'disabled' : ''}
127
+ >
128
+ </div>
129
+ `;
130
+ this._attachEventListeners();
131
+ }
132
+
133
+ // ========================
134
+ // EVENT LISTENERS (Memory-Safe)
135
+ // ========================
136
+
137
+ _attachEventListeners() {
138
+ const input = this.querySelector('input');
139
+
140
+ // ✅ CORRETTO: Usa this.listen() per auto-cleanup
141
+ this.listen(input, 'input', (e) => {
142
+ this.dispatchEvent(new CustomEvent('mu-input', {
143
+ bubbles: true,
144
+ detail: { value: e.target.value }
145
+ }));
146
+ });
147
+
148
+ // ✅ CORRETTO: this.listen() per eventi window/document
149
+ this.listen(window, 'resize', () => this._handleResize());
150
+
151
+ // ✅ CORRETTO: Keyboard accessibility
152
+ if (!this.disabled) {
153
+ this.setupActivation(() => this._handleClick());
154
+ }
155
+ }
156
+
157
+ // ========================
158
+ // TIMER (Memory-Safe)
159
+ // ========================
160
+
161
+ _startPolling() {
162
+ // ✅ CORRETTO: Usa this.setInterval() per auto-cleanup
163
+ this.setInterval(() => this._poll(), 5000);
164
+ }
165
+
166
+ _debounce() {
167
+ // ✅ CORRETTO: Usa this.setTimeout() per auto-cleanup
168
+ this.setTimeout(() => this._doSomething(), 300);
169
+ }
170
+
171
+ // ========================
172
+ // ERROR REPORTING (AI Agent)
173
+ // ========================
174
+
175
+ _validate() {
176
+ if (!this.label) {
177
+ // ✅ Segnala errori per AI agent debugging
178
+ this.logError('MISSING_LABEL', 'mu-example requires a label attribute');
179
+ }
180
+ }
181
+ }
182
+
183
+ // Registration (idempotent)
184
+ if (!customElements.get('mu-example')) {
185
+ customElements.define('mu-example', MuExample);
186
+ }
187
+ ```
188
+
189
+ ### ⚠️ ANTI-PATTERN: Cosa NON Fare Mai
190
+
191
+ ```javascript
192
+ // ❌ VIETATO: addEventListener diretto (memory leak)
193
+ this.querySelector('button').addEventListener('click', handler);
194
+
195
+ // ❌ VIETATO: setTimeout/setInterval raw (memory leak)
196
+ setTimeout(() => this.update(), 1000);
197
+ setInterval(() => this.poll(), 5000);
198
+
199
+ // ❌ VIETATO: Nessun super.connectedCallback() (AbortController non inizializzato)
200
+ connectedCallback() {
201
+ this.render(); // this.listen() non funzionerà!
202
+ }
203
+
204
+ // ❌ VIETATO: innerHTML senza escaping (XSS vulnerability)
205
+ this.innerHTML = `<div>${userInput}</div>`;
206
+
207
+ // ❌ VIETATO: removeEventListener con arrow function (non funziona mai)
208
+ window.removeEventListener('resize', () => this.handleResize());
209
+
210
+ // ❌ VIETATO: Temporal dead zone con setTimeout
211
+ const id = setTimeout(() => { this._timers.delete(id); }, delay);
212
+
213
+ // ❌ VIETATO: Shadow DOM
214
+ this.attachShadow({ mode: 'open' });
215
+
216
+ // ❌ VIETATO: DOM Teleportation senza reset del flag listener (Bug Jan 2026)
217
+ // Quando un componente sposta SE STESSO nel DOM (es. appendChild(this)),
218
+ // disconnectedCallback aborta i listener ma il flag impedisce ri-registrazione
219
+ connectedCallback() {
220
+ if (!this._listenerSetup) { // ⚠️ Il flag resta true dopo disconnect!
221
+ this._listenerSetup = true;
222
+ this.listen(this, 'click', handler); // Perso dopo teleportation
223
+ }
224
+ }
225
+ // ✅ CORRETTO: Reset del flag in disconnectedCallback
226
+ disconnectedCallback() {
227
+ super.disconnectedCallback();
228
+ this._listenerSetup = false; // Permette ri-registrazione dopo move
229
+ }
230
+ ```
231
+
232
+ ### ✅ PATTERN OBBLIGATORI
233
+
234
+ | Pattern | Metodo | Perché |
235
+ |---------|--------|--------|
236
+ | **Event Listeners** | `this.listen(target, type, handler)` | Auto-cleanup via AbortController |
237
+ | **Timers** | `this.setTimeout()` / `this.setInterval()` | Auto-cleanup su disconnectedCallback |
238
+ | **XSS Prevention** | `escapeHTML(userInput)` | Previene injection in innerHTML |
239
+ | **Keyboard A11y** | `this.setupActivation(callback)` | Gestisce Enter/Space per elementi interattivi |
240
+ | **AI Debugging** | `this.logError(code, message)` | Errori strutturati per AI agents |
241
+ | **Lifecycle** | `super.connectedCallback()` | Inizializza AbortController per this.listen() |
242
+ | **DOM Teleportation** | Reset flag in `disconnectedCallback()` | Previene listener orfani dopo move |
243
+
244
+ ### 🚀 SOTA Patterns (Feb 2026)
245
+
246
+ Pattern moderni che dovrebbero essere usati per nuovi componenti e per migrazioni.
247
+
248
+ #### ✅ Performance CSS (COMPLETATO - v3.5.28)
249
+
250
+ Il framework applica **by default** le seguenti ottimizzazioni:
251
+
252
+ | Tecnica | File | Beneficio |
253
+ |---------|------|-----------|
254
+ | `will-change` | animations.css | GPU compositor hints per 13+ classi animazione |
255
+ | `CSS contain` | layout.css, components.css | Isola repaint scope per mu-layout, modals |
256
+ | `content-visibility: auto` | components.css | Skip rendering off-screen per mu-example |
257
+ | Speculation Rules API | shell.html | Prefetch route bundle predittivo |
258
+ | View Transitions API | shell.html | Animazioni smooth tra pagine SPA |
259
+
260
+ **Lighthouse Score**: 98/100 Performance, 100/100 Accessibility/BP/SEO
261
+
262
+ ```css
263
+ /* Framework applica automaticamente: */
264
+ mu-example {
265
+ content-visibility: auto;
266
+ contain-intrinsic-size: auto 300px;
267
+ }
268
+
269
+ mu-layout {
270
+ contain: layout style;
271
+ }
272
+
273
+ .mu-spin {
274
+ animation: mu-spin 1s linear infinite;
275
+ will-change: transform;
276
+ }
277
+ ```
278
+
279
+ #### ✅ Native `<dialog>` per Modal (COMPLETATO)
280
+ **mu-modal** usa ora `<dialog>` nativo con `showModal()`:
281
+ - Top-layer automatico (niente z-index manuali)
282
+ - Focus trap built-in
283
+ - ESC key gestito nativamente
284
+ - `::backdrop` per scrim animato
285
+ - `margin: auto` per centrare
286
+
287
+ ```javascript
288
+ // ✅ SOTA: Native dialog
289
+ this.#dialog = document.createElement('dialog');
290
+ this.#dialog.showModal();
291
+
292
+ // ❌ VECCHIO: Position fixed + z-index + manual focus trap
293
+ this.style.cssText = 'position:fixed;z-index:9999;';
294
+ document.body.appendChild(this);
295
+ ```
296
+
297
+ #### 🔜 Popover API (CANDIDATI)
298
+ I seguenti componenti potrebbero beneficiare della **Popover API**:
299
+
300
+ | Componente | Pattern Attuale | SOTA Migration |
301
+ |------------|----------------|----------------|
302
+ | `mu-dropdown` | Portal container + z-index | `popover` attribute |
303
+ | `mu-tooltip` | Position fixed + z-index | `popover` attribute |
304
+
305
+ **Benefici Popover API**:
306
+ - Light-dismiss automatico (click outside)
307
+ - Top-layer (niente z-index issues)
308
+ - `showPopover()` / `hidePopover()` / `togglePopover()`
309
+ - Keyboard accessibility built-in
310
+
311
+ ```html
312
+ <!-- SOTA: Declarative popover -->
313
+ <button popovertarget="my-menu">Open</button>
314
+ <div id="my-menu" popover>Menu content</div>
315
+ ```
316
+
317
+ ### 🔒 Security Checklist
318
+
319
+ Prima di completare un componente, verifica:
320
+
321
+ - [ ] Tutti i contenuti user-facing usano `escapeHTML()`
322
+ - [ ] Attributi interpolati in innerHTML sono escaped
323
+ - [ ] Slot content controllato (developer-provided) o escaped
324
+ - [ ] Nessun `eval()`, `Function()`, `document.write()`
325
+
326
+ ### ♿ Accessibility Checklist
327
+
328
+ - [ ] Elementi interattivi hanno `tabindex="0"`
329
+ - [ ] Role ARIA appropriato (`role="button"`, `role="menuitem"`, etc.)
330
+ - [ ] `setupActivation()` per attivazione via keyboard (Enter/Space)
331
+ - [ ] `aria-label` o label associata per screen reader
332
+ - [ ] Stati (`aria-checked`, `aria-expanded`, etc.) aggiornati dinamicamente
333
+
334
+ ### 🧪 Testing Checklist
335
+
336
+ - [ ] Unit test con `bun run scripts/test-isolated.js`
337
+ - [ ] E2E test per interazioni browser reali
338
+ - [ ] Coverage ≥97%
339
+ - [ ] Test memory leak: componente aggiunto/rimosso dal DOM non lascia listener
340
+
341
+ ---
342
+
343
+ ## TL;DR - Copy-Paste Templates
344
+
345
+ ### Agent-First Features (v3.5)
346
+ microUI is optimized for AI Agents with deep introspection, runtime feedback, enterprise-scale infrastructure, and 2026 multimodal/MCP support.
347
+
348
+ **1. Introspection (Metadata)**
349
+ A `custom-elements.json` manifest is included in the root. Agents can read this to understand:
350
+ - Component Attributes (names, types, defaults)
351
+ - Events and payloads
352
+ - CSS Shadow Parts
353
+
354
+ **2. Runtime Debugging (Agent Logger)**
355
+ Components validate usage and log structured errors to `window.__MICROUI_ERRORS__`.
356
+ ```javascript
357
+ // Check for errors in E2E tests
358
+ const errors = await page.evaluate(() => window.__MICROUI_ERRORS__);
359
+ if (errors.length > 0) console.error('microUI Errors:', errors);
360
+ ```
361
+
362
+ **3. Agent API (v3.3) - LLM-Friendly Component Access**
363
+ Direct API for LLM agents to introspect and interact with components:
364
+
365
+ ```javascript
366
+ // Get all components as structured data (for agent parsing)
367
+ const tree = microUI.getMuComponentTree();
368
+ // Returns: [{ tag, id, label, description, state, actions, rect, interactive, visible }]
369
+
370
+ // Get only interactive visible components
371
+ const buttons = microUI.getMuComponentTree(document.body, {
372
+ visibleOnly: true,
373
+ interactiveOnly: true
374
+ });
375
+
376
+ // Describe a component in natural language
377
+ microUI.describeComponent('#submit-btn');
378
+ // Returns: 'Button "Submit". Click to activate.'
379
+
380
+ // Find components by label (fuzzy match)
381
+ const matches = microUI.findByLabel('save');
382
+
383
+ // Get all registered microUI components
384
+ const components = microUI.getRegisteredComponents();
385
+ // Returns: Map<tag, constructor>
386
+ ```
387
+
388
+ **4. Enterprise-Scale Features (v3.4) - Large Application Infrastructure**
389
+
390
+ For building complex, multi-team applications:
391
+
392
+ ```javascript
393
+ // === NAMESPACED STORES ===
394
+ // Isolated state per feature/team - no conflicts
395
+ const userStore = microUI.createNamespacedStore('user', { profile: null });
396
+ const cartStore = microUI.createNamespacedStore('cart', { items: [] });
397
+
398
+ // Access stores by namespace
399
+ microUI.getStore('user'); // Returns userStore
400
+ microUI.getAllStores(); // { user: ..., cart: ... }
401
+
402
+ // Serialize/restore entire app state
403
+ const snapshot = microUI.captureAppState();
404
+ localStorage.setItem('state', JSON.stringify(snapshot));
405
+ microUI.restoreAppState(JSON.parse(localStorage.getItem('state')));
406
+
407
+ // === FEATURE REGISTRY ===
408
+ // Organize code by feature for multi-team development
409
+ microUI.createFeature('user', {
410
+ routes: ['/profile', '/settings'],
411
+ store: userStore,
412
+ components: ['mu-user-card', 'mu-user-form'],
413
+ meta: { team: 'user-team' }
414
+ });
415
+
416
+ microUI.getFeatures(); // All features
417
+ microUI.getFeatureSummary(); // Overview with stats
418
+ microUI.getFeatureComponents('user'); // Components in feature
419
+
420
+ // === OBSERVABILITY ===
421
+ // Track state changes for debugging
422
+ microUI.enableObservability();
423
+ microUI.getStateHistory('user'); // Timeline of state changes
424
+ microUI.getErrors(); // All caught errors
425
+ ```
426
+
427
+ ```html
428
+ <!-- === ERROR BOUNDARIES === -->
429
+ <!-- Catch errors with fallback UI - one error doesn't crash everything -->
430
+ <mu-error-boundary fallback="<p>Something went wrong</p>">
431
+ <mu-complex-widget></mu-complex-widget>
432
+ </mu-error-boundary>
433
+
434
+ <!-- Custom error handling -->
435
+ <mu-error-boundary onerror="logError(event.detail.error)">
436
+ <mu-data-table :items="${data}"></mu-data-table>
437
+ </mu-error-boundary>
438
+ ```
439
+
440
+ **5. Agent Automation (v3.5) - 2026 Multimodal & MCP Support**
441
+
442
+ Based on UI-TARS, A2UI, and Anthropic MCP research (2025-2026):
443
+
444
+ ```javascript
445
+ // === VISUAL MARKERS (for screenshot-based agents) ===
446
+ // Enable overlay labels for multimodal AI agents
447
+ microUI.enableVisualMarkers({ style: 'badge' }); // or 'outline'
448
+
449
+ // Agent takes screenshot, sees: [B1] Save, [B2] Cancel, [I1] Email
450
+ // Then references elements by marker ID:
451
+ const saveBtn = microUI.getMarkerElement('B1');
452
+ saveBtn.click();
453
+
454
+ // Get all marker mappings
455
+ microUI.getMarkerMap();
456
+ // { B1: { tag: 'mu-button', label: 'Save' }, I1: { tag: 'mu-input'... } }
457
+
458
+ microUI.disableVisualMarkers(); // Clean up
459
+
460
+ // === STRUCTURED SCHEMA (for code generation) ===
461
+ // Get JSON Schema for type-safe code gen
462
+ microUI.getComponentSchema('mu-button');
463
+ // { properties: { variant: { enum: ['filled'...] } }, actions: ['click'] }
464
+
465
+ microUI.getAllSchemas(); // All 11 documented components
466
+ microUI.getSchemaQuickRef('mu-input'); // Minimal summary
467
+
468
+ // === MCP ACTIONS (standard agent-tool protocol) ===
469
+ // MCP-compatible action definitions (Anthropic/Google 2026 standard)
470
+ microUI.getMCPActions();
471
+ // Returns 10 standardized actions:
472
+ // click_button, fill_input, toggle_checkbox, select_option,
473
+ // open_modal, close_modal, select_tab, get_component_tree,
474
+ // enable_visual_markers, confirm_dialog
475
+ ```
476
+
477
+ **Semantic Attributes (auto-inferred):**
478
+ ```html
479
+ <!-- Components auto-add data-mu-action and data-mu-role -->
480
+ <mu-button variant="filled">Save</mu-button>
481
+ <!-- Becomes: data-mu-action="submit" data-mu-role="primary-action" -->
482
+
483
+ <mu-button variant="text">Cancel</mu-button>
484
+ <!-- Becomes: data-mu-action="cancel" data-mu-role="tertiary-action" -->
485
+ ```
486
+
487
+ ### Minimal Page Setup
488
+ ```html
489
+ <!DOCTYPE html>
490
+ <html lang="en">
491
+ <head>
492
+ <meta charset="UTF-8">
493
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
494
+ <link rel="stylesheet" href="dist/microui.css">
495
+ </head>
496
+ <body>
497
+ <!-- Your content here -->
498
+ <script type="module" src="dist/microui.esm.js"></script>
499
+ </body>
500
+ </html>
501
+ ```
502
+
503
+ ### Common Patterns (Copy These)
504
+
505
+ ```html
506
+ <!-- Form with validation -->
507
+ <mu-form>
508
+ <mu-stack gap="md">
509
+ <mu-input label="Email" type="email" required></mu-input>
510
+ <mu-input label="Password" type="password" required></mu-input>
511
+ <mu-button variant="filled">Submit</mu-button>
512
+ </mu-stack>
513
+ </mu-form>
514
+
515
+ <!-- Card layout -->
516
+ <mu-grid cols="3" gap="md">
517
+ <mu-card variant="elevated">
518
+ <h3>Title</h3>
519
+ <p>Content</p>
520
+ <mu-button variant="text">Action</mu-button>
521
+ </mu-card>
522
+ </mu-grid>
523
+
524
+ <!-- Navigation tabs -->
525
+ <mu-tabs active="0">
526
+ <mu-tab>Tab 1</mu-tab>
527
+ <mu-tab>Tab 2</mu-tab>
528
+ </mu-tabs>
529
+
530
+ <!-- Modal dialog -->
531
+ <mu-modal id="my-modal">
532
+ <h2>Modal Title</h2>
533
+ <p>Content</p>
534
+ <mu-button onclick="this.closest('mu-modal').close()">Close</mu-button>
535
+ </mu-modal>
536
+ <mu-button onclick="document.getElementById('my-modal').open()">Open Modal</mu-button>
537
+
538
+ <!-- Toast notification -->
539
+ <script type="module">
540
+ import { showToast } from './dist/microui.esm.js';
541
+ showToast('Success!', { variant: 'success', duration: 3000 });
542
+ </script>
543
+ ```
544
+
545
+ ---
546
+
547
+ ## Quick Reference
548
+
549
+ ### All Components (49)
550
+
551
+ | Category | Components | Common Attributes |
552
+ |----------|------------|-------------------|
553
+ | **Layout** | `mu-container`, `mu-stack`, `mu-grid`, `mu-navbar`, `mu-sidebar`, `mu-divider` | `gap="xs\|sm\|md\|lg"`, `direction="row\|column"` |
554
+ | **Form** | `mu-button`, `mu-input`, `mu-textarea`, `mu-checkbox`, `mu-switch`, `mu-radio`, `mu-radio-group`, `mu-dropdown`, `mu-option`, `mu-chip`, `mu-form` | `variant`, `disabled`, `required`, `value` |
555
+ | **Display** | `mu-card`, `mu-tabs`, `mu-tab`, `mu-table`, `mu-avatar`, `mu-badge`, `mu-progress`, `mu-skeleton`, `mu-alert`, `mu-icon` | `variant="elevated\|outlined\|filled"` |
556
+ | **Feedback** | `mu-modal`, `mu-toast`, `mu-toast-container`, `mu-tooltip`, `mu-spinner`, `mu-confirm` | `duration`, `position` |
557
+ | **Performance** | `mu-virtual-list`, `mu-lazy`, `mu-repeat` | `items`, `renderItem` |
558
+ | **Data** | `mu-fetch` | `url`, `auto`, `interval` |
559
+ | **Enterprise** | `mu-error-boundary` | `fallback` |
560
+ | **Utility** | `mu-theme-toggle` | - |
561
+
562
+ ### Attribute Cheatsheet
563
+
564
+ ```html
565
+ <!-- Sizes -->
566
+ size="xs" | size="sm" | size="md" | size="lg" | size="xl"
567
+
568
+ <!-- Gaps (for layout) -->
569
+ gap="xs" (4px) | gap="sm" (8px) | gap="md" (16px) | gap="lg" (24px) | gap="xl" (32px)
570
+
571
+ <!-- Button variants -->
572
+ variant="filled" | variant="elevated" | variant="tonal" | variant="outlined" | variant="text" | variant="danger"
573
+
574
+ <!-- Card variants -->
575
+ variant="elevated" | variant="outlined" | variant="filled"
576
+
577
+ <!-- Alert variants -->
578
+ variant="info" | variant="success" | variant="warning" | variant="error"
579
+
580
+ <!-- Direction (stack) -->
581
+ direction="row" | direction="column"
582
+
583
+ <!-- Alignment -->
584
+ align="start" | align="center" | align="end" | align="stretch"
585
+ justify="start" | justify="center" | justify="end" | justify="space-between"
586
+ ```
587
+
588
+ ---
589
+
590
+ ## Component Details
591
+
592
+ ### mu-button
593
+ ```html
594
+ <mu-button variant="filled" size="md" disabled>Label</mu-button>
595
+ ```
596
+ | Attribute | Values | Default |
597
+ |-----------|--------|---------|
598
+ | `variant` | `filled`, `elevated`, `tonal`, `outlined`, `text`, `danger` | `filled` |
599
+ | `size` | `sm`, `md`, `lg` | `md` |
600
+ | `disabled` | boolean | `false` |
601
+
602
+ **Events:** `click` (native)
603
+
604
+ ### mu-input
605
+ ```html
606
+ <mu-input label="Email" type="email" placeholder="Enter email" required></mu-input>
607
+ ```
608
+ | Attribute | Values | Default |
609
+ |-----------|--------|---------|
610
+ | `type` | `text`, `email`, `password`, `number`, `tel`, `url` | `text` |
611
+ | `label` | string | - |
612
+ | `placeholder` | string | - |
613
+ | `value` | string | `""` |
614
+ | `disabled` | boolean | `false` |
615
+ | `required` | boolean | `false` |
616
+ | `variant` | `outlined`, `filled` | `outlined` |
617
+
618
+ **Events:** `mu-input` (on keystroke), `mu-change` (on blur)
619
+ **Accessibility:** Uses `for/id` association, `aria-label` fallback
620
+
621
+ **Accessibility:** `role="checkbox"`, `aria-checked="true|false|mixed"`, keyboard (Space/Enter)
622
+
623
+ ### mu-switch
624
+ ```html
625
+ <mu-switch label="Dark mode" checked></mu-switch>
626
+ ```
627
+ | Attribute | Values | Default |
628
+ |-----------|--------|---------|
629
+ | `label` | string | - |
630
+ | `checked` | boolean | `false` |
631
+ | `disabled` | boolean | `false` |
632
+
633
+ **Events:** `mu-change` with `{ checked: boolean }`
634
+ **Accessibility:** `role="switch"`, `aria-checked`, keyboard (Space/Enter)
635
+
636
+ ### mu-radio-group / mu-radio
637
+ ```html
638
+ <mu-radio-group name="size" value="md">
639
+ <mu-radio value="sm">Small</mu-radio>
640
+ <mu-radio value="md">Medium</mu-radio>
641
+ <mu-radio value="lg">Large</mu-radio>
642
+ </mu-radio-group>
643
+ ```
644
+ | Attribute | Values | Default |
645
+ |-----------|--------|---------|
646
+ | `name` | string (group) | - |
647
+ | `value` | string | - |
648
+ | `disabled` | boolean | `false` |
649
+
650
+ **Events:** `mu-change` with `{ value: string }` on group
651
+ **Accessibility:** `role="radiogroup"`, `role="radio"`, `aria-checked`
652
+
653
+ ### mu-dropdown
654
+ ```html
655
+ <mu-dropdown placeholder="Select option" value="1">
656
+ <mu-option value="1">Option 1</mu-option>
657
+ <mu-option value="2">Option 2</mu-option>
658
+ </mu-dropdown>
659
+ ```
660
+ | Attribute | Values | Default |
661
+ |-----------|--------|---------|
662
+ | `placeholder` | string | - |
663
+ | `value` | string | - |
664
+ | `disabled` | boolean | `false` |
665
+
666
+ **Events:** `mu-change` with `{ value: string, label: string }`
667
+ **Events:** `mu-change` with `{ value: string, label: string }`
668
+ **Accessibility:** `role="listbox"`, `aria-expanded`, full keyboard navigation (Arrows, Enter, Escape, Space)
669
+
670
+ ### mu-modal
671
+ ```html
672
+ <mu-modal id="dialog">
673
+ <h2>Title</h2>
674
+ <p>Content</p>
675
+ </mu-modal>
676
+ ```
677
+ **Methods:** `.open()`, `.close()`
678
+ **Events:** `mu-open`, `mu-close`
679
+
680
+ ### mu-tabs / mu-tab
681
+ ```html
682
+ <mu-tabs active="0" id="my-tabs">
683
+ <mu-tab>First</mu-tab>
684
+ <mu-tab>Second</mu-tab>
685
+ </mu-tabs>
686
+ ```
687
+ **Events:** `mu-change` with `{ index: number }`
688
+
689
+ ### mu-stack (Flexbox layout)
690
+ ```html
691
+ <mu-stack direction="row" gap="md" align="center" justify="space-between">
692
+ <div>Item 1</div>
693
+ <div>Item 2</div>
694
+ </mu-stack>
695
+ ```
696
+
697
+ ### mu-grid (CSS Grid)
698
+ ```html
699
+ <mu-grid cols="3" gap="md">
700
+ <mu-card>1</mu-card>
701
+ <mu-card>2</mu-card>
702
+ <mu-card>3</mu-card>
703
+ </mu-grid>
704
+ ```
705
+ **Responsive Behavior:** Grids with `cols="3"`, `cols="4"`, or `cols="6"` **automatically collapse to 1 column** on mobile (< 600px).
706
+
707
+ ### mu-virtual-list (10K+ items)
708
+ ```javascript
709
+ const list = document.querySelector('mu-virtual-list');
710
+ list.items = largeArray;
711
+ list.itemHeight = 50;
712
+ list.renderItem = (item) => `<div class="item">${item.name}</div>`;
713
+ ```
714
+
715
+ ---
716
+
717
+ ## Responsive Breakpoints (MD3)
718
+
719
+ microUI follows **Material Design 3 Window Size Classes** for adaptive layouts:
720
+
721
+ | Class | Width | Components Behavior |
722
+ |-------|-------|---------------------|
723
+ | **Compact** | < 600px | `mu-grid` → 1 column, `mu-bottom-nav` visible, `mu-drawer` → modal |
724
+ | **Medium** | 600-839px | `mu-drawer` → rail mode |
725
+ | **Expanded** | ≥ 840px | `mu-drawer` → expanded, `mu-bottom-nav` hidden |
726
+
727
+ ### Breakpoints API
728
+ ```javascript
729
+ import { breakpoints } from 'microui/core/breakpoints';
730
+
731
+ // Reactive subscriptions (for components that need to react to changes)
732
+ const unsubscribe = breakpoints.subscribe((size) => {
733
+ console.log(`Now in ${size} mode`); // 'compact', 'medium', 'expanded'
734
+ });
735
+
736
+ // Direct checks (live queries - always current, no caching)
737
+ if (breakpoints.isCompact) {
738
+ // Mobile-specific logic
739
+ }
740
+
741
+ if (breakpoints.isMedium) {
742
+ // Tablet-specific logic
743
+ }
744
+
745
+ if (breakpoints.isExpanded) {
746
+ // Desktop-specific logic
747
+ }
748
+
749
+ // Cleanup when done
750
+ unsubscribe();
751
+ ```
752
+
753
+ ### Best Practices for Responsive Agents
754
+
755
+ 1. **Don't hardcode media queries** - Use the centralized `breakpoints` utility
756
+ 2. **Use CSS for simple responsive** - `mu-grid` automatically collapses via CSS
757
+ 3. **Subscribe for complex logic** - Components needing JS-driven adaptation should subscribe
758
+ 4. **Clean up subscriptions** - Always unsubscribe in `disconnectedCallback()`
759
+
760
+ ```javascript
761
+ // Example: Component that adapts to breakpoints
762
+ class MyComponent extends MuElement {
763
+ #unsubscribe = null;
764
+
765
+ connectedCallback() {
766
+ super.connectedCallback();
767
+ this.#unsubscribe = breakpoints.subscribe(() => {
768
+ this.#updateLayout();
769
+ });
770
+ }
771
+
772
+ disconnectedCallback() {
773
+ this.#unsubscribe?.();
774
+ }
775
+
776
+ #updateLayout() {
777
+ if (breakpoints.isCompact) {
778
+ this.classList.add('mobile');
779
+ } else {
780
+ this.classList.remove('mobile');
781
+ }
782
+ }
783
+ }
784
+ ```
785
+
786
+ ---
787
+
788
+ ## JavaScript API
789
+
790
+ ### Signals (Reactive State)
791
+ ```javascript
792
+ import { signal, computed, effect } from 'microui';
793
+
794
+ const count = signal(0, 'my-counter'); // name for debugging
795
+ const doubled = computed(() => count() * 2);
796
+
797
+ effect(() => {
798
+ console.log('Count:', count()); // Auto-tracks dependencies
799
+ });
800
+
801
+ count.set(5); // Set value
802
+ count.update(n => n + 1); // Update with function
803
+ count.peek(); // Read without tracking
804
+ ```
805
+
806
+ ### EventBus (CrossBus)
807
+ ```javascript
808
+ import { bus } from 'microui';
809
+
810
+ // Subscribe
811
+ const unsub = bus.on('my:event', (data) => console.log(data));
812
+
813
+ // Emit (111M ops/sec - synchronous)
814
+ bus.emit('my:event', { key: 'value' });
815
+
816
+ // Cleanup
817
+ unsub();
818
+ ```
819
+
820
+ ### Keyboard Manager (ESC Stack)
821
+
822
+ Centralized LIFO stack for handling ESC key in modal-like components. When multiple modals/dropdowns are open, ESC closes only the **topmost** one.
823
+
824
+ ```javascript
825
+ import { keyboard } from 'microui/core/keyboard';
826
+
827
+ class MuMyModal extends MuElement {
828
+ #unsubEsc = null;
829
+
830
+ open() {
831
+ this.hidden = false;
832
+ // Register ESC handler - returns unsubscribe function
833
+ this.#unsubEsc = keyboard.pushEscapeHandler(this, () => this.close());
834
+ }
835
+
836
+ close() {
837
+ this.hidden = true;
838
+ // Always unsubscribe when closing
839
+ this.#unsubEsc?.();
840
+ this.#unsubEsc = null;
841
+ }
842
+
843
+ disconnectedCallback() {
844
+ super.disconnectedCallback();
845
+ this.#unsubEsc?.(); // Cleanup on removal
846
+ }
847
+ }
848
+ ```
849
+
850
+ **API:**
851
+ | Method | Returns | Description |
852
+ |--------|---------|-------------|
853
+ | `pushEscapeHandler(element, callback)` | `() => void` | Register handler, returns unsubscribe function |
854
+ | `isTopmost(element)` | `boolean` | Check if element is top of stack |
855
+ | `stackDepth` | `number` | Current stack depth (for debugging) |
856
+
857
+ ### Layer System (Z-Index)
858
+
859
+ Centralized z-index constants to avoid magic numbers. Auto-injected as CSS custom properties.
860
+
861
+ ```javascript
862
+ import { Z_INDEX } from 'microui/core/layers';
863
+
864
+ // Use in JavaScript
865
+ this.style.zIndex = Z_INDEX.modal;
866
+ ```
867
+
868
+ ```css
869
+ /* Use in CSS (auto-injected as --z-* tokens) */
870
+ .my-modal { z-index: var(--z-modal); }
871
+ .my-tooltip { z-index: var(--z-tooltip); }
872
+ ```
873
+
874
+ **Layer Hierarchy (lowest to highest):**
875
+ | Token | Value | Use For |
876
+ |-------|-------|---------|
877
+ | `base` | 1 | Default content |
878
+ | `sticky` | 100 | Sticky headers, FABs |
879
+ | `dropdown` | 1000 | Dropdowns, menus, popovers |
880
+ | `drawer` | 1100 | Navigation drawers |
881
+ | `modal` | 1200 | Modal dialogs |
882
+ | `toast` | 1300 | Toast notifications |
883
+ | `tooltip` | 1400 | Tooltips |
884
+ | `overlay` | 9999 | Full-screen overlays |
885
+ | `devtools` | 999999 | Agent dev tools (always on top) |
886
+
887
+ ### Confirm Dialog (Agent-Friendly)
888
+ ```javascript
889
+ import { muConfirm } from 'microui';
890
+
891
+ // Simple confirm - returns Promise<boolean>
892
+ const ok = await muConfirm('Delete this item?');
893
+ if (ok) deleteItem();
894
+
895
+ // With options
896
+ const confirmed = await muConfirm('Are you sure?', {
897
+ title: 'Confirm Delete',
898
+ confirmText: 'Delete',
899
+ cancelText: 'Cancel',
900
+ variant: 'danger' // 'primary' | 'danger' | 'warning'
901
+ });
902
+ ```
903
+
904
+ ### Data Fetcher Component
905
+ ```html
906
+ <!-- Declarative data fetching with automatic states -->
907
+ <mu-fetch url="/api/users" id="users">
908
+ <template slot="loading"><mu-skeleton lines="3"></mu-skeleton></template>
909
+ <template slot="error"><mu-alert variant="error">${error}</mu-alert></template>
910
+ <template slot="empty"><p>No users found</p></template>
911
+ </mu-fetch>
912
+
913
+ <script type="module">
914
+ const fetcher = document.getElementById('users');
915
+
916
+ // Set render function for array data
917
+ fetcher.renderItem = (user) => `
918
+ <mu-card variant="outlined">
919
+ <strong>${user.name}</strong> - ${user.email}
920
+ </mu-card>
921
+ `;
922
+
923
+ // Programmatic access
924
+ fetcher.data; // Current data
925
+ fetcher.state; // 'idle' | 'loading' | 'success' | 'error'
926
+ fetcher.refetch(); // Re-fetch data
927
+
928
+ // Events
929
+ fetcher.addEventListener('mu-success', (e) => {
930
+ console.log('Data loaded:', e.detail.data);
931
+ });
932
+ </script>
933
+ ```
934
+
935
+ ### Form State Management
936
+ ```javascript
937
+ import { createFormState } from 'microui';
938
+
939
+ const form = document.querySelector('mu-form');
940
+ const state = createFormState(form, {
941
+ email: { required: true, type: 'email' },
942
+ password: { required: true, minLength: 8 },
943
+ name: { maxLength: 50 }
944
+ });
945
+
946
+ // Check validation
947
+ console.log(state.isValid); // true if all fields valid
948
+ console.log(state.isDirty); // true if any field changed
949
+
950
+ // Per-field state
951
+ state.fields.email.value; // Current value
952
+ state.fields.email.error; // Error message or null
953
+ state.fields.email.touched; // true after blur
954
+ state.fields.email.dirty; // true if changed from initial
955
+
956
+ // Actions
957
+ state.validate(); // Validate all, mark touched
958
+ state.getValues(); // { email: '...', password: '...' }
959
+ state.getErrors(); // { email: null, password: 'Min 8 chars' }
960
+ state.reset(); // Reset to initial values
961
+ ```
962
+
963
+
964
+ ### Toast Notifications
965
+ ```javascript
966
+ import { showToast } from 'microui';
967
+
968
+ showToast('Operation successful', {
969
+ variant: 'success', // 'info' | 'success' | 'warning' | 'error'
970
+ duration: 3000 // ms, 0 for persistent
971
+ });
972
+ ```
973
+
974
+ ### Theme Control
975
+ ```javascript
976
+ import { Theme } from 'microui';
977
+
978
+ Theme.init(); // Auto-detect or use saved
979
+ Theme.set('dark'); // 'light' | 'dark' | 'system'
980
+ Theme.toggle(); // Switch light/dark
981
+ const current = Theme.get();
982
+ ```
983
+
984
+ ### View Transitions
985
+ ```javascript
986
+ import { transition } from 'microui';
987
+
988
+ await transition(() => {
989
+ document.querySelector('#content').innerHTML = newHTML;
990
+ });
991
+ ```
992
+
993
+ ---
994
+
995
+ ## Cache Control (PWA)
996
+
997
+ microUI includes a Service Worker with intelligent caching. Control via console:
998
+
999
+ ```javascript
1000
+ // Clear all caches and reload
1001
+ await microUICache.clear();
1002
+
1003
+ // Get cache status
1004
+ await microUICache.status();
1005
+ // Returns: { version: "2.0.18", caches: { "microui-precache-2.0.18": 5 } }
1006
+
1007
+ // Force check for updates
1008
+ await microUICache.update();
1009
+
1010
+ // Prefetch URLs into cache
1011
+ await microUICache.prefetch(['/assets/image.webp', '/data.json']);
1012
+ ```
1013
+
1014
+ ---
1015
+
1016
+ ## PWA Development for Agents
1017
+
1018
+ > **Complete guide for AI agents building Progressive Web Apps with microUI.**
1019
+
1020
+ ### 1. Service Worker Setup
1021
+
1022
+ Copy this template to your app root as `sw.js`:
1023
+
1024
+ ```javascript
1025
+ // sw.js - microUI Service Worker Template
1026
+ const VERSION = '1.0.0'; // Bump on each deploy
1027
+ const CACHE_PREFIX = 'myapp';
1028
+ const PRECACHE_NAME = `${CACHE_PREFIX}-precache-${VERSION}`;
1029
+ const RUNTIME_NAME = `${CACHE_PREFIX}-runtime-${VERSION}`;
1030
+
1031
+ // Critical assets (precached on install)
1032
+ const PRECACHE_ASSETS = [
1033
+ '/',
1034
+ '/shell.html',
1035
+ '/dist/microui.css',
1036
+ '/dist/routes/shell.js',
1037
+ '/dist/routes/home.js',
1038
+ '/favicon.png'
1039
+ ];
1040
+
1041
+ // INSTALL: Precache critical assets
1042
+ self.addEventListener('install', (event) => {
1043
+ event.waitUntil(
1044
+ caches.open(PRECACHE_NAME)
1045
+ .then(cache => cache.addAll(PRECACHE_ASSETS))
1046
+ .then(() => self.skipWaiting())
1047
+ );
1048
+ });
1049
+
1050
+ // ACTIVATE: Cleanup old caches
1051
+ self.addEventListener('activate', (event) => {
1052
+ event.waitUntil(
1053
+ caches.keys().then(names =>
1054
+ Promise.all(names
1055
+ .filter(n => n.startsWith(CACHE_PREFIX) && n !== PRECACHE_NAME && n !== RUNTIME_NAME)
1056
+ .map(n => caches.delete(n))
1057
+ )
1058
+ ).then(() => self.clients.claim())
1059
+ );
1060
+ });
1061
+
1062
+ // FETCH: Route requests
1063
+ self.addEventListener('fetch', (event) => {
1064
+ const { request } = event;
1065
+ if (request.method !== 'GET') return;
1066
+
1067
+ const url = new URL(request.url);
1068
+ if (url.origin !== self.location.origin) return;
1069
+
1070
+ // HTML: Network-first (always fresh)
1071
+ if (request.destination === 'document') {
1072
+ event.respondWith(networkFirst(request));
1073
+ return;
1074
+ }
1075
+
1076
+ // JS/CSS: Stale-while-revalidate (fast + fresh)
1077
+ event.respondWith(staleWhileRevalidate(request));
1078
+ });
1079
+
1080
+ async function networkFirst(request) {
1081
+ try {
1082
+ const response = await fetch(request);
1083
+ const cache = await caches.open(PRECACHE_NAME);
1084
+ cache.put(request, response.clone());
1085
+ return response;
1086
+ } catch {
1087
+ return caches.match(request) || caches.match('/');
1088
+ }
1089
+ }
1090
+
1091
+ async function staleWhileRevalidate(request) {
1092
+ const cache = await caches.open(RUNTIME_NAME);
1093
+ const cached = await cache.match(request);
1094
+
1095
+ const fetchPromise = fetch(request).then(response => {
1096
+ if (response.ok) cache.put(request, response.clone());
1097
+ return response;
1098
+ }).catch(() => null);
1099
+
1100
+ return cached || fetchPromise || caches.match('/');
1101
+ }
1102
+ ```
1103
+
1104
+ ### 2. Register Service Worker
1105
+
1106
+ Add to your `shell.html` (after app shell loads):
1107
+
1108
+ ```html
1109
+ <script>
1110
+ if ('serviceWorker' in navigator) {
1111
+ navigator.serviceWorker.register('/sw.js')
1112
+ .then(reg => console.log('SW registered:', reg.scope))
1113
+ .catch(err => console.error('SW failed:', err));
1114
+ }
1115
+ </script>
1116
+ ```
1117
+
1118
+ ### 3. Caching Strategy Guide
1119
+
1120
+ | Asset Type | Strategy | Why |
1121
+ |------------|----------|-----|
1122
+ | **HTML** | Network-first | Always get fresh content |
1123
+ | **JS/CSS/Images** | Stale-while-revalidate | Fast load + background update |
1124
+ | **Fonts (woff2)** | Cache-first | Immutable, never changes |
1125
+ | **API calls** | Network-only | Data must be fresh |
1126
+
1127
+ ### 4. App Shell + Service Worker Integration
1128
+
1129
+ ```
1130
+ Initial Load Flow:
1131
+ ─────────────────────────────────────────────────────────
1132
+ Browser → SW Installed? ─No→ Network → Cache critical assets
1133
+
1134
+ Yes
1135
+
1136
+
1137
+ Cache → Return shell.js (instant)
1138
+
1139
+
1140
+ Render App Shell (navbar, theme)
1141
+
1142
+
1143
+ Lazy load route (home.js)
1144
+
1145
+
1146
+ Background: Check for SW update
1147
+ ```
1148
+
1149
+ ### 5. PWA Checklist for Agents
1150
+
1151
+ ```
1152
+ [ ] manifest.json created (name, icons, theme_color)
1153
+ [ ] sw.js in app root
1154
+ [ ] PRECACHE_ASSETS includes shell.js + CSS
1155
+ [ ] VERSION bumped on each deploy
1156
+ [ ] Meta tags: theme-color, apple-mobile-web-app-capable
1157
+ [ ] Favicon + PWA icons (192x192, 512x512)
1158
+ [ ] HTTPS enabled (required for SW)
1159
+ [ ] Tested offline mode
1160
+ ```
1161
+
1162
+ ### 6. Manifest Template
1163
+
1164
+ ```json
1165
+ {
1166
+ "name": "My microUI App",
1167
+ "short_name": "MyApp",
1168
+ "start_url": "/",
1169
+ "display": "standalone",
1170
+ "background_color": "#FFFBFE",
1171
+ "theme_color": "#6750A4",
1172
+ "icons": [
1173
+ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
1174
+ { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
1175
+ ]
1176
+ }
1177
+ ```
1178
+
1179
+ ### 7. Agent Workflow: Creating a PWA
1180
+
1181
+ ```bash
1182
+ # 1. Copy SW template
1183
+ cp demo/sw.js my-app/sw.js
1184
+
1185
+ # 2. Update PRECACHE_ASSETS with your routes
1186
+ # 3. Create manifest.json
1187
+ # 4. Add meta tags to shell.html
1188
+ # 5. Register SW in shell.html
1189
+ # 6. Build and test
1190
+ bun run build
1191
+ ```
1192
+
1193
+ ---
1194
+
1195
+ ## Accessibility Compliance (WCAG 2.1)
1196
+
1197
+ All form components include proper ARIA attributes:
1198
+
1199
+ | Component | Role | Key Attributes |
1200
+ |-----------|------|----------------|
1201
+ | `mu-checkbox` | `checkbox` | `aria-checked="true\|false\|mixed"`, `tabindex` |
1202
+ | `mu-switch` | `switch` | `aria-checked`, `aria-label` |
1203
+ | `mu-radio` | `radio` | `aria-checked`, `tabindex` |
1204
+ | `mu-radio-group` | `radiogroup` | - |
1205
+ | `mu-dropdown` | trigger + listbox | `aria-haspopup`, `aria-expanded` |
1206
+ | `mu-button` | `button` | `tabindex` |
1207
+ | `mu-input` | - | `for/id` label association, `aria-label` fallback |
1208
+
1209
+ **Keyboard navigation:** All interactive components support `Tab`, `Space`, `Enter`.
1210
+
1211
+ ---
1212
+
1213
+ ## Best Practices for AI Agents
1214
+
1215
+ ### DO ✅
1216
+ 1. **Use semantic components** - `<mu-input label="Email">` not `<input placeholder="Email">`
1217
+ 2. **Use layout components** - `<mu-stack gap="md">` not manual CSS flexbox
1218
+ 3. **Use declarative HTML** - Attributes over JavaScript for configuration
1219
+ 4. **Always include labels** - For accessibility compliance
1220
+ 5. **Use `variant` attributes** - For consistent MD3 styling
1221
+ 6. **Use `mu-virtual-list`** - For lists with 100+ items
1222
+ 7. **Use `mu-lazy`** - For below-the-fold heavy components
1223
+
1224
+ ### DON'T ❌
1225
+ 1. **Don't use raw CSS for layout** - Use `mu-stack`, `mu-grid`, `mu-container`
1226
+ 2. **Don't forget `variant`** - Buttons/cards need explicit variant
1227
+ 3. **Don't manipulate Shadow DOM** - Components manage their own internals
1228
+ 4. **Don't use inline styles for spacing** - Use `gap` attributes
1229
+ 5. **Don't create custom form controls** - Use the provided accessible ones
1230
+
1231
+ ### CSS Fallback Pattern ⚡
1232
+ Layout components (`mu-stack`, `mu-grid`) have **CSS fallback styles** that apply BEFORE JavaScript loads. This prevents Flash of Unstyled Content (FOUC).
1233
+
1234
+ ```css
1235
+ /* CSS attribute selectors work immediately */
1236
+ mu-stack { display: flex; flex-direction: column; gap: 16px; }
1237
+ mu-stack[gap="lg"] { gap: 24px; }
1238
+
1239
+ mu-grid { display: grid; gap: 16px; }
1240
+ mu-grid[cols="3"] { grid-template-columns: repeat(3, 1fr); }
1241
+ ```
1242
+
1243
+ **Why this matters:** HTML is parsed before JavaScript executes. Without CSS fallback, layout elements have no styling until custom element upgrade completes.
1244
+
1245
+ ### Common Mistakes to Avoid
1246
+ ```html
1247
+ <!-- WRONG: Missing variant -->
1248
+ <mu-button>Click</mu-button>
1249
+
1250
+ <!-- CORRECT: Explicit variant -->
1251
+ <mu-button variant="filled">Click</mu-button>
1252
+
1253
+ <!-- WRONG: Raw div layout -->
1254
+ <div style="display: flex; gap: 16px;">
1255
+
1256
+ <!-- CORRECT: Use mu-stack -->
1257
+ <mu-stack direction="row" gap="md">
1258
+
1259
+ <!-- WRONG: Placeholder only (accessibility issue) -->
1260
+ <mu-input placeholder="Email"></mu-input>
1261
+
1262
+ <!-- CORRECT: Use label -->
1263
+ <mu-input label="Email"></mu-input>
1264
+ ```
1265
+
1266
+ ### XSS Prevention Patterns ⚠️
1267
+
1268
+ microUI provides centralized utilities for safe HTML rendering. **Always use these patterns when handling dynamic content.**
1269
+
1270
+ ```javascript
1271
+ // Import centralized utility
1272
+ import { escapeHTML } from 'microui';
1273
+
1274
+ // ✅ SAFE: Escape user-provided content
1275
+ const userName = '<script>alert("xss")</script>';
1276
+ element.innerHTML = `<span>${escapeHTML(userName)}</span>`;
1277
+ // Result: <span>&lt;script&gt;alert("xss")&lt;/script&gt;</span>
1278
+
1279
+ // ✅ SAFE: Use textContent for plain text
1280
+ element.textContent = userName;
1281
+
1282
+ // ✅ SAFE: Use createElement + property assignment
1283
+ const img = document.createElement('img');
1284
+ img.src = userProvidedUrl;
1285
+ img.alt = userProvidedAlt; // Property, not attribute string
1286
+ element.appendChild(img);
1287
+
1288
+ // ❌ UNSAFE: Never use innerHTML with unescaped user data
1289
+ element.innerHTML = `<span>${userName}</span>`; // XSS RISK!
1290
+ ```
1291
+
1292
+ | Pattern | When to Use |
1293
+ |---------|-------------|
1294
+ | `escapeHTML(str)` | Dynamic values in template literals with `innerHTML` |
1295
+ | `textContent` | Displaying plain text (no HTML needed) |
1296
+ | `createElement` | Creating elements with user-provided attributes |
1297
+ | Static templates | When content is hardcoded (safe) |
1298
+
1299
+ **Components using these patterns:** `mu-datatable`, `mu-api-table`, `mu-avatar`, `mu-fetch`
1300
+
1301
+ ---
1302
+
1303
+ ## File Structure
1304
+ ```
1305
+ dist/
1306
+ ├── microui.esm.js # Full ESM bundle (230 KB)
1307
+ ├── microui.min.js # Full IIFE bundle (231 KB)
1308
+ ├── microui.css # Combined CSS (64 KB)
1309
+ ├── microui.d.ts # TypeScript definitions
1310
+ ├── routes/ # v3.5 Route Bundles (SOTA)
1311
+ │ ├── shell.js # App Shell (Navbar, Theme, Bus) - 5 KB
1312
+ │ ├── home.js # Home route components
1313
+ │ ├── chunk-*.js # Shared dependencies (deduplicated)
1314
+ │ ├── route-deps.json # Auto-generated page dependencies
1315
+ │ └── page-components.json # Debug: components per page
1316
+ └── AGENTS.md # This file
1317
+ ```
1318
+
1319
+ ---
1320
+
1321
+ ## Automatic Bundle Dependencies (v3.5)
1322
+
1323
+ > **Build-time static analysis** detects which components each page uses.
1324
+
1325
+ The build process scans HTML for `<mu-*>` tags and automatically:
1326
+ 1. **Generates `route-deps.json`** - Maps pages to their required route bundles
1327
+ 2. **Generates `page-components.json`** - Lists all mu-* components found per page
1328
+
1329
+ ### How It Works
1330
+
1331
+ ```bash
1332
+ bun run build:framework
1333
+ # Output:
1334
+ # ✅ route-deps.json (19 pages with dependencies)
1335
+ # ✅ page-components.json (20 pages scanned)
1336
+ ```
1337
+
1338
+ **Generated `dist/routes/route-deps.json`:**
1339
+ ```json
1340
+ {
1341
+ "enterprise": ["alerts", "buttons", "cards", "tabs"],
1342
+ "buttons": ["cards", "layout", "tabs"],
1343
+ "home": ["cards", "icons", "layout"]
1344
+ }
1345
+ ```
1346
+
1347
+ **Runtime:** The demo loads this JSON and auto-fetches required routes before rendering:
1348
+ ```javascript
1349
+ // Automatic dependency loading
1350
+ const routeDeps = await fetch('dist/routes/route-deps.json').then(r => r.json());
1351
+ async function loadRouteWithDeps(name) {
1352
+ await Promise.all((routeDeps[name] || []).map(dep => loadRoute(dep)));
1353
+ await loadRoute(name);
1354
+ }
1355
+ ```
1356
+
1357
+ ### Benefits
1358
+
1359
+ | Feature | Before (Manual) | After (Automatic) |
1360
+ |---------|----------------|-------------------|
1361
+ | Maintenance | Edit `routeDependencies` map | Zero - auto-detected |
1362
+ | New pages | Must update build config | Just add HTML |
1363
+ | Debugging | Guess missing deps | Check `page-components.json` |
1364
+
1365
+ ### Build Utils API
1366
+
1367
+ ```javascript
1368
+ import { scanPageComponents, componentsToRoutes } from './scripts/build-utils.js';
1369
+
1370
+ // Extract mu-* tags from HTML
1371
+ const components = scanPageComponents(htmlContent);
1372
+ // { "home": ["mu-card", "mu-icon", "mu-stack"], "buttons": [...] }
1373
+
1374
+ // Map components to route bundles
1375
+ const routes = componentsToRoutes(["mu-tabs", "mu-button"]);
1376
+ // [\"buttons\", \"tabs\"]
1377
+ ```
1378
+
1379
+ ### Auto-Versioning (v3.5.2)
1380
+
1381
+ The build script automatically increments the version cache buster in `demo/shell.html`:
1382
+
1383
+ ```bash
1384
+ bun run build:framework
1385
+ # Output:
1386
+ # 🔄 Updating version cache buster...
1387
+ # ✅ Version updated to 2.0.23
1388
+ ```
1389
+
1390
+ **How it works:**
1391
+ - Extracts current version from `?v=X.X.X` pattern
1392
+ - Increments patch version (e.g., `2.0.21` → `2.0.22`)
1393
+ - Replaces all occurrences in demo/shell.html
1394
+
1395
+ **Benefits:**
1396
+ - No more stale cache issues after component updates
1397
+ - Zero manual version management needed
1398
+ - Browser automatically downloads updated bundles
1399
+
1400
+
1401
+ ---
1402
+
1403
+ ## App Shell Architecture (v3.2)
1404
+
1405
+ For SOTA performance, use the **App Shell** pattern. Load the critical shell first, then lazily load routes.
1406
+
1407
+ ```html
1408
+ <head>
1409
+ <link rel="stylesheet" href="dist/microui.css">
1410
+ </head>
1411
+ <body>
1412
+ <!-- Global Shell Components -->
1413
+ <mu-navbar>...</mu-navbar>
1414
+ <mu-toast-container></mu-toast-container>
1415
+
1416
+ <!-- App Router -->
1417
+ <mu-router base="/app-dist/pages" default="home"></mu-router>
1418
+
1419
+ <!-- Loader Script -->
1420
+ <script type="module">
1421
+ // 1. Load Shell (Navbar, Theme, Signals, Toasts)
1422
+ await import('./dist/routes/shell.js');
1423
+
1424
+ // 2. Initialize Router (which loads pages + their bundles)
1425
+ const router = document.querySelector('mu-router');
1426
+
1427
+ // Helper to manually load a route bundle if needed
1428
+ window.loadRoute = async (name) => {
1429
+ await import(`./dist/routes/${name}.js`);
1430
+ };
1431
+ </script>
1432
+ </body>
1433
+ ```
1434
+
1435
+ | Bundle | Content | Size (gzip) |
1436
+ |--------|---------|-------------|
1437
+ | `shell.js` | Navbar, Theme, CrossBus, Toasts | ~5 KB |
1438
+ | `home.js` | Landing page components | ~2 KB |
1439
+ | `forms.js` | (Example) Input, Button, Checkbox | ~7 KB |
1440
+
1441
+ **Why this is SOTA:**
1442
+ - The user only downloads `shell.js` (small) to see the UI.
1443
+ - Page bundles (`home.js`) are only downloaded when navigating to that page.
1444
+ - Common code (CrossBus, BaseClass) is shared via `chunk-*.js` files automatically.
1445
+
1446
+ ---
1447
+
1448
+ ## Performance Metrics
1449
+ - **Bundle size:** 230 KB full / 8 KB route-based (gzipped: 75 KB / 3 KB)
1450
+ - **Lighthouse:** 100% Performance, 100% Accessibility, 100% SEO, 100% Best Practices
1451
+ - **EventBus:** 111M ops/sec (CrossBus emitSync)
1452
+ - **Virtual List:** Handles 10K+ items at 60fps
1453
+ - **First paint:** <100ms (precached assets)
1454
+
1455
+ ---
1456
+
1457
+ ## Performance Patterns (SOTA)
1458
+
1459
+ > **Critical patterns for 100% Lighthouse scores and excellent UX.**
1460
+
1461
+ ### 1. Skeleton Loading (Perceived Performance)
1462
+
1463
+ Use `mu-skeleton` to show content placeholders while loading:
1464
+
1465
+ ```html
1466
+ <!-- Before data loads -->
1467
+ <mu-card>
1468
+ <mu-skeleton type="text" width="60%"></mu-skeleton>
1469
+ <mu-skeleton type="text" lines="3"></mu-skeleton>
1470
+ <mu-skeleton type="button"></mu-skeleton>
1471
+ </mu-card>
1472
+
1473
+ <!-- Replace with real content when ready -->
1474
+ <mu-card>
1475
+ <h3>User Profile</h3>
1476
+ <p>Actual content...</p>
1477
+ <mu-button>Edit</mu-button>
1478
+ </mu-card>
1479
+ ```
1480
+
1481
+ | Attribute | Values | Default |
1482
+ |-----------|--------|---------|
1483
+ | `type` | `text`, `avatar`, `button`, `image` | `text` |
1484
+ | `width` | CSS width | `100%` |
1485
+ | `lines` | Number of text lines | `1` |
1486
+ | `animated` | boolean | `true` |
1487
+
1488
+ ### 2. Lazy Loading (Below-the-fold)
1489
+
1490
+ Use `mu-lazy` to defer loading of non-critical content:
1491
+
1492
+ ```html
1493
+ <!-- Content loads only when scrolled into view -->
1494
+ <mu-lazy threshold="200px">
1495
+ <mu-card>
1496
+ <img src="heavy-image.webp">
1497
+ <p>This loads lazily</p>
1498
+ </mu-card>
1499
+ </mu-lazy>
1500
+
1501
+ <!-- With loading placeholder -->
1502
+ <mu-lazy placeholder="<mu-skeleton type='image'></mu-skeleton>">
1503
+ <img src="hero.webp" alt="Hero">
1504
+ </mu-lazy>
1505
+ ```
1506
+
1507
+ | Attribute | Description | Default |
1508
+ |-----------|-------------|---------|
1509
+ | `threshold` | Distance from viewport to trigger load | `100px` |
1510
+ | `placeholder` | HTML to show while loading | (empty) |
1511
+
1512
+ ### 3. Resource Hints (Preload Critical Assets)
1513
+
1514
+ Add to `<head>` for faster loading:
1515
+
1516
+ ```html
1517
+ <head>
1518
+ <!-- Preconnect to external origins (fonts, CDN) -->
1519
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1520
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
1521
+
1522
+ <!-- Preload critical assets (above-the-fold) -->
1523
+ <link rel="preload" href="/dist/microui.css" as="style">
1524
+ <link rel="preload" href="/dist/routes/shell.js" as="script" crossorigin>
1525
+ <link rel="preload" href="/assets/logo.webp" as="image" type="image/webp">
1526
+
1527
+ <!-- Prefetch likely next pages (after current page loads) -->
1528
+ <link rel="prefetch" href="/dist/routes/home.js">
1529
+ </head>
1530
+ ```
1531
+
1532
+ | Hint | When to Use | Priority |
1533
+ |------|-------------|----------|
1534
+ | `preconnect` | External domains you'll fetch from | 🔴 High |
1535
+ | `preload` | Critical above-the-fold assets | 🔴 High |
1536
+ | `prefetch` | Next likely navigation | 🟢 Low |
1537
+ | `dns-prefetch` | External domains (fallback) | 🟢 Low |
1538
+
1539
+ ### 4. Core Web Vitals Optimization
1540
+
1541
+ | Metric | Target | How to Achieve |
1542
+ |--------|--------|----------------|
1543
+ | **LCP** (Largest Contentful Paint) | < 2.5s | Preload hero image, inline critical CSS |
1544
+ | **FID** (First Input Delay) | < 100ms | Code split, defer non-critical JS |
1545
+ | **CLS** (Cumulative Layout Shift) | < 0.1 | Set dimensions on images, reserve space |
1546
+
1547
+ ```html
1548
+ <!-- LCP: Preload hero image with fetchpriority -->
1549
+ <link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
1550
+
1551
+ <!-- CLS: Always set dimensions -->
1552
+ <img src="photo.webp" width="800" height="600" alt="...">
1553
+
1554
+ <!-- CLS: Reserve space for dynamic content -->
1555
+ <div style="min-height: 300px;">
1556
+ <mu-lazy><mu-card>...</mu-card></mu-lazy>
1557
+ </div>
1558
+ ```
1559
+
1560
+ ### 5. View Transitions (Smooth Navigation)
1561
+
1562
+ Use the View Transitions API for cinematic page changes:
1563
+
1564
+ ```javascript
1565
+ import { transition, transitionNamed } from 'microui';
1566
+
1567
+ // Basic transition
1568
+ await transition(() => {
1569
+ document.getElementById('content').innerHTML = newHTML;
1570
+ });
1571
+
1572
+ // Named transition with custom animation
1573
+ await transitionNamed(() => {
1574
+ router.navigate('/users');
1575
+ }, { name: 'page-slide' });
1576
+ ```
1577
+
1578
+ ```css
1579
+ /* Custom transition animation */
1580
+ ::view-transition-old(page-slide) {
1581
+ animation: slide-out 0.3s ease-out;
1582
+ }
1583
+ ::view-transition-new(page-slide) {
1584
+ animation: slide-in 0.3s ease-out;
1585
+ }
1586
+ ```
1587
+
1588
+ ### 6. Virtual List (Large Datasets)
1589
+
1590
+ For lists with 100+ items, use `mu-virtual-list`:
1591
+
1592
+ ```javascript
1593
+ const list = document.querySelector('mu-virtual-list');
1594
+ list.items = largeArray; // 10K+ items OK
1595
+ list.itemHeight = 48; // Fixed height for calculation
1596
+ list.renderItem = (item, index) => `
1597
+ <div class="list-item">
1598
+ <span>${item.name}</span>
1599
+ <span>${item.email}</span>
1600
+ </div>
1601
+ `;
1602
+ ```
1603
+
1604
+ ### 7. Speculative Route Preloading
1605
+
1606
+ Preload routes on hover for instant navigation:
1607
+
1608
+ ```javascript
1609
+ // Add to navigation links
1610
+ document.querySelectorAll('a[data-route]').forEach(link => {
1611
+ link.addEventListener('mouseenter', () => {
1612
+ const route = link.dataset.route;
1613
+ // Prefetch route bundle
1614
+ import(`/dist/routes/${route}.js`);
1615
+ });
1616
+ });
1617
+ ```
1618
+
1619
+ ---
1620
+
1621
+ ## Error Handling Patterns
1622
+
1623
+ ### 1. Runtime Error Collection
1624
+
1625
+ microUI logs all component errors to `window.__MICROUI_ERRORS__`:
1626
+
1627
+ ```javascript
1628
+ // In E2E tests or debug console
1629
+ const errors = window.__MICROUI_ERRORS__ || [];
1630
+ errors.forEach(err => {
1631
+ console.error(`[${err.component}] ${err.message}`, err.details);
1632
+ });
1633
+ ```
1634
+
1635
+ ### 2. Async Error Handling
1636
+
1637
+ Wrap async operations with proper error handling:
1638
+
1639
+ ```javascript
1640
+ async function loadUserData() {
1641
+ try {
1642
+ const response = await fetch('/api/users');
1643
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
1644
+ return await response.json();
1645
+ } catch (error) {
1646
+ // Show user-friendly error
1647
+ showToast('Failed to load data. Please retry.', { variant: 'error' });
1648
+ // Log for debugging
1649
+ console.error('loadUserData failed:', error);
1650
+ return null;
1651
+ }
1652
+ }
1653
+ ```
1654
+
1655
+ ### 3. Error Boundary Pattern
1656
+
1657
+ Create a wrapper to catch render errors:
1658
+
1659
+ ```javascript
1660
+ function safeRender(container, renderFn) {
1661
+ try {
1662
+ container.innerHTML = renderFn();
1663
+ } catch (error) {
1664
+ container.innerHTML = `
1665
+ <mu-alert variant="error">
1666
+ Something went wrong. Please refresh the page.
1667
+ </mu-alert>
1668
+ `;
1669
+ console.error('Render error:', error);
1670
+ }
1671
+ }
1672
+
1673
+ // Usage
1674
+ safeRender(document.getElementById('content'), () => {
1675
+ return `<mu-card>${userData.name}</mu-card>`;
1676
+ });
1677
+ ```
1678
+
1679
+ ### 4. Network Error Handling
1680
+
1681
+ ```javascript
1682
+ // Fetch with timeout and retry
1683
+ async function fetchWithRetry(url, options = {}, retries = 3) {
1684
+ for (let i = 0; i < retries; i++) {
1685
+ try {
1686
+ const controller = new AbortController();
1687
+ const timeout = setTimeout(() => controller.abort(), 5000);
1688
+
1689
+ const response = await fetch(url, {
1690
+ ...options,
1691
+ signal: controller.signal
1692
+ });
1693
+
1694
+ clearTimeout(timeout);
1695
+ if (response.ok) return response;
1696
+ } catch (error) {
1697
+ if (i === retries - 1) throw error;
1698
+ await new Promise(r => setTimeout(r, 1000 * (i + 1)));
1699
+ }
1700
+ }
1701
+ }
1702
+ ```
1703
+
1704
+ ### 5. Agent Debugging Tips
1705
+
1706
+ ```javascript
1707
+ // Enable verbose logging
1708
+ window.__MICROUI_DEBUG__ = true;
1709
+
1710
+ // Check component registration
1711
+ customElements.get('mu-button'); // Returns class or undefined
1712
+
1713
+ // Inspect component state
1714
+ const btn = document.querySelector('mu-button');
1715
+ console.log(btn.getAttribute('variant'), btn.disabled);
1716
+
1717
+ // Force component re-render
1718
+ btn.render?.();
1719
+ ```
1720
+
1721
+ ---
1722
+
1723
+ ## Page-Based App Development (v3.2)
1724
+
1725
+ > **For AI Agents building apps with microUI.** This is the recommended architecture.
1726
+
1727
+ ### Creating a New Page
1728
+
1729
+ Create a single file in `app/pages/`:
1730
+
1731
+ ```html
1732
+ <!-- app/pages/users.html -->
1733
+ <mu-page route="users" title="Users">
1734
+ <script type="x-dependencies">
1735
+ mu-table
1736
+ mu-button
1737
+ mu-stack
1738
+ mu-tabs
1739
+ </script>
1740
+
1741
+ <template>
1742
+ <h1 class="page-title">Users</h1>
1743
+ <mu-table id="users-table"></mu-table>
1744
+ <mu-button onclick="addUser()">Add User</mu-button>
1745
+ </template>
1746
+ </mu-page>
1747
+ ```
1748
+
1749
+ ### Build & Run
1750
+
1751
+ ```bash
1752
+ # Build everything (framework + app)
1753
+ bun run build
1754
+
1755
+ # Or separately:
1756
+ bun run build:framework # → dist/
1757
+ bun run build:app # → app-dist/
1758
+
1759
+ # Serve
1760
+ npx serve app-dist
1761
+ ```
1762
+
1763
+ ### File Structure
1764
+
1765
+ ```
1766
+ microUI/
1767
+ ├── src/ # Framework source
1768
+ │ └── components/ # 49 components
1769
+
1770
+ ├── dist/ # Framework build
1771
+ │ ├── microui.esm.js # Full bundle
1772
+ │ ├── microui.css # CSS
1773
+ │ └── components/ # Per-component builds
1774
+
1775
+ ├── app/ # App source
1776
+ │ ├── pages/ # One file per page
1777
+ │ │ ├── home.html
1778
+ │ │ ├── users.html
1779
+ │ │ └── settings.html
1780
+ │ └── shell.html # App shell
1781
+
1782
+ └── app-dist/ # App build (auto-generated)
1783
+ ├── routes/ # Auto-bundled from x-dependencies
1784
+ ├── pages/ # Copied HTML pages
1785
+ └── pages.json # Manifest
1786
+ ```
1787
+
1788
+ ### How It Works
1789
+
1790
+ 1. **Agent creates** `app/pages/mypage.html` with `<mu-page>` component
1791
+ 2. **Build extracts** dependencies from `<script type="x-dependencies">`
1792
+ 3. **Build generates** route bundle in `app-dist/routes/mypage.js`
1793
+ 4. **Router lazy-loads** page HTML and route bundle on navigation
1794
+
1795
+ ### mu-router
1796
+
1797
+ ```html
1798
+ <mu-router base="/app-dist/pages" default="home"></mu-router>
1799
+ ```
1800
+
1801
+ | Attribute | Description | Default |
1802
+ |-----------|-------------|---------|
1803
+ | `base` | Path to pages directory | `/app/pages` |
1804
+ | `default` | Default route when no hash | `home` |
1805
+
1806
+ **Events:** `mu-route-change`, `mu-page-loaded`, `mu-page-error`
1807
+
1808
+ **Methods:** `.navigate(route)`
1809
+
1810
+ ### mu-page
1811
+
1812
+ ```html
1813
+ <mu-page route="mypage" title="My Page">
1814
+ <script type="x-dependencies">mu-button, mu-card</script>
1815
+ <template><!-- content --></template>
1816
+ </mu-page>
1817
+ ```
1818
+
1819
+ | Attribute | Description |
1820
+ |-----------|-------------|
1821
+ | `route` | Route name (used in URL hash) |
1822
+ | `title` | Page title (set on navigation) |
1823
+
1824
+ ---
1825
+
1826
+ ## Agent Workflow: Adding a Page
1827
+
1828
+ ```bash
1829
+ # 1. Create page file
1830
+ cat > app/pages/dashboard.html << 'EOF'
1831
+ <mu-page route="dashboard" title="Dashboard">
1832
+ <script type="x-dependencies">
1833
+ mu-card
1834
+ mu-stack
1835
+ mu-grid
1836
+ </script>
1837
+ <template>
1838
+ <h1 class="page-title">Dashboard</h1>
1839
+ <mu-grid cols="3" gap="md">
1840
+ <mu-card variant="elevated">Widget 1</mu-card>
1841
+ <mu-card variant="elevated">Widget 2</mu-card>
1842
+ <mu-card variant="elevated">Widget 3</mu-card>
1843
+ </mu-grid>
1844
+ </template>
1845
+ </mu-page>
1846
+ EOF
1847
+
1848
+ # 2. Build
1849
+ bun run build:app
1850
+
1851
+ # 3. Navigate to: http://localhost:5001/app-dist/#dashboard
1852
+ ```
1853
+
1854
+ ---
1855
+
1856
+ ## Agent Workflow: Adding a Component
1857
+
1858
+ ```bash
1859
+ # 1. Create component file
1860
+ cat > src/components/mu-counter.js << 'EOF'
1861
+ import { MuElement, define } from '../core/MuElement.js';
1862
+
1863
+ export class MuCounter extends MuElement {
1864
+ static baseClass = 'mu-counter';
1865
+ static observedAttributes = ['value'];
1866
+
1867
+ render() {
1868
+ const value = parseInt(this.attr('value', '0'));
1869
+ this.innerHTML = `
1870
+ <button class="mu-counter__dec">-</button>
1871
+ <span class="mu-counter__value">${value}</span>
1872
+ <button class="mu-counter__inc">+</button>
1873
+ `;
1874
+ this.#setupEvents();
1875
+ }
1876
+
1877
+ #setupEvents() {
1878
+ this.querySelector('.mu-counter__dec')
1879
+ .addEventListener('click', () => this.#change(-1));
1880
+ this.querySelector('.mu-counter__inc')
1881
+ .addEventListener('click', () => this.#change(1));
1882
+ }
1883
+
1884
+ #change(delta) {
1885
+ const current = parseInt(this.attr('value', '0'));
1886
+ this.setAttribute('value', current + delta);
1887
+ this.emit('mu-change', { value: current + delta });
1888
+ }
1889
+ }
1890
+
1891
+ define('mu-counter', MuCounter);
1892
+ EOF
1893
+
1894
+ # 2. Add to index.js exports (if needed)
1895
+ # 3. Build framework
1896
+ bun run build:framework
1897
+ ```
1898
+
1899
+ ---
1900
+
1901
+ ## State Management for Complex Apps
1902
+
1903
+ > **For apps with shared state across components and pages.**
1904
+
1905
+ ### Global Store Pattern
1906
+ ```javascript
1907
+ // store/index.js - Centralized reactive state
1908
+ import { signal, computed, effect } from 'microui';
1909
+
1910
+ export const store = {
1911
+ // Core state
1912
+ user: signal(null, 'user'),
1913
+ items: signal([], 'items'),
1914
+ loading: signal(false, 'loading'),
1915
+
1916
+ // Computed values (auto-update when dependencies change)
1917
+ itemCount: computed(() => store.items().length),
1918
+ isAuthenticated: computed(() => store.user() !== null),
1919
+
1920
+ // Actions
1921
+ async fetchItems() {
1922
+ store.loading.set(true);
1923
+ try {
1924
+ const data = await fetch('/api/items').then(r => r.json());
1925
+ store.items.set(data);
1926
+ } finally {
1927
+ store.loading.set(false);
1928
+ }
1929
+ },
1930
+
1931
+ login(userData) {
1932
+ store.user.set(userData);
1933
+ },
1934
+
1935
+ logout() {
1936
+ store.user.set(null);
1937
+ }
1938
+ };
1939
+
1940
+ // Auto-persist to localStorage
1941
+ effect(() => {
1942
+ const user = store.user();
1943
+ if (user) localStorage.setItem('user', JSON.stringify(user));
1944
+ else localStorage.removeItem('user');
1945
+ });
1946
+
1947
+ // Restore on load
1948
+ const savedUser = localStorage.getItem('user');
1949
+ if (savedUser) store.user.set(JSON.parse(savedUser));
1950
+ ```
1951
+
1952
+ ### Binding State to Components
1953
+ ```javascript
1954
+ import { store } from './store/index.js';
1955
+ import { effect } from 'microui';
1956
+
1957
+ // Auto-update UI when state changes
1958
+ effect(() => {
1959
+ const user = store.user();
1960
+ document.querySelector('#user-name').textContent = user?.name || 'Guest';
1961
+ document.querySelector('#login-btn').hidden = !!user;
1962
+ document.querySelector('#logout-btn').hidden = !user;
1963
+ });
1964
+
1965
+ // Show loading state
1966
+ effect(() => {
1967
+ document.querySelector('#spinner').hidden = !store.loading();
1968
+ });
1969
+ ```
1970
+
1971
+ ### State in Custom Components
1972
+ ```javascript
1973
+ export class MuUserCard extends MuElement {
1974
+ connectedCallback() {
1975
+ super.connectedCallback();
1976
+ // Re-render when user changes
1977
+ this.#unsubscribe = effect(() => {
1978
+ this.user = store.user();
1979
+ this.render();
1980
+ });
1981
+ }
1982
+
1983
+ disconnectedCallback() {
1984
+ this.#unsubscribe?.();
1985
+ }
1986
+ }
1987
+ ```
1988
+
1989
+ ---
1990
+
1991
+ ## Data-Driven Components
1992
+
1993
+ > **Components that render dynamic data from arrays/objects.**
1994
+
1995
+ ### mu-repeat (Declarative Lists)
1996
+ ```html
1997
+ <mu-repeat id="user-list"></mu-repeat>
1998
+
1999
+ <script type="module">
2000
+ const list = document.getElementById('user-list');
2001
+
2002
+ // Set data and render template
2003
+ list.items = await fetch('/api/users').then(r => r.json());
2004
+ list.renderItem = (user, index) => `
2005
+ <mu-card variant="outlined">
2006
+ <mu-stack direction="row" gap="md" align="center">
2007
+ <mu-avatar initials="${user.name[0]}" size="md"></mu-avatar>
2008
+ <mu-stack gap="xs">
2009
+ <strong>${user.name}</strong>
2010
+ <small>${user.email}</small>
2011
+ </mu-stack>
2012
+ <mu-button variant="text" data-id="${user.id}">Edit</mu-button>
2013
+ </mu-stack>
2014
+ </mu-card>
2015
+ `;
2016
+
2017
+ // Listen for item events
2018
+ list.addEventListener('click', (e) => {
2019
+ const btn = e.target.closest('[data-id]');
2020
+ if (btn) console.log('Edit user:', btn.dataset.id);
2021
+ });
2022
+ </script>
2023
+ ```
2024
+
2025
+ ### mu-table (Data Tables with Sorting)
2026
+ ```html
2027
+ <mu-table id="data-table"></mu-table>
2028
+
2029
+ <script type="module">
2030
+ const table = document.getElementById('data-table');
2031
+
2032
+ // Configure columns
2033
+ table.columns = [
2034
+ { key: 'name', label: 'Name', sortable: true },
2035
+ { key: 'email', label: 'Email', sortable: true },
2036
+ { key: 'role', label: 'Role', sortable: true },
2037
+ { key: 'actions', label: '', render: (row) => `
2038
+ <mu-button variant="text" size="sm" data-action="edit" data-id="${row.id}">
2039
+ <mu-icon name="edit"></mu-icon>
2040
+ </mu-button>
2041
+ `}
2042
+ ];
2043
+
2044
+ // Set data
2045
+ table.data = await fetch('/api/users').then(r => r.json());
2046
+
2047
+ // Handle actions
2048
+ table.addEventListener('click', (e) => {
2049
+ const btn = e.target.closest('[data-action]');
2050
+ if (btn?.dataset.action === 'edit') {
2051
+ console.log('Edit:', btn.dataset.id);
2052
+ }
2053
+ });
2054
+ </script>
2055
+ ```
2056
+
2057
+ ### mu-virtual-list (10K+ Items)
2058
+ ```javascript
2059
+ const list = document.querySelector('mu-virtual-list');
2060
+ list.items = hugeArray; // 10,000+ items OK
2061
+ list.itemHeight = 56; // Fixed height for virtualization
2062
+ list.renderItem = (item) => `
2063
+ <div class="list-row">${item.name}</div>
2064
+ `;
2065
+ ```
2066
+
2067
+ ---
2068
+
2069
+ ## Error Handling Patterns
2070
+
2071
+ > **Production-ready error handling for complex apps.**
2072
+
2073
+ ### Runtime Error Detection
2074
+ ```javascript
2075
+ // Check for microUI component errors
2076
+ const errors = window.__MICROUI_ERRORS__ || [];
2077
+ if (errors.length) {
2078
+ console.error('microUI component errors:', errors);
2079
+ // Report to monitoring service
2080
+ fetch('/api/errors', {
2081
+ method: 'POST',
2082
+ body: JSON.stringify({ errors, url: location.href })
2083
+ });
2084
+ }
2085
+ ```
2086
+
2087
+ ### Global Error Boundary
2088
+ ```javascript
2089
+ // Catch all unhandled errors
2090
+ window.addEventListener('error', (event) => {
2091
+ console.error('Uncaught error:', event.error);
2092
+ showToast('Something went wrong', { variant: 'error' });
2093
+
2094
+ // Prevent default browser error UI
2095
+ event.preventDefault();
2096
+ });
2097
+
2098
+ // Catch unhandled promise rejections
2099
+ window.addEventListener('unhandledrejection', (event) => {
2100
+ console.error('Unhandled rejection:', event.reason);
2101
+ showToast('Network or async error', { variant: 'warning' });
2102
+ });
2103
+ ```
2104
+
2105
+ ### API Error Handling
2106
+ ```javascript
2107
+ // services/api.js
2108
+ export async function fetchAPI(endpoint, options = {}) {
2109
+ try {
2110
+ const res = await fetch(`/api${endpoint}`, {
2111
+ headers: { 'Content-Type': 'application/json' },
2112
+ ...options
2113
+ });
2114
+
2115
+ if (!res.ok) {
2116
+ const error = await res.json().catch(() => ({}));
2117
+ throw new Error(error.message || `HTTP ${res.status}`);
2118
+ }
2119
+
2120
+ return res.json();
2121
+ } catch (err) {
2122
+ showToast(err.message || 'Network error', { variant: 'error' });
2123
+ throw err;
2124
+ }
2125
+ }
2126
+
2127
+ // Usage
2128
+ import { fetchAPI } from './services/api.js';
2129
+ const users = await fetchAPI('/users');
2130
+ ```
2131
+
2132
+ ---
2133
+
2134
+ ## Architecture for Large Apps (10+ Pages)
2135
+
2136
+ > **Recommended structure for complex multi-page applications.**
2137
+
2138
+ ### Directory Structure
2139
+ ```
2140
+ my-app/
2141
+ ├── shell.html # App shell (minimal HTML)
2142
+ ├── sw.js # Service Worker
2143
+ ├── manifest.json # PWA manifest
2144
+ ├── store/
2145
+ │ └── index.js # Global reactive state (signals)
2146
+ ├── pages/
2147
+ │ ├── home.js # Lazy-loaded route modules
2148
+ │ ├── users.js
2149
+ │ ├── settings.js
2150
+ │ └── not-found.js
2151
+ ├── components/
2152
+ │ ├── user-card.js # Reusable custom components
2153
+ │ ├── data-table.js
2154
+ │ └── nav-menu.js
2155
+ ├── services/
2156
+ │ └── api.js # API client with error handling
2157
+ └── assets/
2158
+ ├── logo.webp
2159
+ └── icons/
2160
+ ```
2161
+
2162
+ ### Route-Based Code Splitting
2163
+ ```html
2164
+ <!-- shell.html - App Shell -->
2165
+ <body>
2166
+ <mu-navbar>...</mu-navbar>
2167
+ <main id="app"></main>
2168
+ <mu-toast-container></mu-toast-container>
2169
+
2170
+ <script type="module">
2171
+ // Route configuration
2172
+ const routes = {
2173
+ home: () => import('./pages/home.js'),
2174
+ users: () => import('./pages/users.js'),
2175
+ settings: () => import('./pages/settings.js'),
2176
+ default: () => import('./pages/not-found.js')
2177
+ };
2178
+
2179
+ async function navigate() {
2180
+ const hash = location.hash.slice(1) || 'home';
2181
+ const loader = routes[hash] || routes.default;
2182
+
2183
+ try {
2184
+ const module = await loader();
2185
+ document.getElementById('app').innerHTML = '';
2186
+ await module.render(document.getElementById('app'));
2187
+ } catch (err) {
2188
+ console.error('Route error:', err);
2189
+ document.getElementById('app').innerHTML = `
2190
+ <mu-alert variant="error">Failed to load page</mu-alert>
2191
+ `;
2192
+ }
2193
+ }
2194
+
2195
+ window.addEventListener('hashchange', navigate);
2196
+ navigate(); // Initial load
2197
+ </script>
2198
+ </body>
2199
+ ```
2200
+
2201
+ ### Page Module Pattern
2202
+ ```javascript
2203
+ // pages/users.js
2204
+ import { store } from '../store/index.js';
2205
+ import { fetchAPI } from '../services/api.js';
2206
+
2207
+ export async function render(container) {
2208
+ // Show skeleton while loading
2209
+ container.innerHTML = `
2210
+ <mu-doc-page title="Users">
2211
+ <mu-skeleton type="text" lines="5"></mu-skeleton>
2212
+ </mu-doc-page>
2213
+ `;
2214
+
2215
+ // Fetch data
2216
+ const users = await fetchAPI('/users');
2217
+ store.users.set(users);
2218
+
2219
+ // Render content
2220
+ container.innerHTML = `
2221
+ <mu-doc-page title="Users">
2222
+ <mu-stack gap="md">
2223
+ <mu-stack direction="row" justify="space-between">
2224
+ <h2>All Users (${users.length})</h2>
2225
+ <mu-button variant="filled" id="add-user">Add User</mu-button>
2226
+ </mu-stack>
2227
+ <mu-repeat id="user-list"></mu-repeat>
2228
+ </mu-stack>
2229
+ </mu-doc-page>
2230
+ `;
2231
+
2232
+ // Setup repeat
2233
+ const list = container.querySelector('#user-list');
2234
+ list.items = users;
2235
+ list.renderItem = (user) => `
2236
+ <mu-card variant="outlined">
2237
+ <strong>${user.name}</strong> - ${user.email}
2238
+ </mu-card>
2239
+ `;
2240
+ }
2241
+ ```
2242
+
2243
+ ### Performance Checklist for Large Apps
2244
+ ```
2245
+ [ ] Use route-based code splitting (only load needed code)
2246
+ [ ] Preload critical routes with <link rel="prefetch">
2247
+ [ ] Use mu-virtual-list for lists with 100+ items
2248
+ [ ] Use mu-lazy for below-the-fold content
2249
+ [ ] Implement skeleton loading for perceived performance
2250
+ [ ] Cache API responses with Service Worker
2251
+ [ ] Use signals for state (not prop drilling)
2252
+ [ ] Minimize bundle size with tree-shaking
2253
+ ```
2254
+
2255
+ ---
2256
+
2257
+ ## 🚀 Development Server
2258
+
2259
+ > **CRITICAL**: Use the project's official `server.js` for development and Lighthouse testing.
2260
+
2261
+ ### Quick Start
2262
+ ```bash
2263
+ # Start the Lighthouse-optimized dev server
2264
+ bun server.js
2265
+
2266
+ # Server runs on port 5001 (auto-increments if busy)
2267
+ # 🚀 http://localhost:5001/
2268
+ ```
2269
+
2270
+ ### URL Routing
2271
+
2272
+ | URL | Maps To | Description |
2273
+ |-----|---------|-------------|
2274
+ | `/` | `/demo/shell.html` | Main showcase (App Shell pattern with dynamic route loading) |
2275
+ | `/shell.html` | `/demo/shell.html` | App Shell pattern (dynamic route loading) |
2276
+ | `/content/*` | `/demo/content/*` | Content fragments for shell.html |
2277
+ | `/sw.js` | `/demo/sw.js` | Service Worker |
2278
+ | `/manifest.json` | `/demo/manifest.json` | PWA manifest |
2279
+ | `/favicon.png` | `/demo/favicon.png` | Favicon |
2280
+ | `/-/health` | - | Health check endpoint |
2281
+ | `/-/clear-cache` | - | Clear browser cache |
2282
+
2283
+ ### Server Features
2284
+
2285
+ The `server.js` is optimized for **Lighthouse 100** scores:
2286
+
2287
+ 1. **Cache-Control Headers**
2288
+ - `1 year` for versioned assets (`/dist/`, `/assets/`)
2289
+ - `must-revalidate` for HTML (bf-cache friendly)
2290
+ - `1 hour` for other resources
2291
+
2292
+ 2. **Gzip Compression**
2293
+ - Auto-compresses `.html`, `.css`, `.js`, `.json`, `.svg`
2294
+
2295
+ 3. **Link Preload Headers**
2296
+ - Sends HTTP `Link` headers for critical resources
2297
+
2298
+ 4. **Security**
2299
+ - Directory traversal protection
2300
+ - `X-Content-Type-Options: nosniff`
2301
+
2302
+ ### Running Lighthouse Audits
2303
+ ```bash
2304
+ # Run the automated Lighthouse audit script
2305
+ source ~/.nvm/nvm.sh && nvm use 22
2306
+ node lighthouse-audit.mjs
2307
+
2308
+ # Output shows scores for shell.html
2309
+ # Results saved to /tmp/lighthouse-shell.json
2310
+ ```
2311
+
2312
+ ### Current Lighthouse Scores (v0.1.0)
2313
+
2314
+ | Page | Performance | Accessibility | Best Practices | SEO |
2315
+ |------|-------------|---------------|----------------|-----|
2316
+ | `shell.html` | 98 | 100 | 100 | 100 |
2317
+
2318
+ ### CLS Prevention Pattern
2319
+
2320
+ To prevent Cumulative Layout Shift from custom elements, pre-define dimensions:
2321
+
2322
+ ```css
2323
+ /* In <head> style block - REQUIRED for Lighthouse Performance */
2324
+ mu-bottom-nav:not(:defined) {
2325
+ display: block;
2326
+ position: fixed;
2327
+ bottom: 0;
2328
+ left: 0;
2329
+ right: 0;
2330
+ height: var(--mu-bottom-nav-height, 80px);
2331
+ z-index: 1000;
2332
+ }
2333
+ ```
2334
+
2335
+ ### Srcset for Retina Images
2336
+
2337
+ Always use srcset for hero images to pass "Serves images with low resolution":
2338
+
2339
+ ```html
2340
+ <picture>
2341
+ <source srcset="logo-400.webp 1x, logo-800.webp 2x" type="image/webp">
2342
+ <img src="logo-400.jpg" width="400" height="223" alt="Description">
2343
+ </picture>
2344
+
2345
+ <!-- Preload with imagesrcset -->
2346
+ <link rel="preload" as="image" type="image/webp"
2347
+ href="logo-400.webp"
2348
+ imagesrcset="logo-400.webp 1x, logo-800.webp 2x"
2349
+ fetchpriority="high">
2350
+ ```
2351
+
2352
+ ### Skip Link Anchors (Accessibility)
2353
+
2354
+ All hash links must have focusable targets:
2355
+
2356
+ ```html
2357
+ <!-- Before each page section, add anchor with tabindex -->
2358
+ <div id="installation" tabindex="-1" aria-hidden="true"></div>
2359
+ <div id="page-installation" class="page">
2360
+ <!-- Page content -->
2361
+ </div>
2362
+ ```
2363
+
2364
+ ---
2365
+
2366
+ *Last updated: v0.1.0 - 2026-02-02*