@invisibleloop/pulse 0.16.0 → 0.17.1
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/.claude/commands/build-page.md +1 -1
- package/.claude/commands/verify.md +2 -2
- package/.claude/pulse-checklist.md +14 -13
- package/CLAUDE.md +18 -5
- package/README.md +3 -3
- package/docs/.claude/pulse-checklist.md +4 -3
- package/docs/src/pages/project-structure.js +2 -0
- package/examples/.claude/pulse-checklist.md +9 -6
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/src/agent/checklist.md +4 -3
- package/src/agent/commands/verify.md +62 -32
- package/src/agent/coverage-check.js +5 -0
- package/src/agent/guide-design-gallery.md +7 -5
- package/src/agent/guide-design-references.md +2 -2
- package/src/agent/guide-spec.md +6 -0
- package/src/agent/identity.md +21 -6
- package/src/agent/skills/build-page/SKILL.md +7 -5
- package/src/agent/skills/verify/SKILL.md +2 -2
- package/src/agent/workflow.md +14 -3
- package/src/cli/scaffold.js +18 -2
- package/src/html.js +1 -0
- package/src/mcp/mcp.test.js +171 -0
- package/src/mcp/server.js +29 -2
- package/src/md/index.js +29 -2
- package/src/md/md.test.js +36 -0
- package/src/runtime/ssr.js +1 -0
- package/src/runtime/tt.js +8 -0
- package/src/server/index.js +87 -20
- package/src/server/server.test.js +106 -1
- package/src/spec/schema.js +10 -0
|
@@ -50,7 +50,7 @@ Check for:
|
|
|
50
50
|
- Layout looks correct
|
|
51
51
|
- No console errors via `mcp__chrome-devtools__list_console_messages`
|
|
52
52
|
|
|
53
|
-
Then
|
|
53
|
+
Then ask the user: "Happy with the design and layout, or would you like any changes before I run tests and Lighthouse?" — if your host provides a question tool (e.g. AskUserQuestion), offer the choices `["Yes, looks good — run tests and Lighthouse", "I'd like some changes first"]`; it waits for the answer without ending the turn. If you must ask in plain prose and end your turn, call `pulse_await_approval` first so the Stop hooks let the turn end. If a `VERIFY REQUIRED` stop-hook block fires while your question is unanswered, that is not permission to continue — call `pulse_await_approval`, re-ask, and end your turn.
|
|
54
54
|
|
|
55
55
|
**Do not proceed to step 7 until the user explicitly confirms.** If they want changes, make them and return to step 6.
|
|
56
56
|
|
|
@@ -15,8 +15,8 @@ If $ARGUMENTS is provided, use it as the file path or route. Otherwise, identify
|
|
|
15
15
|
If this is a new page build (came through `pulse_intake` or `build-page`):
|
|
16
16
|
|
|
17
17
|
1. Take a screenshot and describe what you see to the user.
|
|
18
|
-
2.
|
|
19
|
-
3. **Do not proceed to step 3 until the user explicitly confirms.** If they want changes, stop here, make the changes, then restart from step 2. Lighthouse takes ~90 seconds — do not waste it on a design the user hasn't approved.
|
|
18
|
+
2. Ask the user: "Happy with the design and layout, or would you like any changes before I run Lighthouse?" — if your host provides a question tool (e.g. AskUserQuestion), offer the choices `["Yes, looks good — proceed", "I'd like some changes first"]`; it waits for the answer without ending the turn. If you must ask in plain prose and end your turn, call `pulse_await_approval` first so the Stop hooks let the turn end.
|
|
19
|
+
3. **Do not proceed to step 3 until the user explicitly confirms.** If they want changes, stop here, make the changes, then restart from step 2. Lighthouse takes ~90 seconds — do not waste it on a design the user hasn't approved. If a `VERIFY REQUIRED` stop-hook block fires while your question is unanswered, that is not permission to continue — call `pulse_await_approval`, re-ask, and end your turn.
|
|
20
20
|
|
|
21
21
|
### 3. Validate the spec
|
|
22
22
|
|
|
@@ -22,10 +22,10 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
22
22
|
|
|
23
23
|
- **Always restart the server and reload the browser after every file edit — before checking the result.** The dev server restarts on file changes but the browser tab stays stale. The required sequence is: `pulse_restart_server` → `navigate_page`. Never attempt to debug a visual problem without doing both steps first.
|
|
24
24
|
|
|
25
|
-
### Components first
|
|
25
|
+
### Components first — or creative override
|
|
26
26
|
|
|
27
27
|
- **Before writing any HTML by hand, check `src/ui/index.js`.** There are 50+ components. Use `button`, `card`, `alert`, `input`, `spinner`, `badge`, `modal`, `nav`, `pagination`, `table`, etc. before writing equivalent HTML from scratch.
|
|
28
|
-
- **Do not reinvent component patterns.** These patterns have dedicated components — never write custom HTML for them:
|
|
28
|
+
- **Do not reinvent component patterns.** These patterns have dedicated components — never write custom HTML for them unless a creative override applies (see below):
|
|
29
29
|
- Hero sections → `hero({ title, subtitle, actions, image, ... })`
|
|
30
30
|
- Product/service cards → `card({ title, body, image, footer, ... })`
|
|
31
31
|
- Image + text two-column layouts → `media({ image, content, reverse })`
|
|
@@ -35,10 +35,16 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
35
35
|
- CTAs → `cta({ title, subtitle, actions })`
|
|
36
36
|
- Navigation → `nav({ logo, links, actions })`
|
|
37
37
|
- Footers → `footer({ logo, links, columns, legal, ... })`
|
|
38
|
-
|
|
39
|
-
If you find yourself writing `class="hero"` or `class="product-card"` or `class="testimonial"`, stop — import the component instead.
|
|
40
38
|
|
|
41
|
-
- **
|
|
39
|
+
- **Creative override — raw HTML throughout is permitted** when the design intent genuinely calls for it: a highly custom visual identity, an asymmetric or typographically-driven layout, a brutalist/editorial/retro/neon/paper vibe where components would constrain the expression, or a layout that requires features a component simply doesn't support (e.g. full-viewport height, custom gradient glows, clamp-scaled display type). This is a deliberate design decision, not a shortcut. When taking this path:
|
|
40
|
+
- **Declare it in a comment at the top of the spec** so the reviewer recognises it: `// component-free — creative override: <reason>`. This is the signal the review tool reads.
|
|
41
|
+
- Functional atoms (`button`, `input`, `badge`, `modal`) should still come from components unless there is a specific design reason not to.
|
|
42
|
+
- `/pulse-ui.css` is always required — it provides the token system your custom CSS will use.
|
|
43
|
+
- The quality gate replaces the component checklist: **Lighthouse 100 on Accessibility, Best Practices, and SEO — desktop and mobile — plus CLS 0.00 is the pass bar.** (Performance is reported but not gated; it varies with machine load.) A component-free page that passes every audit is correct. A component-heavy page that fails accessibility is not.
|
|
44
|
+
|
|
45
|
+
- **When reviewing**: before flagging a class name like `.hero` as a violation, first ask whether the `hero()` component could actually reproduce the layout. If the answer is no, it is a creative override — check Lighthouse instead of refactoring. Do not flatten a good custom layout into a generic component just to clear a pattern-match.
|
|
46
|
+
|
|
47
|
+
- **The review tool auto-detects creative override** from the spec comment. When detected, component pattern checks are shown as advisory (`⚡`) not as failures, and the Lighthouse gate is the stated pass bar.
|
|
42
48
|
|
|
43
49
|
- **Never write App Store or Google Play download buttons by hand.** Always use `appBadge({ store: 'apple', href })` and `appBadge({ store: 'google', href })`. Raw `<a>` tags with badge images are incorrect — `appBadge` renders the correct accessible, styled badge for each store.
|
|
44
50
|
|
|
@@ -60,8 +66,8 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
60
66
|
```js
|
|
61
67
|
const card = ({ title, body }) => `<div class="card"><h3>${title}</h3><p>${body}</p></div>`
|
|
62
68
|
```
|
|
63
|
-
- **Create a shared component in `src/
|
|
64
|
-
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction.
|
|
69
|
+
- **Create a shared component in `src/components/` when the same pattern is needed across 2 or more different specs.** A named export that returns an HTML string — same shape as the `layout()` pattern in the routing guide. Never paste a section's HTML into a second spec: extract it at the second use, then import it from both. (`src/ui/` is the framework's own library inside the package — project-level shared code always goes in `src/components/`.)
|
|
70
|
+
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction. Within a single spec, wait until the third use before extracting a helper; across specs, extract at the second use.
|
|
65
71
|
|
|
66
72
|
### Correctness
|
|
67
73
|
|
|
@@ -100,6 +106,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
100
106
|
|
|
101
107
|
- **`app.css` must contain no hex values or raw colour values.** A lint hook enforces this and will block the build. Hex values belong in `public/theme.css` as token definitions; `app.css` references them via `var()` only.
|
|
102
108
|
- **Load order in `meta.styles`:** `pulse-ui.css` → `theme.css` → `app.css`. Theme tokens must be defined before `app.css` references them.
|
|
109
|
+
- **CSS for a shared section lives in the shared `app.css`, once.** Every spec that renders the section lists the same file in `meta.styles`. Never copy a CSS block into a second per-page stylesheet — two copies drift apart. Per-page stylesheets are for styles only one page uses.
|
|
103
110
|
|
|
104
111
|
### Security
|
|
105
112
|
|
|
@@ -132,12 +139,6 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
132
139
|
- **Call the same `md()` fetcher in both `meta` and `server`** — it caches on `ctx._mdCache` so the file is only read once per request. Do not create two separate fetchers for the same file.
|
|
133
140
|
- **Always add `onViewError` on dynamic markdown routes** (routes with `:param` segments loading `.md` files). A missing file throws `{ status: 404 }` which must be caught gracefully.
|
|
134
141
|
|
|
135
|
-
### Markdown
|
|
136
|
-
|
|
137
|
-
- **Use `md()` from `@invisibleloop/pulse/md` for `.md` file content.** Never read and parse markdown manually. Pass the result's `html` to `prose()`.
|
|
138
|
-
- **Call the same `md()` fetcher in both `meta` and `server`** — it caches on `ctx._mdCache` so the file is only read once per request. Do not create two separate fetchers for the same file.
|
|
139
|
-
- **Always add `onViewError` on dynamic markdown routes** (routes with `:param` segments loading `.md` files). A missing file throws `{ status: 404 }` which must be caught gracefully.
|
|
140
|
-
|
|
141
142
|
### View error handling
|
|
142
143
|
|
|
143
144
|
- **Define `onViewError` on any page where the view could throw due to bad or missing data.** Without it, a runtime view error returns a 500 on the server and shows a generic inline message on the client. With it, the server returns 200 with your fallback HTML instead of a 500, and the client renders your fallback:
|
package/CLAUDE.md
CHANGED
|
@@ -5,8 +5,8 @@ Pulse is a spec-first frontend framework. The spec is the source of truth. No co
|
|
|
5
5
|
## Commands
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm run dev # start dev server at http://localhost:3001
|
|
9
|
-
npm test # run
|
|
8
|
+
npm run dev # start dev server at http://localhost:3000 (pulse_build serves production on 3001)
|
|
9
|
+
npm test # run the full unit test suite
|
|
10
10
|
npm run build # bundle for production → public/dist/
|
|
11
11
|
```
|
|
12
12
|
|
|
@@ -37,6 +37,8 @@ public/
|
|
|
37
37
|
|
|
38
38
|
A spec is a plain JS object. Every property is optional except `route` and `view`. `state` defaults to `{}` — omit it on purely server-rendered pages.
|
|
39
39
|
|
|
40
|
+
**One spec = one page = one file.** A site is multiple specs, one per route. A dynamic route (`route: '/blog/:slug'`) is still a single spec serving every matching URL — never one spec per item, and never several pages folded into one spec with `ctx` conditionals. Shared sections live in `src/components/` and are imported by each spec.
|
|
41
|
+
|
|
40
42
|
```js
|
|
41
43
|
export const mySpec = {
|
|
42
44
|
route: '/path', // URL pattern, supports :params
|
|
@@ -57,6 +59,12 @@ export const mySpec = {
|
|
|
57
59
|
// Timeout for all server fetchers on this page (ms). Overrides createServer fetcherTimeout.
|
|
58
60
|
serverTimeout: 5000,
|
|
59
61
|
|
|
62
|
+
// Query-string keys that change this page's output, for the in-process page cache.
|
|
63
|
+
// The cache IGNORES the query string by default — arbitrary ?n=… params cannot mint
|
|
64
|
+
// unbounded cache entries (memory-DoS guard). If the rendered output depends on a
|
|
65
|
+
// query param, list it here so distinct values get distinct cache entries.
|
|
66
|
+
cacheVary: ['q', 'page'],
|
|
67
|
+
|
|
60
68
|
// Global store keys this page subscribes to — appears in view's server arg
|
|
61
69
|
// Store mutations update all subscribed pages without a server round-trip
|
|
62
70
|
store: ['user', 'cart'],
|
|
@@ -354,6 +362,8 @@ There are two CSS files and they have distinct roles — do not mix them:
|
|
|
354
362
|
|
|
355
363
|
**`app.css` must never contain hex values.** `rgba()` and `hsla()` are allowed for translucency — but if the same translucent colour is used more than once, extract it as a token in `theme.css` (e.g. `--brand-overlay-12: rgba(0,0,0,0.12)`). If you need to define or override a colour token, put it in `public/theme.css` and reference it via `var()` in `app.css`.
|
|
356
364
|
|
|
365
|
+
**CSS for a section used by more than one spec lives in the shared `app.css`, once** — every spec that renders the section lists the same file in `meta.styles`. Never copy a CSS block into a second per-page stylesheet; two copies drift. Per-page stylesheets are only for styles a single page uses.
|
|
366
|
+
|
|
357
367
|
```css
|
|
358
368
|
/* public/theme.css — hex values live here */
|
|
359
369
|
[data-theme="light"] {
|
|
@@ -458,7 +468,7 @@ For any **new page, landing page, or branded site**, run this sequence first. Do
|
|
|
458
468
|
3. pulse_intent(description) → archetype + scaffold + guide list
|
|
459
469
|
```
|
|
460
470
|
|
|
461
|
-
**Before calling `pulse_intake`**, gather the required information by asking the user — one question at a time, in plain prose, no bullet lists. You need at minimum: the product name, what it does (pitch), and its key features.
|
|
471
|
+
**Before calling `pulse_intake`**, gather the required information by asking the user — one question at a time, in plain prose, no bullet lists. You need at minimum: the product name, what it does (pitch), and its key features. **Always ask first whether they have design inspiration** (a site they love, a screenshot, a mood board — dropped into `public/intake/` or pasted as a URL), and check `public/intake/` for images already there. Then ask for palette, vibe, and anti-style if not mentioned.
|
|
462
472
|
|
|
463
473
|
**`pulse_sketch` is mandatory** for `playful`, `bold`, `brutalist`, `retro`, or `neon` vibes — those vibes fight the default centred-hero layout. Skip it only for `corporate` or `minimal` when no structural preference has been stated.
|
|
464
474
|
|
|
@@ -470,13 +480,16 @@ Every build task follows this sequence. Each phase has a pass gate — do not ad
|
|
|
470
480
|
|
|
471
481
|
**Before calling any slow tool (`pulse_build`, `lighthouse_audit`), output a status message to the user first** — e.g. "Building for production — ~30 s…" or "Running Lighthouse desktop audit…". Never call a slow tool silently.
|
|
472
482
|
|
|
483
|
+
> **Canonical workflow:** `pulse://workflow` (`src/agent/workflow.md`) is the authoritative version, including complexity tiers (not every task needs all phases). The table below is the summary — if they ever disagree, the MCP resource wins.
|
|
484
|
+
|
|
473
485
|
| Phase | Action | Gate |
|
|
474
486
|
|---|---|---|
|
|
475
487
|
| 1. Understand | Read guides, call `pulse_list_structure` | — |
|
|
476
488
|
| 2. Plan | Present plan to user, wait for confirmation | User confirms |
|
|
477
489
|
| 3. Build | Write spec + related files | — |
|
|
478
490
|
| 4. Validate | `pulse_validate` — fix all errors + warnings | Clean output |
|
|
479
|
-
|
|
|
491
|
+
| 5a. Design approval *(new builds only)* | Screenshot → describe → ask the user | ⛔ User approves layout before any slow checks run (skip for edits/fixes) |
|
|
492
|
+
| 5b. Browser | `pulse_design_review` (if intake ran) → `pulse_layout_review` → `/verify` (Lighthouse desktop + mobile + `pulse_review`) | ⛔ Do not skip: all gates must pass + `.pulse-verified` stamp written |
|
|
480
493
|
| 6. Tests | Write tests, run them, fix failures | All pass |
|
|
481
494
|
| 7. Code Review | `pulse_review` — only after phases 4–6 pass | — |
|
|
482
495
|
| 8. Fix | Fix every review issue, re-run affected gates | All gates still pass |
|
|
@@ -487,7 +500,7 @@ Every build task follows this sequence. Each phase has a pass gate — do not ad
|
|
|
487
500
|
|
|
488
501
|
**The Code Review is always last.** Never invoke `pulse_review` before validate, Lighthouse, design review, and tests all pass.
|
|
489
502
|
|
|
490
|
-
The `/verify` command runs the browser check loop (phases 4–
|
|
503
|
+
The `/verify` command runs the browser check loop (phases 4–5b) automatically. Use it.
|
|
491
504
|
|
|
492
505
|
## Pulse vs React — Do Not Do These
|
|
493
506
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ Write a plain JavaScript object that describes what a page does. Pulse handles r
|
|
|
7
7
|
```js
|
|
8
8
|
export default {
|
|
9
9
|
route: '/counter',
|
|
10
|
-
hydrate: '/src/pages/counter.js',
|
|
11
10
|
meta: {
|
|
12
11
|
title: 'Counter',
|
|
13
12
|
styles: ['/app.css'],
|
|
@@ -54,7 +53,7 @@ Full documentation: **[pulseframework.dev](https://pulseframework.dev)**
|
|
|
54
53
|
|
|
55
54
|
**The spec is the source of truth.** Every page is a single JS object — data fetching, state, mutations, async actions, and view all in one place. No separate files for routes, controllers, or templates.
|
|
56
55
|
|
|
57
|
-
**Performance is built in.** Streaming SSR, immutable asset caching, and zero layout shift are automatic. Every scaffolded project targets Lighthouse 100
|
|
56
|
+
**Performance is built in.** Streaming SSR, immutable asset caching, and zero layout shift are automatic. Every scaffolded project targets Lighthouse 100 for Accessibility, Best Practices, and SEO — and is fast by design (streaming HTML before data resolves, CLS 0.00).
|
|
58
57
|
|
|
59
58
|
**No client-side dependencies.** Pulse ships no runtime framework to the browser — only a small hydration bundle (~2 kB brotli) that binds your spec's mutations and actions to the DOM.
|
|
60
59
|
|
|
@@ -83,12 +82,13 @@ Full reference at [pulseframework.dev](https://pulseframework.dev):
|
|
|
83
82
|
- **Guard** — authentication and authorisation before data fetches
|
|
84
83
|
- **Validation & constraints** — declarative form validation and state bounds
|
|
85
84
|
- **Raw responses** — RSS feeds, sitemaps, JSON APIs
|
|
85
|
+
- **Markdown** — built-in parser for blog posts and docs pages, zero browser JS
|
|
86
86
|
- **Performance** — how the framework hits Lighthouse 100
|
|
87
87
|
- **UI components** — 50+ built-in components
|
|
88
88
|
|
|
89
89
|
## Status
|
|
90
90
|
|
|
91
|
-
This is an early-access release. The core architecture is stable and production-quality, but the API may evolve before v1. Feedback and issues welcome on [GitHub](https://github.com/invisibleloop/pulse).
|
|
91
|
+
This is an early-access release. The core architecture is stable and production-quality, but the API may evolve before v1. Feedback and issues welcome on [GitHub](https://github.com/invisibleloop/pulse-framework).
|
|
92
92
|
|
|
93
93
|
## License
|
|
94
94
|
|
|
@@ -40,7 +40,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
40
40
|
- **Declare it in a comment at the top of the spec** so the reviewer recognises it: `// component-free — creative override: <reason>`. This is the signal the review tool reads.
|
|
41
41
|
- Functional atoms (`button`, `input`, `badge`, `modal`) should still come from components unless there is a specific design reason not to.
|
|
42
42
|
- `/pulse-ui.css` is always required — it provides the token system your custom CSS will use.
|
|
43
|
-
- The quality gate replaces the component checklist: **100
|
|
43
|
+
- The quality gate replaces the component checklist: **Lighthouse 100 on Accessibility, Best Practices, and SEO — desktop and mobile — plus CLS 0.00 is the pass bar.** (Performance is reported but not gated; it varies with machine load.) A component-free page that passes every audit is correct. A component-heavy page that fails accessibility is not.
|
|
44
44
|
|
|
45
45
|
- **When reviewing**: before flagging a class name like `.hero` as a violation, first ask whether the `hero()` component could actually reproduce the layout. If the answer is no, it is a creative override — check Lighthouse instead of refactoring. Do not flatten a good custom layout into a generic component just to clear a pattern-match.
|
|
46
46
|
|
|
@@ -66,8 +66,8 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
66
66
|
```js
|
|
67
67
|
const card = ({ title, body }) => `<div class="card"><h3>${title}</h3><p>${body}</p></div>`
|
|
68
68
|
```
|
|
69
|
-
- **Create a shared component in `src/
|
|
70
|
-
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction.
|
|
69
|
+
- **Create a shared component in `src/components/` when the same pattern is needed across 2 or more different specs.** A named export that returns an HTML string — same shape as the `layout()` pattern in the routing guide. Never paste a section's HTML into a second spec: extract it at the second use, then import it from both. (`src/ui/` is the framework's own library inside the package — project-level shared code always goes in `src/components/`.)
|
|
70
|
+
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction. Within a single spec, wait until the third use before extracting a helper; across specs, extract at the second use.
|
|
71
71
|
|
|
72
72
|
### Correctness
|
|
73
73
|
|
|
@@ -106,6 +106,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
106
106
|
|
|
107
107
|
- **`app.css` must contain no hex values or raw colour values.** A lint hook enforces this and will block the build. Hex values belong in `public/theme.css` as token definitions; `app.css` references them via `var()` only.
|
|
108
108
|
- **Load order in `meta.styles`:** `pulse-ui.css` → `theme.css` → `app.css`. Theme tokens must be defined before `app.css` references them.
|
|
109
|
+
- **CSS for a shared section lives in the shared `app.css`, once.** Every spec that renders the section lists the same file in `meta.styles`. Never copy a CSS block into a second per-page stylesheet — two copies drift apart. Per-page stylesheets are for styles only one page uses.
|
|
109
110
|
|
|
110
111
|
### Security
|
|
111
112
|
|
|
@@ -82,6 +82,8 @@ export default {
|
|
|
82
82
|
${section('one-file-per-page', 'One file per page')}
|
|
83
83
|
<p>Pulse enforces <strong>one spec per file</strong>. Every page is self-contained — state, view, mutations, actions, and validation in one place. This is not a style preference. It is how the framework eliminates the question of where things live.</p>
|
|
84
84
|
|
|
85
|
+
<p>This holds for dynamic routes too: <code>route: '/blog/:slug'</code> is <strong>one spec serving every matching URL</strong> — the <code>:param</code> does the multiplying, so a thousand blog posts are still a single file. A site is therefore a small collection of specs, one per route, sharing layouts and sections from <code>src/components/</code> rather than repeating them.</p>
|
|
86
|
+
|
|
85
87
|
${callout('tip', 'For large pages, view fragments and helpers can be imported from other modules. The spec file remains the coordination point — the structure is always clear regardless of how the implementation is organised.')}
|
|
86
88
|
`,
|
|
87
89
|
}),
|
|
@@ -36,13 +36,15 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
36
36
|
- Navigation → `nav({ logo, links, actions })`
|
|
37
37
|
- Footers → `footer({ logo, links, columns, legal, ... })`
|
|
38
38
|
|
|
39
|
-
- **Creative override — raw HTML throughout is permitted** when the design intent genuinely calls for it: a highly custom visual identity, an asymmetric or typographically-driven layout, a brutalist/editorial/retro/neon/paper vibe where components would constrain the expression. This is a deliberate design decision, not a shortcut. When taking this path:
|
|
40
|
-
-
|
|
39
|
+
- **Creative override — raw HTML throughout is permitted** when the design intent genuinely calls for it: a highly custom visual identity, an asymmetric or typographically-driven layout, a brutalist/editorial/retro/neon/paper vibe where components would constrain the expression, or a layout that requires features a component simply doesn't support (e.g. full-viewport height, custom gradient glows, clamp-scaled display type). This is a deliberate design decision, not a shortcut. When taking this path:
|
|
40
|
+
- **Declare it in a comment at the top of the spec** so the reviewer recognises it: `// component-free — creative override: <reason>`. This is the signal the review tool reads.
|
|
41
41
|
- Functional atoms (`button`, `input`, `badge`, `modal`) should still come from components unless there is a specific design reason not to.
|
|
42
42
|
- `/pulse-ui.css` is always required — it provides the token system your custom CSS will use.
|
|
43
|
-
- The quality gate replaces the component checklist: **100
|
|
43
|
+
- The quality gate replaces the component checklist: **Lighthouse 100 on Accessibility, Best Practices, and SEO — desktop and mobile — plus CLS 0.00 is the pass bar.** (Performance is reported but not gated; it varies with machine load.) A component-free page that passes every audit is correct. A component-heavy page that fails accessibility is not.
|
|
44
44
|
|
|
45
|
-
- When reviewing a
|
|
45
|
+
- **When reviewing**: before flagging a class name like `.hero` as a violation, first ask whether the `hero()` component could actually reproduce the layout. If the answer is no, it is a creative override — check Lighthouse instead of refactoring. Do not flatten a good custom layout into a generic component just to clear a pattern-match.
|
|
46
|
+
|
|
47
|
+
- **The review tool auto-detects creative override** from the spec comment. When detected, component pattern checks are shown as advisory (`⚡`) not as failures, and the Lighthouse gate is the stated pass bar.
|
|
46
48
|
|
|
47
49
|
- **Never write App Store or Google Play download buttons by hand.** Always use `appBadge({ store: 'apple', href })` and `appBadge({ store: 'google', href })`. Raw `<a>` tags with badge images are incorrect — `appBadge` renders the correct accessible, styled badge for each store.
|
|
48
50
|
|
|
@@ -64,8 +66,8 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
64
66
|
```js
|
|
65
67
|
const card = ({ title, body }) => `<div class="card"><h3>${title}</h3><p>${body}</p></div>`
|
|
66
68
|
```
|
|
67
|
-
- **Create a shared component in `src/
|
|
68
|
-
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction.
|
|
69
|
+
- **Create a shared component in `src/components/` when the same pattern is needed across 2 or more different specs.** A named export that returns an HTML string — same shape as the `layout()` pattern in the routing guide. Never paste a section's HTML into a second spec: extract it at the second use, then import it from both. (`src/ui/` is the framework's own library inside the package — project-level shared code always goes in `src/components/`.)
|
|
70
|
+
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction. Within a single spec, wait until the third use before extracting a helper; across specs, extract at the second use.
|
|
69
71
|
|
|
70
72
|
### Correctness
|
|
71
73
|
|
|
@@ -104,6 +106,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
104
106
|
|
|
105
107
|
- **`app.css` must contain no hex values or raw colour values.** A lint hook enforces this and will block the build. Hex values belong in `public/theme.css` as token definitions; `app.css` references them via `var()` only.
|
|
106
108
|
- **Load order in `meta.styles`:** `pulse-ui.css` → `theme.css` → `app.css`. Theme tokens must be defined before `app.css` references them.
|
|
109
|
+
- **CSS for a shared section lives in the shared `app.css`, once.** Every spec that renders the section lists the same file in `meta.styles`. Never copy a CSS block into a second per-page stylesheet — two copies drift apart. Per-page stylesheets are for styles only one page uses.
|
|
107
110
|
|
|
108
111
|
### Security
|
|
109
112
|
|
package/package.json
CHANGED
package/public/.pulse-ui-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.17.1
|
package/src/agent/checklist.md
CHANGED
|
@@ -40,7 +40,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
40
40
|
- **Declare it in a comment at the top of the spec** so the reviewer recognises it: `// component-free — creative override: <reason>`. This is the signal the review tool reads.
|
|
41
41
|
- Functional atoms (`button`, `input`, `badge`, `modal`) should still come from components unless there is a specific design reason not to.
|
|
42
42
|
- `/pulse-ui.css` is always required — it provides the token system your custom CSS will use.
|
|
43
|
-
- The quality gate replaces the component checklist: **100
|
|
43
|
+
- The quality gate replaces the component checklist: **Lighthouse 100 on Accessibility, Best Practices, and SEO — desktop and mobile — plus CLS 0.00 is the pass bar.** (Performance is reported but not gated; it varies with machine load.) A component-free page that passes every audit is correct. A component-heavy page that fails accessibility is not.
|
|
44
44
|
|
|
45
45
|
- **When reviewing**: before flagging a class name like `.hero` as a violation, first ask whether the `hero()` component could actually reproduce the layout. If the answer is no, it is a creative override — check Lighthouse instead of refactoring. Do not flatten a good custom layout into a generic component just to clear a pattern-match.
|
|
46
46
|
|
|
@@ -66,8 +66,8 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
66
66
|
```js
|
|
67
67
|
const card = ({ title, body }) => `<div class="card"><h3>${title}</h3><p>${body}</p></div>`
|
|
68
68
|
```
|
|
69
|
-
- **Create a shared component in `src/
|
|
70
|
-
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction.
|
|
69
|
+
- **Create a shared component in `src/components/` when the same pattern is needed across 2 or more different specs.** A named export that returns an HTML string — same shape as the `layout()` pattern in the routing guide. Never paste a section's HTML into a second spec: extract it at the second use, then import it from both. (`src/ui/` is the framework's own library inside the package — project-level shared code always goes in `src/components/`.)
|
|
70
|
+
- **Do not abstract a pattern that appears only once.** Duplication is cheaper than the wrong abstraction. Within a single spec, wait until the third use before extracting a helper; across specs, extract at the second use.
|
|
71
71
|
|
|
72
72
|
### Correctness
|
|
73
73
|
|
|
@@ -106,6 +106,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
106
106
|
|
|
107
107
|
- **`app.css` must contain no hex values or raw colour values.** A lint hook enforces this and will block the build. Hex values belong in `public/theme.css` as token definitions; `app.css` references them via `var()` only.
|
|
108
108
|
- **Load order in `meta.styles`:** `pulse-ui.css` → `theme.css` → `app.css`. Theme tokens must be defined before `app.css` references them.
|
|
109
|
+
- **CSS for a shared section lives in the shared `app.css`, once.** Every spec that renders the section lists the same file in `meta.styles`. Never copy a CSS block into a second per-page stylesheet — two copies drift apart. Per-page stylesheets are for styles only one page uses.
|
|
109
110
|
|
|
110
111
|
### Security
|
|
111
112
|
|
|
@@ -8,73 +8,103 @@ Run the full verification loop on the current page or the page specified in $ARG
|
|
|
8
8
|
|
|
9
9
|
If $ARGUMENTS is provided, use it as the file path or route. Otherwise, identify the most recently edited spec file from context.
|
|
10
10
|
|
|
11
|
-
### 2.
|
|
11
|
+
### 2. Design approval (new builds only)
|
|
12
|
+
|
|
13
|
+
**Skip this step for edits, bug fixes, or "add X to existing Y" tasks.**
|
|
14
|
+
|
|
15
|
+
If this is a new page build (came through `pulse_intake` or `build-page`):
|
|
16
|
+
|
|
17
|
+
1. Take a screenshot and describe what you see to the user.
|
|
18
|
+
2. Ask the user: "Happy with the design and layout, or would you like any changes before I run Lighthouse?" — if your host provides a question tool (e.g. AskUserQuestion), offer the choices `["Yes, looks good — proceed", "I'd like some changes first"]`; it waits for the answer without ending the turn. If you must ask in plain prose and end your turn, call `pulse_await_approval` first so the Stop hooks let the turn end.
|
|
19
|
+
3. **Do not proceed to step 3 until the user explicitly confirms.** If they want changes, stop here, make the changes, then restart from step 2. Lighthouse takes ~90 seconds — do not waste it on a design the user hasn't approved. If a `VERIFY REQUIRED` stop-hook block fires while your question is unanswered, that is not permission to continue — call `pulse_await_approval`, re-ask, and end your turn.
|
|
20
|
+
|
|
21
|
+
### 3. Validate the spec
|
|
12
22
|
|
|
13
23
|
Use `pulse_validate` with the spec file content. If validation fails, report every error clearly and stop — do not proceed to screenshot until the spec is valid.
|
|
14
24
|
|
|
15
|
-
###
|
|
25
|
+
### 4. Check the dev server
|
|
16
26
|
|
|
17
27
|
Use `pulse_fetch_page` to confirm the server is responding for the route. If it errors, use `pulse_restart_server` and retry once.
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
### 4. Screenshot
|
|
29
|
+
### 5. Screenshot
|
|
22
30
|
|
|
23
31
|
Use `mcp__chrome-devtools__navigate_page` to load the page route, then `mcp__chrome-devtools__take_screenshot` to capture the result. Describe what you see — layout, content, any obvious rendering issues.
|
|
24
32
|
|
|
25
|
-
###
|
|
33
|
+
### 6. Lighthouse — desktop
|
|
34
|
+
|
|
35
|
+
**Pre-flight (required before every Lighthouse run):**
|
|
36
|
+
1. Call `pulse_build` to produce a production build and start the production server on port 3001.
|
|
37
|
+
2. Call `navigate_page` with `url: "http://localhost:3001/"` so the browser is on the production server.
|
|
38
|
+
|
|
39
|
+
Then run `mcp__chrome-devtools__lighthouse_audit` with `{ "device": "desktop" }`.
|
|
40
|
+
|
|
41
|
+
**Pass bar: Accessibility, Best Practices, and SEO must all be 100.** Performance is measured and reported but is not a hard requirement (it varies with machine load). Report the actual scores. If Accessibility, Best Practices, or SEO is below 100, identify the failing audit(s), fix the issue, and restart from step 3.
|
|
26
42
|
|
|
27
|
-
|
|
43
|
+
### 7. Lighthouse — mobile
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
The browser should still be on `http://localhost:3001/` from step 6. Run `mcp__chrome-devtools__lighthouse_audit` with `{ "device": "mobile" }`.
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
**Same pass bar: Accessibility, Best Practices, and SEO must all be 100.** Fix any failures and restart from step 3.
|
|
32
48
|
|
|
33
|
-
|
|
49
|
+
After both Lighthouse runs pass, call `pulse_restart_server` to return to the dev server.
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
### 8. Mobile layout check
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
Emulate a mobile viewport and take a screenshot to catch wrapping, overflow, and layout issues before they become Lighthouse failures.
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
```
|
|
56
|
+
mcp__chrome-devtools__emulate viewport: "390x844x2,mobile,touch"
|
|
57
|
+
mcp__chrome-devtools__navigate_page url: "http://localhost:3000/"
|
|
58
|
+
mcp__chrome-devtools__take_screenshot
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Describe what you see. Look specifically for:
|
|
62
|
+
- Text or buttons overflowing the viewport
|
|
63
|
+
- Content too small to read or tap
|
|
64
|
+
- Navigation that overlaps content
|
|
65
|
+
- Images that break the layout
|
|
66
|
+
|
|
67
|
+
Fix any issues, then reset to desktop before continuing:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
mcp__chrome-devtools__emulate viewport: "1440x900x1"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 9. Performance
|
|
40
74
|
|
|
41
75
|
Navigate to the page route, then run `mcp__chrome-devtools__performance_start_trace` with `reload: true` and `autoStop: true`. Report LCP and CLS. Flag any LCP insight that suggests a fixable problem (render-blocking resources, large image load delay, etc.).
|
|
42
76
|
|
|
43
|
-
###
|
|
77
|
+
### 10. Console errors
|
|
44
78
|
|
|
45
79
|
Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
|
|
46
80
|
|
|
47
|
-
###
|
|
81
|
+
### 11. Code review
|
|
48
82
|
|
|
49
|
-
Call `pulse_review` with the
|
|
83
|
+
Call `pulse_review` with the spec file path. This runs a full code review AND atomically writes the `.pulse-verified` stamp server-side, eliminating any race condition with the stop hook.
|
|
50
84
|
|
|
51
|
-
|
|
85
|
+
Work through every item the review returns. Fix anything that fails before proceeding.
|
|
52
86
|
|
|
53
|
-
|
|
87
|
+
### 12. Close extra browser tabs
|
|
54
88
|
|
|
55
|
-
|
|
89
|
+
Use `mcp__chrome-devtools__list_pages` to get all open pages. Close every page **except the last one** — `close_page` refuses to close the final tab. `pageId` must be a number, not a string.
|
|
90
|
+
|
|
91
|
+
If there is only one page open, skip this step — there is nothing to close.
|
|
92
|
+
|
|
93
|
+
### 13. Write verification stamp
|
|
56
94
|
|
|
57
95
|
Call `pulse_stamp`. This writes `.pulse-verified` via the MCP server, which is more reliable than the `date +%s` shell command — the MCP write always lands after all spec edits, avoiding the mtime race condition that can cause the stop hook to block immediately after verification.
|
|
58
96
|
|
|
59
97
|
**This must be the last operation before stopping.** The stop hook compares each edited spec's mtime against this stamp — if any spec is newer than the stamp, the hook blocks. Do not edit any spec file after calling `pulse_stamp`.
|
|
60
98
|
|
|
61
|
-
###
|
|
99
|
+
### 14. Report
|
|
62
100
|
|
|
63
101
|
Summarise:
|
|
64
102
|
- Validation: pass or fail (with errors if any)
|
|
65
|
-
- Visual:
|
|
66
|
-
- Lighthouse desktop: scores for
|
|
67
|
-
- Lighthouse mobile: scores for
|
|
103
|
+
- Visual: desktop screenshot and mobile layout check — any issues found and fixed
|
|
104
|
+
- Lighthouse desktop: scores for Accessibility, Best Practices, SEO
|
|
105
|
+
- Lighthouse mobile: scores for Accessibility, Best Practices, SEO
|
|
68
106
|
- Performance: LCP and CLS values, any flagged insights
|
|
69
107
|
- Console: any errors
|
|
70
108
|
- Review: pass or issues found and fixed
|
|
71
109
|
|
|
72
|
-
|
|
73
|
-
- Validation: clean (no errors, no warnings)
|
|
74
|
-
- Lighthouse desktop: Accessibility, Best Practices, SEO all 100
|
|
75
|
-
- Lighthouse mobile: same
|
|
76
|
-
- CLS: 0.00 — any layout shift is a blocker
|
|
77
|
-
- LCP: report the value; flag and fix any insight that identifies a fixable cause
|
|
78
|
-
- Console: no errors
|
|
79
|
-
|
|
80
|
-
If any gate fails, fix the issue and run `/verify` again.
|
|
110
|
+
Only confirm the page is good when validation passes, both Lighthouse runs are 100/100/100, CLS is 0.00, and there are no console errors. Otherwise, fix and run `/verify` again.
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
import { execSync } from 'child_process'
|
|
14
14
|
import fs from 'fs'
|
|
15
15
|
|
|
16
|
+
// Agent is ending its turn to wait for the user's answer (design-approval gate) —
|
|
17
|
+
// let the turn end. The marker is consumed on the user's next prompt, so the
|
|
18
|
+
// gates return in full force afterwards.
|
|
19
|
+
if (fs.existsSync('.pulse-awaiting-approval')) process.exit(0)
|
|
20
|
+
|
|
16
21
|
// No test files → nothing to check
|
|
17
22
|
const hasTests = fs.existsSync('src/pages') && (function scan(d) {
|
|
18
23
|
for (const f of fs.readdirSync(d)) {
|
|
@@ -15,7 +15,7 @@ Warm cream background, hand-crafted feel. Photo-forward hero with a split layout
|
|
|
15
15
|
**Key components:** `hero({ layout:'split' })`, `section`, `container`, `grid`, `card` (with `footer`), `stat`, `testimonial`, `cta`, `badge`
|
|
16
16
|
|
|
17
17
|
**Distinguishing moves:**
|
|
18
|
-
- Gradient hero `linear-gradient(135deg, #fdf6ee, #fdebd0)` with `color:#3d2b1e`
|
|
18
|
+
- Gradient hero `linear-gradient(135deg, #fdf6ee, #fdebd0)` with `color:#3d2b1e` — define these hex values as tokens in `public/theme.css` and reference them via `var()`; the lint hook blocks raw hex in any other stylesheet
|
|
19
19
|
- Nav with `background: rgba(255,250,245,0.92)` and matching text colour
|
|
20
20
|
- Cards with price footer: `<div class="u-flex u-items-center u-justify-between"><span>From</span><strong>£35</strong></div>`
|
|
21
21
|
- Credentials list: icon + span with `iconCheckCircle`
|
|
@@ -105,6 +105,8 @@ Long-form article layout with `prose` component for markdown rendering, breadcru
|
|
|
105
105
|
|
|
106
106
|
## Component Combinations by Use Case
|
|
107
107
|
|
|
108
|
+
> Inline `style=""` in these snippets is limited to structural image properties (`aspect-ratio`, `object-fit`, fixed heights) that have no utility-class equivalent — this is the permitted exception to the no-inline-style rule. Colours and theming still belong in tokens.
|
|
109
|
+
|
|
108
110
|
### Image Card (Card with Cover Photo)
|
|
109
111
|
Card doesn't have a native image slot — use `flush:true` and put the image inside `content`:
|
|
110
112
|
|
|
@@ -218,16 +220,16 @@ section({
|
|
|
218
220
|
```
|
|
219
221
|
|
|
220
222
|
### Booking / Contact Form Inside a Card
|
|
223
|
+
Use the `input()` component for fields — it renders the label, id wiring, and required marker accessibly. Do not write raw `<input>`/`<label>` HTML:
|
|
224
|
+
|
|
221
225
|
```js
|
|
222
226
|
card({
|
|
223
227
|
title: 'Book an Appointment',
|
|
224
228
|
content: `
|
|
225
229
|
<p class="u-text-muted u-text-sm u-mb-4">Fill in the form and we'll confirm within a few hours.</p>
|
|
226
230
|
<form class="u-flex u-flex-col u-gap-3" data-action="submit">
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<input id="name" name="name" type="text" class="u-input" required>
|
|
230
|
-
</div>
|
|
231
|
+
${input({ label: 'Your name', name: 'name', required: true })}
|
|
232
|
+
${input({ label: 'Email', name: 'email', type: 'email', required: true })}
|
|
231
233
|
${button({ label: 'Send Enquiry', type: 'submit', variant: 'primary', fullWidth: true })}
|
|
232
234
|
</form>
|
|
233
235
|
`,
|
|
@@ -62,12 +62,12 @@ When you choose a design direction, tell the user which one you're using and why
|
|
|
62
62
|
**Feels like:** Duolingo, Headspace, Loom, Figma landing page, pop-art toy brand
|
|
63
63
|
**vibe:** `playful`
|
|
64
64
|
**Geometry:** very rounded (18–24px), friendly icons, colourful
|
|
65
|
-
**Typography:** rounded or friendly sans, loose spacing, emoji
|
|
65
|
+
**Typography:** rounded or friendly sans, loose spacing, friendly icon accents (icon library, never emoji)
|
|
66
66
|
**Palette pattern:** bright bg (coral / mint / lavender) + dark text
|
|
67
67
|
**Signature moves (use these, not 3-col feature grids):**
|
|
68
68
|
- **Full-colour section blocks** — each section has its own bg colour (yellow stats strip, cyan testimonials, hot-pink CTA). Palette is structure, not accent.
|
|
69
69
|
- **Oversized hero element** — the product's key visual (a game board, an illustration, an icon) fills most of the hero viewport. `phoneFrame` is one option, not the default.
|
|
70
|
-
- **Marquee strip** — giant repeated glyphs,
|
|
70
|
+
- **Marquee strip** — giant repeated icon-library glyphs, brand marks, or product icons scrolling across the page between sections (`marquee` component). Use icons from `src/ui/icons.js`, not emoji — emoji are banned in UI output (not accessible, not theme-aware).
|
|
71
71
|
- **Tilted `photoCard`s** — testimonial or feature cards at ±2–4° with `photoCard` or CSS `transform: rotate()`
|
|
72
72
|
- **Memphis-style geometric shapes** between sections — use `decorate(pattern:'circles')` or CSS background shapes
|
|
73
73
|
- **Social proof as ticker** — a `marquee` of short quotes or star ratings, not a 3-col grid
|
package/src/agent/guide-spec.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
Pulse is a spec-first frontend framework. Pages are JS files that export a default spec object.
|
|
2
2
|
|
|
3
|
+
## Mental model — one spec per page
|
|
4
|
+
|
|
5
|
+
- **One spec = one route = one file** under `src/pages/`. Every page of a site is its own spec file — a site is a collection of specs, not one big spec.
|
|
6
|
+
- **A dynamic route is still one spec.** `route: '/blog/:slug'` is a single file serving *every* blog post URL — the `:param` does the multiplying. Never create one spec per item (`post-1.js`, `post-2.js`, …), and never merge several distinct pages into one spec with conditionals on `ctx`.
|
|
7
|
+
- **Specs share code, they don't repeat it.** Shared sections, layouts, nav, and footer live in `src/components/` and are imported by each spec that needs them (see `pulse://guide/routing` for the `layout()` pattern).
|
|
8
|
+
|
|
3
9
|
## Spec structure
|
|
4
10
|
|
|
5
11
|
```js
|