@invisibleloop/pulse 0.1.23 → 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 +12 -1
  2. package/.github/workflows/publish.yml +11 -19
  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 +6 -2
  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,383 +0,0 @@
1
- import{b as n}from"./runtime-VMJA3Z4N.js";import{a as o}from"./runtime-QFURDKA2.js";import{a as i,b as r}from"./runtime-B73WLANC.js";var l={current:null};var c=o(`export default {
2
- route: '/dashboard',
3
- meta: {
4
- title: 'Dashboard \u2014 My App',
5
- styles: ['/app.css'],
6
- },
7
- server: {
8
- data: async (ctx) => {
9
- const user = await db.users.find(ctx.cookies.userId)
10
- return { user, stats: await db.stats.forUser(user.id) }
11
- },
12
- },
13
- state: { filter: 'all' },
14
- mutations: {
15
- setFilter: (state, event) => ({ filter: event.target.value }),
16
- },
17
- view: (state, server) => \`
18
- <main id="main-content">
19
- <h1>Hello, \${server.data.user.name}</h1>
20
- <select data-event="change:setFilter">
21
- <option value="all">All time</option>
22
- <option value="week">This week</option>
23
- </select>
24
- <p>\${server.data.stats[state.filter].total} requests</p>
25
- </main>
26
- \`,
27
- }`,"js"),a={route:"/",meta:{title:"Pulse \u2014 The spec-first web framework",description:"Pulse is a server-first web framework with streaming SSR, zero client JS by default, and Lighthouse 100 built into the architecture. One spec format. One way to build. Production quality by design.",styles:["/pulse-ui.css","/docs.css"]},state:{},server:{metrics:()=>l.current},view:(d,t)=>`
28
- <div class="home">
29
- <nav class="home-nav" aria-label="Site navigation">
30
- <a href="/" class="logo-link">
31
- <svg width="22" height="22" 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
- Pulse
35
- </a>
36
- <div class="home-nav-links">
37
- <a href="/getting-started">Docs</a>
38
- <a href="https://github.com/invisibleloop/pulse" target="_blank" rel="noopener">GitHub</a>
39
- </div>
40
- </nav>
41
-
42
- <main id="main-content">
43
- <section class="hero">
44
- <div class="hero-icon" aria-hidden="true">
45
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none">
46
- <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
47
- </svg>
48
- </div>
49
- <div class="hero-badge">v0.1 \u2014 EARLY ACCESS</div>
50
- <h1 class="hero-title">Describe the outcome. Pulse guarantees it.</h1>
51
- <p class="hero-subtitle">One spec object per page \u2014 server data, state, mutations, and view, co-located in plain JS. Streaming SSR, security headers, and production caching are enforced by the framework, not left to configuration.<br><br>Designed for AI agents. Production-quality architecture.</p>
52
- <div class="hero-ctas">
53
- <a href="/getting-started" class="btn-primary">Get Started</a>
54
- <a href="/spec" class="btn-secondary">Read the Spec</a>
55
- </div>
56
- </section>
57
-
58
- <section class="home-stats">
59
- <div class="home-stat">
60
- <span class="home-stat-value">Fast LCP</span>
61
- <span class="home-stat-label">SSR-first architecture</span>
62
- </div>
63
- <div class="home-stat-divider"></div>
64
- <div class="home-stat">
65
- <span class="home-stat-value">3.5 kB</span>
66
- <span class="home-stat-label">Runtime JS, first visit (brotli)</span>
67
- </div>
68
- <div class="home-stat-divider"></div>
69
- <div class="home-stat">
70
- <span class="home-stat-value">0.00</span>
71
- <span class="home-stat-label">Cumulative Layout Shift</span>
72
- </div>
73
- <div class="home-stat-divider"></div>
74
- <div class="home-stat">
75
- <span class="home-stat-value">100</span>
76
- <span class="home-stat-label">Lighthouse score</span>
77
- </div>
78
- </section>
79
-
80
- <section class="how">
81
- <div class="how-inner">
82
- <h2 class="section-label">How it works</h2>
83
- <div class="how-steps">
84
- <div class="how-step">
85
- <div class="how-step-num">1</div>
86
- <h3>Write a spec</h3>
87
- <p>Everything for a page lives in one object: server data, state, mutations, view. One format, no conventions to learn.</p>
88
- </div>
89
- <div class="how-connector" aria-hidden="true"></div>
90
- <div class="how-step">
91
- <div class="how-step-num">2</div>
92
- <h3>Validate automatically</h3>
93
- <p>The schema enforces a single, correct structure. Either the spec is valid, or it\u2019s rejected \u2014 no ambiguity, no runtime surprises.</p>
94
- </div>
95
- <div class="how-connector" aria-hidden="true"></div>
96
- <div class="how-step">
97
- <div class="how-step-num">3</div>
98
- <h3>Ship with it built in</h3>
99
- <p>Streaming SSR, security headers, and immutable caching come from the framework \u2014 not your config. Follow the spec, and the results follow.</p>
100
- </div>
101
- </div>
102
- </div>
103
- </section>
104
-
105
- <section class="home-code">
106
- <div class="home-code-inner">
107
- <div class="home-code-header">
108
- <h2>Everything in one object</h2>
109
- <p>Server data, state, mutations, and view are co-located. No split files. No hidden conventions. The spec <strong>is</strong> the page.</p>
110
- </div>
111
- <div class="home-code-block">
112
- ${n({content:c,filename:"src/pages/dashboard.js",lang:"JavaScript"})}
113
- </div>
114
- </div>
115
- </section>
116
-
117
- <section class="ai-first">
118
- <div class="section-label">Why Pulse + AI</div>
119
- <h2 class="ai-first-title">Designed for AI agents. Enforced by the framework.</h2>
120
- <p class="ai-first-lead"><strong>Traditional frameworks</strong> were built for humans \u2014 multiple valid patterns, optional decisions, enough surface area for output to drift. Pulse is different.</p>
121
- <div class="ai-cols">
122
- <div class="ai-col">
123
- <h3 class="ai-col-title ai-col-title--bad">AI + existing frameworks</h3>
124
- <ul class="ai-col-list">
125
- <li>Multiple valid patterns per page \u2014 the agent picks one, the next picks another.</li>
126
- <li>Security headers, SSR config, and caching are optional decisions the agent can miss.</li>
127
- <li>Output drifts over time as different agents make different choices.</li>
128
- <li>Reviewing AI output requires knowing every pattern it could have used.</li>
129
- </ul>
130
- </div>
131
- <div class="ai-col ai-col--pulse">
132
- <h3 class="ai-col-title ai-col-title--good">Pulse + AI enforces structure</h3>
133
- <ul class="ai-col-list">
134
- <li>One spec format per page.</li>
135
- <li>Architecture enforces SSR, security, and caching.</li>
136
- <li>Agents fill in the contract, never off-pattern.</li>
137
- <li>Reading AI output means reading one JS object \u2014 nothing hidden.</li>
138
- </ul>
139
- </div>
140
- </div>
141
- </section>
142
-
143
- <section class="versus">
144
- <div class="section-label">How Pulse compares</div>
145
- <h2 class="versus-title">Constraints enforced. Not recommended.</h2>
146
- <p class="versus-sub">Pulse enforces constraints and correctness out of the box; other frameworks leave it to the developer.</p>
147
- <div class="versus-table-wrap table-sticky-col">
148
- <table class="versus-table">
149
- <thead>
150
- <tr>
151
- <th></th>
152
- <th>
153
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true" style="vertical-align:middle;margin-right:.35rem">
154
- <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
155
- </svg>Pulse
156
- </th>
157
- <th>Next.js / Remix</th>
158
- <th>SvelteKit</th>
159
- </tr>
160
- </thead>
161
- <tbody>
162
- <tr>
163
- <th scope="row">Ways to write a page</th>
164
- <td class="v-yes">One \u2014 the spec schema</td>
165
- <td class="v-no">App Router, Pages Router, RSC, client components, loaders\u2026</td>
166
- <td class="v-no">+page.svelte, +page.server.js, load(), form actions\u2026</td>
167
- </tr>
168
- <tr>
169
- <th scope="row">Agent-readable structure</th>
170
- <td class="v-yes">One JS object per page</td>
171
- <td class="v-no">Files, folders, magic exports spread across dirs</td>
172
- <td class="v-no">Files, folders, Svelte syntax</td>
173
- </tr>
174
- <tr>
175
- <th scope="row">SSR out of the box</th>
176
- <td class="v-yes">Streaming SSR, zero config</td>
177
- <td class="v-partial">Yes, but client hydration adds JS overhead on every page</td>
178
- <td class="v-partial">Yes, but requires an adapter and client runtime on every page</td>
179
- </tr>
180
- <tr>
181
- <th scope="row">Client JS shipped</th>
182
- <td class="v-yes">3.5 kB brotli (shared runtime, first visit)</td>
183
- <td class="v-no">50-200 kB+ depending on features</td>
184
- <td class="v-partial">~15 kB brotli</td>
185
- </tr>
186
- <tr>
187
- <th scope="row">Security headers</th>
188
- <td class="v-yes">On every response, built in</td>
189
- <td class="v-no">Manual middleware or plugin</td>
190
- <td class="v-no">Manual hooks setup</td>
191
- </tr>
192
- <tr>
193
- <th scope="row">CLS</th>
194
- <td class="v-yes">Targets 0.00 \u2014 shell renders before data arrives</td>
195
- <td class="v-partial">Depends on implementation</td>
196
- <td class="v-partial">Depends on implementation</td>
197
- </tr>
198
- <tr>
199
- <th scope="row">Runtime dependencies</th>
200
- <td class="v-yes">Zero \u2014 pure Node.js HTTP</td>
201
- <td class="v-no">React, 50+ transitive packages</td>
202
- <td class="v-no">Svelte runtime + adapters</td>
203
- </tr>
204
- <tr>
205
- <th scope="row">Production build step</th>
206
- <td class="v-yes">None \u2014 <code>node server.js</code> is production</td>
207
- <td class="v-no">Required \u2014 <code>next build</code></td>
208
- <td class="v-no">Required \u2014 <code>vite build</code></td>
209
- </tr>
210
- </tbody>
211
- </table>
212
- </div>
213
- </section>
214
-
215
- <section class="usp-blocks">
216
-
217
- <div class="usp-block">
218
- <div class="usp-block-aside">
219
- <div class="usp-icon">
220
- <svg width="28" height="28" viewBox="0 0 24 24" fill="none" aria-hidden="true">
221
- <path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1.5" stroke-linejoin="round"/>
222
- </svg>
223
- </div>
224
- <h2>Performance by design</h2>
225
- <p>Pulse does not offer performance as an option \u2014 it enforces it structurally. A high Lighthouse score is the baseline. There is nothing to configure because there is nothing to get wrong.</p>
226
- </div>
227
- <ul class="usp-points">
228
- <li>
229
- <strong>Fast LCP by design.</strong>
230
- The shell renders and streams instantly. Deferred segments arrive as data resolves \u2014 no blocking, no flash.
231
- </li>
232
- <li>
233
- <strong>3.5 kB of JS on first visit.</strong>
234
- The shared runtime is brotli-compressed and cached across all navigations. Subsequent pages cost 0.35\u20130.5 kB.
235
- </li>
236
- <li>
237
- <strong>Zero CLS.</strong>
238
- The shell occupies the correct layout before data arrives. No placeholder juggling, no layout shift.
239
- </li>
240
- <li>
241
- <strong>Immutable bundle caching.</strong>
242
- Production bundles are content-hashed and served with <code>immutable, max-age=31536000</code>. Browsers cache them forever \u2014 deploys are instant for returning visitors.
243
- </li>
244
- </ul>
245
- </div>
246
-
247
- <div class="usp-block usp-block-alt">
248
- <div class="usp-block-aside">
249
- <div class="usp-icon">
250
- <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
251
- <rect x="3" y="11" width="18" height="11" rx="2"/>
252
- <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
253
- </svg>
254
- </div>
255
- <h2>Safe by design</h2>
256
- <p>Security is not a plugin or a checklist in Pulse \u2014 it is part of the response pipeline. Every page ships the headers most frameworks leave to the developer to remember.</p>
257
- </div>
258
- <ul class="usp-points">
259
- <li>
260
- <strong>Security headers on every response.</strong>
261
- <code>X-Frame-Options</code>, <code>X-Content-Type-Options</code>, <code>Referrer-Policy</code>, <code>Permissions-Policy</code>, <code>Cross-Origin-Opener-Policy</code> \u2014 all set automatically, including on 404 and 500 pages.
262
- </li>
263
- <li>
264
- <strong>Declarative state constraints.</strong>
265
- <code>constraints</code> enforce min/max bounds on state after every mutation. The value can never go out of range regardless of what the client sends.
266
- </li>
267
- <li>
268
- <strong>Co-located validation.</strong>
269
- Validation rules live next to the state they guard. The agent can see what is being validated and why, in one place.
270
- </li>
271
- <li>
272
- <strong>Guard before data.</strong>
273
- The <code>guard</code> function runs before any server fetcher executes \u2014 authentication and authorisation checks cannot be accidentally bypassed.
274
- </li>
275
- </ul>
276
- </div>
277
-
278
- <div class="usp-block">
279
- <div class="usp-block-aside">
280
- <div class="usp-icon">
281
- <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
282
- <circle cx="12" cy="12" r="10"/>
283
- <line x1="12" y1="8" x2="12" y2="12"/>
284
- <line x1="12" y1="16" x2="12.01" y2="16"/>
285
- </svg>
286
- </div>
287
- <h2>Nothing to configure</h2>
288
- <p>No bundler config. No framework boilerplate. No runtime dependencies to install, audit, or upgrade. Pulse eliminates the category of problems that come from misconfiguration.</p>
289
- </div>
290
- <ul class="usp-points">
291
- <li>
292
- <strong>Zero runtime dependencies.</strong>
293
- The server is pure Node.js HTTP. No Express, no Fastify, no React. Nothing to add to <code>package.json</code> to run a production server.
294
- </li>
295
- <li>
296
- <strong>No production build step.</strong>
297
- <code>node server.js</code> is production. The build step is only needed to generate content-hashed client bundles \u2014 the server runs without it in dev.
298
- </li>
299
- <li>
300
- <strong>esbuild only in development.</strong>
301
- The one dev dependency that compiles client bundles is esbuild. Fast, no plugins to configure, never part of the production runtime.
302
- </li>
303
- <li>
304
- <strong>No framework upgrades breaking your app.</strong>
305
- Because the spec is a plain JS object with no framework imports in page files, there is no framework API surface to break across versions.
306
- </li>
307
- </ul>
308
- </div>
309
-
310
- </section>
311
-
312
- ${t.metrics?`<section class="metrics-report" aria-labelledby="metrics-title">
313
- <div class="metrics-header">
314
- <div class="section-label">By the numbers</div>
315
- <h2 id="metrics-title" class="metrics-title">Performance you can measure.</h2>
316
- <p class="metrics-generated">Report generated ${t.metrics.generatedAt} \xB7 measured from a real Pulse build</p>
317
- </div>
318
- <div class="metrics-groups">
319
- <div class="metrics-group">
320
- <div class="metrics-group-label">Lighthouse</div>
321
- <div class="metrics-items">
322
- ${t.metrics.lighthouse.map(e=>`
323
- <div class="metric-item">
324
- <span class="metric-val metric-val--green">${e.value}</span>
325
- <span class="metric-label">${e.label}</span>
326
- </div>`).join("")}
327
- </div>
328
- </div>
329
- <div class="metrics-group">
330
- <div class="metrics-group-label">Bundle sizes</div>
331
- <div class="metrics-items">
332
- ${t.metrics.bundles.map(e=>`
333
- <div class="metric-item">
334
- <span class="metric-val">${e.value}</span>
335
- <span class="metric-label">${e.label}</span>
336
- </div>`).join("")}
337
- </div>
338
- </div>
339
- <div class="metrics-group">
340
- <div class="metrics-group-label">Web Vitals</div>
341
- <div class="metrics-items">
342
- ${t.metrics.vitals.map(e=>`
343
- <div class="metric-item">
344
- <span class="metric-val metric-val--green">${e.value}</span>
345
- <span class="metric-label">${e.label}</span>
346
- </div>`).join("")}
347
- </div>
348
- </div>
349
- <div class="metrics-group">
350
- <div class="metrics-group-label">Architecture</div>
351
- <div class="metrics-items">
352
- ${t.metrics.architecture.map(e=>`
353
- <div class="metric-item">
354
- <span class="metric-val metric-val--accent">${e.value}</span>
355
- <span class="metric-label">${e.label}</span>
356
- </div>`).join("")}
357
- </div>
358
- </div>
359
- </div>
360
- </section>`:""}
361
-
362
- <section class="home-cta">
363
- <h2>The spec is the contract.<br>Your agent fills it in.</h2>
364
- <ul class="home-cta-checks">
365
- <li>Writes the spec</li>
366
- <li>Validates against the schema</li>
367
- <li>Checks Lighthouse \u2014 desktop and mobile</li>
368
- <li>Runs the tests</li>
369
- <li>Ships production quality</li>
370
- </ul>
371
- <p>MIT licensed and available now. Production quality is not the goal. It is the starting point.</p>
372
- <div class="home-cta-actions">
373
- <a href="/getting-started" class="btn-primary">Get Started</a>
374
- <a href="/spec" class="btn-secondary">Read the Spec</a>
375
- </div>
376
- </section>
377
-
378
- </main>
379
- <footer class="home-footer">
380
- <p>MIT License \xB7 <a href="https://github.com/invisibleloop/pulse" target="_blank" rel="noopener">GitHub</a> \xB7 <a href="/getting-started">Get started in 2 minutes</a></p>
381
- </footer>
382
- </div>
383
- `};var s=document.getElementById("pulse-root");s&&!s.dataset.pulseMounted&&(s.dataset.pulseMounted="1",i(a,s,window.__PULSE_SERVER__||{},{ssr:!0}),r(s,i));var w=a;export{w as default};
@@ -1,104 +0,0 @@
1
- import{ab as u,c as e}from"./runtime-UVPXO4IR.js";import"./runtime-VMJA3Z4N.js";import{a,b as n,c as d,d as i,e as t,i as c}from"./runtime-L2HNXIHW.js";import{a as s,b as l}from"./runtime-B73WLANC.js";var{prev:h,next:p}=a("/how-it-works"),g=u({items:[{dot:"1",dotColor:"accent",label:"Understand",content:e({content:'<strong style="color:var(--ui-text)">Read the guide and inspect the project</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">The agent fetches <code>pulse://workflow</code> and the relevant <code>pulse://guide/*</code> sections, then calls <code>pulse_list_structure</code> to see every existing page, component, and store. Ambiguities are surfaced before any files are touched.</p>'})},{dot:"2",dotColor:"accent",label:"Plan",content:e({content:'<strong style="color:var(--ui-text)">Present a plan \u2014 wait for confirmation</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Before writing anything the agent describes what it intends to build: the route, state shape, server data, interactions, and any components or integrations it will use. The task does not proceed until you confirm.</p>'})},{dot:"3",dotColor:"accent",label:"Build",content:e({content:'<strong style="color:var(--ui-text)">Write the spec and any supporting files</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">The spec is written as a plain JS object: route, state, server data, mutations, actions, view. The guide constrains every decision \u2014 where state lives, how validation is wired, how the view is structured.</p>'})},{dot:"4",dotColor:"accent",label:"Validate",content:e({content:'<strong style="color:var(--ui-text)">Call <code>pulse_validate</code> \u2014 fix all errors and warnings</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">The spec is checked against the Pulse schema. Every error and warning is resolved before moving on \u2014 missing hydrate, heading order violations, missing escaping, structural mistakes. A clean output is the gate to the next phase.</p>'})},{dot:"5",dotColor:"accent",label:"Browser",content:e({content:'<strong style="color:var(--ui-text)">Screenshot + Lighthouse \u2014 desktop and mobile</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">The agent navigates to the route, takes a screenshot to confirm the rendered output, then runs Lighthouse on both desktop and mobile. Accessibility, Best Practices, and SEO must all be 100. Any failure is fixed and re-verified before continuing.</p>'})},{dot:"6",dotColor:"accent",label:"Tests",content:e({content:'<strong style="color:var(--ui-text)">Write tests, run them, fix failures</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Unit tests cover any pure logic extracted from the spec. View tests use <code>renderSync</code> / <code>render</code> to assert HTML output. All tests must pass. When fixing a bug, a failing test is written first to pin the behaviour.</p>'})},{dot:"7",dotColor:"accent",label:"Review",content:e({content:'<strong style="color:var(--ui-text)">Call <code>pulse_review</code> \u2014 only after phases 4\u20136 all pass</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">The agent switches into reviewer mode \u2014 reading the source and rendered output against the full spec checklist. Accessibility, empty states, error handling, component usage, and security are all checked. The review agent is always last.</p>'})},{dot:"8",dotColor:"accent",label:"Fix and re-verify",content:e({content:'<strong style="color:var(--ui-text)">Fix every review issue \u2014 re-run affected gates</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Every issue raised in review is resolved. Validate, Lighthouse, and tests are re-run to confirm all gates still pass. The task is complete only when every phase clears cleanly.</p>'})},{dot:"\u2713",dotColor:"success",label:"Done",content:e({content:'<strong style="color:var(--ui-text)">The spec is the source of truth</strong><p style="color:var(--ui-muted);margin:.25rem 0 0">Validate clean. Lighthouse 100. Tests passing. Review clear. When all four gates pass, the page is done.</p>'})}]}),r={route:"/how-it-works",meta:{title:"How It Works \u2014 Pulse Docs",description:"How the Pulse MCP server gives an AI agent the knowledge and tools to build Pulse apps correctly.",styles:["/pulse-ui.css","/docs.css"]},state:{},view:()=>n({currentHref:"/how-it-works",prev:h,next:p,content:`
2
- ${d("How It Works")}
3
- ${i("When you run <code>pulse</code> in a project directory, a Model Context Protocol (MCP) server starts alongside the dev server. That MCP server is what gives your AI agent the knowledge, tools, and guardrails to build Pulse pages correctly \u2014 without you having to explain the framework in every prompt.")}
4
-
5
- ${t("mcp","The Pulse MCP server")}
6
- <p>MCP is a standard protocol that connects AI agents to external tools and knowledge. When Claude (or any MCP-compatible agent) opens a Pulse project, the MCP server is automatically detected and two things happen:</p>
7
- <ul>
8
- <li>The agent gains access to <strong>resources</strong> \u2014 read-only documents it can fetch at any time</li>
9
- <li>The agent gains access to <strong>tools</strong> \u2014 functions it can call to inspect and modify the project</li>
10
- </ul>
11
- <p>Together these replace the need to paste documentation into a system prompt or rely on the agent's training data to know how Pulse works.</p>
12
-
13
- ${t("resources","Resources: what the agent knows")}
14
- <p>The MCP server exposes two resources the agent reads before doing any work:</p>
15
- <ul>
16
- <li><strong><code>pulse://guide</code></strong> \u2014 the complete framework reference, split into focused sections so each fits comfortably in a single read. The agent fetches whichever sections are relevant to the task at hand.</li>
17
- <li><strong><code>pulse://persona</code></strong> \u2014 the quality bar the agent holds itself to: always write correct SSR, always handle errors, never skip empty states, never hardcode colours, never use <code>data-event</code> on text inputs. The persona defines what "done" means.</li>
18
- </ul>
19
-
20
- <p>The guide sections are:</p>
21
- <dl class="definition-list">
22
- <dt><code>pulse://guide/spec</code></dt>
23
- <dd>Spec format \u2014 state, mutations, actions, streaming SSR, validation, key rules, and form layout.</dd>
24
-
25
- <dt><code>pulse://guide/server</code></dt>
26
- <dd>Server data fetchers, global store, persist, cookies, redirects, and POST handling.</dd>
27
-
28
- <dt><code>pulse://guide/styles</code></dt>
29
- <dd>CSS tokens, theming, custom fonts, and utility classes.</dd>
30
-
31
- <dt><code>pulse://guide/routing</code></dt>
32
- <dd>Client-side navigation, page discovery, and dynamic routes.</dd>
33
-
34
- <dt><code>pulse://guide/components</code></dt>
35
- <dd>All UI components and their props, icons, charts, and composition patterns.</dd>
36
-
37
- <dt><code>pulse://guide/examples</code></dt>
38
- <dd>Complete working page examples covering common patterns.</dd>
39
- </dl>
40
-
41
- ${c("note","The guide is authoritative. When there is a question about how something should be structured \u2014 where state lives, how validation is wired, how streaming works \u2014 the guide answers it. The agent does not guess.")}
42
-
43
- ${t("tools","Tools: what the agent can do")}
44
- <p>The MCP server provides tools across four categories:</p>
45
-
46
- <h3 class="doc-h3">Scaffolding</h3>
47
- <dl class="definition-list">
48
- <dt><code>pulse_create_page</code></dt>
49
- <dd>Creates a new page spec file from a correct template. The filename determines the route \u2014 <code>about.js</code> becomes <code>/about</code>, <code>posts/[slug].js</code> becomes <code>/posts/:slug</code>. The file is written to <code>src/pages/</code> and picked up by the server automatically.</dd>
50
-
51
- <dt><code>pulse_create_component</code></dt>
52
- <dd>Creates a reusable view component in <code>src/components/</code>. Components export named functions that return HTML strings \u2014 no classes, no JSX, no lifecycle hooks.</dd>
53
-
54
- <dt><code>pulse_create_store</code></dt>
55
- <dd>Creates a global store module in <code>src/store/</code>. Stores hold shared state (cart, user session, theme) that multiple pages can subscribe to and mutate.</dd>
56
-
57
- <dt><code>pulse_create_action</code></dt>
58
- <dd>Scaffolds a reusable server action \u2014 useful for shared form submission logic, API calls, or mutations that appear on more than one page.</dd>
59
- </dl>
60
-
61
- <h3 class="doc-h3">Inspection</h3>
62
- <dl class="definition-list">
63
- <dt><code>pulse_list_structure</code></dt>
64
- <dd>Lists every page, component, and store that already exists in the project. The agent calls this before creating anything \u2014 to avoid duplication and to understand what the codebase already provides.</dd>
65
-
66
- <dt><code>pulse_fetch_page</code></dt>
67
- <dd>Reads the full source of an existing spec file. Used when the agent needs to understand an existing page before editing it, or when a review of the current state is needed.</dd>
68
- </dl>
69
-
70
- <h3 class="doc-h3">Validation &amp; review</h3>
71
- <dl class="definition-list">
72
- <dt><code>pulse_validate</code></dt>
73
- <dd>Validates a spec against the Pulse schema. Returns errors (which block progress) and warnings (which must also be resolved). The agent calls this after writing or editing every spec file.</dd>
74
-
75
- <dt><code>pulse_review</code></dt>
76
- <dd>Switches the agent into reviewer mode. Returns the spec source, the rendered HTML output, and a structured checklist covering accessibility, empty states, error handling, component usage, security, and correctness. The agent reads its own output critically and fixes every issue before continuing.</dd>
77
- </dl>
78
-
79
- <h3 class="doc-h3">Server</h3>
80
- <dl class="definition-list">
81
- <dt><code>pulse_restart_server</code></dt>
82
- <dd>Restarts the Pulse dev server. Called after structural changes \u2014 adding a new page, modifying the server entry \u2014 to reload all specs and re-register routes.</dd>
83
-
84
- <dt><code>pulse_build</code></dt>
85
- <dd>Builds the project for production. Bundles specs, hashes assets, and writes the manifest. Called when the agent needs to verify the production build or prepare a release.</dd>
86
- </dl>
87
-
88
- <h3 class="doc-h3">Maintenance</h3>
89
- <dl class="definition-list">
90
- <dt><code>pulse_check_version</code></dt>
91
- <dd>Reports the installed Pulse version and whether an update is available.</dd>
92
-
93
- <dt><code>pulse_update</code></dt>
94
- <dd>Updates the Pulse package to the latest version.</dd>
95
- </dl>
96
-
97
- ${t("flow","What happens when you ask for something")}
98
-
99
- ${g}
100
-
101
- ${t("why-mcp","Why MCP instead of a system prompt")}
102
- <p>A system prompt is static \u2014 it cannot see your project, cannot validate your code, and cannot guarantee the agent uses the right version of the framework. The MCP server is dynamic: it reads the guide from the installed version of Pulse, inspects the actual files in your project, and calls real validation on the spec the agent just wrote.</p>
103
- <p>The result is that the agent builds correctly on the first attempt far more often \u2014 not because it is smarter, but because the feedback loop is tighter and the knowledge is always current.</p>
104
- `})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",s(r,o,window.__PULSE_SERVER__||{},{ssr:!0}),l(o,s));var T=r;export{T as default};
@@ -1,78 +0,0 @@
1
- import{a as o}from"./runtime-QFURDKA2.js";import{a,b as d,c,d as p,e,g as t,i as r}from"./runtime-L2HNXIHW.js";import{a as i,b as u}from"./runtime-B73WLANC.js";var{prev:l,next:h}=a("/hydration"),s={route:"/hydration",meta:{title:"Hydration \u2014 Pulse Docs",description:"How Pulse hydrates server-rendered HTML on the client \u2014 the hydrate field, mount(), and production bundles.",styles:["/docs.css"]},state:{},view:()=>d({currentHref:"/hydration",prev:l,next:h,content:`
2
- ${c("Hydration")}
3
- ${p("Hydration in Pulse is opt-in and minimal. Omit <code>hydrate</code> and zero JavaScript is sent to the browser. Add it and Pulse binds events to the server-rendered HTML without re-rendering it \u2014 the SSR-painted content is preserved exactly as the server sent it.")}
4
-
5
- ${e("enabling","Enabling hydration")}
6
- <p>Set the <code>hydrate</code> field to a browser-importable path to your spec file. Pulse uses this to generate the bootstrap script that mounts the client runtime:</p>
7
- ${t(o(`export default {
8
- route: '/counter',
9
- hydrate: '/src/pages/counter.js', // browser path to this file
10
- state: { count: 0 },
11
- mutations: {
12
- increment: (state) => ({ count: state.count + 1 }),
13
- decrement: (state) => ({ count: state.count - 1 }),
14
- },
15
- view: (state) => \`
16
- <div>
17
- <button data-event="decrement">-</button>
18
- <span>\${state.count}</span>
19
- <button data-event="increment">+</button>
20
- </div>
21
- \`,
22
- }`,"js"))}
23
-
24
- ${e("dev-bootstrap","Development bootstrap")}
25
- <p>In development (when <code>hydrate</code> is a source file path, not a <code>/dist/</code> bundle), Pulse emits an inline bootstrap script:</p>
26
- ${t(o(`<script type="module">
27
- import spec from '/src/pages/counter.js'
28
- import { mount } from '/src/runtime/index.js'
29
- import { initNavigation } from '/src/runtime/navigate.js'
30
- mount(spec, root, window.__PULSE_SERVER__ || {}, { ssr: true })
31
- initNavigation(root, mount)
32
- <\/script>`,"html"))}
33
- <p>This imports the spec and runtime source files directly \u2014 no build step required for development.</p>
34
-
35
- ${e("production","Production bundles")}
36
- <p>Run <code>npm run build</code> to generate production bundles. This creates content-hashed files in <code>public/dist/</code> and a <code>manifest.json</code> mapping spec hydrate paths to bundle paths.</p>
37
- ${t(o(`# Generated by npm run build
38
- public/dist/
39
- runtime-abc123.js # shared runtime (~2.1 kB brotli)
40
- counter.boot-def456.js # per-page spec bundle (~0.5 kB brotli)
41
- manifest.json # { '/src/pages/counter.js': '/dist/counter.boot-def456.js' }`,"bash"))}
42
- <p>When Pulse detects a manifest (via <code>staticDir</code> auto-detection or explicit <code>manifest</code> option), it resolves the <code>hydrate</code> path to the bundle path and emits a single <code>&lt;script src&gt;</code> tag instead of the inline bootstrap:</p>
43
- ${t(o('<script type="module" src="/dist/counter.boot-def456.js"><\/script>',"html"))}
44
-
45
- ${e("ssr-true","The { ssr: true } option")}
46
- <p>The bootstrap script calls <code>mount(spec, root, serverState, { ssr: true })</code>. This tells the runtime to skip the initial re-render and bind event listeners to the existing DOM only.</p>
47
- <p>This is what keeps LCP fast. The server-painted HTML is the LCP element. The JavaScript binds events without touching the DOM \u2014 no flash, no layout shift, no JS-rendered replacement.</p>
48
- ${r("warning","Never set <code>{ ssr: false }</code> on a server-rendered page. It re-renders the entire DOM on mount, replacing the server-painted HTML \u2014 causing a visible flash and pushing LCP to 400\u2013600ms.")}
49
-
50
- ${e("mount","mount()")}
51
- <p>The <code>mount</code> function attaches the Pulse runtime to a DOM element:</p>
52
- ${t(o(`import { mount } from '@invisibleloop/pulse/runtime'
53
-
54
- mount(
55
- spec, // the page spec
56
- rootEl, // the DOM element to mount into
57
- serverState, // window.__PULSE_SERVER__ (server data from SSR)
58
- { ssr: true } // skip re-render on first mount
59
- )`,"js"))}
60
- <p>After mount, all <code>data-event</code> and <code>data-action</code> attributes in the DOM are wired to the spec's mutations and actions. State updates trigger a full view re-render via <code>innerHTML</code> replacement.</p>
61
-
62
- ${e("no-hydrate","Pages without hydration")}
63
- <p>Omit <code>hydrate</code> and Pulse sends zero JavaScript to the browser \u2014 no runtime overhead, no hydration cost. This is the correct default for:</p>
64
- <ul>
65
- <li>Documentation pages</li>
66
- <li>Marketing/landing pages</li>
67
- <li>Blog posts and articles</li>
68
- <li>Any page with no client-side interactivity</li>
69
- </ul>
70
- ${r("tip",'Start without <code>hydrate</code> and only add it when actual client interactivity is needed. Many pages that appear to need JavaScript can be handled server-side with <a href="/routing">routing</a> and <a href="/server-data">server data</a>.')}
71
-
72
- ${e("server-state","Passing server state to the client")}
73
- <p>Server data fetched via <code>server.data()</code> is serialised into the page HTML as <code>window.__PULSE_SERVER__</code>. The client runtime reads this on mount, making server data available to the view during client re-renders without an additional network request.</p>
74
- ${t(o(`// Emitted in the page HTML
75
- <script id="__PULSE_SERVER__" type="application/json">
76
- {"product":{"id":1,"name":"Widget","price":9.99}}
77
- <\/script>`,"html"))}
78
- `})};var n=document.getElementById("pulse-root");n&&!n.dataset.pulseMounted&&(n.dataset.pulseMounted="1",i(s,n,window.__PULSE_SERVER__||{},{ssr:!0}),u(n,i));var P=s;export{P as default};