@invisibleloop/pulse 0.1.28 → 0.1.29

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 (117) hide show
  1. package/.claude/settings.local.json +113 -0
  2. package/.github/workflows/publish.yml +11 -21
  3. package/docs/public/.pulse-ui-version +1 -1
  4. package/docs/public/docs.css +19 -1
  5. package/docs/public/pulse-ui.css +1 -0
  6. package/docs/server.js +5 -2
  7. package/docs/src/lib/highlight.js +57 -13
  8. package/docs/src/lib/layout.js +5 -2
  9. package/docs/src/pages/faq.js +5 -2
  10. package/docs/src/pages/home.js +9 -5
  11. package/docs/src/pages/meta.js +21 -0
  12. package/docs/src/pages/routing.js +12 -1
  13. package/package.json +1 -1
  14. package/src/agent/guide-routing.md +20 -0
  15. package/src/agent/guide-spec.md +9 -1
  16. package/src/cli/scaffold.js +63 -2
  17. package/src/server/index.js +21 -6
  18. package/src/server/server.test.js +47 -0
  19. package/docs/public/dist/accessibility.boot-5DVTARJU.js +0 -115
  20. package/docs/public/dist/actions.boot-P66HKQEM.js +0 -164
  21. package/docs/public/dist/auth.boot-IMAJAUPH.js +0 -140
  22. package/docs/public/dist/caching.boot-DVR6KDE7.js +0 -53
  23. package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +0 -11
  24. package/docs/public/dist/components--alert.boot-GCEXOZAC.js +0 -6
  25. package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +0 -6
  26. package/docs/public/dist/components--avatar.boot-PSW24EVA.js +0 -5
  27. package/docs/public/dist/components--badge.boot-TYDY2RMK.js +0 -7
  28. package/docs/public/dist/components--banner.boot-EI5PZSZK.js +0 -7
  29. package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +0 -34
  30. package/docs/public/dist/components--button.boot-J54BQM2E.js +0 -23
  31. package/docs/public/dist/components--card.boot-PZGNDIB6.js +0 -138
  32. package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +0 -12
  33. package/docs/public/dist/components--charts.boot-2EOYQWKL.js +0 -108
  34. package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +0 -54
  35. package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +0 -9
  36. package/docs/public/dist/components--code-window.boot-2GR2DV33.js +0 -20
  37. package/docs/public/dist/components--container.boot-7LOOGK2K.js +0 -5
  38. package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +0 -11
  39. package/docs/public/dist/components--divider.boot-3NI2C3QG.js +0 -6
  40. package/docs/public/dist/components--empty.boot-YX2UR3PV.js +0 -7
  41. package/docs/public/dist/components--feature.boot-MUD7NSUO.js +0 -13
  42. package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +0 -19
  43. package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +0 -52
  44. package/docs/public/dist/components--footer.boot-EYUK5FRG.js +0 -14
  45. package/docs/public/dist/components--grid.boot-URDQVDDR.js +0 -59
  46. package/docs/public/dist/components--heading.boot-BPQKU43E.js +0 -44
  47. package/docs/public/dist/components--hero.boot-4RAPRGAB.js +0 -17
  48. package/docs/public/dist/components--icons.boot-ZITNU5JP.js +0 -68
  49. package/docs/public/dist/components--image.boot-XEEGHQZF.js +0 -19
  50. package/docs/public/dist/components--input.boot-SGASZG5K.js +0 -7
  51. package/docs/public/dist/components--list.boot-W3XC5MHD.js +0 -55
  52. package/docs/public/dist/components--media.boot-5VFIETZO.js +0 -13
  53. package/docs/public/dist/components--modal.boot-RZUYXBN2.js +0 -47
  54. package/docs/public/dist/components--nav.boot-ODBOHU7O.js +0 -33
  55. package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +0 -21
  56. package/docs/public/dist/components--progress.boot-GHAGYZOK.js +0 -30
  57. package/docs/public/dist/components--prose.boot-QANJL6JI.js +0 -67
  58. package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +0 -22
  59. package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +0 -75
  60. package/docs/public/dist/components--rating.boot-QBAN6DEL.js +0 -38
  61. package/docs/public/dist/components--search.boot-PXH5O5AG.js +0 -17
  62. package/docs/public/dist/components--section.boot-AQGIYHWW.js +0 -12
  63. package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +0 -33
  64. package/docs/public/dist/components--select.boot-47X5RHOC.js +0 -10
  65. package/docs/public/dist/components--slider.boot-PSRRX7XL.js +0 -47
  66. package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +0 -22
  67. package/docs/public/dist/components--stack.boot-DI4NJXBF.js +0 -9
  68. package/docs/public/dist/components--stat.boot-QMFUWBQT.js +0 -9
  69. package/docs/public/dist/components--stepper.boot-34PP2NEV.js +0 -22
  70. package/docs/public/dist/components--table.boot-FCQGSFIQ.js +0 -11
  71. package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +0 -11
  72. package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +0 -4
  73. package/docs/public/dist/components--timeline.boot-26LN52P2.js +0 -95
  74. package/docs/public/dist/components--toggle.boot-IQQEI76S.js +0 -29
  75. package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +0 -9
  76. package/docs/public/dist/components.boot-SE6PQ4P7.js +0 -103
  77. package/docs/public/dist/config.boot-DTRRWUE6.js +0 -126
  78. package/docs/public/dist/constraints.boot-DUHDZBMC.js +0 -71
  79. package/docs/public/dist/deploy.boot-SLAD3NI2.js +0 -163
  80. package/docs/public/dist/docs-8e3d4b5c.css +0 -1
  81. package/docs/public/dist/extending.boot-UA3CN243.js +0 -159
  82. package/docs/public/dist/faq.boot-6EQAWLQR.js +0 -43
  83. package/docs/public/dist/getting-started.boot-TDKIFL5U.js +0 -86
  84. package/docs/public/dist/guard.boot-AUHAWTG4.js +0 -80
  85. package/docs/public/dist/home.boot-BVQXRH32.js +0 -383
  86. package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +0 -104
  87. package/docs/public/dist/hydration.boot-JRM6IPJL.js +0 -78
  88. package/docs/public/dist/images.boot-M6ZVKTZS.js +0 -80
  89. package/docs/public/dist/manifest.json +0 -94
  90. package/docs/public/dist/meta.boot-7NXGPHR4.js +0 -79
  91. package/docs/public/dist/mutations.boot-F6F43UDX.js +0 -79
  92. package/docs/public/dist/navigation.boot-AOXWS3ZF.js +0 -57
  93. package/docs/public/dist/performance.boot-C3UPCOBK.js +0 -98
  94. package/docs/public/dist/persist.boot-WT32PQOQ.js +0 -61
  95. package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +0 -63
  96. package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +0 -31
  97. package/docs/public/dist/pulse-ui-81a85c03.css +0 -1
  98. package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +0 -104
  99. package/docs/public/dist/routing.boot-FNX5FDGH.js +0 -70
  100. package/docs/public/dist/runtime-B73WLANC.js +0 -1
  101. package/docs/public/dist/runtime-KO4BHUQ3.js +0 -49
  102. package/docs/public/dist/runtime-L2HNXIHW.js +0 -59
  103. package/docs/public/dist/runtime-QFURDKA2.js +0 -5
  104. package/docs/public/dist/runtime-UVPXO4IR.js +0 -375
  105. package/docs/public/dist/runtime-VMJA3Z4N.js +0 -10
  106. package/docs/public/dist/runtime-ZJ4FXT5O.js +0 -11
  107. package/docs/public/dist/server-api.boot-K7X3LCFB.js +0 -219
  108. package/docs/public/dist/server-data.boot-Y7HQYC4R.js +0 -157
  109. package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +0 -26
  110. package/docs/public/dist/spec.boot-2WU7ZHCV.js +0 -159
  111. package/docs/public/dist/state.boot-B24GUE3R.js +0 -73
  112. package/docs/public/dist/store.boot-TLIB4XHH.js +0 -150
  113. package/docs/public/dist/streaming.boot-W2DZSMW4.js +0 -80
  114. package/docs/public/dist/stripe.boot-QN3C2GEL.js +0 -164
  115. package/docs/public/dist/supabase.boot-BG4XXLZE.js +0 -303
  116. package/docs/public/dist/testing.boot-6U4WKMTE.js +0 -130
  117. package/docs/public/dist/validation.boot-PQHYGW5B.js +0 -100
@@ -1,95 +0,0 @@
1
- import{a as e,b as m}from"./runtime-ZJ4FXT5O.js";import{$a as o,Ca as r,a as u,ab as t,b as a,c as l}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as d,h as i}from"./runtime-L2HNXIHW.js";import{a as s,b as p}from"./runtime-B73WLANC.js";var{prev:g,next:v}=d("/components/timeline"),b='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',h='<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',y='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',c={route:"/components/timeline",meta:{title:"Timeline \u2014 Pulse Docs",description:"Timeline component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>m({currentHref:"/components/timeline",prev:g,next:v,name:"timeline",description:"Ordered sequence of events or steps connected by a line. Supports vertical (default) and horizontal orientations. Each item accepts a raw HTML content slot \u2014 pass any text, component, or markup.",content:`
2
-
3
- <h2 class="doc-h2" id="vertical">Vertical (default)</h2>
4
- <p>Steps flow downward. The connector line links each dot to the next.</p>
5
- ${e(t({items:[{label:"Jan 2023",content:'<strong style="color:var(--ui-text)">Project kicked off</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Initial scope agreed with stakeholders. Repository created.</p>'},{label:"Mar 2023",content:'<strong style="color:var(--ui-text)">Alpha release</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Internal testing with 12 pilot users. Core features stable.</p>'},{label:"Jun 2023",content:'<strong style="color:var(--ui-text)">Public beta</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Open sign-up enabled. 400 users in first week.</p>'},{label:"Sep 2023",content:'<strong style="color:var(--ui-text)">v1.0 launched</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Billing live, docs published, ProductHunt launch.</p>'}]}),`timeline({
6
- items: [
7
- { label: 'Jan 2023', content: '<strong>Project kicked off</strong><p>Initial scope agreed.</p>' },
8
- { label: 'Mar 2023', content: '<strong>Alpha release</strong><p>Internal testing with 12 pilot users.</p>' },
9
- { label: 'Jun 2023', content: '<strong>Public beta</strong><p>Open sign-up enabled. 400 users in first week.</p>' },
10
- { label: 'Sep 2023', content: '<strong>v1.0 launched</strong><p>Billing live, docs published.</p>' },
11
- ],
12
- })`)}
13
-
14
- <h2 class="doc-h2" id="horizontal">Horizontal</h2>
15
- <p>Steps flow left to right \u2014 good for process flows or numbered stages.</p>
16
- ${e(t({direction:"horizontal",items:[{label:"Step 1",content:'<p style="color:var(--ui-muted);margin:0;font-size:.85rem">Sign up</p>'},{label:"Step 2",content:'<p style="color:var(--ui-muted);margin:0;font-size:.85rem">Connect data</p>'},{label:"Step 3",content:'<p style="color:var(--ui-muted);margin:0;font-size:.85rem">Invite team</p>'},{label:"Step 4",content:'<p style="color:var(--ui-muted);margin:0;font-size:.85rem">Go live</p>'}]}),`timeline({
17
- direction: 'horizontal',
18
- items: [
19
- { label: 'Step 1', content: '<p>Sign up</p>' },
20
- { label: 'Step 2', content: '<p>Connect data</p>' },
21
- { label: 'Step 3', content: '<p>Invite team</p>' },
22
- { label: 'Step 4', content: '<p>Go live</p>' },
23
- ],
24
- })`)}
25
-
26
- <h2 class="doc-h2" id="dot-colors">Dot colours</h2>
27
- <p>Use <code>dotColor</code> to convey status: <code>'accent'</code> \xB7 <code>'success'</code> \xB7 <code>'warning'</code> \xB7 <code>'error'</code> \xB7 <code>'muted'</code>.</p>
28
- ${e(t({items:[{dotColor:"success",label:"Deployed",content:'<p style="color:var(--ui-muted);margin:0">Production deploy completed successfully.</p>'},{dotColor:"success",label:"Tested",content:'<p style="color:var(--ui-muted);margin:0">All 92 tests passed. Coverage 98%.</p>'},{dotColor:"warning",label:"Review",content:'<p style="color:var(--ui-muted);margin:0">Awaiting sign-off from design lead.</p>'},{dotColor:"error",label:"Blocked",content:'<p style="color:var(--ui-muted);margin:0">Dependency on payment API not yet ready.</p>'},{dotColor:"muted",label:"Planned",content:'<p style="color:var(--ui-muted);margin:0">Mobile app release \u2014 Q1 2025.</p>'}]}),`timeline({
29
- items: [
30
- { dotColor: 'success', label: 'Deployed', content: '...' },
31
- { dotColor: 'success', label: 'Tested', content: '...' },
32
- { dotColor: 'warning', label: 'Review', content: '...' },
33
- { dotColor: 'error', label: 'Blocked', content: '...' },
34
- { dotColor: 'muted', label: 'Planned', content: '...' },
35
- ],
36
- })`)}
37
-
38
- <h2 class="doc-h2" id="icon-dots">Icon dots</h2>
39
- <p>Pass any SVG or emoji as <code>dot</code>. The dot grows to accommodate the content and uses a tinted background matching its colour variant.</p>
40
- ${e(t({items:[{dot:b,dotColor:"success",label:"Completed",content:'<strong style="color:var(--ui-text)">Onboarding</strong><p style="color:var(--ui-muted);margin:.2rem 0 0">Profile set up, preferences saved.</p>'},{dot:h,dotColor:"accent",label:"Milestone",content:'<strong style="color:var(--ui-text)">First 1,000 users</strong><p style="color:var(--ui-muted);margin:.2rem 0 0">Reached organically in 18 days.</p>'},{dot:y,dotColor:"warning",label:"Incident",content:'<strong style="color:var(--ui-text)">Partial outage</strong><p style="color:var(--ui-muted);margin:.2rem 0 0">CDN edge node failed \u2014 resolved in 4 minutes.</p>'}]}),`timeline({
41
- items: [
42
- {
43
- dot: checkSvg,
44
- dotColor: 'success',
45
- label: 'Completed',
46
- content: '<strong>Onboarding</strong><p>Profile set up, preferences saved.</p>',
47
- },
48
- {
49
- dot: starSvg,
50
- dotColor: 'accent',
51
- label: 'Milestone',
52
- content: '<strong>First 1,000 users</strong>',
53
- },
54
- ],
55
- })`)}
56
-
57
- <h2 class="doc-h2" id="rich-content">Rich content slot</h2>
58
- <p>The <code>content</code> slot accepts any HTML \u2014 including other Pulse components like <code>card()</code>, <code>badge()</code>, or <code>stat()</code>.</p>
59
- ${e(t({items:[{dotColor:"success",label:"Q1 2024",content:l({title:"Series A closed",content:'<div style="display:flex;gap:1.5rem;flex-wrap:wrap">'+r({label:"Raised",value:"$4.2M"})+r({label:"Valuation",value:"$18M"})+r({label:"Investors",value:"6"})+"</div>"})},{dotColor:"accent",label:"Q3 2024",content:l({title:"Product launch",content:'<p style="color:var(--ui-muted);margin:0 0 .75rem">Shipped v1.0 to general availability. Three tiers, 14-day trial.</p><div style="display:flex;gap:.5rem;flex-wrap:wrap">'+a({label:"Launch",variant:"info"})+a({label:"Billing live",variant:"success"})+"</div>"})},{dotColor:"muted",label:"Q1 2025 (planned)",content:l({title:"Mobile apps",content:'<p style="color:var(--ui-muted);margin:0 0 .75rem">iOS and Android apps in development. Public beta planned.</p>'+u({label:"Join waitlist",size:"sm",variant:"secondary"})})}]}),`timeline({
60
- items: [
61
- {
62
- dotColor: 'success',
63
- label: 'Q1 2024',
64
- content: card({
65
- title: 'Series A closed',
66
- content: stat({ label: 'Raised', value: '$4.2M' }) + ...,
67
- }),
68
- },
69
- {
70
- dotColor: 'accent',
71
- label: 'Q3 2024',
72
- content: card({
73
- title: 'Product launch',
74
- content: '<p>Shipped v1.0 to general availability.</p>' +
75
- badge({ label: 'Billing live', variant: 'success' }),
76
- }),
77
- },
78
- ],
79
- })`)}
80
-
81
- <h2 class="doc-h2" id="item-fn">Using timelineItem()</h2>
82
- <p>Build items individually with <code>timelineItem()</code> and pass the joined HTML as <code>content</code>. Useful for dynamic or conditional lists.</p>
83
- ${e(t({content:o({dotColor:"success",label:"Done",content:'<p style="color:var(--ui-muted);margin:0">Design system tokens agreed</p>'})+o({dotColor:"success",label:"Done",content:'<p style="color:var(--ui-muted);margin:0">Component library built</p>'})+o({dotColor:"accent",label:"Current",content:'<p style="color:var(--ui-muted);margin:0">Documentation in progress</p>'})+o({dotColor:"muted",label:"Next",content:'<p style="color:var(--ui-muted);margin:0">Public launch</p>'})}),`timeline({
84
- content:
85
- timelineItem({ dotColor: 'success', label: 'Done', content: '...' }) +
86
- timelineItem({ dotColor: 'success', label: 'Done', content: '...' }) +
87
- timelineItem({ dotColor: 'accent', label: 'Current', content: '...' }) +
88
- timelineItem({ dotColor: 'muted', label: 'Next', content: '...' }),
89
- })`)}
90
-
91
- ${i(["Prop","Type","Default",""],[["<code>direction</code>","string","'vertical'","'vertical' \xB7 'horizontal'"],["<code>items</code>","array","[]","Array of <code>timelineItem</code> option objects"],["<code>content</code>","string (HTML)","\u2014","Raw HTML alternative to <code>items</code> \u2014 use with <code>timelineItem()</code>"]])}
92
-
93
- <h3 class="doc-h3" style="margin-top:2rem">timelineItem() props</h3>
94
- ${i(["Prop","Type","Default",""],[["<code>content</code>","string (HTML)","\u2014","Raw HTML body \u2014 accepts any component output"],["<code>label</code>","string","\u2014","Timestamp or step label (escaped)"],["<code>dot</code>","string (HTML)","\u2014","Raw HTML inside the dot \u2014 SVG or emoji; grows the dot to 2rem"],["<code>dotColor</code>","string","'accent'","'accent' \xB7 'success' \xB7 'warning' \xB7 'error' \xB7 'muted'"]])}
95
- `})};var n=document.getElementById("pulse-root");n&&!n.dataset.pulseMounted&&(n.dataset.pulseMounted="1",s(c,n,window.__PULSE_SERVER__||{},{ssr:!0}),p(n,s));var M=c;export{M as default};
@@ -1,29 +0,0 @@
1
- import{a as o,b as r}from"./runtime-ZJ4FXT5O.js";import{Qa as a,bb as e}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as n,h as s,i as c}from"./runtime-L2HNXIHW.js";import{a as i,b as l}from"./runtime-B73WLANC.js";var{prev:m,next:u}=n("/components/toggle"),d={route:"/components/toggle",meta:{title:"Toggle \u2014 Pulse Docs",description:"iOS-style switch toggle component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>r({currentHref:"/components/toggle",prev:m,next:u,name:"toggle",description:'iOS-style switch that renders a visually hidden <code>&lt;input type="checkbox"&gt;</code> with a custom track and thumb. No JavaScript required \u2014 state is read from FormData on submission.',content:`
2
-
3
- <h2 class="doc-h2" id="default">Default</h2>
4
- ${o(a({gap:"md",content:e({name:"notifications",label:"Email notifications"})+e({name:"updates",label:"Product updates",checked:!0})}),`toggle({ name: 'notifications', label: 'Email notifications' })
5
- toggle({ name: 'updates', label: 'Product updates', checked: true })`,{col:!0})}
6
-
7
- <h2 class="doc-h2" id="hint">With hint</h2>
8
- <p>Use <code>hint</code> to add supporting text below the switch.</p>
9
- ${o(a({gap:"lg",content:e({name:"marketing",label:"Marketing emails",hint:"Receive tips, product news, and special offers."})+e({name:"digest",label:"Weekly digest",hint:"A summary of activity sent every Monday morning.",checked:!0})}),`toggle({
10
- name: 'marketing',
11
- label: 'Marketing emails',
12
- hint: 'Receive tips, product news, and special offers.',
13
- })
14
- toggle({
15
- name: 'digest',
16
- label: 'Weekly digest',
17
- hint: 'A summary of activity sent every Monday morning.',
18
- checked: true,
19
- })`,{col:!0})}
20
-
21
- <h2 class="doc-h2" id="disabled">Disabled</h2>
22
- ${o(a({gap:"md",content:e({name:"a",label:"Off and disabled",disabled:!0})+e({name:"b",label:"On and disabled",disabled:!0,checked:!0})}),`toggle({ name: 'a', label: 'Off and disabled', disabled: true })
23
- toggle({ name: 'b', label: 'On and disabled', disabled: true, checked: true })`,{col:!0})}
24
-
25
- <h2 class="doc-h2" id="in-forms">In forms</h2>
26
- ${c("note","The switch submits as <code>'on'</code> under its <code>name</code> when checked. When unchecked, the field is absent from FormData entirely \u2014 the same behaviour as a native checkbox. Read it with <code>formData.get('name') === 'on'</code>.")}
27
-
28
- ${s(["Prop","Type","Default",""],[["<code>name</code>","string","\u2014","Field name \u2014 submitted in FormData"],["<code>label</code>","string","\u2014","Visible label text"],["<code>checked</code>","boolean","false","Initial on/off state"],["<code>disabled</code>","boolean","false",""],["<code>hint</code>","string","\u2014","Helper text below the switch"],["<code>id</code>","string","\u2014","Override the generated <code>id</code>"],["<code>class</code>","string","\u2014",""]])}
29
- `})};var t=document.getElementById("pulse-root");t&&!t.dataset.pulseMounted&&(t.dataset.pulseMounted="1",i(d,t,window.__PULSE_SERVER__||{},{ssr:!0}),l(t,i));var w=d;export{w as default};
@@ -1,9 +0,0 @@
1
- import{a,b as l}from"./runtime-ZJ4FXT5O.js";import{Ra as c,Va as o,a as t}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import"./runtime-QFURDKA2.js";import{a as i,h as p}from"./runtime-L2HNXIHW.js";import{a as r,b as s}from"./runtime-B73WLANC.js";var{prev:d,next:m}=i("/components/tooltip"),n={route:"/components/tooltip",meta:{title:"Tooltip \u2014 Pulse Docs",description:"Tooltip component for Pulse UI.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>l({currentHref:"/components/tooltip",prev:d,next:m,name:"tooltip",description:"CSS-powered tooltip that wraps any element. No JavaScript required \u2014 the bubble appears on hover and <code>:focus-within</code>. Supports four placements.",content:`
2
- ${a(c({gap:"lg",justify:"center",content:o({content:"This appears on top",position:"top",trigger:t({label:"Top",variant:"secondary"})})+o({content:"This appears below",position:"bottom",trigger:t({label:"Bottom",variant:"secondary"})})+o({content:"This appears to the left",position:"left",trigger:t({label:"Left",variant:"secondary"})})+o({content:"This appears to the right",position:"right",trigger:t({label:"Right",variant:"secondary"})})}),`tooltip({
3
- content: 'This appears on top',
4
- position: 'top', // top | bottom | left | right
5
- trigger: button({ label: 'Hover me', variant: 'secondary' }),
6
- })`)}
7
-
8
- ${p(["Prop","Type","Default",""],[["<code>content</code>","string","\u2014","Tooltip text (plain text only)"],["<code>trigger</code>","string (HTML)","\u2014","Raw HTML slot \u2014 the element the tooltip wraps"],["<code>position</code>","<code>top | bottom | left | right</code>","<code>top</code>",""],["<code>class</code>","string","\u2014",""]])}
9
- `})};var e=document.getElementById("pulse-root");e&&!e.dataset.pulseMounted&&(e.dataset.pulseMounted="1",r(n,e,window.__PULSE_SERVER__||{},{ssr:!0}),s(e,r));var w=n;export{w as default};
@@ -1,103 +0,0 @@
1
- import{a}from"./runtime-QFURDKA2.js";import{a as i,b as d,c as l,d as p,e as o,g as t,h as e,i as c}from"./runtime-L2HNXIHW.js";import{a as r,b as u}from"./runtime-B73WLANC.js";var{prev:h,next:m}=i("/components"),s={route:"/components",meta:{title:"Component Library \u2014 Pulse Docs",description:"Server-rendered UI components for Pulse \u2014 button, card, input, alert, stat, avatar, table and more. Fully accessible, mobile-ready, zero client JS.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>d({currentHref:"/components",prev:h,next:m,content:`
2
- ${l("Component Library")}
3
- ${p("Server-rendered building blocks. Each component is a pure function that returns an HTML string \u2014 no client-side JS, no build step, accessible and mobile-ready by default. The output is the same every time the same props are passed.")}
4
-
5
- ${o("setup","Setup")}
6
- <p>Components are imported directly into the spec file alongside the stylesheet reference in <code>meta.styles</code>. No build step, no registration.</p>
7
- ${t(a(`import { button, card, input } from '@invisibleloop/pulse/ui'
8
- import { escHtml } from '@invisibleloop/pulse/html'
9
-
10
- export default {
11
- route: '/example',
12
- meta: {
13
- title: 'Example',
14
- styles: ['/pulse-ui.css', '/app.css'],
15
- },
16
- view: () => \`
17
- <main id="main-content">
18
- \${card({
19
- title: 'Welcome',
20
- content: button({ label: 'Get started', href: '/start' }),
21
- })}
22
- </main>
23
- \`,
24
- }`,"js"))}
25
-
26
- ${o("theming","Theming")}
27
- <p>All visual values flow through CSS custom properties. Override any <code>--ui-*</code> token in <code>:root</code> in your <code>app.css</code> to retheme the entire library at once.</p>
28
- ${t(a(`/* app.css */
29
- :root {
30
- --ui-accent: #6366f1;
31
- --ui-accent-hover: #818cf8;
32
- --ui-accent-dim: rgba(99, 102, 241, .12);
33
- --ui-radius: 6px;
34
- --ui-bg: #ffffff;
35
- --ui-surface: #f9fafb;
36
- --ui-text: #111827;
37
- --ui-muted: #6b7280;
38
- --ui-border: #e5e7eb;
39
- }`,"css"))}
40
- <p>New variants follow the <code>ui-btn--{name}</code> CSS modifier pattern. A brand-coloured button, for example, is just a new class in <code>app.css</code> \u2014 the component itself stays untouched.</p>
41
- ${t(a(`.ui-btn--brand {
42
- background: var(--brand);
43
- color: #fff;
44
- border: none;
45
- }
46
- .ui-btn--brand:hover:not(.ui-btn--disabled) {
47
- background: var(--brand-hover);
48
- }`,"css"))}
49
-
50
- ${o("components","Components")}
51
- <p>Each component has its own page with full demos, code examples, and a props reference.</p>
52
-
53
- <h3 class="doc-h3">UI</h3>
54
- ${e(["Component","Description"],[['<a href="/components/button">button</a>',"Renders as <code>&lt;button&gt;</code> or <code>&lt;a&gt;</code>. Four variants, three sizes."],['<a href="/components/badge">badge</a>',"Inline status label. Five semantic colour variants."],['<a href="/components/card">card</a>',"Content surface with title, body, and optional footer."],['<a href="/components/input">input</a>',"Labelled text input with hint and error support."],['<a href="/components/fieldset">fieldset</a>',"Semantic grouping of related fields with an accessible legend."],['<a href="/components/select">select</a>',"Styled select with option groups and current-value support."],['<a href="/components/textarea">textarea</a>',"Multi-line input with label, hint, and error."],['<a href="/components/alert">alert</a>',"Inline feedback banner. ARIA roles wired by variant."],['<a href="/components/stat">stat</a>',"Numeric metric with optional trend arrow."],['<a href="/components/avatar">avatar</a>',"User avatar \u2014 image with fallback to initials."],['<a href="/components/empty">empty</a>',"Empty state with title, description, and optional CTA."],['<a href="/components/table">table</a>',"Accessible data table with scroll wrapper."]])}
55
-
56
- <h3 class="doc-h3">Landing page</h3>
57
- ${e(["Component","Description"],[['<a href="/components/nav">nav</a>',"Site header with logo, links, and optional CTA."],['<a href="/components/hero">hero</a>',"Full-width hero section with eyebrow, title, and actions."],['<a href="/components/app-badge">appBadge</a>',"App Store / Google Play download badge."],['<a href="/components/feature">feature</a>',"Icon + title + description block for feature grids."],['<a href="/components/testimonial">testimonial</a>',"Customer quote with avatar and star rating."],['<a href="/components/pricing">pricing</a>',"Plan card with feature list and CTA. Supports highlighted state."],['<a href="/components/accordion">accordion</a>',"Collapsible FAQ items \u2014 no JS, native <code>&lt;details&gt;</code>."],['<a href="/components/cta">cta</a>',"Call-to-action block with eyebrow, heading, body, and actions slot."]])}
58
-
59
- <h3 class="doc-h3">Layout</h3>
60
- ${e(["Component","Description"],[['<a href="/components/container">container</a>',"Max-width wrapper with horizontal padding."],['<a href="/components/section">section</a>',"Vertical padding block with background variant."],['<a href="/components/grid">grid</a>',"Responsive CSS grid. Collapses to one column on mobile."],['<a href="/components/stack">stack</a>',"Flex column with consistent vertical gap."],['<a href="/components/cluster">cluster</a>',"Flex row with wrapping \u2014 for badges, buttons, etc."],['<a href="/components/divider">divider</a>',"Horizontal rule, optionally with centred label."],['<a href="/components/banner">banner</a>',"Full-width announcement bar above the nav."],['<a href="/components/media">media</a>',"Two-column image + text layout, stacks on mobile."],['<a href="/components/code-window">codeWindow</a>',"macOS window chrome around a code block. Accepts pre-highlighted HTML."],['<a href="/components/footer">footer</a>',"Site footer with logo, nav links, and legal text. Stacks on mobile."]])}
61
-
62
- ${o("utilities","Utility classes")}
63
- <p><code>pulse-ui.css</code> ships a utility layer (prefix <code>u-</code>) for common spacing, typography, and layout needs. Reach for these before writing custom CSS \u2014 they use the same <code>--ui-*</code> tokens as components so theme overrides apply everywhere.</p>
64
-
65
- <h3 class="doc-h3">Spacing</h3>
66
- <p>Scale: 1=4px 2=8px 3=12px 4=16px 5=20px 6=24px 8=32px 10=40px 12=48px 16=64px</p>
67
- ${e(["Class","Property"],[["<code>u-mt-{0\u201316}</code>","margin-top"],["<code>u-mb-{0\u201316}</code>","margin-bottom"],["<code>u-mx-auto</code>","margin-left + right: auto"],["<code>u-p-{0\u20138}</code>","padding (all sides)"],["<code>u-px-{0\u20138}</code>","padding-left + right"],["<code>u-py-{0\u20138}</code>","padding-top + bottom"]])}
68
-
69
- <h3 class="doc-h3">Typography</h3>
70
- ${e(["Class","Effect"],[["<code>u-text-{xs,sm,base,lg,xl,2xl,3xl,4xl}</code>","Font size + matching line-height"],["<code>u-font-{normal,medium,semibold,bold}</code>","Font weight"],["<code>u-text-{left,center,right}</code>","Text alignment"],["<code>u-text-{default,muted,accent,green,red,yellow,blue}</code>","Token colour"],["<code>u-leading-{tight,snug,normal,relaxed,loose}</code>","Line height"]])}
71
-
72
- <h3 class="doc-h3">Layout</h3>
73
- ${e(["Class","Effect"],[["<code>u-flex</code> / <code>u-flex-col</code>","Flex row or column"],["<code>u-items-{start,center,end,stretch}</code>","align-items"],["<code>u-justify-{start,center,end,between}</code>","justify-content"],["<code>u-gap-{1\u20138}</code>","gap"],["<code>u-w-full</code>","width: 100%"],["<code>u-max-w-{xs,sm,md,lg,xl,prose}</code>","max-width (320px\u20131024px, 65ch)"],["<code>u-hidden</code> / <code>u-block</code> / <code>u-inline-block</code>","display"]])}
74
-
75
- <h3 class="doc-h3">Visual</h3>
76
- ${e(["Class","Effect"],[["<code>u-rounded</code> / <code>u-rounded-md</code> / <code>u-rounded-lg</code> / <code>u-rounded-full</code>","border-radius"],["<code>u-border</code> / <code>u-border-t</code> / <code>u-border-b</code>","1px solid --ui-border"],["<code>u-bg-surface</code> / <code>u-bg-surface2</code> / <code>u-bg-accent</code>","background token"],["<code>u-overflow-hidden</code> / <code>u-overflow-auto</code>","overflow"],["<code>u-opacity-50</code> / <code>u-opacity-75</code>","opacity"]])}
77
-
78
- <p>Utilities compose naturally with components and with each other:</p>
79
- ${t(a(`<!-- centred hero block \u2014 no custom CSS needed -->
80
- <div class="u-flex u-flex-col u-items-center u-text-center u-py-16 u-gap-4">
81
- <h1 class="u-text-4xl u-font-bold">Hello</h1>
82
- <p class="u-text-lg u-text-muted u-max-w-prose">Subtitle goes here.</p>
83
- \${button({ label: 'Get started', href: '/start' })}
84
- </div>`,"html"))}
85
-
86
- ${c("note",'Never use inline <code>style=""</code> attributes. Reach for utility classes first, and only add to <code>app.css</code> when you need something utilities cannot provide \u2014 a unique animation, a custom grid, or a one-off component variant.')}
87
-
88
- ${o("composing","Composing components")}
89
- <p>Components compose naturally \u2014 pass the output of one as the <code>content</code> or <code>footer</code> of another. Here's a stat dashboard card:</p>
90
- ${t(a(`import { card, stat, button } from '@invisibleloop/pulse/ui'
91
-
92
- card({
93
- title: 'This week',
94
- content: \`
95
- \${stat({ label: 'Page views', value: '48,291', change: '+12%', trend: 'up' })}
96
- \${stat({ label: 'New users', value: '1,042', change: '+4%', trend: 'up' })}
97
- \${stat({ label: 'Bounced', value: '22%', change: '+1%', trend: 'down' })}
98
- \`,
99
- footer: button({ label: 'View full report', href: '/analytics', variant: 'ghost', size: 'sm' }),
100
- })`,"js"))}
101
-
102
- ${c("note","Text props like <code>label</code>, <code>title</code>, <code>value</code>, and <code>error</code> are escaped automatically. <code>content</code>, <code>footer</code>, <code>rows</code>, <code>actions</code>, <code>icon</code>, <code>action</code>, and <code>logo</code> are raw HTML slots \u2014 they pass through as-is, so any user-supplied data going into those slots should go through <code>escHtml()</code> first.")}
103
- `})};var n=document.getElementById("pulse-root");n&&!n.dataset.pulseMounted&&(n.dataset.pulseMounted="1",r(s,n,window.__PULSE_SERVER__||{},{ssr:!0}),u(n,r));var S=s;export{S as default};
@@ -1,126 +0,0 @@
1
- import{a as r}from"./runtime-QFURDKA2.js";import{a,b as i,c as l,d as u,e,g as o,h as t,i as d}from"./runtime-L2HNXIHW.js";import{a as s,b as h}from"./runtime-B73WLANC.js";var{prev:p,next:m}=a("/config"),n={route:"/config",meta:{title:"Configuration \u2014 Pulse Docs",description:"Full reference for pulse.config.js \u2014 port, Lighthouse thresholds, load test config, environments, and per-route overrides.",styles:["/docs.css"]},state:{},view:()=>i({currentHref:"/config",prev:p,next:m,content:`
2
- ${l("Configuration")}
3
- ${u(`<code>pulse.config.js</code> sets the performance and load thresholds that Pulse enforces across your application. All fields are optional \u2014 the defaults match Google's Core Web Vitals "good" band. Configuration here is not about enabling features; it is about deciding where, if anywhere, you need to lower a guaranteed baseline.`)}
4
-
5
- ${e("schema","Full schema")}
6
- ${o(r(`// pulse.config.js
7
- export default {
8
- port: 3000,
9
-
10
- lighthouse: {
11
- // Category scores \u2014 0 to 100. Default: 100 for all four.
12
- performance: 100,
13
- accessibility: 100,
14
- bestPractices: 100,
15
- seo: 100,
16
-
17
- // Core Web Vitals and timing metrics.
18
- // Defaults are the Google "good" thresholds.
19
- lcp: 2500, // Largest Contentful Paint (ms)
20
- cls: 0.1, // Cumulative Layout Shift
21
- tbt: 200, // Total Blocking Time (ms)
22
- fcp: 1800, // First Contentful Paint (ms)
23
- si: 3400, // Speed Index (ms)
24
- inp: 200, // Interaction to Next Paint (ms)
25
- },
26
-
27
- load: {
28
- duration: 10,
29
- connections: 10,
30
- thresholds: {
31
- rps: undefined, // minimum req/s (optional)
32
- p99: undefined, // maximum p99 latency ms (optional)
33
- errors: 0,
34
- },
35
- },
36
-
37
- environments: {
38
- // Environment names are bespoke \u2014 choose whatever suits your project.
39
- local: { url: 'http://localhost:3000', default: true },
40
- staging: {
41
- url: 'https://staging.myapp.com',
42
- headers: { Authorization: \`Bearer \${process.env.STAGING_TOKEN}\` },
43
- load: { duration: 30, connections: 50 },
44
- lighthouse: { performance: 90 },
45
- },
46
- production: { url: 'https://myapp.com' },
47
- },
48
-
49
- routes: {
50
- // Per-route overrides \u2014 merged on top of global lighthouse/load config.
51
- // Only specify what differs from the global defaults.
52
- '/dashboard': {
53
- lighthouse: {
54
- performance: 85,
55
- lcp: 4000,
56
- },
57
- },
58
- '/embed': {
59
- lighthouse: {
60
- bestPractices: 85,
61
- },
62
- },
63
- },
64
- }`,"js"))}
65
-
66
- ${e("port","port")}
67
- ${t(["Field","Type","Default","Description"],[["<code>port</code>","<code>number</code>","<code>3000</code>","Port the dev and production servers listen on."]])}
68
-
69
- ${e("lighthouse","lighthouse")}
70
- <p>Global Lighthouse thresholds that every page must meet. <code>/pulse-report</code> enforces these after every audit \u2014 any page that falls below a threshold is reported as a failure, not a warning.</p>
71
- ${t(["Field","Type","Default","Description"],[["<code>performance</code>","<code>number</code>","<code>100</code>","Lighthouse Performance category score (0\u2013100)."],["<code>accessibility</code>","<code>number</code>","<code>100</code>","Lighthouse Accessibility category score (0\u2013100)."],["<code>bestPractices</code>","<code>number</code>","<code>100</code>","Lighthouse Best Practices category score (0\u2013100)."],["<code>seo</code>","<code>number</code>","<code>100</code>","Lighthouse SEO category score (0\u2013100)."],["<code>lcp</code>","<code>number</code>","<code>2500</code>","Largest Contentful Paint budget (ms)."],["<code>cls</code>","<code>number</code>","<code>0.1</code>","Cumulative Layout Shift budget."],["<code>tbt</code>","<code>number</code>","<code>200</code>","Total Blocking Time budget (ms)."],["<code>fcp</code>","<code>number</code>","<code>1800</code>","First Contentful Paint budget (ms)."],["<code>si</code>","<code>number</code>","<code>3400</code>","Speed Index budget (ms)."],["<code>inp</code>","<code>number</code>","<code>200</code>","Interaction to Next Paint budget (ms)."]])}
72
- ${d("note","Unset fields default to the values above. Setting a field to <code>null</code> removes that check for the project \u2014 use sparingly. Raising a threshold is always preferable to disabling it.")}
73
-
74
- ${e("routes","routes")}
75
- <p>Route-specific overrides let you lower a threshold for a specific page without relaxing the global guarantee. Only specify the fields that differ \u2014 everything else inherits from the global config.</p>
76
- ${o(r(`routes: {
77
- // This route uses a third-party chart library \u2014 relax performance only.
78
- '/dashboard': {
79
- lighthouse: {
80
- performance: 85,
81
- lcp: 4000,
82
- },
83
- },
84
- }`,"js"))}
85
- ${d("note","Route keys must exactly match the spec <code>route</code> field, including any leading slash. Dynamic segments are not supported \u2014 create a specific override for each route pattern.")}
86
-
87
- ${e("load","load")}
88
- <p>Load test thresholds enforced by <code>/pulse-load</code>. All fields are optional \u2014 omitting a threshold means that check is not enforced.</p>
89
- ${t(["Field","Type","Default","Description"],[["<code>duration</code>","<code>number</code>","<code>10</code>","Test duration in seconds."],["<code>connections</code>","<code>number</code>","<code>10</code>","Number of concurrent request chains."],["<code>thresholds.rps</code>","<code>number</code>","<code>undefined</code>","Minimum acceptable requests per second. Unset = no check."],["<code>thresholds.p99</code>","<code>number</code>","<code>undefined</code>","Maximum acceptable p99 latency (ms). Unset = no check."],["<code>thresholds.errors</code>","<code>number</code>","<code>0</code>","Maximum acceptable error count."]])}
90
- ${o(r(`load: {
91
- duration: 30,
92
- connections: 20,
93
- thresholds: {
94
- rps: 100, // fail if below 100 req/s
95
- p99: 500, // fail if p99 exceeds 500ms
96
- errors: 0,
97
- },
98
- },`,"js"))}
99
- <p>Per-route overrides follow the same pattern as <code>lighthouse</code>:</p>
100
- ${o(r(`routes: {
101
- '/feed': {
102
- load: { connections: 5, thresholds: { rps: 20 } },
103
- },
104
- }`,"js"))}
105
- <p>Results are saved to <code>.pulse/load-reports/</code> and displayed in the Load Tests tab of the report dashboard, alongside the Lighthouse Performance tab for the same route.</p>
106
-
107
- ${e("environments","environments")}
108
- <p>Named environments let you enforce thresholds against different targets \u2014 local, staging, production \u2014 from the same config. Thresholds are applied per-environment, so staging and production can have different performance floors.</p>
109
- ${t(["Field","Type","Description"],[["<code>url</code>","<code>string</code>","Base URL to test against. If it contains <code>localhost</code> or <code>127.0.0.1</code>, a local production build is spun up automatically. Remote URLs are tested directly."],["<code>default</code>","<code>boolean</code>","The environment used when none is explicitly specified by <code>pulse report</code> and <code>pulse load-test</code>."],["<code>headers</code>","<code>object</code>","HTTP headers sent with every request to this environment. Useful for authorization tokens on protected staging deployments \u2014 read values from <code>process.env</code> rather than hardcoding them."],["<code>load</code>","<code>object</code>","Load test config overrides for this environment. Same fields as the global <code>load</code> block. Merged on top of global config."],["<code>lighthouse</code>","<code>object</code>","Lighthouse threshold overrides for this environment. Same fields as the global <code>lighthouse</code> block. Merged on top of global config."]])}
110
- ${d("note","Environment names are fully bespoke \u2014 <code>local</code>, <code>staging</code>, <code>production</code>, <code>preview</code>, <code>eu</code> \u2014 whatever maps to your project's infrastructure. There are no reserved names.")}
111
- <p>Threshold merge order: <strong>global config \u2192 environment override \u2192 per-route override</strong>.</p>
112
- ${o(r(`environments: {
113
- local: { url: 'http://localhost:3000', default: true },
114
- staging: {
115
- url: 'https://staging.myapp.com',
116
- headers: { Authorization: \`Bearer \${process.env.STAGING_TOKEN}\` },
117
- load: { duration: 30, connections: 50, thresholds: { rps: 500 } },
118
- lighthouse: { performance: 90 },
119
- },
120
- production: { url: 'https://myapp.com' },
121
- }`,"js"))}
122
-
123
- ${e("defaults","CWV default thresholds")}
124
- <p>The default metric thresholds match the Google "good" band from the Core Web Vitals specification:</p>
125
- ${t(["Metric","Default",'Google "good" threshold'],[["LCP","<code>2500 ms</code>","\u2264 2500 ms"],["CLS","<code>0.1</code>","\u2264 0.1"],["TBT","<code>200 ms</code>","\u2264 200 ms"],["FCP","<code>1800 ms</code>","\u2264 1800 ms"],["SI","<code>3400 ms</code>","\u2264 3400 ms"],["INP","<code>200 ms</code>","\u2264 200 ms"]])}
126
- `})};var c=document.getElementById("pulse-root");c&&!c.dataset.pulseMounted&&(c.dataset.pulseMounted="1",s(n,c,window.__PULSE_SERVER__||{},{ssr:!0}),h(c,s));var P=n;export{P as default};
@@ -1,71 +0,0 @@
1
- import{a}from"./runtime-QFURDKA2.js";import{a as i,b as r,c,d as l,e as t,g as e,h as d,i as m}from"./runtime-L2HNXIHW.js";import{a as n,b as u}from"./runtime-B73WLANC.js";var{prev:p,next:h}=i("/constraints"),s={route:"/constraints",meta:{title:"Constraints \u2014 Pulse Docs",description:"Automatic min/max bounds on state values, enforced after every mutation.",styles:["/docs.css"]},state:{},view:()=>r({currentHref:"/constraints",prev:p,next:h,content:`
2
- ${c("Constraints")}
3
- ${l("Constraints are always-on bounds for numeric state values. After every mutation, Pulse clamps constrained values to their declared range before the view re-renders. The value can never go out of range \u2014 there is no code path where it can.")}
4
-
5
- ${t("declaring","Declaring constraints")}
6
- <p>The <code>constraints</code> field maps top-level state keys to bounds objects with optional <code>min</code> and <code>max</code> properties:</p>
7
- ${e(a(`export default {
8
- route: '/cart',
9
- state: {
10
- quantity: 1,
11
- zoom: 1.0,
12
- rating: 0,
13
- },
14
- constraints: {
15
- quantity: { min: 1, max: 99 },
16
- zoom: { min: 0.5, max: 3.0 },
17
- rating: { min: 0, max: 5 },
18
- },
19
- mutations: {
20
- increaseQty: (state) => ({ quantity: state.quantity + 1 }),
21
- decreaseQty: (state) => ({ quantity: state.quantity - 1 }),
22
- zoomIn: (state) => ({ zoom: state.zoom + 0.1 }),
23
- zoomOut: (state) => ({ zoom: state.zoom - 0.1 }),
24
- },
25
- }`,"js"))}
26
- <p>When <code>decreaseQty</code> runs and <code>quantity</code> is already 1, the constraint clamps it back to 1 before the view renders. The mutation does not need to check bounds \u2014 the spec declares them once and Pulse enforces them everywhere.</p>
27
-
28
- ${t("vs-validation","Constraints vs Validation")}
29
- ${d(["","Constraints","Validation"],[["When it runs","After <strong>every</strong> mutation, automatically","Only when an action has <code>validate: true</code>"],["What it does","Clamps numeric values silently","Rejects the action and surfaces errors"],["User feedback","None \u2014 state is silently corrected","Explicit error messages shown in the view"],["Best for","Numeric ranges that must never be exceeded","Form field correctness before submission"]])}
30
-
31
- ${m("note","Constraints and validation serve different purposes. Constraints silently enforce numeric bounds at every mutation \u2014 they cannot be bypassed. Validation rejects invalid form data before an action's async work begins \u2014 it only runs when explicitly declared.")}
32
-
33
- ${t("one-sided","One-sided bounds")}
34
- <p>Either <code>min</code> or <code>max</code> can be declared alone \u2014 both are optional:</p>
35
- ${e(a(`constraints: {
36
- count: { min: 0 }, // no upper limit
37
- discount: { max: 100 }, // no lower limit
38
- offset: { min: 0, max: 999 } // both bounds
39
- }`,"js"))}
40
-
41
- ${t("how-it-works","How clamping works")}
42
- <p>After Pulse applies a mutation's partial state update, it iterates over all declared constraints and applies <code>Math.max(min, Math.min(max, value))</code> to each constrained key. The view is then called with the clamped state.</p>
43
- ${e(a(`// state.count = 10, constraints.count = { min: 0, max: 10 }
44
- mutations: {
45
- increment: (state) => ({ count: state.count + 1 }),
46
- // mutation returns { count: 11 }
47
- // constraint clamps to 10
48
- // view receives { count: 10 }
49
- }`,"js"))}
50
-
51
- ${t("top-level","Top-level keys only")}
52
- <p>Constraints apply to top-level state keys. To constrain nested values, consider flattening your state structure or applying bounds logic in the mutation itself:</p>
53
- ${e(a(`// Cannot do:
54
- constraints: {
55
- 'player.health': { min: 0, max: 100 } // \u2717 nested paths not supported
56
- }
57
-
58
- // Do instead:
59
- state: { playerHealth: 100 },
60
- constraints: { playerHealth: { min: 0, max: 100 } },
61
-
62
- // Or handle in the mutation:
63
- mutations: {
64
- takeDamage: (state, _, amount) => ({
65
- player: {
66
- ...state.player,
67
- health: Math.max(0, state.player.health - amount),
68
- }
69
- })
70
- }`,"js"))}
71
- `})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",n(s,o,window.__PULSE_SERVER__||{},{ssr:!0}),u(o,n));var k=s;export{k as default};