@invisibleloop/pulse 0.14.2 → 0.15.0
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.
- package/docs/.claude/pulse-checklist.md +5 -3
- package/docs/public/.pulse-ui-version +1 -1
- package/docs/public/docs.css +236 -0
- package/docs/public/home-brut.css +998 -0
- package/docs/public/menu.js +237 -6
- package/docs/public/pulse-ui.css +44 -0
- package/docs/public/theme.css +8 -0
- package/docs/server.js +24 -0
- package/docs/src/lib/layout.js +51 -3
- package/docs/src/pages/getting-started.js +1 -1
- package/docs/src/pages/home.js +231 -312
- package/docs/src/pages/how-it-works.js +67 -12
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +127 -21
- package/src/agent/guide-examples.md +310 -32
- package/src/cli/dev.js +43 -0
package/docs/src/pages/home.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
// component-free — creative override: brutalist design intent; asymmetric typography-driven layout with exposed grid structure, raw borders, and oversized type that component patterns cannot express
|
|
2
|
+
import { highlight } from '../lib/highlight.js'
|
|
3
|
+
import { iconZap, iconShield, iconSettings } from '../../../src/ui/icons.js'
|
|
2
4
|
import { metricsStore } from '../lib/metrics-store.js'
|
|
3
5
|
import { codeWindow } from '../../../src/ui/code-window.js'
|
|
4
6
|
import pkg from '../../../package.json' with { type: 'json' }
|
|
@@ -37,8 +39,8 @@ export default {
|
|
|
37
39
|
route: '/',
|
|
38
40
|
meta: {
|
|
39
41
|
title: 'Pulse — The spec-first web framework',
|
|
40
|
-
description: 'Pulse is a server-first Node.js framework with zero runtime dependencies. One spec object per page
|
|
41
|
-
styles: ['/pulse-ui.css', '/docs.css'],
|
|
42
|
+
description: 'Pulse is a server-first Node.js framework with zero runtime dependencies. One spec object per page: server data, state, mutations, and view in plain JS. Streaming SSR, security headers, and production caching are enforced by the architecture.',
|
|
43
|
+
styles: ['https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800;900&display=swap', '/pulse-ui.css', '/theme.css', '/docs.css', '/home-brut.css'],
|
|
42
44
|
schema: {
|
|
43
45
|
'@context': 'https://schema.org',
|
|
44
46
|
'@type': 'SoftwareApplication',
|
|
@@ -57,361 +59,278 @@ export default {
|
|
|
57
59
|
metrics: () => metricsStore.current,
|
|
58
60
|
},
|
|
59
61
|
view: (state, server) => `
|
|
60
|
-
<div class="
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
<div class="brut">
|
|
63
|
+
|
|
64
|
+
<nav class="brut-nav" aria-label="Site navigation">
|
|
65
|
+
<a href="/" class="brut-nav-logo" aria-label="Pulse home">
|
|
66
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
67
|
+
<path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="#f0e642" stroke="#f0e642" stroke-width="1" stroke-linejoin="round"/>
|
|
65
68
|
</svg>
|
|
66
|
-
|
|
69
|
+
<span>PULSE</span>
|
|
67
70
|
</a>
|
|
68
|
-
<div class="
|
|
71
|
+
<div class="brut-nav-links">
|
|
69
72
|
<a href="/getting-started">Docs</a>
|
|
70
73
|
<a href="https://github.com/invisibleloop/pulse-framework" target="_blank" rel="noopener" aria-label="GitHub (opens in new tab)">GitHub</a>
|
|
74
|
+
<a href="/getting-started" class="brut-nav-cta">Get Started</a>
|
|
71
75
|
</div>
|
|
72
76
|
</nav>
|
|
73
77
|
|
|
74
78
|
<main id="main-content">
|
|
75
|
-
<section class="hero">
|
|
76
|
-
<div class="hero-icon" aria-hidden="true">
|
|
77
|
-
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
|
|
78
|
-
<path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="var(--accent)" stroke="var(--accent)" stroke-width="1" stroke-linejoin="round"/>
|
|
79
|
-
</svg>
|
|
80
|
-
</div>
|
|
81
|
-
<div class="hero-badge">v${version} — EARLY ACCESS</div>
|
|
82
|
-
<p class="hero-kicker">A server-first Node.js framework. Zero runtime dependencies.</p>
|
|
83
|
-
<h1 class="hero-title">One spec. One way to build. Production quality by design.</h1>
|
|
84
|
-
<p class="hero-subtitle">Each page is a single plain JS object — server data, client state, mutations, and view co-located in one place. Streaming SSR, security headers, and production caching are enforced by the framework, not left to configuration.<br><strong>Designed for AI agents. Simple enough to write yourself.</strong></p>
|
|
85
|
-
<div class="hero-ctas">
|
|
86
|
-
<a href="/getting-started" class="btn-primary">Get Started</a>
|
|
87
|
-
<a href="/spec" class="btn-secondary">Read the Spec</a>
|
|
88
|
-
</div>
|
|
89
|
-
</section>
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
80
|
+
<section class="brut-hero">
|
|
81
|
+
<div class="brut-hero-tag">v${version} — EARLY ACCESS</div>
|
|
82
|
+
<h1 class="brut-hero-h1">
|
|
83
|
+
<span class="brut-hero-kicker">with Pulse</span>
|
|
84
|
+
<span class="brut-hero-line1">YOU CAN'T</span>
|
|
85
|
+
<span class="brut-hero-line2" role="img" aria-label="SHIP">
|
|
86
|
+
<svg class="brut-hero-svg" aria-hidden="true">
|
|
87
|
+
<text class="brut-hero-svg-text" y="0.82em" dominant-baseline="auto"
|
|
88
|
+
font-family="Inter, system-ui, sans-serif" font-weight="900"
|
|
89
|
+
fill="var(--brut-yellow)" stroke="var(--brut-ink)" stroke-width="0.055em" paint-order="stroke fill">SHIP</text>
|
|
90
|
+
</svg>
|
|
91
|
+
</span>
|
|
92
|
+
<span class="brut-hero-line3">A BAD PAGE.</span>
|
|
93
|
+
</h1>
|
|
94
|
+
<div class="brut-hero-aside">
|
|
95
|
+
<p class="brut-hero-tag-inline">ONE SPEC. ONE WAY TO BUILD.</p>
|
|
96
|
+
<p class="brut-hero-desc">A server-first Node.js framework. Streaming SSR, security headers, and production caching are enforced by the architecture, not left to configuration.</p>
|
|
97
|
+
<p class="brut-hero-sub"><strong>So constrained that doing it wrong isn't an option. Whether you write it or an AI does.</strong></p>
|
|
98
|
+
<div class="brut-hero-ctas">
|
|
99
|
+
<a href="/getting-started" class="brut-btn-primary">Get Started</a>
|
|
100
|
+
<a href="/spec" class="brut-btn-ghost">Read the Spec</a>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</section>
|
|
112
104
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<div class="how-step">
|
|
118
|
-
<div class="how-step-num">1</div>
|
|
119
|
-
<h3>The spec is the page</h3>
|
|
120
|
-
<p>Everything a page needs lives in one plain JS object — server data, client state, mutations, and view. One format, no split files, no hidden conventions.</p>
|
|
105
|
+
<div class="brut-stats-bar">
|
|
106
|
+
<div class="brut-stat">
|
|
107
|
+
<span class="brut-stat-val">4 kB</span>
|
|
108
|
+
<span class="brut-stat-lbl">Runtime JS · first visit</span>
|
|
121
109
|
</div>
|
|
122
|
-
<div class="
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
<h3>The schema is the contract</h3>
|
|
126
|
-
<p>Every spec is validated at startup. Either it's correct or it's rejected — no ambiguity, no misconfiguration that surfaces later in production.</p>
|
|
110
|
+
<div class="brut-stat">
|
|
111
|
+
<span class="brut-stat-val">0.00</span>
|
|
112
|
+
<span class="brut-stat-lbl">Cumulative Layout Shift</span>
|
|
127
113
|
</div>
|
|
128
|
-
<div class="
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
114
|
+
<div class="brut-stat">
|
|
115
|
+
<span class="brut-stat-val">100</span>
|
|
116
|
+
<span class="brut-stat-lbl">Lighthouse · by design</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="brut-stat">
|
|
119
|
+
<span class="brut-stat-val">0</span>
|
|
120
|
+
<span class="brut-stat-lbl">Runtime dependencies</span>
|
|
133
121
|
</div>
|
|
134
122
|
</div>
|
|
135
|
-
</div>
|
|
136
|
-
</section>
|
|
137
123
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<div class="
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
124
|
+
<section class="brut-idea">
|
|
125
|
+
<div class="brut-idea-label">THE IDEA</div>
|
|
126
|
+
<div class="brut-idea-grid">
|
|
127
|
+
<div class="brut-idea-step">
|
|
128
|
+
<div class="brut-idea-num" aria-hidden="true">01</div>
|
|
129
|
+
<h2>The spec is the page</h2>
|
|
130
|
+
<p>Everything a page needs lives in one plain JS object: server data, client state, mutations, and view. One format. No split files. No hidden conventions.</p>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="brut-idea-step">
|
|
133
|
+
<div class="brut-idea-num" aria-hidden="true">02</div>
|
|
134
|
+
<h2>The schema is the contract</h2>
|
|
135
|
+
<p>Every spec is validated at startup. Either it's correct or it's rejected. No ambiguity, no misconfiguration that surfaces later in production.</p>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="brut-idea-step">
|
|
138
|
+
<div class="brut-idea-num" aria-hidden="true">03</div>
|
|
139
|
+
<h2>The framework is the guarantee</h2>
|
|
140
|
+
<p>Streaming SSR, security headers, and production caching come from the architecture. You write the product logic. The framework ships the quality.</p>
|
|
141
|
+
</div>
|
|
146
142
|
</div>
|
|
147
|
-
</
|
|
148
|
-
</section>
|
|
143
|
+
</section>
|
|
149
144
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<div class="ai-cols">
|
|
155
|
-
<div class="ai-col">
|
|
156
|
-
<h3 class="ai-col-title">For developers</h3>
|
|
157
|
-
<ul class="ai-col-list">
|
|
158
|
-
<li>One format to learn — server, state, mutations, view in one place.</li>
|
|
159
|
-
<li>Production quality is the starting point, not a final audit.</li>
|
|
160
|
-
<li>Specs are short, self-contained, and easy to review.</li>
|
|
161
|
-
<li>Nothing to misconfigure — the framework doesn't give you the choice.</li>
|
|
162
|
-
</ul>
|
|
145
|
+
<section class="brut-code-section">
|
|
146
|
+
<div class="brut-code-header">
|
|
147
|
+
<h2>Everything a page needs.<br>Nothing it doesn't.</h2>
|
|
148
|
+
<p>Server fetchers, client state, mutations, and view. All co-located in one object. The spec <em>is</em> the page.</p>
|
|
163
149
|
</div>
|
|
164
|
-
<div class="
|
|
165
|
-
|
|
166
|
-
<ul class="ai-col-list">
|
|
167
|
-
<li>One valid structure per page — agents can't pick the wrong pattern.</li>
|
|
168
|
-
<li>The schema rejects bad output at startup, not in production.</li>
|
|
169
|
-
<li>Security, SSR, and caching can't be accidentally omitted.</li>
|
|
170
|
-
<li>Consistent output across agents, sessions, and team members.</li>
|
|
171
|
-
</ul>
|
|
150
|
+
<div class="brut-code-block">
|
|
151
|
+
${codeWindow({ content: exampleSpec, filename: 'src/pages/dashboard.js', lang: 'JavaScript' })}
|
|
172
152
|
</div>
|
|
173
|
-
</
|
|
174
|
-
</section>
|
|
153
|
+
</section>
|
|
175
154
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
</
|
|
189
|
-
</
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<td class="v-yes">One JS object per page</td>
|
|
204
|
-
<td class="v-no">Files, folders, magic exports spread across dirs</td>
|
|
205
|
-
<td class="v-no">Files, folders, Svelte syntax</td>
|
|
206
|
-
</tr>
|
|
207
|
-
<tr>
|
|
208
|
-
<th scope="row">SSR out of the box</th>
|
|
209
|
-
<td class="v-yes">Streaming SSR, zero config</td>
|
|
210
|
-
<td class="v-partial">Yes, but client hydration adds JS on every page</td>
|
|
211
|
-
<td class="v-partial">Yes, but requires an adapter and client runtime on every page</td>
|
|
212
|
-
</tr>
|
|
213
|
-
<tr>
|
|
214
|
-
<th scope="row">Client JS shipped</th>
|
|
215
|
-
<td class="v-yes">~4 kB brotli on first visit; 0 kB on static pages</td>
|
|
216
|
-
<td class="v-no">50–200 kB+ depending on features used</td>
|
|
217
|
-
<td class="v-partial">~15 kB brotli</td>
|
|
218
|
-
</tr>
|
|
219
|
-
<tr>
|
|
220
|
-
<th scope="row">Security headers</th>
|
|
221
|
-
<td class="v-yes">On every response, built in</td>
|
|
222
|
-
<td class="v-no">Manual middleware or plugin</td>
|
|
223
|
-
<td class="v-no">Manual hooks setup</td>
|
|
224
|
-
</tr>
|
|
225
|
-
<tr>
|
|
226
|
-
<th scope="row">CLS</th>
|
|
227
|
-
<td class="v-yes">Targets 0.00 — shell renders before data arrives</td>
|
|
228
|
-
<td class="v-partial">Depends on implementation</td>
|
|
229
|
-
<td class="v-partial">Depends on implementation</td>
|
|
230
|
-
</tr>
|
|
231
|
-
<tr>
|
|
232
|
-
<th scope="row">Runtime dependencies</th>
|
|
233
|
-
<td class="v-yes">Zero — pure Node.js HTTP</td>
|
|
234
|
-
<td class="v-no">React, 50+ transitive packages</td>
|
|
235
|
-
<td class="v-no">Svelte runtime + adapters</td>
|
|
236
|
-
</tr>
|
|
237
|
-
<tr>
|
|
238
|
-
<th scope="row">Production build step</th>
|
|
239
|
-
<td class="v-yes">Server needs none — <code>node server.js</code> is production. Client bundles are optional.</td>
|
|
240
|
-
<td class="v-no">Required — <code>next build</code></td>
|
|
241
|
-
<td class="v-no">Required — <code>vite build</code></td>
|
|
242
|
-
</tr>
|
|
243
|
-
</tbody>
|
|
244
|
-
</table>
|
|
245
|
-
</div>
|
|
246
|
-
</section>
|
|
155
|
+
<section class="brut-constraint">
|
|
156
|
+
<div class="brut-constraint-inner">
|
|
157
|
+
<div class="brut-constraint-label">AI + PULSE</div>
|
|
158
|
+
<h2 class="brut-constraint-h2">Constrained enough<br>to be trusted.<br>Free enough<br>to be creative.</h2>
|
|
159
|
+
<p class="brut-constraint-lead">The spec format tells an agent exactly how to wire up data, state, and behaviour. What it doesn't do is dictate the design. Layout, typography, CSS — those are still real decisions. The agent makes them. Pulse just makes sure the page it builds actually works.</p>
|
|
160
|
+
<div class="brut-constraint-cols">
|
|
161
|
+
<div class="brut-col">
|
|
162
|
+
<div class="brut-col-head">The structure is fixed</div>
|
|
163
|
+
<ul class="brut-col-list">
|
|
164
|
+
<li>One spec format. No ambiguity about how a page should be built</li>
|
|
165
|
+
<li>Schema validation at startup. Bad output is rejected before it ships</li>
|
|
166
|
+
<li>Security, SSR, and caching are part of the architecture. Not optional</li>
|
|
167
|
+
<li>Consistent, reviewable output across every agent and every session</li>
|
|
168
|
+
</ul>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="brut-col brut-col--hi">
|
|
171
|
+
<div class="brut-col-head brut-col-head--hi">The design is not</div>
|
|
172
|
+
<ul class="brut-col-list">
|
|
173
|
+
<li>The view is a plain JS function. The agent writes whatever HTML it wants</li>
|
|
174
|
+
<li>CSS, layout, and typography are entirely up to the agent</li>
|
|
175
|
+
<li>A component library is there when needed. Custom HTML when it isn't</li>
|
|
176
|
+
<li>The result looks considered because the agent had room to make it so</li>
|
|
177
|
+
</ul>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</section>
|
|
247
182
|
|
|
248
|
-
|
|
183
|
+
<section class="brut-versus">
|
|
184
|
+
<div class="brut-versus-header">
|
|
185
|
+
<div class="brut-versus-label">HOW PULSE COMPARES</div>
|
|
186
|
+
<h2 class="brut-versus-h2">Constraints enforced.<br>Not recommended.</h2>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="versus-table-wrap table-sticky-col">
|
|
189
|
+
<table class="brut-table">
|
|
190
|
+
<thead>
|
|
191
|
+
<tr>
|
|
192
|
+
<th></th>
|
|
193
|
+
<th class="brut-th-pulse">
|
|
194
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" aria-hidden="true" style="vertical-align:middle;margin-right:.3rem">
|
|
195
|
+
<path d="M13 2L4.5 13.5H11L10 22L19.5 10.5H13L13 2Z" fill="#f0e642" stroke="#f0e642" stroke-width="1" stroke-linejoin="round"/>
|
|
196
|
+
</svg>PULSE
|
|
197
|
+
</th>
|
|
198
|
+
<th>Next.js / Remix</th>
|
|
199
|
+
<th>SvelteKit</th>
|
|
200
|
+
</tr>
|
|
201
|
+
</thead>
|
|
202
|
+
<tbody>
|
|
203
|
+
<tr>
|
|
204
|
+
<th scope="row">Ways to write a page</th>
|
|
205
|
+
<td class="v-yes">One: the spec schema</td>
|
|
206
|
+
<td class="v-no">App Router, Pages Router, RSC, client components, loaders…</td>
|
|
207
|
+
<td class="v-no">+page.svelte, +page.server.js, load(), form actions…</td>
|
|
208
|
+
</tr>
|
|
209
|
+
<tr>
|
|
210
|
+
<th scope="row">Agent-readable structure</th>
|
|
211
|
+
<td class="v-yes">One JS object per page</td>
|
|
212
|
+
<td class="v-no">Files, folders, magic exports spread across dirs</td>
|
|
213
|
+
<td class="v-no">Files, folders, Svelte syntax</td>
|
|
214
|
+
</tr>
|
|
215
|
+
<tr>
|
|
216
|
+
<th scope="row">SSR out of the box</th>
|
|
217
|
+
<td class="v-yes">Streaming SSR, zero config</td>
|
|
218
|
+
<td class="v-partial">Yes, but client hydration adds JS on every page</td>
|
|
219
|
+
<td class="v-partial">Yes, but requires an adapter and client runtime on every page</td>
|
|
220
|
+
</tr>
|
|
221
|
+
<tr>
|
|
222
|
+
<th scope="row">Client JS shipped</th>
|
|
223
|
+
<td class="v-yes">~4 kB brotli on first visit; 0 kB on static pages</td>
|
|
224
|
+
<td class="v-no">50–200 kB+ depending on features used</td>
|
|
225
|
+
<td class="v-partial">~15 kB brotli</td>
|
|
226
|
+
</tr>
|
|
227
|
+
<tr>
|
|
228
|
+
<th scope="row">Security headers</th>
|
|
229
|
+
<td class="v-yes">On every response, built in</td>
|
|
230
|
+
<td class="v-no">Manual middleware or plugin</td>
|
|
231
|
+
<td class="v-no">Manual hooks setup</td>
|
|
232
|
+
</tr>
|
|
233
|
+
<tr>
|
|
234
|
+
<th scope="row">Runtime dependencies</th>
|
|
235
|
+
<td class="v-yes">Zero. Pure Node.js HTTP</td>
|
|
236
|
+
<td class="v-no">React, 50+ transitive packages</td>
|
|
237
|
+
<td class="v-no">Svelte runtime + adapters</td>
|
|
238
|
+
</tr>
|
|
239
|
+
<tr>
|
|
240
|
+
<th scope="row">Production build step</th>
|
|
241
|
+
<td class="v-yes">Server needs none. <code>node server.js</code> is production</td>
|
|
242
|
+
<td class="v-no">Required: <code>next build</code></td>
|
|
243
|
+
<td class="v-no">Required: <code>vite build</code></td>
|
|
244
|
+
</tr>
|
|
245
|
+
</tbody>
|
|
246
|
+
</table>
|
|
247
|
+
</div>
|
|
248
|
+
</section>
|
|
249
249
|
|
|
250
|
-
<
|
|
251
|
-
<div class="
|
|
252
|
-
<div class="
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
<section class="brut-pillars">
|
|
251
|
+
<div class="brut-pillar">
|
|
252
|
+
<div class="brut-pillar-icon">${iconZap({ size: 36 })}</div>
|
|
253
|
+
<div class="brut-pillar-body">
|
|
254
|
+
<h3>Performance by design</h3>
|
|
255
|
+
<p>The shell streams to the browser instantly. Deferred segments arrive as data resolves. ~4 kB of JS on first visit. Zero CLS. Immutable bundle caching on deploy. Not configured. Structural.</p>
|
|
256
256
|
</div>
|
|
257
|
-
<h2>Performance by design</h2>
|
|
258
|
-
<p>Performance is not a Pulse feature — it is a structural outcome. A high Lighthouse score is the baseline. There is nothing to configure because the architecture makes the right choices automatically.</p>
|
|
259
257
|
</div>
|
|
260
|
-
<
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
<li>
|
|
266
|
-
<strong>~4 kB of JS on first visit.</strong>
|
|
267
|
-
The shared runtime is brotli-compressed and cached across all navigations. Subsequent pages cost 0.4–0.9 kB. Static pages ship zero JS.
|
|
268
|
-
</li>
|
|
269
|
-
<li>
|
|
270
|
-
<strong>Zero CLS.</strong>
|
|
271
|
-
The shell occupies the correct layout before data arrives. No layout shift because there is no placeholder to replace.
|
|
272
|
-
</li>
|
|
273
|
-
<li>
|
|
274
|
-
<strong>Immutable bundle caching.</strong>
|
|
275
|
-
Production bundles are content-hashed and served with <code>immutable, max-age=31536000</code>. Returning visitors pay no JS cost on deploy.
|
|
276
|
-
</li>
|
|
277
|
-
</ul>
|
|
278
|
-
</div>
|
|
279
|
-
|
|
280
|
-
<div class="usp-block usp-block-alt">
|
|
281
|
-
<div class="usp-block-aside">
|
|
282
|
-
<div class="usp-icon">
|
|
283
|
-
<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">
|
|
284
|
-
<rect x="3" y="11" width="18" height="11" rx="2"/>
|
|
285
|
-
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
|
286
|
-
</svg>
|
|
258
|
+
<div class="brut-pillar brut-pillar--alt">
|
|
259
|
+
<div class="brut-pillar-icon">${iconShield({ size: 36 })}</div>
|
|
260
|
+
<div class="brut-pillar-body">
|
|
261
|
+
<h3>Safe by design</h3>
|
|
262
|
+
<p>Security headers on every response, including 404 and 500. Declarative constraints enforce state bounds after every mutation. Guard runs before any server fetcher. Not a plugin. Part of the pipeline.</p>
|
|
287
263
|
</div>
|
|
288
|
-
<h2>Safe by design</h2>
|
|
289
|
-
<p>Security is not a plugin in Pulse — it is part of the response pipeline. Every response ships the headers most frameworks leave to the developer to remember to add.</p>
|
|
290
264
|
</div>
|
|
291
|
-
<
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
<li>
|
|
297
|
-
<strong>Declarative state constraints.</strong>
|
|
298
|
-
<code>constraints</code> enforce min/max bounds on state after every mutation. Values can never go out of range regardless of what the client sends.
|
|
299
|
-
</li>
|
|
300
|
-
<li>
|
|
301
|
-
<strong>Co-located validation.</strong>
|
|
302
|
-
Validation rules live next to the state they guard. Easy to read, easy to review, impossible to misplace.
|
|
303
|
-
</li>
|
|
304
|
-
<li>
|
|
305
|
-
<strong>Guard before data.</strong>
|
|
306
|
-
The <code>guard</code> function runs before any server fetcher executes — authentication and authorisation checks cannot be accidentally bypassed.
|
|
307
|
-
</li>
|
|
308
|
-
</ul>
|
|
309
|
-
</div>
|
|
310
|
-
|
|
311
|
-
<div class="usp-block">
|
|
312
|
-
<div class="usp-block-aside">
|
|
313
|
-
<div class="usp-icon">
|
|
314
|
-
<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">
|
|
315
|
-
<circle cx="12" cy="12" r="10"/>
|
|
316
|
-
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
317
|
-
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
318
|
-
</svg>
|
|
265
|
+
<div class="brut-pillar">
|
|
266
|
+
<div class="brut-pillar-icon">${iconSettings({ size: 36 })}</div>
|
|
267
|
+
<div class="brut-pillar-body">
|
|
268
|
+
<h3>Nothing to configure</h3>
|
|
269
|
+
<p>No bundler config. No runtime dependencies to install, audit, or upgrade. <code>node server.js</code> is production. No breaking upgrades. Page files have no framework imports to version.</p>
|
|
319
270
|
</div>
|
|
320
|
-
<h2>Nothing to configure</h2>
|
|
321
|
-
<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>
|
|
322
271
|
</div>
|
|
323
|
-
|
|
324
|
-
<li>
|
|
325
|
-
<strong>Zero runtime dependencies.</strong>
|
|
326
|
-
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.
|
|
327
|
-
</li>
|
|
328
|
-
<li>
|
|
329
|
-
<strong>No server build step.</strong>
|
|
330
|
-
<code>node server.js</code> is production. The optional build step generates content-hashed client bundles — the server runs without it.
|
|
331
|
-
</li>
|
|
332
|
-
<li>
|
|
333
|
-
<strong>esbuild only in development.</strong>
|
|
334
|
-
The one dev dependency that compiles client bundles is esbuild. Fast, zero plugins to configure, never part of the production runtime.
|
|
335
|
-
</li>
|
|
336
|
-
<li>
|
|
337
|
-
<strong>No breaking upgrades.</strong>
|
|
338
|
-
Page files have no framework imports. The spec is a plain JS object. There is no framework API surface in your code to break across versions.
|
|
339
|
-
</li>
|
|
340
|
-
</ul>
|
|
341
|
-
</div>
|
|
342
|
-
|
|
343
|
-
</section>
|
|
272
|
+
</section>
|
|
344
273
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
<
|
|
348
|
-
<
|
|
349
|
-
<
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
<div class="metrics-group">
|
|
353
|
-
<div class="metrics-group-label">Lighthouse</div>
|
|
354
|
-
<div class="metrics-items">
|
|
274
|
+
${server.metrics ? `<section class="brut-metrics">
|
|
275
|
+
<div class="brut-metrics-label">BY THE NUMBERS</div>
|
|
276
|
+
<h2 class="brut-metrics-h2">Performance you can measure.</h2>
|
|
277
|
+
<p class="brut-metrics-sub">Report generated ${server.metrics.generatedAt} · measured from a real Pulse build</p>
|
|
278
|
+
<div class="brut-metrics-grid">
|
|
279
|
+
<div class="brut-metrics-group">
|
|
280
|
+
<div class="brut-metrics-group-label">Lighthouse</div>
|
|
355
281
|
${server.metrics.lighthouse.map(m => `
|
|
356
|
-
<div class="metric
|
|
357
|
-
<span class="metric-val metric-val--
|
|
358
|
-
<span class="metric-
|
|
282
|
+
<div class="brut-metric">
|
|
283
|
+
<span class="brut-metric-val brut-metric-val--hi">${m.value}</span>
|
|
284
|
+
<span class="brut-metric-lbl">${m.label}</span>
|
|
359
285
|
</div>`).join('')}
|
|
360
286
|
</div>
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
<div class="metrics-group-label">Bundle sizes</div>
|
|
364
|
-
<div class="metrics-items">
|
|
287
|
+
<div class="brut-metrics-group">
|
|
288
|
+
<div class="brut-metrics-group-label">Bundle sizes</div>
|
|
365
289
|
${server.metrics.bundles.map(m => `
|
|
366
|
-
<div class="metric
|
|
367
|
-
<span class="metric-val">${m.value}</span>
|
|
368
|
-
<span class="metric-
|
|
290
|
+
<div class="brut-metric">
|
|
291
|
+
<span class="brut-metric-val">${m.value}</span>
|
|
292
|
+
<span class="brut-metric-lbl">${m.label}</span>
|
|
369
293
|
</div>`).join('')}
|
|
370
294
|
</div>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
<div class="metrics-group-label">Web Vitals</div>
|
|
374
|
-
<div class="metrics-items">
|
|
295
|
+
<div class="brut-metrics-group">
|
|
296
|
+
<div class="brut-metrics-group-label">Web Vitals</div>
|
|
375
297
|
${server.metrics.vitals.map(m => `
|
|
376
|
-
<div class="metric
|
|
377
|
-
<span class="metric-val metric-val--
|
|
378
|
-
<span class="metric-
|
|
298
|
+
<div class="brut-metric">
|
|
299
|
+
<span class="brut-metric-val brut-metric-val--hi">${m.value}</span>
|
|
300
|
+
<span class="brut-metric-lbl">${m.label}</span>
|
|
379
301
|
</div>`).join('')}
|
|
380
302
|
</div>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
<div class="metrics-group-label">Architecture</div>
|
|
384
|
-
<div class="metrics-items">
|
|
303
|
+
<div class="brut-metrics-group">
|
|
304
|
+
<div class="brut-metrics-group-label">Architecture</div>
|
|
385
305
|
${server.metrics.architecture.map(m => `
|
|
386
|
-
<div class="metric
|
|
387
|
-
<span class="metric-val metric-val--accent">${m.value}</span>
|
|
388
|
-
<span class="metric-
|
|
306
|
+
<div class="brut-metric">
|
|
307
|
+
<span class="brut-metric-val brut-metric-val--accent">${m.value}</span>
|
|
308
|
+
<span class="brut-metric-lbl">${m.label}</span>
|
|
389
309
|
</div>`).join('')}
|
|
390
310
|
</div>
|
|
391
311
|
</div>
|
|
392
|
-
</
|
|
393
|
-
</section>` : ''}
|
|
312
|
+
</section>` : ''}
|
|
394
313
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
<
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
</
|
|
404
|
-
<p>Pulse is in early access. The goal is not to compete on features — it is to eliminate the class of problems that come from having too many of them.</p>
|
|
405
|
-
<div class="home-cta-actions">
|
|
406
|
-
<a href="/getting-started" class="btn-primary">Get Started</a>
|
|
407
|
-
<a href="/spec" class="btn-secondary">Read the Spec</a>
|
|
408
|
-
</div>
|
|
409
|
-
</section>
|
|
314
|
+
<section class="brut-cta">
|
|
315
|
+
<h2 class="brut-cta-h2">Your first page<br>in under<br>2 minutes.</h2>
|
|
316
|
+
<p class="brut-cta-lead">One spec object. Server data, client state, mutations, and view. In one place. Streaming SSR, security headers, and 100 Lighthouse scores come with it. Nothing to configure.</p>
|
|
317
|
+
<p class="brut-cta-note">Pulse is in early access. The goal is not to compete on features. It is to eliminate the class of problems that come from having too many of them.</p>
|
|
318
|
+
<div class="brut-cta-actions">
|
|
319
|
+
<a href="/getting-started" class="brut-btn-primary brut-btn-primary--inv">Get Started</a>
|
|
320
|
+
<a href="/spec" class="brut-btn-ghost brut-btn-ghost--light">Read the Spec</a>
|
|
321
|
+
</div>
|
|
322
|
+
</section>
|
|
410
323
|
|
|
411
324
|
</main>
|
|
412
|
-
|
|
413
|
-
|
|
325
|
+
|
|
326
|
+
<footer class="brut-footer">
|
|
327
|
+
<span>MIT License</span>
|
|
328
|
+
<span class="brut-footer-sep">·</span>
|
|
329
|
+
<a href="https://github.com/invisibleloop/pulse-framework" target="_blank" rel="noopener" aria-label="GitHub (opens in new tab)">GitHub</a>
|
|
330
|
+
<span class="brut-footer-sep">·</span>
|
|
331
|
+
<a href="/getting-started">Get started in 2 minutes</a>
|
|
414
332
|
</footer>
|
|
333
|
+
|
|
415
334
|
</div>
|
|
416
335
|
`,
|
|
417
336
|
}
|