@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,255 @@
1
+ /**
2
+ * Pulse Docs — Server-side syntax highlighter
3
+ *
4
+ * Character-by-character tokeniser. No dependencies, no client JS.
5
+ * Returns an HTML string of <span> elements ready to drop inside <code>.
6
+ */
7
+
8
+ const KEYWORDS = new Set([
9
+ 'export', 'default', 'import', 'from', 'as',
10
+ 'async', 'await', 'const', 'let', 'var',
11
+ 'return', 'if', 'else', 'for', 'while', 'of', 'in',
12
+ 'true', 'false', 'null', 'undefined',
13
+ 'function', 'class', 'new', 'this', 'typeof', 'instanceof',
14
+ 'try', 'catch', 'finally', 'throw',
15
+ ])
16
+
17
+ function esc(s) {
18
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
19
+ }
20
+
21
+ function span(cls, text) {
22
+ return `<span class="${cls}">${esc(text)}</span>`
23
+ }
24
+
25
+ export function highlight(code, lang = 'js') {
26
+ if (lang === 'bash') return highlightBash(code)
27
+ if (lang === 'html') return highlightHtml(code)
28
+ return highlightJs(code)
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // JavaScript tokeniser
33
+ // ---------------------------------------------------------------------------
34
+
35
+ function highlightJs(code) {
36
+ let out = ''
37
+ let i = 0
38
+ const n = code.length
39
+
40
+ while (i < n) {
41
+ const ch = code[i]
42
+ const ch2 = code[i + 1]
43
+
44
+ // Single-line comment
45
+ if (ch === '/' && ch2 === '/') {
46
+ const end = code.indexOf('\n', i)
47
+ const tok = end === -1 ? code.slice(i) : code.slice(i, end)
48
+ out += span('tok-cmt', tok)
49
+ i += tok.length
50
+ continue
51
+ }
52
+
53
+ // Block comment
54
+ if (ch === '/' && ch2 === '*') {
55
+ const end = code.indexOf('*/', i + 2)
56
+ const tok = end === -1 ? code.slice(i) : code.slice(i, end + 2)
57
+ out += span('tok-cmt', tok)
58
+ i += tok.length
59
+ continue
60
+ }
61
+
62
+ // Template literal
63
+ if (ch === '`') {
64
+ let tok = '`'
65
+ i++
66
+ let depth = 0
67
+ while (i < n) {
68
+ if (code[i] === '\\') {
69
+ tok += code[i] + (code[i + 1] || '')
70
+ i += 2
71
+ continue
72
+ }
73
+ if (code[i] === '$' && code[i + 1] === '{') {
74
+ tok += '${'
75
+ i += 2
76
+ depth++
77
+ continue
78
+ }
79
+ if (code[i] === '}' && depth > 0) {
80
+ tok += '}'
81
+ i++
82
+ depth--
83
+ continue
84
+ }
85
+ if (code[i] === '`' && depth === 0) {
86
+ tok += '`'
87
+ i++
88
+ break
89
+ }
90
+ tok += code[i++]
91
+ }
92
+ out += span('tok-str', tok)
93
+ continue
94
+ }
95
+
96
+ // Single or double quoted string
97
+ if (ch === '"' || ch === "'") {
98
+ let tok = ch
99
+ i++
100
+ while (i < n && code[i] !== ch && code[i] !== '\n') {
101
+ if (code[i] === '\\') {
102
+ tok += code[i] + (code[i + 1] || '')
103
+ i += 2
104
+ continue
105
+ }
106
+ tok += code[i++]
107
+ }
108
+ if (i < n && code[i] === ch) tok += code[i++]
109
+ out += span('tok-str', tok)
110
+ continue
111
+ }
112
+
113
+ // Number
114
+ if (/\d/.test(ch) && (i === 0 || !/\w/.test(code[i - 1]))) {
115
+ let tok = ''
116
+ while (i < n && /[\d.xXa-fA-F]/.test(code[i])) tok += code[i++]
117
+ out += span('tok-num', tok)
118
+ continue
119
+ }
120
+
121
+ // Identifier / keyword
122
+ if (/[a-zA-Z_$]/.test(ch)) {
123
+ let tok = ''
124
+ while (i < n && /[\w$]/.test(code[i])) tok += code[i++]
125
+
126
+ if (KEYWORDS.has(tok)) {
127
+ out += span('tok-kw', tok)
128
+ } else {
129
+ // Look ahead: followed by ( → function call
130
+ let j = i
131
+ while (j < n && code[j] === ' ') j++
132
+ if (code[j] === '(') {
133
+ out += span('tok-fn', tok)
134
+ } else {
135
+ out += esc(tok)
136
+ }
137
+ }
138
+ continue
139
+ }
140
+
141
+ // Operators
142
+ if (/[=!<>|&?.]/.test(ch)) {
143
+ let tok = ch
144
+ // Grab two-char operators
145
+ if (/[=!<>|&?]/.test(ch) && /[=|&>?]/.test(ch2)) {
146
+ tok = code.slice(i, i + 2)
147
+ // Three-char: ===, !==, ...
148
+ if (code[i + 2] === '=') tok = code.slice(i, i + 3)
149
+ }
150
+ out += span('tok-op', tok)
151
+ i += tok.length
152
+ continue
153
+ }
154
+
155
+ // Punctuation
156
+ if (/[{}()[\],;:]/.test(ch)) {
157
+ out += span('tok-punct', ch)
158
+ i++
159
+ continue
160
+ }
161
+
162
+ // Everything else (whitespace, newlines, etc.)
163
+ out += esc(ch)
164
+ i++
165
+ }
166
+
167
+ return out
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // Bash tokeniser (minimal)
172
+ // ---------------------------------------------------------------------------
173
+
174
+ function highlightBash(code) {
175
+ return code.split('\n').map(line => {
176
+ // Comment line
177
+ if (/^\s*#/.test(line)) return span('tok-cmt', line)
178
+
179
+ let out = ''
180
+ let i = 0
181
+ while (i < line.length) {
182
+ // Inline comment
183
+ if (line[i] === '#') {
184
+ out += span('tok-cmt', line.slice(i))
185
+ break
186
+ }
187
+ // String
188
+ if (line[i] === '"' || line[i] === "'") {
189
+ const q = line[i]
190
+ let tok = q
191
+ i++
192
+ while (i < line.length && line[i] !== q) tok += line[i++]
193
+ tok += line[i] === q ? line[i++] : ''
194
+ out += span('tok-str', tok)
195
+ continue
196
+ }
197
+ // Flag/option
198
+ if (line[i] === '-' && /\w/.test(line[i + 1] || '')) {
199
+ let tok = '-'
200
+ i++
201
+ while (i < line.length && /[-\w]/.test(line[i])) tok += line[i++]
202
+ out += span('tok-op', tok)
203
+ continue
204
+ }
205
+ out += esc(line[i++])
206
+ }
207
+ return out
208
+ }).join('\n')
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // HTML tokeniser (minimal)
213
+ // ---------------------------------------------------------------------------
214
+
215
+ function highlightHtml(code) {
216
+ let out = ''
217
+ let i = 0
218
+ const n = code.length
219
+
220
+ while (i < n) {
221
+ // Comment
222
+ if (code.slice(i, i + 4) === '<!--') {
223
+ const end = code.indexOf('-->', i + 4)
224
+ const tok = end === -1 ? code.slice(i) : code.slice(i, end + 3)
225
+ out += span('tok-cmt', tok)
226
+ i += tok.length
227
+ continue
228
+ }
229
+
230
+ // Tag
231
+ if (code[i] === '<') {
232
+ const end = code.indexOf('>', i)
233
+ if (end === -1) { out += esc(code[i++]); continue }
234
+ const tag = code.slice(i, end + 1)
235
+ // Simple: highlight tag name + attributes
236
+ const highlighted = tag.replace(
237
+ /^(<\/?)([\w-]+)/,
238
+ (_, lt, name) => esc(lt) + span('tok-kw', name)
239
+ ).replace(
240
+ /([\w-]+)(=)/g,
241
+ (_, attr, eq) => span('tok-fn', attr) + esc(eq)
242
+ ).replace(
243
+ /("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/g,
244
+ (_, str) => span('tok-str', str)
245
+ )
246
+ out += highlighted
247
+ i = end + 1
248
+ continue
249
+ }
250
+
251
+ out += esc(code[i++])
252
+ }
253
+
254
+ return out
255
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Pulse Docs — Page layout
3
+ *
4
+ * renderLayout({ currentHref, content, prev, next }) → HTML string
5
+ * Used by every docs page except the home page.
6
+ */
7
+
8
+ import { NAV } from './nav.js'
9
+
10
+ function esc(s) {
11
+ return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
12
+ }
13
+
14
+ function sidebar(currentHref) {
15
+ const sections = NAV.map(({ section, items }) => {
16
+ const links = items.map(item => {
17
+ const active = item.href === currentHref
18
+ return `<a href="${esc(item.href)}" class="nav-link${active ? ' active' : ''}"${active ? ' aria-current="page"' : ''}>${esc(item.label)}</a>`
19
+ }).join('')
20
+ return `
21
+ <div class="nav-section">
22
+ <p class="nav-section-title">${esc(section)}</p>
23
+ ${links}
24
+ </div>`
25
+ }).join('')
26
+
27
+ return `
28
+ <aside class="docs-sidebar" aria-label="Documentation navigation">
29
+ <div class="sidebar-logo">
30
+ <a href="/" class="logo-link" aria-label="Pulse home">
31
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
32
+ <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
33
+ </svg>
34
+ <span class="logo-name">Pulse</span>
35
+ </a>
36
+ <span class="version-badge">v0.1</span>
37
+ </div>
38
+ <nav class="sidebar-nav">
39
+ ${sections}
40
+ </nav>
41
+ </aside>`
42
+ }
43
+
44
+ function prevNextBar(prev, next) {
45
+ if (!prev && !next) return ''
46
+ return `
47
+ <nav class="doc-prev-next" aria-label="Previous and next pages">
48
+ <div class="prev-next-grid">
49
+ ${prev ? `<a href="${esc(prev.href)}" class="prev-next-link prev-link">
50
+ <span class="prev-next-label">← Previous</span>
51
+ <span class="prev-next-title">${esc(prev.label)}</span>
52
+ </a>` : '<div></div>'}
53
+ ${next ? `<a href="${esc(next.href)}" class="prev-next-link next-link">
54
+ <span class="prev-next-label">Next →</span>
55
+ <span class="prev-next-title">${esc(next.label)}</span>
56
+ </a>` : '<div></div>'}
57
+ </div>
58
+ </nav>`
59
+ }
60
+
61
+ export function renderLayout({ currentHref, content, prev = null, next = null }) {
62
+ return `
63
+ <div class="sidebar-overlay" aria-hidden="true"></div>
64
+ ${sidebar(currentHref)}
65
+ <div class="docs-main">
66
+ <header class="docs-header">
67
+ <button class="mobile-menu-btn" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="docs-sidebar">
68
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
69
+ <path fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z" clip-rule="evenodd"/>
70
+ </svg>
71
+ </button>
72
+ <a href="/" class="header-logo-mobile" aria-label="Pulse home">
73
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true">
74
+ <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
75
+ </svg>
76
+ </a>
77
+ <a href="https://github.com/invisibleloop/pulse" class="header-github" aria-label="View on GitHub" target="_blank" rel="noopener noreferrer">
78
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
79
+ <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/>
80
+ </svg>
81
+ GitHub
82
+ </a>
83
+ </header>
84
+ <main class="docs-content">
85
+ ${content}
86
+ ${prevNextBar(prev, next)}
87
+ </main>
88
+ </div>
89
+ <script src="/menu.js"></script>
90
+ <script src="/pulse-ui.js"></script>`
91
+ }
92
+
93
+ /**
94
+ * Convenience helpers used in every page's view function
95
+ */
96
+
97
+ export function h1(text) {
98
+ return `<h1 class="doc-h1">${esc(text)}</h1>`
99
+ }
100
+
101
+ export function lead(text) {
102
+ return `<p class="doc-lead">${text}</p>`
103
+ }
104
+
105
+ export function section(id, title) {
106
+ return `<h2 class="doc-h2" id="${esc(id)}"><a href="#${esc(id)}" class="heading-anchor">${esc(title)}</a></h2>`
107
+ }
108
+
109
+ export function sub(id, title) {
110
+ const label = title ?? id
111
+ return `<h3 class="doc-h3" id="${esc(id)}"><a href="#${esc(id)}" class="heading-anchor">${esc(label)}</a></h3>`
112
+ }
113
+
114
+ export function codeBlock(highlighted, filename = '') {
115
+ const header = filename ? `<div class="code-filename">${esc(filename)}</div>` : ''
116
+ return `${header}<pre class="code-block"><code>${highlighted}</code></pre>`
117
+ }
118
+
119
+ export function table(headers, rows) {
120
+ const ths = headers.map(h => `<th>${h}</th>`).join('')
121
+ const trs = rows.map(row =>
122
+ `<tr>${row.map(cell => `<td>${cell}</td>`).join('')}</tr>`
123
+ ).join('')
124
+ return `<div class="table-wrap"><table><thead><tr>${ths}</tr></thead><tbody>${trs}</tbody></table></div>`
125
+ }
126
+
127
+ export function callout(type, content) {
128
+ // type: 'note' | 'warning' | 'tip'
129
+ const icons = { note: 'ℹ', warning: '⚠', tip: '✦' }
130
+ return `<div class="callout callout-${esc(type)}"><span class="callout-icon" aria-hidden="true">${icons[type] || 'ℹ'}</span><div class="callout-body">${content}</div></div>`
131
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared metrics holder. Populated by docs/server.js at startup
3
+ * before createServer() is called. home.js reads from here so
4
+ * the spec file stays free of Node built-ins (safe to bundle for browser).
5
+ */
6
+ export const metricsStore = { current: null }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Pulse Docs — Navigation structure
3
+ */
4
+
5
+ export const NAV = [
6
+ {
7
+ section: 'Framework',
8
+ items: [
9
+ { label: 'Overview', href: '/' },
10
+ { label: 'FAQ', href: '/faq' },
11
+ { label: 'Project Structure', href: '/project-structure' },
12
+ { label: 'Spec Reference', href: '/spec' },
13
+ { label: 'Configuration', href: '/config' },
14
+ ],
15
+ },
16
+ {
17
+ section: 'Developer Guide',
18
+ items: [
19
+ { label: 'Getting Started', href: '/getting-started' },
20
+ { label: 'How It Works', href: '/how-it-works' },
21
+ { label: 'Slash Commands', href: '/slash-commands' },
22
+ { label: 'Prompt Examples', href: '/prompt-examples' },
23
+ { label: 'Component Library', href: '/components' },
24
+ ],
25
+ },
26
+ {
27
+ section: 'State & Behaviour',
28
+ items: [
29
+ { label: 'State', href: '/state' },
30
+ { label: 'Mutations', href: '/mutations' },
31
+ { label: 'Actions', href: '/actions' },
32
+ { label: 'Validation', href: '/validation' },
33
+ { label: 'Constraints', href: '/constraints' },
34
+ { label: 'Persist', href: '/persist' },
35
+ ],
36
+ },
37
+ {
38
+ section: 'Server',
39
+ items: [
40
+ { label: 'Server Data', href: '/server-data' },
41
+ { label: 'Global Store', href: '/store' },
42
+ { label: 'Routing', href: '/routing' },
43
+ { label: 'Streaming SSR', href: '/streaming' },
44
+ { label: 'Caching', href: '/caching' },
45
+ { label: 'Guard', href: '/guard' },
46
+ { label: 'Raw Responses', href: '/raw-responses' },
47
+ { label: 'Server API', href: '/server-api' },
48
+ { label: 'Extending Pulse', href: '/extending' },
49
+ ],
50
+ },
51
+ {
52
+ section: 'Client',
53
+ items: [
54
+ { label: 'Hydration', href: '/hydration' },
55
+ { label: 'Navigation', href: '/navigation' },
56
+ { label: 'Images', href: '/images' },
57
+ ],
58
+ },
59
+ {
60
+ section: 'Deployment',
61
+ items: [
62
+ { label: 'Deployment', href: '/deploy' },
63
+ ],
64
+ },
65
+ {
66
+ section: 'Integrations',
67
+ items: [
68
+ { label: 'Supabase', href: '/supabase' },
69
+ { label: 'Auth (Auth0)', href: '/auth' },
70
+ { label: 'Payments (Stripe)', href: '/stripe' },
71
+ ],
72
+ },
73
+ {
74
+ section: 'Reference',
75
+ items: [
76
+ { label: 'Metadata & SEO', href: '/meta' },
77
+ { label: 'Performance', href: '/performance' },
78
+ { label: 'Accessibility', href: '/accessibility' },
79
+ { label: 'Testing', href: '/testing' },
80
+ ],
81
+ },
82
+ {
83
+ section: 'UI Components',
84
+ items: [
85
+ { label: 'Alert', href: '/components/alert' },
86
+ { label: 'Avatar', href: '/components/avatar' },
87
+ { label: 'Badge', href: '/components/badge' },
88
+ { label: 'Breadcrumbs', href: '/components/breadcrumbs' },
89
+ { label: 'Button', href: '/components/button' },
90
+ { label: 'Card', href: '/components/card' },
91
+ { label: 'Checkbox', href: '/components/checkbox' },
92
+ { label: 'Carousel', href: '/components/carousel' },
93
+ { label: 'Charts', href: '/components/charts' },
94
+ { label: 'Empty', href: '/components/empty' },
95
+ { label: 'Fieldset', href: '/components/fieldset' },
96
+ { label: 'File Upload', href: '/components/file-upload' },
97
+ { label: 'Heading', href: '/components/heading' },
98
+ { label: 'Icons', href: '/components/icons' },
99
+ { label: 'Image', href: '/components/image' },
100
+ { label: 'Input', href: '/components/input' },
101
+ { label: 'List', href: '/components/list' },
102
+ { label: 'Modal', href: '/components/modal' },
103
+ { label: 'Progress', href: '/components/progress' },
104
+ { label: 'Prose', href: '/components/prose' },
105
+ { label: 'Pullquote', href: '/components/pullquote' },
106
+ { label: 'Radio', href: '/components/radio' },
107
+ { label: 'Rating', href: '/components/rating' },
108
+ { label: 'Search', href: '/components/search' },
109
+ { label: 'Segmented', href: '/components/segmented' },
110
+ { label: 'Select', href: '/components/select' },
111
+ { label: 'Slider', href: '/components/slider' },
112
+ { label: 'Spinner', href: '/components/spinner' },
113
+ { label: 'Stat', href: '/components/stat' },
114
+ { label: 'Stepper', href: '/components/stepper' },
115
+ { label: 'Table', href: '/components/table' },
116
+ { label: 'Textarea', href: '/components/textarea' },
117
+ { label: 'Timeline', href: '/components/timeline' },
118
+ { label: 'Toggle', href: '/components/toggle' },
119
+ { label: 'Tooltip', href: '/components/tooltip' },
120
+ ],
121
+ },
122
+ {
123
+ section: 'Landing Components',
124
+ items: [
125
+ { label: 'Accordion', href: '/components/accordion' },
126
+ { label: 'App Badge', href: '/components/app-badge' },
127
+ { label: 'CTA', href: '/components/cta' },
128
+ { label: 'Feature', href: '/components/feature' },
129
+ { label: 'Hero', href: '/components/hero' },
130
+ { label: 'Nav', href: '/components/nav' },
131
+ { label: 'Pricing', href: '/components/pricing' },
132
+ { label: 'Testimonial', href: '/components/testimonial' },
133
+ ],
134
+ },
135
+ {
136
+ section: 'Layout Components',
137
+ items: [
138
+ { label: 'Banner', href: '/components/banner' },
139
+ { label: 'Cluster', href: '/components/cluster' },
140
+ { label: 'Code Window', href: '/components/code-window' },
141
+ { label: 'Container', href: '/components/container' },
142
+ { label: 'Divider', href: '/components/divider' },
143
+ { label: 'Footer', href: '/components/footer' },
144
+ { label: 'Grid', href: '/components/grid' },
145
+ { label: 'Media', href: '/components/media' },
146
+ { label: 'Section', href: '/components/section' },
147
+ { label: 'Stack', href: '/components/stack' },
148
+ ],
149
+ },
150
+ ]
151
+
152
+ export function prevNext(currentHref) {
153
+ const all = NAV.flatMap(s => s.items)
154
+ const idx = all.findIndex(i => i.href === currentHref)
155
+ return {
156
+ prev: idx > 0 ? all[idx - 1] : null,
157
+ next: idx < all.length - 1 ? all[idx + 1] : null,
158
+ }
159
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Performance metrics — computed once at server start from real build output.
3
+ * Bundle sizes are measured by compressing the actual dist files with brotli.
4
+ */
5
+
6
+ import fs from 'fs'
7
+ import path from 'path'
8
+ import zlib from 'zlib'
9
+ import { fileURLToPath } from 'url'
10
+
11
+ const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../../../..')
12
+ const DIST = path.join(ROOT, 'benchmark', 'public', 'dist')
13
+
14
+ function brotliKb(filepath) {
15
+ try {
16
+ const buf = fs.readFileSync(filepath)
17
+ return zlib.brotliCompressSync(buf).length / 1024
18
+ } catch {
19
+ return null
20
+ }
21
+ }
22
+
23
+ function measureBundles() {
24
+ if (!fs.existsSync(DIST)) return null
25
+ const files = fs.readdirSync(DIST).filter(f => f.endsWith('.js'))
26
+
27
+ // Main runtime = largest runtime-*.js file
28
+ const runtimes = files
29
+ .filter(f => f.startsWith('runtime-'))
30
+ .map(f => ({ f, kb: brotliKb(path.join(DIST, f)) }))
31
+ .filter(x => x.kb !== null)
32
+ .sort((a, b) => b.kb - a.kb)
33
+
34
+ const counterBoot = files.find(f => f.startsWith('counter.boot-'))
35
+ const staticBoot = files.find(f => f.startsWith('home.boot-'))
36
+
37
+ const runtimeKb = runtimes[0]?.kb ?? null
38
+ const pageBootKb = counterBoot ? brotliKb(path.join(DIST, counterBoot)) : null
39
+ const staticBootKb = staticBoot ? brotliKb(path.join(DIST, staticBoot)) : null
40
+
41
+ return { runtimeKb, pageBootKb, staticBootKb }
42
+ }
43
+
44
+ const bundles = measureBundles()
45
+
46
+ function fmt(kb, fallback) {
47
+ return (kb != null) ? kb.toFixed(kb < 1 ? 2 : 1) : fallback
48
+ }
49
+
50
+ const runtimeKb = fmt(bundles?.runtimeKb, '3.1')
51
+ const firstVisit = bundles?.runtimeKb && bundles?.pageBootKb
52
+ ? (bundles.runtimeKb + bundles.pageBootKb).toFixed(1)
53
+ : '3.5'
54
+ const pageNavKb = fmt(bundles?.pageBootKb, '0.35')
55
+
56
+ export const metrics = {
57
+ generatedAt: new Date().toLocaleString('en-GB', { dateStyle: 'medium', timeStyle: 'short' }),
58
+
59
+ lighthouse: [
60
+ { value: '100', label: 'Accessibility' },
61
+ { value: '100', label: 'Best Practices' },
62
+ { value: '100', label: 'SEO' },
63
+ ],
64
+
65
+ bundles: [
66
+ { value: '0 kB', label: 'Static page — no JS shipped' },
67
+ { value: `${firstVisit} kB`, label: 'Single page app — runtime + page (brotli)' },
68
+ { value: `${runtimeKb} kB`, label: 'Multi-page — shared runtime, cached (brotli)' },
69
+ { value: `${pageNavKb} kB`, label: 'Multi-page — per-page JS bundle (brotli)' },
70
+ ],
71
+
72
+ vitals: [
73
+ { id: 'cls', value: '0.00', label: 'Cumulative Layout Shift' },
74
+ ],
75
+
76
+ architecture: [
77
+ { value: '0', label: 'Runtime dependencies' },
78
+ { value: 'None', label: 'Production build step' },
79
+ { value: 'Brotli', label: 'Automatic compression' },
80
+ ],
81
+ }