@invisibleloop/pulse 0.1.21

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 (344) hide show
  1. package/.claude/commands/build-page.md +59 -0
  2. package/.claude/commands/new-doc-page.md +45 -0
  3. package/.claude/commands/verify.md +52 -0
  4. package/.claude/pulse-checklist.md +111 -0
  5. package/.claude/settings.local.json +102 -0
  6. package/.github/workflows/ci.yml +22 -0
  7. package/.github/workflows/publish.yml +41 -0
  8. package/.pulse/load-reports/home/1773432711417.json +22 -0
  9. package/CLAUDE.md +383 -0
  10. package/README.md +95 -0
  11. package/docs/.claude/pulse-checklist.md +111 -0
  12. package/docs/public/.pulse-ui-version +1 -0
  13. package/docs/public/dist/accessibility.boot-5DVTARJU.js +115 -0
  14. package/docs/public/dist/actions.boot-P66HKQEM.js +164 -0
  15. package/docs/public/dist/auth.boot-IMAJAUPH.js +140 -0
  16. package/docs/public/dist/caching.boot-DVR6KDE7.js +53 -0
  17. package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +11 -0
  18. package/docs/public/dist/components--alert.boot-GCEXOZAC.js +6 -0
  19. package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +6 -0
  20. package/docs/public/dist/components--avatar.boot-PSW24EVA.js +5 -0
  21. package/docs/public/dist/components--badge.boot-TYDY2RMK.js +7 -0
  22. package/docs/public/dist/components--banner.boot-EI5PZSZK.js +7 -0
  23. package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +34 -0
  24. package/docs/public/dist/components--button.boot-J54BQM2E.js +23 -0
  25. package/docs/public/dist/components--card.boot-PZGNDIB6.js +138 -0
  26. package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +12 -0
  27. package/docs/public/dist/components--charts.boot-2EOYQWKL.js +108 -0
  28. package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +54 -0
  29. package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +9 -0
  30. package/docs/public/dist/components--code-window.boot-2GR2DV33.js +20 -0
  31. package/docs/public/dist/components--container.boot-7LOOGK2K.js +5 -0
  32. package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +11 -0
  33. package/docs/public/dist/components--divider.boot-3NI2C3QG.js +6 -0
  34. package/docs/public/dist/components--empty.boot-YX2UR3PV.js +7 -0
  35. package/docs/public/dist/components--feature.boot-MUD7NSUO.js +13 -0
  36. package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +19 -0
  37. package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +52 -0
  38. package/docs/public/dist/components--footer.boot-EYUK5FRG.js +14 -0
  39. package/docs/public/dist/components--grid.boot-URDQVDDR.js +59 -0
  40. package/docs/public/dist/components--heading.boot-BPQKU43E.js +44 -0
  41. package/docs/public/dist/components--hero.boot-4RAPRGAB.js +17 -0
  42. package/docs/public/dist/components--icons.boot-ZITNU5JP.js +68 -0
  43. package/docs/public/dist/components--image.boot-XEEGHQZF.js +19 -0
  44. package/docs/public/dist/components--input.boot-SGASZG5K.js +7 -0
  45. package/docs/public/dist/components--list.boot-W3XC5MHD.js +55 -0
  46. package/docs/public/dist/components--media.boot-5VFIETZO.js +13 -0
  47. package/docs/public/dist/components--modal.boot-RZUYXBN2.js +47 -0
  48. package/docs/public/dist/components--nav.boot-ODBOHU7O.js +33 -0
  49. package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +21 -0
  50. package/docs/public/dist/components--progress.boot-GHAGYZOK.js +30 -0
  51. package/docs/public/dist/components--prose.boot-QANJL6JI.js +67 -0
  52. package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +22 -0
  53. package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +75 -0
  54. package/docs/public/dist/components--rating.boot-QBAN6DEL.js +38 -0
  55. package/docs/public/dist/components--search.boot-PXH5O5AG.js +17 -0
  56. package/docs/public/dist/components--section.boot-AQGIYHWW.js +12 -0
  57. package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +33 -0
  58. package/docs/public/dist/components--select.boot-47X5RHOC.js +10 -0
  59. package/docs/public/dist/components--slider.boot-PSRRX7XL.js +47 -0
  60. package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +22 -0
  61. package/docs/public/dist/components--stack.boot-DI4NJXBF.js +9 -0
  62. package/docs/public/dist/components--stat.boot-QMFUWBQT.js +9 -0
  63. package/docs/public/dist/components--stepper.boot-34PP2NEV.js +22 -0
  64. package/docs/public/dist/components--table.boot-FCQGSFIQ.js +11 -0
  65. package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +11 -0
  66. package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +4 -0
  67. package/docs/public/dist/components--timeline.boot-26LN52P2.js +95 -0
  68. package/docs/public/dist/components--toggle.boot-IQQEI76S.js +29 -0
  69. package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +9 -0
  70. package/docs/public/dist/components.boot-SE6PQ4P7.js +103 -0
  71. package/docs/public/dist/config.boot-DTRRWUE6.js +126 -0
  72. package/docs/public/dist/constraints.boot-DUHDZBMC.js +71 -0
  73. package/docs/public/dist/deploy.boot-SLAD3NI2.js +163 -0
  74. package/docs/public/dist/docs-8e3d4b5c.css +1 -0
  75. package/docs/public/dist/extending.boot-UA3CN243.js +159 -0
  76. package/docs/public/dist/faq.boot-6EQAWLQR.js +43 -0
  77. package/docs/public/dist/getting-started.boot-TDKIFL5U.js +86 -0
  78. package/docs/public/dist/guard.boot-AUHAWTG4.js +80 -0
  79. package/docs/public/dist/home.boot-BVQXRH32.js +383 -0
  80. package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +104 -0
  81. package/docs/public/dist/hydration.boot-JRM6IPJL.js +78 -0
  82. package/docs/public/dist/images.boot-M6ZVKTZS.js +80 -0
  83. package/docs/public/dist/manifest.json +94 -0
  84. package/docs/public/dist/meta.boot-7NXGPHR4.js +79 -0
  85. package/docs/public/dist/mutations.boot-F6F43UDX.js +79 -0
  86. package/docs/public/dist/navigation.boot-AOXWS3ZF.js +57 -0
  87. package/docs/public/dist/performance.boot-C3UPCOBK.js +98 -0
  88. package/docs/public/dist/persist.boot-WT32PQOQ.js +61 -0
  89. package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +63 -0
  90. package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +31 -0
  91. package/docs/public/dist/pulse-ui-81a85c03.css +1 -0
  92. package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +104 -0
  93. package/docs/public/dist/routing.boot-FNX5FDGH.js +70 -0
  94. package/docs/public/dist/runtime-B73WLANC.js +1 -0
  95. package/docs/public/dist/runtime-KO4BHUQ3.js +49 -0
  96. package/docs/public/dist/runtime-L2HNXIHW.js +59 -0
  97. package/docs/public/dist/runtime-QFURDKA2.js +5 -0
  98. package/docs/public/dist/runtime-UVPXO4IR.js +375 -0
  99. package/docs/public/dist/runtime-VMJA3Z4N.js +10 -0
  100. package/docs/public/dist/runtime-ZJ4FXT5O.js +11 -0
  101. package/docs/public/dist/server-api.boot-K7X3LCFB.js +219 -0
  102. package/docs/public/dist/server-data.boot-Y7HQYC4R.js +157 -0
  103. package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +26 -0
  104. package/docs/public/dist/spec.boot-2WU7ZHCV.js +159 -0
  105. package/docs/public/dist/state.boot-B24GUE3R.js +73 -0
  106. package/docs/public/dist/store.boot-TLIB4XHH.js +150 -0
  107. package/docs/public/dist/streaming.boot-W2DZSMW4.js +80 -0
  108. package/docs/public/dist/stripe.boot-QN3C2GEL.js +164 -0
  109. package/docs/public/dist/supabase.boot-BG4XXLZE.js +303 -0
  110. package/docs/public/dist/testing.boot-6U4WKMTE.js +130 -0
  111. package/docs/public/dist/validation.boot-PQHYGW5B.js +100 -0
  112. package/docs/public/docs.css +2020 -0
  113. package/docs/public/menu.js +83 -0
  114. package/docs/public/pulse-ui.css +2739 -0
  115. package/docs/public/pulse-ui.js +236 -0
  116. package/docs/server.js +192 -0
  117. package/docs/src/lib/component-page.js +47 -0
  118. package/docs/src/lib/highlight.js +255 -0
  119. package/docs/src/lib/layout.js +131 -0
  120. package/docs/src/lib/metrics-store.js +6 -0
  121. package/docs/src/lib/nav.js +159 -0
  122. package/docs/src/lib/stats.js +81 -0
  123. package/docs/src/pages/accessibility.js +157 -0
  124. package/docs/src/pages/actions.js +191 -0
  125. package/docs/src/pages/auth.js +177 -0
  126. package/docs/src/pages/caching.js +95 -0
  127. package/docs/src/pages/components/accordion.js +48 -0
  128. package/docs/src/pages/components/alert.js +35 -0
  129. package/docs/src/pages/components/app-badge.js +41 -0
  130. package/docs/src/pages/components/avatar.js +35 -0
  131. package/docs/src/pages/components/badge.js +36 -0
  132. package/docs/src/pages/components/banner.js +45 -0
  133. package/docs/src/pages/components/breadcrumbs.js +94 -0
  134. package/docs/src/pages/components/button.js +84 -0
  135. package/docs/src/pages/components/card.js +225 -0
  136. package/docs/src/pages/components/carousel.js +72 -0
  137. package/docs/src/pages/components/charts.js +278 -0
  138. package/docs/src/pages/components/checkbox.js +129 -0
  139. package/docs/src/pages/components/cluster.js +47 -0
  140. package/docs/src/pages/components/code-window.js +57 -0
  141. package/docs/src/pages/components/container.js +40 -0
  142. package/docs/src/pages/components/cta.js +53 -0
  143. package/docs/src/pages/components/divider.js +37 -0
  144. package/docs/src/pages/components/empty.js +36 -0
  145. package/docs/src/pages/components/feature.js +60 -0
  146. package/docs/src/pages/components/fieldset.js +65 -0
  147. package/docs/src/pages/components/fileupload.js +127 -0
  148. package/docs/src/pages/components/footer.js +58 -0
  149. package/docs/src/pages/components/grid.js +165 -0
  150. package/docs/src/pages/components/heading.js +107 -0
  151. package/docs/src/pages/components/hero.js +65 -0
  152. package/docs/src/pages/components/icons.js +285 -0
  153. package/docs/src/pages/components/image.js +71 -0
  154. package/docs/src/pages/components/input.js +51 -0
  155. package/docs/src/pages/components/list.js +112 -0
  156. package/docs/src/pages/components/media.js +51 -0
  157. package/docs/src/pages/components/modal.js +111 -0
  158. package/docs/src/pages/components/nav.js +86 -0
  159. package/docs/src/pages/components/pricing.js +68 -0
  160. package/docs/src/pages/components/progress.js +102 -0
  161. package/docs/src/pages/components/prose.js +111 -0
  162. package/docs/src/pages/components/pullquote.js +71 -0
  163. package/docs/src/pages/components/radio.js +194 -0
  164. package/docs/src/pages/components/rating.js +106 -0
  165. package/docs/src/pages/components/search.js +61 -0
  166. package/docs/src/pages/components/section.js +59 -0
  167. package/docs/src/pages/components/segmented.js +121 -0
  168. package/docs/src/pages/components/select.js +45 -0
  169. package/docs/src/pages/components/slider.js +114 -0
  170. package/docs/src/pages/components/spinner.js +73 -0
  171. package/docs/src/pages/components/stack.js +48 -0
  172. package/docs/src/pages/components/stat.js +55 -0
  173. package/docs/src/pages/components/stepper.js +66 -0
  174. package/docs/src/pages/components/table.js +45 -0
  175. package/docs/src/pages/components/testimonial.js +49 -0
  176. package/docs/src/pages/components/textarea.js +31 -0
  177. package/docs/src/pages/components/timeline.js +227 -0
  178. package/docs/src/pages/components/toggle.js +84 -0
  179. package/docs/src/pages/components/tooltip.js +48 -0
  180. package/docs/src/pages/components.js +204 -0
  181. package/docs/src/pages/config.js +193 -0
  182. package/docs/src/pages/constraints.js +99 -0
  183. package/docs/src/pages/deploy.js +233 -0
  184. package/docs/src/pages/extending.js +198 -0
  185. package/docs/src/pages/faq.js +96 -0
  186. package/docs/src/pages/getting-started.js +106 -0
  187. package/docs/src/pages/guard.js +121 -0
  188. package/docs/src/pages/home.js +401 -0
  189. package/docs/src/pages/how-it-works.js +183 -0
  190. package/docs/src/pages/hydration.js +98 -0
  191. package/docs/src/pages/images.js +121 -0
  192. package/docs/src/pages/meta.js +120 -0
  193. package/docs/src/pages/mutations.js +106 -0
  194. package/docs/src/pages/navigation.js +85 -0
  195. package/docs/src/pages/performance.js +157 -0
  196. package/docs/src/pages/persist.js +88 -0
  197. package/docs/src/pages/project-structure.js +90 -0
  198. package/docs/src/pages/prompt-examples.js +186 -0
  199. package/docs/src/pages/raw-responses.js +124 -0
  200. package/docs/src/pages/routing.js +99 -0
  201. package/docs/src/pages/server-api.js +281 -0
  202. package/docs/src/pages/server-data.js +185 -0
  203. package/docs/src/pages/slash-commands.js +55 -0
  204. package/docs/src/pages/spec.js +207 -0
  205. package/docs/src/pages/state.js +101 -0
  206. package/docs/src/pages/store.js +181 -0
  207. package/docs/src/pages/streaming.js +108 -0
  208. package/docs/src/pages/stripe.js +193 -0
  209. package/docs/src/pages/supabase.js +323 -0
  210. package/docs/src/pages/testing.js +198 -0
  211. package/docs/src/pages/validation.js +138 -0
  212. package/examples/contact.js +166 -0
  213. package/examples/counter.js +94 -0
  214. package/examples/dev.server.js +91 -0
  215. package/examples/examples.test.js +394 -0
  216. package/examples/pricing.js +244 -0
  217. package/examples/products.js +191 -0
  218. package/examples/quiz.js +208 -0
  219. package/examples/shared.js +78 -0
  220. package/examples/todos.js +162 -0
  221. package/package.json +75 -0
  222. package/public/.pulse-ui-version +1 -0
  223. package/public/chippy-bird.css +246 -0
  224. package/public/examples/contact.css +119 -0
  225. package/public/examples/counter.css +79 -0
  226. package/public/examples/pricing.css +132 -0
  227. package/public/examples/products.css +100 -0
  228. package/public/examples/quiz.css +200 -0
  229. package/public/examples/todos.css +137 -0
  230. package/public/favicon.ico +0 -0
  231. package/public/log-dashboard.css +383 -0
  232. package/public/pulse-ui.css +2740 -0
  233. package/public/pulse-ui.js +236 -0
  234. package/public/pulse.css +149 -0
  235. package/scripts/build.js +411 -0
  236. package/src/agent/checklist.md +111 -0
  237. package/src/agent/coverage-check.js +66 -0
  238. package/src/agent/guide-components.md +274 -0
  239. package/src/agent/guide-examples.md +54 -0
  240. package/src/agent/guide-routing.md +36 -0
  241. package/src/agent/guide-server.md +258 -0
  242. package/src/agent/guide-spec.md +103 -0
  243. package/src/agent/guide-styles.md +191 -0
  244. package/src/agent/guide.md +979 -0
  245. package/src/agent/identity.md +106 -0
  246. package/src/agent/workflow.md +108 -0
  247. package/src/cli/cli.test.js +82 -0
  248. package/src/cli/dev.js +195 -0
  249. package/src/cli/discover.js +113 -0
  250. package/src/cli/index.js +361 -0
  251. package/src/cli/load-report.js +91 -0
  252. package/src/cli/load-runner.js +121 -0
  253. package/src/cli/report-server.js +723 -0
  254. package/src/cli/report.js +116 -0
  255. package/src/cli/scaffold.archive.js +1371 -0
  256. package/src/cli/scaffold.js +349 -0
  257. package/src/cli/start.js +74 -0
  258. package/src/html.js +19 -0
  259. package/src/mcp/server.js +884 -0
  260. package/src/mcp/validate-worker.js +110 -0
  261. package/src/runtime/image.js +74 -0
  262. package/src/runtime/image.test.js +111 -0
  263. package/src/runtime/index.js +621 -0
  264. package/src/runtime/navigate.js +146 -0
  265. package/src/runtime/runtime.test.js +773 -0
  266. package/src/runtime/ssr.js +464 -0
  267. package/src/runtime/ssr.test.js +421 -0
  268. package/src/runtime/store.js +92 -0
  269. package/src/runtime/toast.js +163 -0
  270. package/src/server/index.js +1386 -0
  271. package/src/server/server.test.js +1248 -0
  272. package/src/spec/schema.js +428 -0
  273. package/src/spec/schema.test.js +291 -0
  274. package/src/store/index.js +102 -0
  275. package/src/store/store.test.js +210 -0
  276. package/src/testing/html.js +283 -0
  277. package/src/testing/index.js +249 -0
  278. package/src/testing/testing.test.js +450 -0
  279. package/src/ui/accordion.js +28 -0
  280. package/src/ui/alert.js +43 -0
  281. package/src/ui/app-badge.js +48 -0
  282. package/src/ui/avatar.js +47 -0
  283. package/src/ui/badge.js +24 -0
  284. package/src/ui/banner.js +26 -0
  285. package/src/ui/breadcrumbs.js +38 -0
  286. package/src/ui/button.js +66 -0
  287. package/src/ui/card.js +34 -0
  288. package/src/ui/carousel.js +59 -0
  289. package/src/ui/charts.js +321 -0
  290. package/src/ui/checkbox.js +65 -0
  291. package/src/ui/cluster.js +44 -0
  292. package/src/ui/code-window.js +39 -0
  293. package/src/ui/container.js +24 -0
  294. package/src/ui/cta.js +37 -0
  295. package/src/ui/divider.js +29 -0
  296. package/src/ui/empty.js +33 -0
  297. package/src/ui/feature.js +33 -0
  298. package/src/ui/fieldset.js +37 -0
  299. package/src/ui/fileupload.js +89 -0
  300. package/src/ui/footer.js +38 -0
  301. package/src/ui/grid.js +36 -0
  302. package/src/ui/heading.js +45 -0
  303. package/src/ui/hero.js +37 -0
  304. package/src/ui/icons.js +161 -0
  305. package/src/ui/index.js +89 -0
  306. package/src/ui/input.js +74 -0
  307. package/src/ui/list.js +36 -0
  308. package/src/ui/media.js +44 -0
  309. package/src/ui/modal.js +80 -0
  310. package/src/ui/nav.js +61 -0
  311. package/src/ui/pricing.js +56 -0
  312. package/src/ui/progress.js +62 -0
  313. package/src/ui/prose.js +29 -0
  314. package/src/ui/pullquote.js +34 -0
  315. package/src/ui/radio.js +102 -0
  316. package/src/ui/rating.js +93 -0
  317. package/src/ui/search.js +77 -0
  318. package/src/ui/section.js +69 -0
  319. package/src/ui/segmented.js +50 -0
  320. package/src/ui/select.js +77 -0
  321. package/src/ui/slider.js +84 -0
  322. package/src/ui/spinner.js +34 -0
  323. package/src/ui/stack.js +36 -0
  324. package/src/ui/stat.js +52 -0
  325. package/src/ui/stepper.js +46 -0
  326. package/src/ui/switch.js +57 -0
  327. package/src/ui/table.js +45 -0
  328. package/src/ui/testimonial.js +48 -0
  329. package/src/ui/textarea.js +72 -0
  330. package/src/ui/timeline.js +72 -0
  331. package/src/ui/tooltip.js +28 -0
  332. package/src/ui/ui.test.js +1241 -0
  333. package/src/ui/uiimage.js +65 -0
  334. package/tsconfig.json +13 -0
  335. package/types/html.d.ts +17 -0
  336. package/types/image.d.ts +70 -0
  337. package/types/index.d.ts +7 -0
  338. package/types/navigate.d.ts +38 -0
  339. package/types/runtime.d.ts +63 -0
  340. package/types/schema.d.ts +243 -0
  341. package/types/server.d.ts +145 -0
  342. package/types/ssr.d.ts +110 -0
  343. package/types/testing.d.ts +154 -0
  344. package/types/ui.d.ts +704 -0
@@ -0,0 +1,59 @@
1
+ # Build Page
2
+
3
+ Scaffold a new Pulse page spec, validate it, and visually confirm it renders correctly.
4
+
5
+ ## What to build
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps — follow in order
10
+
11
+ ### 1. Check existing components
12
+
13
+ Read `src/ui/index.js` to see what UI components are available before writing any HTML. Note which components apply to this page.
14
+
15
+ ### 2. Plan
16
+
17
+ State before writing any files:
18
+ - The route (e.g. `/dashboard`)
19
+ - The file path (e.g. `src/pages/dashboard.js`)
20
+ - State shape
21
+ - Mutations and/or actions needed
22
+ - Which UI components will be used
23
+ - Whether `server` data fetchers are needed
24
+ - Whether `hydrate` is required (yes, if any mutations/actions)
25
+
26
+ ### 3. Build the spec
27
+
28
+ Write the spec file. Follow these rules:
29
+ - Use `class` not `className`, template literals not JSX
30
+ - No React imports, hooks, or patterns — see CLAUDE.md "Pulse vs React" table
31
+ - Use components from `src/ui/` — do not hand-write HTML that a component already covers
32
+ - Include `hydrate` if the spec has mutations or actions
33
+ - Use optional chaining (`?.`) on any server data that could be null
34
+ - Escape any user-supplied values interpolated into HTML
35
+
36
+ ### 4. Validate
37
+
38
+ Use `pulse_validate` on the spec. Fix any errors before continuing.
39
+
40
+ ### 5. Register with the server
41
+
42
+ Check the project's server entry file (e.g. `src/server.js` or `pulse.config.js`) and add the new spec if needed.
43
+
44
+ ### 6. Screenshot
45
+
46
+ Use `mcp__chrome-devtools__navigate_page` to load the page, then `mcp__chrome-devtools__take_screenshot`.
47
+
48
+ Check for:
49
+ - Page renders without a blank screen or error
50
+ - Layout looks correct
51
+ - No console errors via `mcp__chrome-devtools__list_console_messages`
52
+
53
+ ### 7. Run the checklist
54
+
55
+ Go through `src/agent/checklist.md` and confirm every applicable point passes. Fix anything that fails.
56
+
57
+ ### 8. Report
58
+
59
+ Summarise what was built, what components were used, and confirm validation + visual check passed.
@@ -0,0 +1,45 @@
1
+ # New Docs Page
2
+
3
+ Create a new documentation page for the Pulse docs site.
4
+
5
+ ## What to build
6
+
7
+ $ARGUMENTS
8
+
9
+ ## Steps — follow in order
10
+
11
+ ### 1. Research
12
+ - Read `docs/src/lib/nav.js` to understand the full nav structure and find where the new page fits
13
+ - Read 2–3 adjacent pages (the pages that will be `prev`/`next` neighbours) to match tone, section depth, and helper usage (`h1`, `lead`, `section`, `sub`, `codeBlock`, `callout`, `table`)
14
+ - Read `docs/src/lib/layout.js` exported functions to confirm available helpers
15
+ - If the feature being documented has a source file (e.g. in `src/`), read it to ensure accuracy
16
+
17
+ ### 2. Plan
18
+ Before writing any files, state:
19
+ - The route (e.g. `/request-bodies`)
20
+ - Where it sits in the nav (section, between which two pages)
21
+ - Sections the page will cover (IDs + titles)
22
+ - Any doc pages that need updating to link to or mention this page
23
+
24
+ Wait for approval if anything is uncertain.
25
+
26
+ ### 3. Implement
27
+
28
+ **a) Write the page file** at `docs/src/pages/<name>.js`:
29
+ - Import from `../lib/layout.js`, `../lib/nav.js`, `../lib/highlight.js`
30
+ - Use `prevNext('/your-route')` for prev/next links
31
+ - Export a valid Pulse spec as `export default { route, meta, state, view }`
32
+ - `meta.styles` must be `['/docs.css']`
33
+ - Use `renderLayout({ currentHref, prev, next, content })` for the page body
34
+ - All code examples must use `${codeBlock(highlight(\`...\`, 'js'))}` — never raw `<pre>`
35
+ - Tables use `${table(['Col1', 'Col2'], [['val', 'val']])}`
36
+ - Callouts use `${callout('note|tip|warning|info', 'text')}`
37
+ - Section anchors use `${section('anchor-id', 'Section Title')}`
38
+
39
+ **b) Register in nav** — add the entry to `docs/src/lib/nav.js` in the correct position
40
+
41
+ **c) Verify syntax** — run `node --check docs/src/pages/<name>.js`
42
+
43
+ **d) Check neighbours** — read the prev and next pages. If they reference topics this new page covers, add a cross-link.
44
+
45
+ **e) Final check** — confirm the new route appears in `prevNext` output for its neighbours (the nav order drives prev/next automatically, so adding to NAV is sufficient).
@@ -0,0 +1,52 @@
1
+ # Verify
2
+
3
+ Run the full verification loop on the current page or the page specified in $ARGUMENTS.
4
+
5
+ ## Steps
6
+
7
+ ### 1. Identify the target
8
+
9
+ If $ARGUMENTS is provided, use it as the file path or route. Otherwise, identify the most recently edited spec file from context.
10
+
11
+ ### 2. Validate the spec
12
+
13
+ Use `pulse_validate` with the spec file content. If validation fails, report every error clearly and stop — do not proceed to screenshot until the spec is valid.
14
+
15
+ ### 3. Check the dev server
16
+
17
+ Use `pulse_fetch_page` to confirm the server is responding for the route. If it errors, use `pulse_restart_server` and retry once.
18
+
19
+ ### 4. Screenshot
20
+
21
+ Use `mcp__chrome-devtools__navigate_page` to load the page route, then `mcp__chrome-devtools__take_screenshot` to capture the result. Describe what you see — layout, content, any obvious rendering issues.
22
+
23
+ ### 5. Lighthouse — desktop
24
+
25
+ Run `mcp__chrome-devtools__lighthouse_audit` on the route with `{ "strategy": "desktop" }`.
26
+
27
+ **Pass bar: Performance, Accessibility, Best Practices, and SEO must all be 100.** Report the actual scores. If any score is below 100, identify the failing audit(s), fix the issue, and restart from step 2.
28
+
29
+ ### 6. Lighthouse — mobile
30
+
31
+ Run `mcp__chrome-devtools__lighthouse_audit` on the same route with `{ "strategy": "mobile" }`.
32
+
33
+ **Same pass bar: all four categories must be 100.** Report the actual scores. If any score is below 100, fix and restart from step 2.
34
+
35
+ ### 7. Console errors
36
+
37
+ Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
38
+
39
+ ### 8. Close the browser
40
+
41
+ Use `mcp__chrome-devtools__list_pages` to get all open pages, then call `mcp__chrome-devtools__close_page` for every page ID returned. This closes the entire browser, not just the tab. `pageId` must be a number, not a string.
42
+
43
+ ### 9. Report
44
+
45
+ Summarise:
46
+ - Validation: pass or fail (with errors if any)
47
+ - Visual: what was visible in the screenshot
48
+ - Lighthouse desktop: scores for all four categories
49
+ - Lighthouse mobile: scores for all four categories
50
+ - Console: any errors
51
+
52
+ Only confirm the page is good when validation passes, both Lighthouse runs are 100/100/100/100, and there are no console errors. Otherwise, fix and run `/verify` again.
@@ -0,0 +1,111 @@
1
+ ## Spec review checklist
2
+
3
+ Before finishing any spec, verify every point below. Fix anything that fails.
4
+
5
+ ### Critical
6
+
7
+ - **`hydrate` is set on every interactive page.** Without it, `data-event` / `data-action` bindings do nothing, `persist` never runs, and client-side navigation cannot re-mount the page. Every spec with `mutations`, `actions`, or `persist` must include:
8
+ ```js
9
+ hydrate: '/src/pages/my-page.js', // browser-importable path to this file
10
+ ```
11
+ Omit `hydrate` only for purely server-rendered pages with zero client interactivity.
12
+
13
+ ### Components first
14
+
15
+ - **Before writing any HTML by hand, check `src/ui/index.js`.** There are 50+ components. Use `button`, `card`, `alert`, `input`, `spinner`, `badge`, `modal`, `nav`, `pagination`, `table`, etc. before writing equivalent HTML from scratch.
16
+
17
+ ### Reuse (DRY)
18
+
19
+ - **Extract a view helper when the same HTML pattern appears 3 or more times in a single spec.** A plain JS function returning an HTML string is sufficient — no framework needed:
20
+ ```js
21
+ const card = ({ title, body }) => `<div class="card"><h3>${title}</h3><p>${body}</p></div>`
22
+ ```
23
+ - **Create a shared component in `src/ui/` when the same pattern is needed across 2 or more different specs.** Follow the existing pattern: a named export that returns an HTML string.
24
+ - **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction. Wait until the third use before extracting.
25
+
26
+ ### Correctness
27
+
28
+ - Mutations return plain partial-state objects and have no side effects (no fetch, no DOM access).
29
+ - `persist` contains only serialisable state that should survive a page reload — not ephemeral UI state like a loading flag or temporary selection.
30
+ - `e.target` assumptions in mutations are safe if the element has child nodes — use `e.target.closest('[data-index]')` rather than assuming `e.target` is the element with the attribute.
31
+ - State shape is consistent — avoid a single field that is sometimes `null`, sometimes a string, sometimes a boolean. Use a dedicated `status` field instead.
32
+ - **Never use `state.modalOpen` or conditional modal rendering.** This destroys the `<dialog>` on every render, breaking focus, animation, and native ESC handling. Instead, always render the `<dialog>` in the DOM and use `data-dialog-open="id"` to open it — the runtime handles this without any spec state:
33
+ ```html
34
+ <!-- always in the view, never conditional -->
35
+ ${modal({ id: 'confirm', title: 'Confirm', content: '...' })}
36
+ <!-- anywhere on the page — opens the dialog, no mutation needed -->
37
+ ${modalTrigger({ target: 'confirm', label: 'Open' })}
38
+ <!-- or inline: -->
39
+ <button data-dialog-open="confirm">Open</button>
40
+ ```
41
+ Close is handled natively by `<form method="dialog">` (inside the modal), ESC key, backdrop click, or `data-dialog-close` on any element.
42
+
43
+ ### Defensive data handling
44
+
45
+ - **Always check `res.ok` before parsing fetch responses.** Never call `res.json()` on a response that may have failed. Never use `await res.text()` as an error message — on a 404 or 500, this returns raw HTML which surfaces directly in toasts and error alerts. The correct pattern:
46
+ ```js
47
+ const res = await fetch('/api/...')
48
+ if (!res.ok) {
49
+ let message = `Request failed: ${res.status}`
50
+ try { const j = await res.json(); message = j.message || j.error || message } catch {}
51
+ throw new Error(message)
52
+ }
53
+ return await res.json()
54
+ ```
55
+ - **Never assume the shape of data from external APIs or server fetchers.** Use optional chaining (`?.`) and nullish coalescing (`??`) at every access point. If a server fetcher can return `null`, the view must handle it — `server.user?.name ?? 'Guest'` not `server.user.name`.
56
+ - **Validate FormData fields before use.** `formData.get('email')` returns `null` if the field is missing. Check for null/empty before passing to an API or database.
57
+ - **Do not trust URL params.** `ctx.params.id` is a raw string from the URL. Validate it before use — check it exists, is the right type, and refers to a real resource. Return a 404 or redirect if it doesn't.
58
+
59
+ ### Security
60
+
61
+ - Any value from user input (URL params, form fields, external APIs) interpolated into view HTML must be escaped.
62
+
63
+ ### Tests
64
+
65
+ - Pure logic functions extracted from a spec (e.g. `checkResult`, `validate`, `formatPrice`) must have unit tests in a corresponding `.test.js` file.
66
+ - **When fixing a bug, write a failing test first.** The test must reproduce the bug before the fix is applied, then pass after. This pins the behaviour so the bug cannot silently return. A fix without a regression test is incomplete.
67
+ - **Use `renderSync` / `render` from `@invisibleloop/pulse/testing` to test view HTML output.** Do not test views with raw `html.includes()` — use the query helpers instead:
68
+ ```js
69
+ import { renderSync, render } from '@invisibleloop/pulse/testing'
70
+
71
+ // Sync — call view directly with mock state/server
72
+ const result = renderSync(mySpec, { state: { count: 5 }, server: { items: [] } })
73
+ assert(result.has('button'))
74
+ assert.equal(result.get('#count').text, '5')
75
+ assert.equal(result.count('li'), 0)
76
+
77
+ // Async — run real server fetchers (integration), or pass server to skip them
78
+ const result = await render(mySpec, { server: { product: mockProduct } })
79
+ assert.equal(result.get('h1').text, mockProduct.name)
80
+ assert.equal(result.attr('img', 'src'), mockProduct.image)
81
+ ```
82
+ Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`, and combinations (`button.primary[disabled]`).
83
+
84
+ ### View error handling
85
+
86
+ - **Define `onViewError` on any page where the view could throw due to bad or missing data.** Without it, a runtime view error returns a 500 on the server and shows a generic inline message on the client. With it, the server returns 200 with your fallback HTML instead of a 500, and the client renders your fallback:
87
+ ```js
88
+ onViewError: (err, state, serverState) => `
89
+ <div class="u-p-4 u-text-center">
90
+ <p>Something went wrong. <a href="">Reload</a></p>
91
+ </div>
92
+ `
93
+ ```
94
+ Use this on pages that render data from external APIs or user-supplied content — any path where the view can encounter `null`, `undefined`, or unexpected shapes that would cause a crash. It is not required on simple pages with predictable data.
95
+
96
+ ### Store updates from actions
97
+
98
+ - **Use `_storeUpdate` to push changes to the global store from an action.** Return it from `onSuccess` alongside the local state update — it is stripped from page state and forwarded to the store. All mounted pages that subscribe to the affected keys re-render immediately:
99
+ ```js
100
+ onSuccess: (state, theme) => ({
101
+ saved: true,
102
+ _storeUpdate: { settings: { theme } }, // ← merged into store state
103
+ }),
104
+ ```
105
+ `_storeUpdate` only merges into the store — it does not appear in the page's own state. The rest of the return is merged into local state as normal. Use this instead of a full-page reload when a user action changes shared data (theme, cart count, user profile, etc.).
106
+
107
+ ### Accessibility
108
+
109
+ - Interactive elements without visible text have an `aria-label`.
110
+ - Disabled state is reflected with the `disabled` attribute, not just CSS.
111
+ - The page has a `<main id="main-content">` landmark.
@@ -0,0 +1,102 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "mcp__chrome-devtools__navigate_page",
5
+ "mcp__chrome-devtools__evaluate_script",
6
+ "Bash(grep:*)",
7
+ "mcp__chrome-devtools__list_console_messages",
8
+ "mcp__chrome-devtools__take_screenshot",
9
+ "Bash(pkill -f \"docs/server.js\")",
10
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:4000/)",
11
+ "Bash(node:*)",
12
+ "Bash(for f:*)",
13
+ "Bash(do echo:*)",
14
+ "Read(//Users/andy.stubbs/Repos/AS/pulse2/src/ui/**)",
15
+ "Bash(/dev/null done:*)",
16
+ "Bash(mkdir -p /Users/andy.stubbs/Repos/AS/agent-test/.claude)",
17
+ "Bash(mv /Users/andy.stubbs/Repos/AS/agent-test/CLAUDE.md /Users/andy.stubbs/Repos/AS/agent-test/.claude/CLAUDE.md)",
18
+ "Read(//Users/andy.stubbs/Repos/AS/agent-test-3/public/**)",
19
+ "Bash(ls /Users/andy.stubbs/Repos/AS/pulse2/public/*.css /Users/andy.stubbs/Repos/AS/pulse2/public/*.js)",
20
+ "Bash(cp /Users/andy.stubbs/Repos/AS/pulse2/public/pulse-ui.css /Users/andy.stubbs/Repos/AS/agent-test-3/public/pulse-ui.css)",
21
+ "Bash(cp /Users/andy.stubbs/Repos/AS/pulse2/public/pulse-ui.js /Users/andy.stubbs/Repos/AS/agent-test-3/public/pulse-ui.js)",
22
+ "Bash(cp /Users/andy.stubbs/Repos/AS/pulse2/public/pulse-ui.css /Users/andy.stubbs/Repos/AS/agent-test/public/pulse-ui.css)",
23
+ "Bash(cp /Users/andy.stubbs/Repos/AS/pulse2/public/pulse-ui.css /Users/andy.stubbs/Repos/AS/my-amazing-pulse-project/public/pulse-ui.css)",
24
+ "Bash(npm test:*)",
25
+ "Bash(npm run:*)",
26
+ "Bash(git config:*)",
27
+ "Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:4000)",
28
+ "Bash(curl:*)",
29
+ "Bash(xargs kill:*)",
30
+ "mcp__chrome-devtools__lighthouse_audit",
31
+ "Bash(git add:*)",
32
+ "Bash(git commit:*)",
33
+ "Bash(git push:*)",
34
+ "Bash(git pull:*)",
35
+ "Bash(git stash:*)",
36
+ "Bash(__NEW_LINE_9ac7abf2e7b711bb__ node -e \":*)",
37
+ "Bash(python3:*)",
38
+ "mcp__chrome-devtools__list_pages",
39
+ "mcp__chrome-devtools__emulate",
40
+ "Bash(npx tsc:*)",
41
+ "Bash(find /Users/andy.stubbs/.claude -name *.json)",
42
+ "Bash(ls /Users/andy.stubbs/Repos/AS/pulse2/src/**/*.test.js)",
43
+ "mcp__chrome-devtools__list_network_requests",
44
+ "mcp__chrome-devtools__get_network_request",
45
+ "Bash(find /Users/andy.stubbs/Repos/AS/forge -name *.md -o -name *.txt)",
46
+ "Bash(node_modules/.bin/tsc --noEmit)",
47
+ "Bash(/Users/andy.stubbs/Repos/AS/forge/node_modules/.bin/tsc --noEmit --project /Users/andy.stubbs/Repos/AS/forge/tsconfig.json)",
48
+ "WebFetch(domain:localhost)",
49
+ "Skill(update-config)",
50
+ "Bash(jq:*)",
51
+ "Bash(ls /Users/andy.stubbs/Repos/AS/tasty/.claude/ grep -r \"pulse-dev\\\\|pulse-stop\\\\|pulse-build\\\\|pulse-start\\\\|Pulse project\" /Users/andy.stubbs/Repos/AS/tasty/.claude/ 2)",
52
+ "Bash(/dev/null grep:*)",
53
+ "Bash(ls -1 /Users/andy.stubbs/Repos/AS/pulse2/src/ui/*.js)",
54
+ "Bash(ls /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/*.js)",
55
+ "Bash(wc -c /Users/andy.stubbs/Repos/AS/pulse2/src/agent/guide-*.md)",
56
+ "Bash(npx:*)",
57
+ "Bash(npm install:*)",
58
+ "Bash(./node_modules/.bin/tsc --noEmit --strict --moduleResolution bundler --module esnext --target esnext --allowJs false --skipLibCheck false types/schema.d.ts types/server.d.ts types/runtime.d.ts types/navigate.d.ts types/ssr.d.ts types/html.d.ts types/image.d.ts types/ui.d.ts types/index.d.ts 2>&1)",
59
+ "Bash(./node_modules/.bin/tsc --noEmit --strict --moduleResolution bundler --module esnext --target esnext --allowJs false --lib esnext,dom types/schema.d.ts types/server.d.ts types/runtime.d.ts types/navigate.d.ts types/ssr.d.ts types/html.d.ts types/image.d.ts types/ui.d.ts types/index.d.ts 2>&1)",
60
+ "Bash(/Users/andy.stubbs/Repos/AS/pulse2/node_modules/.bin/tsc -p /tmp/tsconfig_test.json 2>&1)",
61
+ "Bash(find /Users/andy.stubbs/Repos/AS/pulse2 -name *.test.js -type f)",
62
+ "Bash(wc:*)",
63
+ "Bash(ls -1 *.js)",
64
+ "Bash(grep -l \"<h[2-6]\" /Users/andy.stubbs/Repos/AS/pulse2/src/ui/*.js)",
65
+ "Bash(node --input-type=module --eval \"import ''''/Users/andy.stubbs/Repos/AS/pulse2/src/cli/scaffold.js''''\")",
66
+ "Bash(head -10 grep -n \"level\\\\|heading\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/section.js)",
67
+ "Bash(head -10 grep -n \"level\\\\|heading\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/modal.js)",
68
+ "Bash(head -10 grep -n \"table\\(\\\\|''''Prop''''\\\\|flush\\\\|center\\\\|highlighted\\\\|eyebrow\\\\|align\\\\|size.*md\\\\|ui-modal\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/modal.js)",
69
+ "Bash(head -10 grep -n \"table\\(\\\\|''''Prop''''\\\\|eyebrow\\\\|padding\\\\|variant\\\\|subtitle\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/section.js)",
70
+ "Bash(head -10 grep -n \"table\\(\\\\|''''Prop''''\\\\|highlighted\\\\|badge\\\\|period\\\\|features\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/pricing.js)",
71
+ "Bash(head -10 grep -n \"table\\(\\\\|''''Prop''''\\\\|eyebrow\\\\|subtitle\\\\|actions\\\\|align\" /Users/andy.stubbs/Repos/AS/pulse2/docs/src/pages/components/cta.js)",
72
+ "Bash(echo \"exit: $?\")",
73
+ "Bash(xargs node:*)",
74
+ "Bash(find /Users/andy.stubbs/Repos/AS/ultimate-demo/twizzer -name server.js -o -name *.config.*)",
75
+ "Bash(xargs ls:*)",
76
+ "Bash(xargs -I{} lsof -p {})",
77
+ "Bash(brotli --best -c /Users/andy.stubbs/Repos/AS/ultimate-demo/twizzer/public/dist/home.boot-TJOPUIR4.js)",
78
+ "WebSearch",
79
+ "WebFetch(domain:github.com)",
80
+ "Bash(ls:*)",
81
+ "Bash(git -C /Users/andy.stubbs/Repos/AS/pulse2 log --follow --diff-filter=A --format=\"%ar\" -- public/dist/runtime-YLZ4ZGK4.js)",
82
+ "Read(//Users/andy.stubbs/Repos/AS/pulse2/**)",
83
+ "Bash(GetFileInfo /Users/andy.stubbs/Repos/AS/pulse2/public/dist/runtime-YLZ4ZGK4.js)",
84
+ "mcp__chrome-devtools__performance_start_trace",
85
+ "Bash(pkill -f docs/server.js)",
86
+ "Bash(sleep 1 node -e \":*)",
87
+ "mcp__chrome-devtools__close_page",
88
+ "mcp__chrome-devtools__fill",
89
+ "mcp__chrome-devtools__click",
90
+ "mcp__chrome-devtools__take_snapshot",
91
+ "mcp__chrome-devtools__type_text",
92
+ "Bash(head -5 grep -n \"section\\\\|container\" /Users/andy.stubbs/Repos/AS/pulse2/src/ui/index.js)",
93
+ "Bash(head -35 grep \"ui-section-header\\\\|ui-section-title\\\\|ui-section-eyebrow\\\\|ui-section--\" /Users/andy.stubbs/Repos/AS/pulse2/public/pulse-ui.css)",
94
+ "mcp__chrome-devtools__performance_analyze_insight",
95
+ "Bash(git checkout:*)",
96
+ "Bash(git branch:*)",
97
+ "Bash(gh run:*)",
98
+ "Bash(xargs -I{} gh run delete {} --repo invisibleloop/pulse-framework)",
99
+ "Bash(gh api:*)"
100
+ ]
101
+ }
102
+ }
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 22
18
+ cache: npm
19
+
20
+ - run: npm ci
21
+
22
+ - run: npm test
@@ -0,0 +1,41 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ bump:
7
+ description: Version bump
8
+ type: choice
9
+ options: [patch, minor, major]
10
+ default: patch
11
+ required: true
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: write
18
+ id-token: write
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ token: ${{ secrets.GITHUB_TOKEN }}
23
+
24
+ - uses: actions/setup-node@v4
25
+ with:
26
+ node-version: 22
27
+ cache: npm
28
+ registry-url: https://registry.npmjs.org
29
+
30
+ - run: npm ci
31
+
32
+ - run: npm test
33
+
34
+ - name: Bump version and tag
35
+ run: |
36
+ git config user.name "github-actions[bot]"
37
+ git config user.email "github-actions[bot]@users.noreply.github.com"
38
+ npm version ${{ inputs.bump }} -m "chore: release %s [skip ci]"
39
+ git push --follow-tags
40
+
41
+ - run: npm publish --provenance
@@ -0,0 +1,22 @@
1
+ {
2
+ "timestamp": "2026-03-13T20:11:51.416Z",
3
+ "url": "http://localhost:4000/",
4
+ "config": {
5
+ "duration": 3,
6
+ "connections": 5
7
+ },
8
+ "rps": 17570.5,
9
+ "latency": {
10
+ "mean": 0.2,
11
+ "p50": 0,
12
+ "p95": 1,
13
+ "p99": 1,
14
+ "max": 16
15
+ },
16
+ "requests": {
17
+ "total": 61655,
18
+ "success": 61655,
19
+ "errors": 0
20
+ },
21
+ "duration": 3.5
22
+ }