@invisibleloop/pulse 0.1.28 → 0.1.30

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 (124) hide show
  1. package/.github/workflows/publish.yml +11 -20
  2. package/README.md +1 -1
  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/public/pulse-ui.css +2 -2
  15. package/src/agent/guide-components.md +1 -1
  16. package/src/agent/guide-routing.md +20 -0
  17. package/src/agent/guide-spec.md +10 -1
  18. package/src/agent/guide-styles.md +16 -1
  19. package/src/agent/workflow.md +1 -1
  20. package/src/cli/scaffold.js +63 -2
  21. package/src/mcp/server.js +34 -18
  22. package/src/server/index.js +26 -7
  23. package/src/server/server.test.js +47 -0
  24. package/src/ui/stat.js +1 -1
  25. package/src/ui/ui.test.js +6 -0
  26. package/docs/public/dist/accessibility.boot-5DVTARJU.js +0 -115
  27. package/docs/public/dist/actions.boot-P66HKQEM.js +0 -164
  28. package/docs/public/dist/auth.boot-IMAJAUPH.js +0 -140
  29. package/docs/public/dist/caching.boot-DVR6KDE7.js +0 -53
  30. package/docs/public/dist/components--accordion.boot-3HVKMNWC.js +0 -11
  31. package/docs/public/dist/components--alert.boot-GCEXOZAC.js +0 -6
  32. package/docs/public/dist/components--app-badge.boot-DVT3GCHJ.js +0 -6
  33. package/docs/public/dist/components--avatar.boot-PSW24EVA.js +0 -5
  34. package/docs/public/dist/components--badge.boot-TYDY2RMK.js +0 -7
  35. package/docs/public/dist/components--banner.boot-EI5PZSZK.js +0 -7
  36. package/docs/public/dist/components--breadcrumbs.boot-SMA2E2GO.js +0 -34
  37. package/docs/public/dist/components--button.boot-J54BQM2E.js +0 -23
  38. package/docs/public/dist/components--card.boot-PZGNDIB6.js +0 -138
  39. package/docs/public/dist/components--carousel.boot-TP6LPFZZ.js +0 -12
  40. package/docs/public/dist/components--charts.boot-2EOYQWKL.js +0 -108
  41. package/docs/public/dist/components--checkbox.boot-DS5BSL6T.js +0 -54
  42. package/docs/public/dist/components--cluster.boot-HHVIBBJG.js +0 -9
  43. package/docs/public/dist/components--code-window.boot-2GR2DV33.js +0 -20
  44. package/docs/public/dist/components--container.boot-7LOOGK2K.js +0 -5
  45. package/docs/public/dist/components--cta.boot-FSNZ5YRT.js +0 -11
  46. package/docs/public/dist/components--divider.boot-3NI2C3QG.js +0 -6
  47. package/docs/public/dist/components--empty.boot-YX2UR3PV.js +0 -7
  48. package/docs/public/dist/components--feature.boot-MUD7NSUO.js +0 -13
  49. package/docs/public/dist/components--fieldset.boot-J7BYHMKF.js +0 -19
  50. package/docs/public/dist/components--fileupload.boot-NIKVTTPD.js +0 -52
  51. package/docs/public/dist/components--footer.boot-EYUK5FRG.js +0 -14
  52. package/docs/public/dist/components--grid.boot-URDQVDDR.js +0 -59
  53. package/docs/public/dist/components--heading.boot-BPQKU43E.js +0 -44
  54. package/docs/public/dist/components--hero.boot-4RAPRGAB.js +0 -17
  55. package/docs/public/dist/components--icons.boot-ZITNU5JP.js +0 -68
  56. package/docs/public/dist/components--image.boot-XEEGHQZF.js +0 -19
  57. package/docs/public/dist/components--input.boot-SGASZG5K.js +0 -7
  58. package/docs/public/dist/components--list.boot-W3XC5MHD.js +0 -55
  59. package/docs/public/dist/components--media.boot-5VFIETZO.js +0 -13
  60. package/docs/public/dist/components--modal.boot-RZUYXBN2.js +0 -47
  61. package/docs/public/dist/components--nav.boot-ODBOHU7O.js +0 -33
  62. package/docs/public/dist/components--pricing.boot-4AQ4ZVBY.js +0 -21
  63. package/docs/public/dist/components--progress.boot-GHAGYZOK.js +0 -30
  64. package/docs/public/dist/components--prose.boot-QANJL6JI.js +0 -67
  65. package/docs/public/dist/components--pullquote.boot-Q2WMNAZU.js +0 -22
  66. package/docs/public/dist/components--radio.boot-TJRDQ2OL.js +0 -75
  67. package/docs/public/dist/components--rating.boot-QBAN6DEL.js +0 -38
  68. package/docs/public/dist/components--search.boot-PXH5O5AG.js +0 -17
  69. package/docs/public/dist/components--section.boot-AQGIYHWW.js +0 -12
  70. package/docs/public/dist/components--segmented.boot-BEVTKEJO.js +0 -33
  71. package/docs/public/dist/components--select.boot-47X5RHOC.js +0 -10
  72. package/docs/public/dist/components--slider.boot-PSRRX7XL.js +0 -47
  73. package/docs/public/dist/components--spinner.boot-MZ5MO2OH.js +0 -22
  74. package/docs/public/dist/components--stack.boot-DI4NJXBF.js +0 -9
  75. package/docs/public/dist/components--stat.boot-QMFUWBQT.js +0 -9
  76. package/docs/public/dist/components--stepper.boot-34PP2NEV.js +0 -22
  77. package/docs/public/dist/components--table.boot-FCQGSFIQ.js +0 -11
  78. package/docs/public/dist/components--testimonial.boot-DWQPDKYG.js +0 -11
  79. package/docs/public/dist/components--textarea.boot-QVXLBOJ5.js +0 -4
  80. package/docs/public/dist/components--timeline.boot-26LN52P2.js +0 -95
  81. package/docs/public/dist/components--toggle.boot-IQQEI76S.js +0 -29
  82. package/docs/public/dist/components--tooltip.boot-LGHCO6NN.js +0 -9
  83. package/docs/public/dist/components.boot-SE6PQ4P7.js +0 -103
  84. package/docs/public/dist/config.boot-DTRRWUE6.js +0 -126
  85. package/docs/public/dist/constraints.boot-DUHDZBMC.js +0 -71
  86. package/docs/public/dist/deploy.boot-SLAD3NI2.js +0 -163
  87. package/docs/public/dist/docs-8e3d4b5c.css +0 -1
  88. package/docs/public/dist/extending.boot-UA3CN243.js +0 -159
  89. package/docs/public/dist/faq.boot-6EQAWLQR.js +0 -43
  90. package/docs/public/dist/getting-started.boot-TDKIFL5U.js +0 -86
  91. package/docs/public/dist/guard.boot-AUHAWTG4.js +0 -80
  92. package/docs/public/dist/home.boot-BVQXRH32.js +0 -383
  93. package/docs/public/dist/how-it-works.boot-LTWAKWKW.js +0 -104
  94. package/docs/public/dist/hydration.boot-JRM6IPJL.js +0 -78
  95. package/docs/public/dist/images.boot-M6ZVKTZS.js +0 -80
  96. package/docs/public/dist/manifest.json +0 -94
  97. package/docs/public/dist/meta.boot-7NXGPHR4.js +0 -79
  98. package/docs/public/dist/mutations.boot-F6F43UDX.js +0 -79
  99. package/docs/public/dist/navigation.boot-AOXWS3ZF.js +0 -57
  100. package/docs/public/dist/performance.boot-C3UPCOBK.js +0 -98
  101. package/docs/public/dist/persist.boot-WT32PQOQ.js +0 -61
  102. package/docs/public/dist/project-structure.boot-FB3LRVJ4.js +0 -63
  103. package/docs/public/dist/prompt-examples.boot-YKR4VDK4.js +0 -31
  104. package/docs/public/dist/pulse-ui-81a85c03.css +0 -1
  105. package/docs/public/dist/raw-responses.boot-M4KA5YXL.js +0 -104
  106. package/docs/public/dist/routing.boot-FNX5FDGH.js +0 -70
  107. package/docs/public/dist/runtime-B73WLANC.js +0 -1
  108. package/docs/public/dist/runtime-KO4BHUQ3.js +0 -49
  109. package/docs/public/dist/runtime-L2HNXIHW.js +0 -59
  110. package/docs/public/dist/runtime-QFURDKA2.js +0 -5
  111. package/docs/public/dist/runtime-UVPXO4IR.js +0 -375
  112. package/docs/public/dist/runtime-VMJA3Z4N.js +0 -10
  113. package/docs/public/dist/runtime-ZJ4FXT5O.js +0 -11
  114. package/docs/public/dist/server-api.boot-K7X3LCFB.js +0 -219
  115. package/docs/public/dist/server-data.boot-Y7HQYC4R.js +0 -157
  116. package/docs/public/dist/slash-commands.boot-V2UV7OW2.js +0 -26
  117. package/docs/public/dist/spec.boot-2WU7ZHCV.js +0 -159
  118. package/docs/public/dist/state.boot-B24GUE3R.js +0 -73
  119. package/docs/public/dist/store.boot-TLIB4XHH.js +0 -150
  120. package/docs/public/dist/streaming.boot-W2DZSMW4.js +0 -80
  121. package/docs/public/dist/stripe.boot-QN3C2GEL.js +0 -164
  122. package/docs/public/dist/supabase.boot-BG4XXLZE.js +0 -303
  123. package/docs/public/dist/testing.boot-6U4WKMTE.js +0 -130
  124. package/docs/public/dist/validation.boot-PQHYGW5B.js +0 -100
@@ -1,80 +0,0 @@
1
- import{a as r}from"./runtime-QFURDKA2.js";import{a as i,b as u,c as h,d as l,e,f as s,g as t,h as a,i as d}from"./runtime-L2HNXIHW.js";import{a as c,b as p}from"./runtime-B73WLANC.js";var{prev:f,next:g}=i("/guard"),n={route:"/guard",meta:{title:"Guard \u2014 Pulse Docs",description:"Per-route authorization in Pulse. Guard functions run before server data fetchers and redirect unauthorized requests.",styles:["/docs.css"]},state:{},view:()=>u({currentHref:"/guard",prev:f,next:g,content:`
2
- ${h("Guard")}
3
- ${l("A <code>guard</code> function runs on every request to a route, before any server data is fetched. It is the enforced access control point \u2014 unauthorized requests are redirected before any database queries or data fetchers execute.")}
4
-
5
- ${e("basics","Basic usage")}
6
- <p>A <code>guard</code> function on any spec receives the same <code>ctx</code> object as server data fetchers \u2014 params, query, headers, and cookies.</p>
7
- ${t(r(`export default {
8
- route: '/dashboard',
9
-
10
- guard: async (ctx) => {
11
- if (!ctx.cookies.session) return { redirect: '/login' }
12
- },
13
-
14
- server: {
15
- user: async (ctx) => getCurrentUser(ctx.cookies.session),
16
- },
17
-
18
- state: {},
19
- view: (state, server) => \`
20
- <main id="main-content">
21
- <h1>Welcome, \${server.user.name}</h1>
22
- </main>
23
- \`,
24
- }`,"js"))}
25
-
26
- <p>When the guard returns <code>{ redirect }</code>, the server responds with a <strong>302</strong> and all server data fetchers are skipped \u2014 no data is fetched for unauthorized requests. When the guard returns nothing, the request proceeds normally.</p>
27
-
28
- ${e("ctx","What ctx contains")}
29
- ${a(["Property / Method","Type","Description"],[["<code>ctx.cookies</code>","object","Parsed cookies from the <code>Cookie</code> header"],["<code>ctx.headers</code>","object","Raw request headers"],["<code>ctx.params</code>","object",'Route params e.g. <code>{ id: "42" }</code>'],["<code>ctx.query</code>","object","Parsed query string"],["<code>ctx.pathname</code>","string","URL path e.g. <code>/dashboard</code>"],["<code>ctx.method</code>","string","HTTP method e.g. <code>GET</code>, <code>POST</code>"],["<code>ctx.store</code>","object","Resolved global store state (if a store is registered)"],["<code>ctx.nonce</code>","string","CSP nonce for the current request"],["<code>await ctx.json()</code>","object | null","Parse a JSON request body"],["<code>await ctx.text()</code>","string","Read the body as a plain string"],["<code>await ctx.formData()</code>","object | null","Parse a URL-encoded body into a plain object"],["<code>await ctx.buffer()</code>","Buffer","Read the raw body as a Node.js Buffer"]])}
30
-
31
- ${e("examples","Common patterns")}
32
-
33
- ${s("Session check")}
34
- <p>Redirect to login when no session cookie is present.</p>
35
- ${t(r(`guard: async (ctx) => {
36
- if (!ctx.cookies.session) return { redirect: '/login' }
37
- }`,"js"))}
38
-
39
- ${s("Role-based access")}
40
- <p>Fetch the user from the session and check their role. Keep the lookup fast \u2014 guard runs on every request to the route.</p>
41
- ${t(r(`guard: async (ctx) => {
42
- const user = await getUserFromSession(ctx.cookies.session)
43
- if (!user) return { redirect: '/login' }
44
- if (!user.isAdmin) return { redirect: '/403' }
45
- }`,"js"))}
46
-
47
- ${s("Redirect authenticated users away from login")}
48
- <p>Useful for login and signup pages \u2014 send already-authenticated users somewhere useful.</p>
49
- ${t(r(`export default {
50
- route: '/login',
51
-
52
- guard: async (ctx) => {
53
- if (ctx.cookies.session) return { redirect: '/dashboard' }
54
- },
55
-
56
- state: {},
57
- view: () => \`<main id="main-content">...</main>\`,
58
- }`,"js"))}
59
-
60
- ${d("info","Guard runs server-side on every request, including client-side navigation requests \u2014 those go through the same server pipeline. There is no way to bypass guard from the browser.")}
61
-
62
- ${e("custom-responses","Custom status responses")}
63
- <p>Guard can return a custom HTTP response instead of (or alongside) a redirect. Return <code>{ status, json?, body?, headers? }</code> to send any status code with an optional JSON or text body. This is useful for POST handlers that need to signal validation errors or API-style rejections:</p>
64
- ${t(r(`guard: async (ctx) => {
65
- const token = ctx.headers.authorization
66
- if (!token) return { status: 401, json: { error: 'Unauthorized' } }
67
-
68
- if (ctx.method === 'POST') {
69
- const data = await ctx.formData()
70
- if (!data.email) return { status: 422, json: { error: 'Email required' } }
71
- await db.leads.create(data)
72
- return { redirect: '/contact?sent=1' }
73
- }
74
- // return nothing to let a GET request proceed to the view
75
- }`,"js"))}
76
- ${d("note","To use <code>guard</code> as a POST handler, the spec must declare <code>methods: ['GET', 'POST']</code>. Without it, POST requests are rejected with 405 before guard runs.")}
77
-
78
- ${e("reference","Reference")}
79
- ${a(["Property","Type","Required"],[["<code>guard</code>","<code>async (ctx) =&gt; { redirect?: string } | { status, json?, body?, headers? } | void</code>","No"]])}
80
- `})};var o=document.getElementById("pulse-root");o&&!o.dataset.pulseMounted&&(o.dataset.pulseMounted="1",c(n,o,window.__PULSE_SERVER__||{},{ssr:!0}),p(o,c));var P=n;export{P as default};
@@ -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};