@invisibleloop/pulse 0.20.11 → 0.20.20

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 (266) hide show
  1. package/package.json +6 -5
  2. package/public/.pulse-ui-version +1 -1
  3. package/scripts/build.js +26 -0
  4. package/scripts/release-version.js +17 -8
  5. package/scripts/strip-server.test.js +60 -0
  6. package/src/agent/checklist.md +8 -12
  7. package/src/agent/commands/pulse-start.md +2 -0
  8. package/src/agent/commands/verify.md +30 -8
  9. package/src/agent/guide-components.md +7 -25
  10. package/src/agent/guide-routing.md +0 -12
  11. package/src/agent/guide-server.md +102 -1
  12. package/src/agent/guide-spec.md +8 -1
  13. package/src/agent/guide-styles.md +23 -0
  14. package/src/agent/identity.md +9 -0
  15. package/src/agent/workflow.md +4 -23
  16. package/src/cli/check-bundles.js +113 -0
  17. package/src/cli/check-bundles.test.js +137 -0
  18. package/src/cli/cli.test.js +105 -0
  19. package/src/cli/dev.js +48 -3
  20. package/src/cli/diagnose.js +105 -0
  21. package/src/cli/diagnose.test.js +124 -0
  22. package/src/cli/discover.js +9 -2
  23. package/src/cli/index.js +171 -9
  24. package/src/cli/process-utils.js +57 -0
  25. package/src/cli/process-utils.test.js +114 -0
  26. package/src/cli/review.js +391 -0
  27. package/src/cli/review.test.js +143 -0
  28. package/src/cli/scaffold.js +9 -4
  29. package/src/cli/stamp.js +102 -0
  30. package/src/cli/stamp.test.js +149 -0
  31. package/src/cli/start.js +15 -2
  32. package/src/cli/validate.js +184 -0
  33. package/src/cli/validate.test.js +153 -0
  34. package/src/mcp/mcp.test.js +261 -30
  35. package/src/mcp/server.js +207 -515
  36. package/src/mcp/validate-worker.js +18 -1
  37. package/src/runtime/index.js +144 -11
  38. package/src/runtime/runtime.test.js +199 -0
  39. package/src/runtime/ssr.js +13 -6
  40. package/src/runtime/ssr.test.js +31 -0
  41. package/src/server/index.js +162 -9
  42. package/src/server/server.test.js +225 -2
  43. package/src/spec/schema.js +1 -1
  44. package/src/ui/index.js +0 -5
  45. package/src/ui/ui.test.js +0 -75
  46. package/types/ui.d.ts +0 -21
  47. package/.claude/commands/build-page.md +0 -63
  48. package/.claude/commands/new-doc-page.md +0 -45
  49. package/.claude/commands/pulse-start.md +0 -11
  50. package/.claude/commands/pulse-stop.md +0 -5
  51. package/.claude/commands/verify.md +0 -118
  52. package/.claude/pulse-checklist.md +0 -189
  53. package/.github/workflows/ci.yml +0 -25
  54. package/.github/workflows/publish.yml +0 -45
  55. package/.pulse/load-reports/home/1773432711417.json +0 -22
  56. package/.pulse-verified +0 -1
  57. package/AGENTS.md +0 -697
  58. package/CLAUDE.md +0 -697
  59. package/README.md +0 -95
  60. package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
  61. package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
  62. package/benchmark/public/dist/manifest.json +0 -5
  63. package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
  64. package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
  65. package/benchmark/results/2026-03-27-09-20.json +0 -188
  66. package/benchmark/results/2026-03-27-09-21.json +0 -188
  67. package/benchmark/results/2026-03-27-09-29.json +0 -188
  68. package/benchmark/results/2026-03-27-10-14.json +0 -188
  69. package/benchmark/results/2026-03-27-10-18.json +0 -232
  70. package/benchmark/src/pages/counter.js +0 -37
  71. package/benchmark/src/pages/home.js +0 -21
  72. package/docs/.claude/commands/pulse-build.md +0 -7
  73. package/docs/.claude/commands/pulse-dev.md +0 -7
  74. package/docs/.claude/commands/pulse-start.md +0 -9
  75. package/docs/.claude/commands/pulse-stop.md +0 -5
  76. package/docs/.claude/commands/verify.md +0 -84
  77. package/docs/.claude/coverage-check.js +0 -196
  78. package/docs/.claude/pulse-checklist.md +0 -189
  79. package/docs/public/.pulse-ui-version +0 -1
  80. package/docs/public/andypixel.css +0 -237
  81. package/docs/public/bookworms.css +0 -422
  82. package/docs/public/coutumes.css +0 -561
  83. package/docs/public/docs.css +0 -2388
  84. package/docs/public/favicon.svg +0 -4
  85. package/docs/public/home-brut.css +0 -982
  86. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  87. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  88. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  89. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  90. package/docs/public/images/journey/178.jpg +0 -0
  91. package/docs/public/images/journey/29.jpg +0 -0
  92. package/docs/public/images/retrowheels/hero.jpg +0 -0
  93. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  94. package/docs/public/images/suzukibento/1015.jpg +0 -0
  95. package/docs/public/images/suzukibento/1025.jpg +0 -0
  96. package/docs/public/images/suzukibento/1043.jpg +0 -0
  97. package/docs/public/images/suzukibento/1059.jpg +0 -0
  98. package/docs/public/images/suzukibento/1062.jpg +0 -0
  99. package/docs/public/images/suzukibento/1084.jpg +0 -0
  100. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  101. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  102. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  103. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  104. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  105. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  106. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  107. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  108. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  109. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  110. package/docs/public/img/heic1501a.jpg +0 -0
  111. package/docs/public/img/placeholder-landscape.svg +0 -18
  112. package/docs/public/img/placeholder-square.svg +0 -14
  113. package/docs/public/img/placeholder-tall.svg +0 -22
  114. package/docs/public/journey.css +0 -528
  115. package/docs/public/menu.js +0 -327
  116. package/docs/public/pulse-ui.css +0 -3028
  117. package/docs/public/pulse-ui.js +0 -257
  118. package/docs/public/retrowheels.css +0 -425
  119. package/docs/public/shop.css +0 -269
  120. package/docs/public/sparkys.css +0 -382
  121. package/docs/public/suzukibento.css +0 -517
  122. package/docs/public/swish.css +0 -394
  123. package/docs/public/theme.css +0 -52
  124. package/docs/public/themes/lumio.css +0 -50
  125. package/docs/public/themes/observatory.css +0 -104
  126. package/docs/server.js +0 -161
  127. package/docs/src/lib/component-page.js +0 -49
  128. package/docs/src/lib/highlight.js +0 -2
  129. package/docs/src/lib/layout.js +0 -190
  130. package/docs/src/lib/metrics-store.js +0 -6
  131. package/docs/src/lib/nav.js +0 -171
  132. package/docs/src/lib/stats.js +0 -81
  133. package/docs/src/pages/accessibility.js +0 -157
  134. package/docs/src/pages/actions.js +0 -194
  135. package/docs/src/pages/auth.js +0 -176
  136. package/docs/src/pages/caching.js +0 -95
  137. package/docs/src/pages/components/accordion.js +0 -48
  138. package/docs/src/pages/components/alert.js +0 -35
  139. package/docs/src/pages/components/app-badge.js +0 -41
  140. package/docs/src/pages/components/avatar.js +0 -35
  141. package/docs/src/pages/components/badge.js +0 -36
  142. package/docs/src/pages/components/breadcrumbs.js +0 -94
  143. package/docs/src/pages/components/button.js +0 -84
  144. package/docs/src/pages/components/card.js +0 -225
  145. package/docs/src/pages/components/charts.js +0 -283
  146. package/docs/src/pages/components/checkbox.js +0 -129
  147. package/docs/src/pages/components/cluster.js +0 -47
  148. package/docs/src/pages/components/container.js +0 -40
  149. package/docs/src/pages/components/cta.js +0 -54
  150. package/docs/src/pages/components/divider.js +0 -37
  151. package/docs/src/pages/components/empty.js +0 -36
  152. package/docs/src/pages/components/feature.js +0 -60
  153. package/docs/src/pages/components/fieldset.js +0 -65
  154. package/docs/src/pages/components/fileupload.js +0 -128
  155. package/docs/src/pages/components/footer.js +0 -58
  156. package/docs/src/pages/components/grid.js +0 -169
  157. package/docs/src/pages/components/heading.js +0 -107
  158. package/docs/src/pages/components/hero.js +0 -123
  159. package/docs/src/pages/components/icons.js +0 -410
  160. package/docs/src/pages/components/image.js +0 -71
  161. package/docs/src/pages/components/input.js +0 -51
  162. package/docs/src/pages/components/media.js +0 -51
  163. package/docs/src/pages/components/modal.js +0 -111
  164. package/docs/src/pages/components/nav.js +0 -136
  165. package/docs/src/pages/components/phone-frame.js +0 -74
  166. package/docs/src/pages/components/progress.js +0 -102
  167. package/docs/src/pages/components/prose.js +0 -111
  168. package/docs/src/pages/components/pullquote.js +0 -71
  169. package/docs/src/pages/components/radio.js +0 -194
  170. package/docs/src/pages/components/rating.js +0 -106
  171. package/docs/src/pages/components/search.js +0 -61
  172. package/docs/src/pages/components/section.js +0 -98
  173. package/docs/src/pages/components/segmented.js +0 -121
  174. package/docs/src/pages/components/select.js +0 -45
  175. package/docs/src/pages/components/slider.js +0 -115
  176. package/docs/src/pages/components/spinner.js +0 -73
  177. package/docs/src/pages/components/stack.js +0 -48
  178. package/docs/src/pages/components/stat.js +0 -55
  179. package/docs/src/pages/components/stepper.js +0 -66
  180. package/docs/src/pages/components/table.js +0 -45
  181. package/docs/src/pages/components/textarea.js +0 -31
  182. package/docs/src/pages/components/toggle.js +0 -84
  183. package/docs/src/pages/components/tooltip.js +0 -48
  184. package/docs/src/pages/components.js +0 -200
  185. package/docs/src/pages/config.js +0 -193
  186. package/docs/src/pages/constraints.js +0 -100
  187. package/docs/src/pages/deploy.js +0 -233
  188. package/docs/src/pages/error-pages.js +0 -99
  189. package/docs/src/pages/extending.js +0 -198
  190. package/docs/src/pages/faq.js +0 -99
  191. package/docs/src/pages/forms.js +0 -129
  192. package/docs/src/pages/getting-started.js +0 -147
  193. package/docs/src/pages/guard.js +0 -120
  194. package/docs/src/pages/home.js +0 -350
  195. package/docs/src/pages/how-it-works.js +0 -244
  196. package/docs/src/pages/hydration.js +0 -108
  197. package/docs/src/pages/images.js +0 -120
  198. package/docs/src/pages/markdown.js +0 -156
  199. package/docs/src/pages/meta.js +0 -193
  200. package/docs/src/pages/mutations.js +0 -107
  201. package/docs/src/pages/navigation.js +0 -85
  202. package/docs/src/pages/performance.js +0 -157
  203. package/docs/src/pages/persist.js +0 -89
  204. package/docs/src/pages/project-structure.js +0 -91
  205. package/docs/src/pages/prompt-examples.js +0 -186
  206. package/docs/src/pages/raw-responses.js +0 -122
  207. package/docs/src/pages/routing.js +0 -120
  208. package/docs/src/pages/server-api.js +0 -291
  209. package/docs/src/pages/server-data.js +0 -203
  210. package/docs/src/pages/slash-commands.js +0 -55
  211. package/docs/src/pages/spec.js +0 -207
  212. package/docs/src/pages/state.js +0 -102
  213. package/docs/src/pages/store.js +0 -203
  214. package/docs/src/pages/streaming.js +0 -107
  215. package/docs/src/pages/stripe.js +0 -194
  216. package/docs/src/pages/supabase.js +0 -324
  217. package/docs/src/pages/templates/andypixel.js +0 -260
  218. package/docs/src/pages/templates/blog-post.js +0 -226
  219. package/docs/src/pages/templates/bookworms.js +0 -227
  220. package/docs/src/pages/templates/coutumes.js +0 -237
  221. package/docs/src/pages/templates/journey.js +0 -186
  222. package/docs/src/pages/templates/mobile-app.js +0 -371
  223. package/docs/src/pages/templates/retrowheels.js +0 -355
  224. package/docs/src/pages/templates/shop.js +0 -268
  225. package/docs/src/pages/templates/sparkys.js +0 -264
  226. package/docs/src/pages/templates/suzukibento.js +0 -200
  227. package/docs/src/pages/templates/swish.js +0 -313
  228. package/docs/src/pages/templates.js +0 -534
  229. package/docs/src/pages/testing.js +0 -199
  230. package/docs/src/pages/validation.js +0 -138
  231. package/examples/.claude/commands/pulse-build.md +0 -7
  232. package/examples/.claude/commands/pulse-dev.md +0 -7
  233. package/examples/.claude/commands/pulse-start.md +0 -9
  234. package/examples/.claude/commands/pulse-stop.md +0 -5
  235. package/examples/.claude/commands/verify.md +0 -84
  236. package/examples/.claude/coverage-check.js +0 -196
  237. package/examples/.claude/pulse-checklist.md +0 -189
  238. package/examples/andypixel.css +0 -237
  239. package/examples/andypixel.js +0 -258
  240. package/examples/bookworms.css +0 -422
  241. package/examples/bookworms.js +0 -225
  242. package/examples/contact.js +0 -166
  243. package/examples/counter.js +0 -94
  244. package/examples/dev.server.js +0 -110
  245. package/examples/examples.test.js +0 -394
  246. package/examples/journey.css +0 -528
  247. package/examples/journey.js +0 -186
  248. package/examples/pricing.js +0 -262
  249. package/examples/products.js +0 -190
  250. package/examples/public/.pulse-ui-version +0 -1
  251. package/examples/public/pulse-ui.css +0 -3028
  252. package/examples/public/pulse-ui.js +0 -257
  253. package/examples/quiz.js +0 -208
  254. package/examples/retrowheels.css +0 -425
  255. package/examples/retrowheels.js +0 -353
  256. package/examples/shared.js +0 -84
  257. package/examples/suzukibento.css +0 -513
  258. package/examples/suzukibento.js +0 -200
  259. package/examples/swish.css +0 -394
  260. package/examples/swish.js +0 -311
  261. package/examples/todos.js +0 -162
  262. package/src/ui/fieldset.js +0 -37
  263. package/src/ui/phone-frame.js +0 -40
  264. package/src/ui/search.js +0 -78
  265. package/src/ui/segmented.js +0 -50
  266. package/src/ui/tooltip.js +0 -28
@@ -1,47 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { badge, cluster } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/cluster')
7
-
8
- export default {
9
- route: '/components/cluster',
10
- meta: {
11
- theme: 'light',
12
- title: 'Cluster — Pulse Docs',
13
- description: 'Cluster component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/cluster',
18
- prev,
19
- next,
20
- name: 'cluster',
21
- description: 'Flex row with wrapping. Groups inline elements horizontally — action buttons, badges, app store badges, stat rows.',
22
- content: `
23
- ${demo(
24
- cluster({
25
- gap: 'sm',
26
- content: badge({ label: 'Performance' }) + badge({ label: 'Accessibility' }) + badge({ label: 'Zero JS', variant: 'success' }) + badge({ label: 'SSR', variant: 'info' }),
27
- }),
28
- `cluster({ gap: 'sm', justify: 'center', content:
29
- badge({ label: 'Performance' }) +
30
- badge({ label: 'Zero JS', variant: 'success' }) +
31
- appBadge({ store: 'apple', href: url })
32
- })`
33
- )}
34
-
35
- ${table(
36
- ['Prop', 'Type', 'Default', 'Description'],
37
- [
38
- ['<code>content</code>', 'string (HTML)', '—', 'Raw HTML slot'],
39
- ['<code>gap</code>', 'string', "'md'", "'xs' · 'sm' · 'md' · 'lg'"],
40
- ['<code>justify</code>', 'string', "'start'", "'start' · 'center' · 'end' · 'between'"],
41
- ['<code>align</code>', 'string', "'center'","'start' · 'center' · 'end'"],
42
- ['<code>wrap</code>', 'boolean', 'true', 'Set false to prevent wrapping'],
43
- ]
44
- )}
45
- `,
46
- }),
47
- }
@@ -1,40 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { container } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/container')
7
-
8
- export default {
9
- route: '/components/container',
10
- meta: {
11
- theme: 'light',
12
- title: 'Container — Pulse Docs',
13
- description: 'Container component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/container',
18
- prev,
19
- next,
20
- name: 'container',
21
- description: 'Max-width wrapper with horizontal padding. The four size presets cover the most common layout widths.',
22
- content: `
23
- ${demo(
24
- container({ content: '<p style="background:var(--surface-2);border:1px dashed var(--border);padding:1rem;border-radius:6px;text-align:center;color:var(--muted)">Content constrained to 768px</p>', size: 'md' }),
25
- `container({ size: 'md', content: \`
26
- \${hero({ title: 'Hello' })}
27
- \${grid({ cols: 3, content: features })}
28
- \` })`
29
- )}
30
-
31
- ${table(
32
- ['Prop', 'Type', 'Default', 'Description'],
33
- [
34
- ['<code>content</code>', 'string (HTML)', '—', 'Raw HTML slot'],
35
- ['<code>size</code>', 'string', "'lg'", "'sm' (640px) · 'md' (768px) · 'lg' (1100px) · 'xl' (1280px)"],
36
- ]
37
- )}
38
- `,
39
- }),
40
- }
@@ -1,54 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { cta, button } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/cta')
7
-
8
- export default {
9
- route: '/components/cta',
10
- meta: {
11
- theme: 'light',
12
- title: 'CTA — Pulse Docs',
13
- description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- state: {},
17
- view: () => renderComponentPage({
18
- currentHref: '/components/cta',
19
- prev,
20
- next,
21
- name: 'cta',
22
- description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot. Sits inside <code>section()</code> + <code>container()</code> — adds no padding of its own.',
23
- content: `
24
- ${demo(
25
- cta({
26
- eyebrow: 'Get started today',
27
- title: 'Ready to build?',
28
- subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
29
- actions: button({ label: 'Start building →', href: '#', variant: 'primary', size: 'lg' }) +
30
- button({ label: 'View on GitHub', href: '#', variant: 'ghost', size: 'lg' }),
31
- }),
32
- `cta({
33
- eyebrow: 'Get started today',
34
- title: 'Ready to build?',
35
- subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
36
- actions: button({ label: 'Start building →', href: '/docs', variant: 'primary', size: 'lg' }) +
37
- button({ label: 'View on GitHub', href: 'https://github.com/...', variant: 'ghost', size: 'lg' }),
38
- })`
39
- )}
40
-
41
- ${table(
42
- ['Prop', 'Type', 'Default', 'Description'],
43
- [
44
- ['<code>eyebrow</code>', 'string', '—', 'Small label above the heading'],
45
- ['<code>title</code>', 'string', '—', 'Main heading'],
46
- ['<code>level</code>', 'number', '2', 'Heading tag for the title (1–6). Visual style is unchanged.'],
47
- ['<code>subtitle</code>', 'string', '—', 'Supporting paragraph'],
48
- ['<code>actions</code>', 'string (HTML)', '—', 'Raw HTML slot — typically button() calls'],
49
- ['<code>align</code>', 'string', "'center'", "'center' · 'left'"],
50
- ]
51
- )}
52
- `,
53
- }),
54
- }
@@ -1,37 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { divider, stack } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/divider')
7
-
8
- export default {
9
- route: '/components/divider',
10
- meta: {
11
- theme: 'light',
12
- title: 'Divider — Pulse Docs',
13
- description: 'Divider component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/divider',
18
- prev,
19
- next,
20
- name: 'divider',
21
- description: 'Horizontal rule for visual separation. With a <code>label</code>, the line splits either side of the text — the classic "or" pattern between login options.',
22
- content: `
23
- ${demo(
24
- stack({ gap: 'lg', content: divider() + divider({ label: 'or continue with' }) }),
25
- `divider()
26
- divider({ label: 'or continue with' })`
27
- )}
28
-
29
- ${table(
30
- ['Prop', 'Type', 'Default', 'Description'],
31
- [
32
- ['<code>label</code>', 'string', '—', 'Optional centred text — renders as <code>&lt;div&gt;</code> with role="separator" when provided, <code>&lt;hr&gt;</code> otherwise'],
33
- ]
34
- )}
35
- `,
36
- }),
37
- }
@@ -1,36 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { empty } from '../../../../src/ui/index.js'
4
-
5
- const { prev, next } = prevNext('/components/empty')
6
-
7
- export default {
8
- route: '/components/empty',
9
- meta: {
10
- theme: 'light',
11
- title: 'Empty — Pulse Docs',
12
- description: 'Empty state component for Pulse UI.',
13
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
14
- },
15
- view: () => renderComponentPage({
16
- currentHref: '/components/empty',
17
- prev,
18
- next,
19
- name: 'empty',
20
- description: 'Empty state placeholder. Shows a centred title, optional description, and an optional call-to-action when there is nothing to display.',
21
- content: `
22
- ${demo(
23
- empty({
24
- title: 'No results found',
25
- description: 'Try adjusting your search or clearing the filters.',
26
- action: { label: 'Clear filters', href: '#', variant: 'secondary' },
27
- }),
28
- `empty({
29
- title: 'No results found',
30
- description: 'Try adjusting your search or clearing the filters.',
31
- action: { label: 'Clear filters', href: '/products', variant: 'secondary' },
32
- })`
33
- )}
34
- `,
35
- }),
36
- }
@@ -1,60 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { feature } from '../../../../src/ui/index.js'
5
- import { iconZap, iconLock, iconPhone } from '../../../../src/ui/icons.js'
6
-
7
- const { prev, next } = prevNext('/components/feature')
8
-
9
- export default {
10
- route: '/components/feature',
11
- meta: {
12
- theme: 'light',
13
- title: 'Feature — Pulse Docs',
14
- description: 'Feature component for Pulse UI.',
15
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
16
- },
17
- view: () => renderComponentPage({
18
- currentHref: '/components/feature',
19
- prev,
20
- next,
21
- name: 'feature',
22
- description: 'Icon + title + description block. Compose three or four in a CSS grid for the standard "why us" section.',
23
- content: `
24
- ${demo(
25
- `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
26
- feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.' }) +
27
- feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.' }) +
28
- feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' }) +
29
- `</div>`,
30
- `import { iconZap, iconLock, iconPhone } from '@invisibleloop/pulse/ui'
31
-
32
- feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves.' })
33
- feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers, ever.' })
34
- feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' })`
35
- )}
36
-
37
- ${demo(
38
- `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
39
- feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.', center: true }) +
40
- feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.', center: true }) +
41
- feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.', center: true }) +
42
- `</div>`,
43
- `feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: '...', center: true })
44
- feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: '...', center: true })
45
- feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: '...', center: true })`
46
- )}
47
-
48
- ${table(
49
- ['Prop', 'Type', 'Default', 'Description'],
50
- [
51
- ['<code>icon</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG or emoji; displayed in an accent-tinted box'],
52
- ['<code>title</code>', 'string', '—', ''],
53
- ['<code>level</code>', 'number', '3', 'Heading tag for the title (1–6). Visual style is unchanged.'],
54
- ['<code>description</code>', 'string', '—', ''],
55
- ['<code>center</code>', 'boolean', 'false', 'Centre-align the icon, title, and description'],
56
- ]
57
- )}
58
- `,
59
- }),
60
- }
@@ -1,65 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { fieldset, input, textarea, grid, button } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/fieldset')
7
-
8
- export default {
9
- route: '/components/fieldset',
10
- meta: {
11
- theme: 'light',
12
- title: 'Fieldset — Pulse Docs',
13
- description: 'Semantic grouping of related form fields with an accessible legend.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/fieldset',
18
- prev,
19
- next,
20
- name: 'fieldset',
21
- description: 'Wraps related fields in a semantic <code>&lt;fieldset&gt;</code> with a styled <code>&lt;legend&gt;</code>. Screen readers announce the legend when focus enters the group — use it whenever fields belong together (address, card details, contact info). Works naturally inside a <code>&lt;form data-action="..."&gt;</code>.',
22
- content: `
23
- ${demo(
24
- `<form class="u-flex u-flex-col u-gap-4">` +
25
- fieldset({ legend: 'Your details', content:
26
- grid({ cols: 2, gap: 'md', content:
27
- input({ name: 'first', label: 'First name', required: true }) +
28
- input({ name: 'last', label: 'Last name', required: true })
29
- }) +
30
- input({ name: 'email', label: 'Email address', type: 'email', required: true })
31
- }) +
32
- fieldset({ legend: 'Message', content:
33
- textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })
34
- }) +
35
- button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true }) +
36
- `</form>`,
37
- `import { fieldset, grid, input, textarea, button } from '@invisibleloop/pulse/ui'
38
-
39
- \`<form data-action="submit" class="u-flex u-flex-col u-gap-4">
40
- \${fieldset({ legend: 'Your details', content: \`
41
- \${grid({ cols: 2, gap: 'md', content: \`
42
- \${input({ name: 'first', label: 'First name', required: true })}
43
- \${input({ name: 'last', label: 'Last name', required: true })}
44
- \` })}
45
- \${input({ name: 'email', label: 'Email address', type: 'email', required: true })}
46
- \` })}
47
- \${fieldset({ legend: 'Message', content: \`
48
- \${textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })}
49
- \` })}
50
- \${button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true })}
51
- </form>\``
52
- )}
53
-
54
- ${table(
55
- ['Prop', 'Type', 'Default', 'Description'],
56
- [
57
- ['<code>legend</code>', 'string', '—', 'Group label — rendered as <code>&lt;legend&gt;</code>, announced by screen readers on focus'],
58
- ['<code>content</code>', 'string', '—', 'Raw HTML slot — input(), select(), textarea(), grid(), etc.'],
59
- ['<code>gap</code>', 'string', 'md', 'Vertical gap between fields: xs / sm / md / lg'],
60
- ['<code>class</code>', 'string', '—', 'Extra classes on the <code>&lt;fieldset&gt;</code> element'],
61
- ]
62
- )}
63
- `,
64
- }),
65
- }
@@ -1,128 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table, callout } from '../../lib/layout.js'
4
- import { fileUpload } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/file-upload')
7
-
8
- export default {
9
- route: '/components/file-upload',
10
- meta: {
11
- theme: 'light',
12
- title: 'File Upload — Pulse Docs',
13
- description: 'Drag-and-drop file upload zone component for Pulse UI.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- state: { fileName: '', fileSize: '' },
17
- mutations: {
18
- setFile: (state, e) => {
19
- const file = e.target.files[0]
20
- return file
21
- ? { fileName: file.name, fileSize: (file.size / 1024).toFixed(1) + ' KB' }
22
- : { fileName: '', fileSize: '' }
23
- },
24
- },
25
- view: (state) => renderComponentPage({
26
- currentHref: '/components/file-upload',
27
- prev,
28
- next,
29
- name: 'fileUpload',
30
- description: 'Drag-and-drop upload zone with a hidden <code>&lt;input type="file"&gt;</code>. Clicking or dropping files opens the picker. Requires <code>pulse-ui.js</code> for drag highlighting and click-to-open behaviour.',
31
- content: `
32
-
33
- <h2 class="doc-h2" id="basic">Basic</h2>
34
- ${demo(
35
- fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' }) +
36
- (state.fileName ? `<p class="u-mt-3 u-text-sm">Selected: <strong>${state.fileName}</strong> (${state.fileSize})</p>` : ''),
37
- `fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' })`,
38
- { col: true }
39
- )}
40
-
41
- <h2 class="doc-h2" id="accept">Accepted file types</h2>
42
- ${demo(
43
- fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' }),
44
- `fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' })`,
45
- { col: true }
46
- )}
47
-
48
- <h2 class="doc-h2" id="multiple">Multiple files</h2>
49
- ${demo(
50
- fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' }),
51
- `fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' })`,
52
- { col: true }
53
- )}
54
-
55
- <h2 class="doc-h2" id="error">Error state</h2>
56
- ${demo(
57
- fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' }),
58
- `fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' })`,
59
- { col: true }
60
- )}
61
-
62
- <h2 class="doc-h2" id="disabled">Disabled</h2>
63
- ${demo(
64
- fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' }),
65
- `fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' })`,
66
- { col: true }
67
- )}
68
-
69
- <h2 class="doc-h2" id="with-action">Reading the file in an action</h2>
70
- <p class="u-mb-4 u-text-sm u-color-muted">Use the component inside a <code>&lt;form data-action="..."&gt;</code>. The file is available in FormData under <code>name</code>.</p>
71
- ${demo(
72
- fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true }),
73
- `// view
74
- \`<form data-action="upload" class="u-flex u-flex-col u-gap-4">
75
- \${fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true })}
76
- \${button({ label: 'Upload', type: 'submit', variant: 'primary' })}
77
- </form>\`
78
-
79
- // action
80
- upload: {
81
- onStart: (state) => ({ status: 'loading' }),
82
- run: async (state, serverState, formData) => {
83
- const file = formData.get('attachment') // File object
84
- // process file...
85
- },
86
- onSuccess: (state) => ({ status: 'success' }),
87
- onError: (state, err) => ({ status: 'error', message: err.message }),
88
- }`,
89
- { col: true }
90
- )}
91
-
92
- <h2 class="doc-h2" id="with-mutation">Live file name via mutation</h2>
93
- <p class="u-mb-4 u-text-sm u-color-muted">Use <code>event: 'change:mutationName'</code> to capture the selected filename in state immediately — without a form submission.</p>
94
- ${demo(
95
- fileUpload({ name: 'photo', label: 'Photo', event: 'change:setFile', accept: 'image/*' }),
96
- `// state
97
- { fileName: '' }
98
-
99
- // mutation — e.target.files[0] gives the File object
100
- setFile: (state, e) => ({ fileName: e.target.files[0]?.name ?? '' })
101
-
102
- // view
103
- fileUpload({ name: 'photo', label: 'Photo', accept: 'image/*', event: 'change:setFile' })
104
- \${state.fileName ? \`<p class="u-mt-2 u-text-sm">\${state.fileName}</p>\` : ''}`,
105
- { col: true }
106
- )}
107
-
108
- ${callout('note', 'The file object itself cannot be stored in Pulse state — state must be serialisable. Store the filename or a preview URL (via <code>URL.createObjectURL</code>) instead, and upload the file in an action via FormData.')}
109
-
110
- ${table(
111
- ['Prop', 'Type', 'Default', 'Description'],
112
- [
113
- ['<code>name</code>', 'string', '—', 'Field name — file available in FormData under this key'],
114
- ['<code>label</code>', 'string', '—', 'Visible label text'],
115
- ['<code>hint</code>', 'string', '—', 'Helper text, e.g. accepted types and max size'],
116
- ['<code>error</code>', 'string', '—', 'Validation error message'],
117
- ['<code>accept</code>', 'string', '—', 'Accepted MIME types or extensions, e.g. <code>image/*</code> or <code>.pdf,.docx</code>'],
118
- ['<code>multiple</code>', 'boolean', 'false', 'Allow selecting multiple files'],
119
- ['<code>required</code>', 'boolean', 'false', ''],
120
- ['<code>disabled</code>', 'boolean', 'false', ''],
121
- ['<code>event</code>', 'string', '—', 'data-event on the input — use <code>change:mutationName</code> to capture file selection in state'],
122
- ['<code>id</code>', 'string', '—', 'Override the generated <code>id</code>'],
123
- ['<code>class</code>', 'string', '—', ''],
124
- ]
125
- )}
126
- `,
127
- }),
128
- }
@@ -1,58 +0,0 @@
1
- import { renderComponentPage, demo } from '../../lib/component-page.js'
2
- import { prevNext } from '../../lib/nav.js'
3
- import { table } from '../../lib/layout.js'
4
- import { footer } from '../../../../src/ui/index.js'
5
-
6
- const { prev, next } = prevNext('/components/footer')
7
-
8
- export default {
9
- route: '/components/footer',
10
- meta: {
11
- theme: 'light',
12
- title: 'Footer — Pulse Docs',
13
- description: 'Accessible site footer with logo slot, navigation links, and legal text.',
14
- styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
15
- },
16
- view: () => renderComponentPage({
17
- currentHref: '/components/footer',
18
- prev,
19
- next,
20
- name: 'footer',
21
- description: 'Accessible site footer with logo slot, navigation links, and legal text. Handles responsive stacking, hover states, and focus styles automatically.',
22
- content: `
23
- ${demo(
24
- footer({
25
- logo: 'MyApp',
26
- logoHref: '/',
27
- links: [
28
- { label: 'Docs', href: '/docs' },
29
- { label: 'Pricing', href: '/pricing' },
30
- { label: 'Blog', href: '/blog' },
31
- { label: 'GitHub', href: 'https://github.com' },
32
- ],
33
- legal: '© 2026 MyApp Ltd.',
34
- }),
35
- `footer({
36
- logo: 'MyApp',
37
- logoHref: '/',
38
- links: [
39
- { label: 'Docs', href: '/docs' },
40
- { label: 'Pricing', href: '/pricing' },
41
- { label: 'GitHub', href: 'https://github.com' },
42
- ],
43
- legal: '© 2026 MyApp Ltd.',
44
- })`
45
- )}
46
-
47
- ${table(
48
- ['Prop', 'Type', 'Default', 'Description'],
49
- [
50
- ['<code>logo</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG, img, or text'],
51
- ['<code>logoHref</code>', 'string', "'/'", 'Logo link destination'],
52
- ['<code>links</code>', 'array', '[]', '[{label, href}] — footer navigation links'],
53
- ['<code>legal</code>', 'string', '—', 'Copyright / legal text'],
54
- ]
55
- )}
56
- `,
57
- }),
58
- }