@invisibleloop/pulse 0.12.3 → 0.14.2
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 +5 -1
- package/.claude/commands/verify.md +31 -17
- package/.claude/pulse-checklist.md +21 -1
- package/.github/workflows/publish.yml +1 -0
- package/.pulse-verified +1 -0
- package/CLAUDE.md +56 -15
- package/docs/.claude/commands/verify.md +4 -0
- package/docs/.claude/pulse-checklist.md +20 -2
- package/docs/public/.pulse-ui-version +1 -1
- package/docs/public/andypixel.css +237 -0
- package/docs/public/bookworms.css +422 -0
- package/docs/public/coutumes.css +584 -0
- package/docs/public/images/bookworms/hero-books.jpg +0 -0
- package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
- package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
- package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
- package/docs/public/images/journey/178.jpg +0 -0
- package/docs/public/images/journey/29.jpg +0 -0
- package/docs/public/images/retrowheels/hero.jpg +0 -0
- package/docs/public/images/shop/devbook-pro.jpg +0 -0
- package/docs/public/images/suzukibento/1015.jpg +0 -0
- package/docs/public/images/suzukibento/1025.jpg +0 -0
- package/docs/public/images/suzukibento/1043.jpg +0 -0
- package/docs/public/images/suzukibento/1059.jpg +0 -0
- package/docs/public/images/suzukibento/1062.jpg +0 -0
- package/docs/public/images/suzukibento/1084.jpg +0 -0
- package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
- package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
- package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
- package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
- package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
- package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
- package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
- package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
- package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
- package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
- package/docs/public/journey.css +528 -0
- package/docs/public/pulse-ui.css +1036 -22
- package/docs/public/retrowheels.css +402 -0
- package/docs/public/shop.css +235 -0
- package/docs/public/sparkys.css +349 -0
- package/docs/public/suzukibento.css +517 -0
- package/docs/public/swish.css +394 -0
- package/docs/server.js +19 -4
- package/docs/src/lib/nav.js +14 -3
- package/docs/src/pages/components/decorate.js +74 -0
- package/docs/src/pages/components/gallery.js +102 -0
- package/docs/src/pages/components/hero.js +1 -0
- package/docs/src/pages/components/marquee.js +76 -0
- package/docs/src/pages/components/photo-card.js +74 -0
- package/docs/src/pages/components/section.js +40 -1
- package/docs/src/pages/meta.js +26 -0
- package/docs/src/pages/templates/andypixel.js +260 -0
- package/docs/src/pages/templates/bookworms.js +227 -0
- package/docs/src/pages/templates/coutumes.js +226 -0
- package/docs/src/pages/templates/journey.js +186 -0
- package/docs/src/pages/templates/retrowheels.js +342 -0
- package/docs/src/pages/templates/shop.js +257 -0
- package/docs/src/pages/templates/sparkys.js +249 -0
- package/docs/src/pages/templates/suzukibento.js +200 -0
- package/docs/src/pages/templates/swish.js +313 -0
- package/docs/src/pages/templates.js +313 -2
- package/docs/src/pages/testing.js +2 -1
- package/examples/.claude/commands/pulse-build.md +7 -0
- package/examples/.claude/commands/pulse-dev.md +7 -0
- package/examples/.claude/commands/pulse-start.md +9 -0
- package/examples/.claude/commands/pulse-stop.md +5 -0
- package/examples/.claude/commands/verify.md +84 -0
- package/examples/.claude/coverage-check.js +127 -0
- package/examples/.claude/pulse-checklist.md +177 -0
- package/examples/andypixel.css +237 -0
- package/examples/andypixel.js +258 -0
- package/examples/bookworms.css +422 -0
- package/examples/bookworms.js +225 -0
- package/examples/contact.js +1 -0
- package/examples/counter.js +1 -0
- package/examples/dev.server.js +17 -0
- package/examples/journey.css +528 -0
- package/examples/journey.js +186 -0
- package/examples/pricing.js +1 -0
- package/examples/public/.pulse-ui-version +1 -0
- package/examples/public/pulse-ui.css +4031 -0
- package/examples/public/pulse-ui.js +257 -0
- package/examples/quiz.js +1 -0
- package/examples/retrowheels.css +402 -0
- package/examples/retrowheels.js +340 -0
- package/examples/shared.js +6 -0
- package/examples/suzukibento.css +513 -0
- package/examples/suzukibento.js +200 -0
- package/examples/swish.css +394 -0
- package/examples/swish.js +311 -0
- package/examples/todos.js +1 -0
- package/package.json +2 -3
- package/public/.pulse-ui-version +1 -1
- package/public/images/bookworms/hero-books.jpg +0 -0
- package/public/images/bookworms/how-we-read-now.jpg +0 -0
- package/public/images/bookworms/lantern-keepers.jpg +0 -0
- package/public/images/bookworms/provincial-bookseller.jpg +0 -0
- package/public/images/journey/178.jpg +0 -0
- package/public/images/journey/29.jpg +0 -0
- package/public/images/retrowheels/hero.jpg +0 -0
- package/public/images/shop/devbook-pro.jpg +0 -0
- package/public/images/suzukibento/1015.jpg +0 -0
- package/public/images/suzukibento/1025.jpg +0 -0
- package/public/images/suzukibento/1043.jpg +0 -0
- package/public/images/suzukibento/1059.jpg +0 -0
- package/public/images/suzukibento/1062.jpg +0 -0
- package/public/images/suzukibento/1084.jpg +0 -0
- package/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
- package/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
- package/public/images/swish/1485518882345-15568b007407.jpg +0 -0
- package/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
- package/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
- package/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
- package/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
- package/public/images/swish/1551803091-e20673f15770.jpg +0 -0
- package/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
- package/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
- package/public/pulse-ui.css +1080 -22
- package/scripts/build.js +72 -18
- package/src/agent/checklist.md +22 -2
- package/src/agent/commands/verify.md +4 -0
- package/src/agent/guide-components.md +146 -8
- package/src/agent/guide-design-gallery.md +288 -0
- package/src/agent/guide-design-references.md +172 -0
- package/src/agent/guide-explore.md +313 -0
- package/src/agent/guide-styles.md +212 -11
- package/src/agent/guide-templates.md +223 -10
- package/src/agent/guide.md +9 -4
- package/src/agent/identity.md +2 -1
- package/src/agent/skills/verify/SKILL.md +64 -28
- package/src/agent/workflow.md +247 -61
- package/src/cli/dev.js +39 -14
- package/src/cli/fmt.js +113 -0
- package/src/cli/index.js +60 -33
- package/src/cli/logger.js +145 -0
- package/src/cli/scaffold.js +57 -3
- package/src/html.js +6 -1
- package/src/mcp/server.js +2561 -99
- package/src/mcp/validate-worker.js +49 -5
- package/src/runtime/ssr.js +4 -1
- package/src/server/index.js +29 -5
- package/src/spec/schema.js +21 -0
- package/src/spec/schema.test.js +28 -0
- package/src/testing/html.js +130 -4
- package/src/testing/testing.test.js +58 -0
- package/src/ui/banner.js +24 -9
- package/src/ui/brand-statement.js +58 -0
- package/src/ui/button.js +13 -2
- package/src/ui/card.js +20 -1
- package/src/ui/colophon.js +29 -0
- package/src/ui/contact-row.js +41 -0
- package/src/ui/cta.js +18 -4
- package/src/ui/decorate.js +55 -0
- package/src/ui/display-heading.js +44 -0
- package/src/ui/drop-cap.js +29 -0
- package/src/ui/editorial-hero.js +63 -0
- package/src/ui/entry-list.js +36 -0
- package/src/ui/feature.js +20 -3
- package/src/ui/footer.js +78 -9
- package/src/ui/gallery.js +56 -0
- package/src/ui/hero.js +68 -18
- package/src/ui/image-pair.js +72 -0
- package/src/ui/index.js +20 -0
- package/src/ui/input.js +1 -0
- package/src/ui/marquee.js +48 -0
- package/src/ui/nav.js +19 -9
- package/src/ui/photo-card.js +44 -0
- package/src/ui/product-card.js +65 -0
- package/src/ui/pullquote.js +14 -3
- package/src/ui/search.js +1 -0
- package/src/ui/section-label.js +38 -0
- package/src/ui/section.js +1 -1
- package/src/ui/service-card.js +47 -0
- package/src/ui/stat-grid.js +40 -0
- package/src/ui/stat.js +16 -2
- package/src/ui/testimonial.js +17 -6
- package/src/ui/textarea.js +1 -0
- package/src/ui/top-strip.js +47 -0
- package/src/ui/trust-list.js +46 -0
- package/src/ui/ui.test.js +35 -0
|
@@ -41,7 +41,7 @@ Use `pulse_validate` on the spec. Fix any errors before continuing.
|
|
|
41
41
|
|
|
42
42
|
Check the project's server entry file (e.g. `src/server.js` or `pulse.config.js`) and add the new spec if needed.
|
|
43
43
|
|
|
44
|
-
### 6. Screenshot
|
|
44
|
+
### 6. Screenshot and design approval
|
|
45
45
|
|
|
46
46
|
Use `mcp__chrome-devtools__navigate_page` to load the page, then `mcp__chrome-devtools__take_screenshot`.
|
|
47
47
|
|
|
@@ -50,6 +50,10 @@ Check for:
|
|
|
50
50
|
- Layout looks correct
|
|
51
51
|
- No console errors via `mcp__chrome-devtools__list_console_messages`
|
|
52
52
|
|
|
53
|
+
Then use `ask_user` to ask: "Happy with the design and layout, or would you like any changes before I run tests and Lighthouse?" with choices: `["Yes, looks good — run tests and Lighthouse", "I'd like some changes first"]`
|
|
54
|
+
|
|
55
|
+
**Do not proceed to step 7 until the user explicitly confirms.** If they want changes, make them and return to step 6.
|
|
56
|
+
|
|
53
57
|
### 7. Run the checklist
|
|
54
58
|
|
|
55
59
|
Go through `src/agent/checklist.md` and confirm every applicable point passes. Fix anything that fails.
|
|
@@ -8,57 +8,71 @@ 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. Use `ask_user` to ask: "Happy with the design and layout, or would you like any changes before I run Lighthouse?" — provide choices: `["Yes, looks good — proceed", "I'd like some changes first"]`
|
|
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.
|
|
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
|
-
###
|
|
29
|
+
### 5. Screenshot
|
|
20
30
|
|
|
21
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.
|
|
22
32
|
|
|
23
|
-
###
|
|
33
|
+
### 6. Lighthouse — desktop
|
|
24
34
|
|
|
25
35
|
Run `mcp__chrome-devtools__lighthouse_audit` on the route with `{ "strategy": "desktop" }`.
|
|
26
36
|
|
|
27
|
-
**Pass bar: Performance, Accessibility, Best Practices, and SEO must all be 100.** Report the actual scores. If any score is below 100, identify the failing audit(s), fix the issue, and restart from step
|
|
37
|
+
**Pass bar: Performance, Accessibility, Best Practices, and SEO must all be 100.** Report the actual scores. If any score is below 100, identify the failing audit(s), fix the issue, and restart from step 3.
|
|
28
38
|
|
|
29
|
-
###
|
|
39
|
+
### 7. Lighthouse — mobile
|
|
30
40
|
|
|
31
41
|
Run `mcp__chrome-devtools__lighthouse_audit` on the same route with `{ "strategy": "mobile" }`.
|
|
32
42
|
|
|
33
|
-
**Same pass bar: all four categories must be 100.** Report the actual scores. If any score is below 100, fix and restart from step
|
|
43
|
+
**Same pass bar: all four categories must be 100.** Report the actual scores. If any score is below 100, fix and restart from step 3.
|
|
34
44
|
|
|
35
|
-
###
|
|
45
|
+
### 8. Performance
|
|
36
46
|
|
|
37
47
|
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.).
|
|
38
48
|
|
|
39
|
-
###
|
|
49
|
+
### 9. Console errors
|
|
40
50
|
|
|
41
51
|
Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
|
|
42
52
|
|
|
43
|
-
###
|
|
53
|
+
### 10. Code review
|
|
54
|
+
|
|
55
|
+
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.
|
|
56
|
+
|
|
57
|
+
Work through every item the review returns. Fix anything that fails before proceeding.
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
### 11. Close extra browser tabs
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
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.
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
If there is only one page open, skip this step — there is nothing to close.
|
|
50
64
|
|
|
51
|
-
###
|
|
65
|
+
### 12. Write verification stamp
|
|
52
66
|
|
|
53
|
-
|
|
67
|
+
`pulse_review` writes the stamp automatically. As a belt-and-suspenders backup, also run:
|
|
54
68
|
|
|
55
69
|
```bash
|
|
56
70
|
date +%s > .pulse-verified
|
|
57
71
|
```
|
|
58
72
|
|
|
59
|
-
This
|
|
73
|
+
This ensures the stamp is present even if `pulse_review` was not called with a file path.
|
|
60
74
|
|
|
61
|
-
###
|
|
75
|
+
### 13. Report
|
|
62
76
|
|
|
63
77
|
Summarise:
|
|
64
78
|
- Validation: pass or fail (with errors if any)
|
|
@@ -25,6 +25,20 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
25
25
|
### Components first
|
|
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:
|
|
29
|
+
- Hero sections → `hero({ title, subtitle, actions, image, ... })`
|
|
30
|
+
- Product/service cards → `card({ title, body, image, footer, ... })`
|
|
31
|
+
- Image + text two-column layouts → `media({ image, content, reverse })`
|
|
32
|
+
- Horizontal strips/banners → `banner({ content, variant })`
|
|
33
|
+
- Feature tiles → `feature({ icon, title, description })`
|
|
34
|
+
- Testimonials → `testimonial({ quote, name, role, src })`
|
|
35
|
+
- CTAs → `cta({ title, subtitle, actions })`
|
|
36
|
+
- Navigation → `nav({ logo, links, actions })`
|
|
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
|
+
|
|
41
|
+
- **Exception — brutalist, editorial, and playful vibes:** these vibes intentionally break from polished templates. `pulse://guide/explore` explicitly endorses zone-based raw HTML for distinctive layouts. For `brutalist`, `editorial`, `retro`, or `neon` vibes, raw structural HTML is correct — components can still be used for functional atoms (`button`, `input`, `modal`) but structural sections do not need to come from components. When reviewing a brutalist or editorial spec, do not flag raw section HTML as a violation.
|
|
28
42
|
|
|
29
43
|
- **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.
|
|
30
44
|
|
|
@@ -110,7 +124,13 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
110
124
|
assert.equal(result.get('h1').text, mockProduct.name)
|
|
111
125
|
assert.equal(result.attr('img', 'src'), mockProduct.image)
|
|
112
126
|
```
|
|
113
|
-
Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`,
|
|
127
|
+
Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`, combinations (`button.primary[disabled]`), and **descendant combinators** (`result.count('.parent li')`, `result.find('tbody tr')`). Descendant selectors match the rightmost token within the ancestor context — they do not return 0 if the ancestor doesn't exist, they simply find no matching descendants.
|
|
128
|
+
|
|
129
|
+
### Markdown
|
|
130
|
+
|
|
131
|
+
- **Use `md()` from `@invisibleloop/pulse/md` for `.md` file content.** Never read and parse markdown manually. Pass the result's `html` to `prose()`.
|
|
132
|
+
- **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
|
+
- **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.
|
|
114
134
|
|
|
115
135
|
### Markdown
|
|
116
136
|
|
|
@@ -40,5 +40,6 @@ jobs:
|
|
|
40
40
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
41
41
|
git add package.json package-lock.json public/.pulse-ui-version
|
|
42
42
|
git commit -m "chore: release $VERSION [skip ci]" || true
|
|
43
|
+
git pull --rebase
|
|
43
44
|
git push
|
|
44
45
|
npm publish --provenance --access public
|
package/.pulse-verified
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1780010184
|
package/CLAUDE.md
CHANGED
|
@@ -318,23 +318,30 @@ For multi-brand setups, each brand theme file just overrides `--font` in `:root`
|
|
|
318
318
|
|
|
319
319
|
## Overriding Theme Colours
|
|
320
320
|
|
|
321
|
-
`pulse-ui.css`
|
|
322
|
-
|
|
323
|
-
Always target `[data-theme="light"]` when overriding colours for the light theme:
|
|
321
|
+
Use **input tokens** (unprefixed: `--accent`, `--bg`, `--text`, etc.) in `theme.css`. `pulse-ui.css` maps them to output tokens via `var()` in **both** the dark (`:root`) and light (`[data-theme="light"]`) blocks:
|
|
324
322
|
|
|
325
323
|
```css
|
|
326
|
-
/*
|
|
324
|
+
/* Dark theme — in :root */
|
|
327
325
|
:root {
|
|
328
|
-
--
|
|
326
|
+
--accent: #9b8dff;
|
|
329
327
|
}
|
|
330
328
|
|
|
331
|
-
/*
|
|
329
|
+
/* Light theme — in [data-theme="light"] */
|
|
332
330
|
[data-theme="light"] {
|
|
333
|
-
--
|
|
331
|
+
--accent: #e25; /* overrides the light default */
|
|
334
332
|
}
|
|
335
333
|
```
|
|
336
334
|
|
|
337
|
-
|
|
335
|
+
Setting `--accent` in `[data-theme="light"]` works correctly — `pulse-ui.css` resolves `var(--accent, ...)` there just like it does in `:root`.
|
|
336
|
+
|
|
337
|
+
If you need to override an output token directly (e.g. one with no input equivalent), target `[data-theme="light"]` explicitly:
|
|
338
|
+
|
|
339
|
+
```css
|
|
340
|
+
/* Direct output token override for light theme only */
|
|
341
|
+
[data-theme="light"] {
|
|
342
|
+
--ui-nav-sticky-bg: rgba(255, 255, 255, 0.95);
|
|
343
|
+
}
|
|
344
|
+
```
|
|
338
345
|
|
|
339
346
|
## CSS File Responsibilities
|
|
340
347
|
|
|
@@ -342,10 +349,10 @@ There are two CSS files and they have distinct roles — do not mix them:
|
|
|
342
349
|
|
|
343
350
|
| File | Purpose | Allowed content |
|
|
344
351
|
|---|---|---|
|
|
345
|
-
| `public/theme.css` | Token definitions — hex values, raw colours, font URLs | Hex values, `rgb()`, raw values, `@font-face`, `@import` |
|
|
346
|
-
| `app.css` (or per-page) | Layout and component overrides | `var()` tokens **only** — no hex
|
|
352
|
+
| `public/theme.css` | Token definitions — hex values, raw colours, font URLs | Hex values, `rgb()`, `rgba()`, `hsl()`, `hsla()`, raw values, `@font-face`, `@import` |
|
|
353
|
+
| `app.css` (or per-page) | Layout and component overrides | `var()` tokens **only** — no hex values |
|
|
347
354
|
|
|
348
|
-
**`app.css` must never contain hex values
|
|
355
|
+
**`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`.
|
|
349
356
|
|
|
350
357
|
```css
|
|
351
358
|
/* public/theme.css — hex values live here */
|
|
@@ -422,7 +429,8 @@ const result = await render(mySpec, { server: { product: mockProduct } })
|
|
|
422
429
|
const result = await render(mySpec, { ctx: { params: { id: '1' } } }) // real fetchers
|
|
423
430
|
```
|
|
424
431
|
|
|
425
|
-
**Supported selectors:** `button`, `.class`, `#id`, `[attr]`, `[attr="value"]`, and combinations: `button.primary[type="submit"]`.
|
|
432
|
+
**Supported selectors:** `button`, `.class`, `#id`, `[attr]`, `[attr="value"]`, and combinations: `button.primary[type="submit"]`. **Descendant combinators are supported:** `result.count('.parent li')` or `result.find('tbody tr')`.
|
|
433
|
+
|
|
426
434
|
`Element` also has `.find()`, `.findAll()`, `.has()`, `.attr()`, `.text`, `.tag`, `.attrs`.
|
|
427
435
|
|
|
428
436
|
## Key Decisions (Do Not Reverse)
|
|
@@ -439,6 +447,25 @@ const result = await render(mySpec, { ctx: { params: { id: '1' } } }) // real fe
|
|
|
439
447
|
|
|
440
448
|
## Build Workflow
|
|
441
449
|
|
|
450
|
+
### New page or new site? Start with intake — always
|
|
451
|
+
|
|
452
|
+
For any **new page, landing page, or branded site**, run this sequence first. Do not skip to building.
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
0. pulse_extract_inspiration(url/image) → structured design brief [if user shares reference]
|
|
456
|
+
1. pulse_intake(name, pitch, features, ...) → product brief + contrast check
|
|
457
|
+
2. pulse_sketch(brief, vibe?, antiStyle?) → 3 layout directions to choose from
|
|
458
|
+
3. pulse_intent(description) → archetype + scaffold + guide list
|
|
459
|
+
```
|
|
460
|
+
|
|
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. Ask for inspiration references, palette, vibe, and anti-style if the user hasn't mentioned them.
|
|
462
|
+
|
|
463
|
+
**`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
|
+
|
|
465
|
+
For **small edits, bug fixes, or "add X to existing Y"** — skip intake/sketch and go straight to Understand below.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
442
469
|
Every build task follows this sequence. Each phase has a pass gate — do not advance until it clears.
|
|
443
470
|
|
|
444
471
|
**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.
|
|
@@ -449,14 +476,16 @@ Every build task follows this sequence. Each phase has a pass gate — do not ad
|
|
|
449
476
|
| 2. Plan | Present plan to user, wait for confirmation | User confirms |
|
|
450
477
|
| 3. Build | Write spec + related files | — |
|
|
451
478
|
| 4. Validate | `pulse_validate` — fix all errors + warnings | Clean output |
|
|
452
|
-
| 5. Browser | Screenshot
|
|
479
|
+
| 5. Browser | Screenshot → `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 |
|
|
453
480
|
| 6. Tests | Write tests, run them, fix failures | All pass |
|
|
454
|
-
| 7. Review
|
|
481
|
+
| 7. Code Review | `pulse_review` — only after phases 4–6 pass | — |
|
|
455
482
|
| 8. Fix | Fix every review issue, re-run affected gates | All gates still pass |
|
|
456
483
|
|
|
457
484
|
**Skip phase 2 confirmation only for trivially small, unambiguous tasks.** When in doubt, confirm.
|
|
458
485
|
|
|
459
|
-
**
|
|
486
|
+
**Template mode exception:** when adapting a pre-built template (see `pulse://guide/templates`), replace the full plan with a single compact confirmation block — template, substitutions, files. One turn is enough; do not run the full phase 2 ceremony.
|
|
487
|
+
|
|
488
|
+
**The Code Review is always last.** Never invoke `pulse_review` before validate, Lighthouse, design review, and tests all pass.
|
|
460
489
|
|
|
461
490
|
The `/verify` command runs the browser check loop (phases 4–5) automatically. Use it.
|
|
462
491
|
|
|
@@ -521,6 +550,18 @@ export default {
|
|
|
521
550
|
|
|
522
551
|
Before writing any UI HTML by hand, check `src/ui/index.js` — there are 50+ components available. Use them. Do not reinvent `button`, `card`, `alert`, `modal`, `spinner`, `badge`, `input`, etc.
|
|
523
552
|
|
|
553
|
+
**Common patterns with dedicated components — never write these from scratch:**
|
|
554
|
+
|
|
555
|
+
- Hero sections → `hero({ title, subtitle, actions, image, layout })`
|
|
556
|
+
- Product/service cards → `card({ title, body, image, footer })`
|
|
557
|
+
- Image + text layouts → `media({ image, content, reverse })`
|
|
558
|
+
- Horizontal strips → `banner({ content, variant })`
|
|
559
|
+
- Feature tiles → `feature({ icon, title, description })`
|
|
560
|
+
- Testimonials → `testimonial({ quote, name, role, src })`
|
|
561
|
+
- Call-to-action sections → `cta({ title, subtitle, actions })`
|
|
562
|
+
|
|
563
|
+
If you're about to write `class="hero"` or `class="product-card"`, stop — import the component instead. You can add custom CSS on top of components for brand-specific styling, but the structure and accessibility must come from the component.
|
|
564
|
+
|
|
524
565
|
@src/agent/checklist.md
|
|
525
566
|
|
|
526
567
|
## Performance Baseline
|
|
@@ -16,6 +16,8 @@ Use `pulse_validate` with the spec file content. If validation fails, report eve
|
|
|
16
16
|
|
|
17
17
|
Use `pulse_fetch_page` to confirm the server is responding for the route. If it errors, use `pulse_restart_server` and retry once.
|
|
18
18
|
|
|
19
|
+
**If `pulse_fetch_page` returns unexpected HTML (e.g. a different project's page title, a Next.js 404, or any non-Pulse response):** check the `X-Pulse` response header — Pulse servers always send this. If it is absent, the wrong server is running on that port. Stop and inform the user before continuing.
|
|
20
|
+
|
|
19
21
|
### 4. Screenshot
|
|
20
22
|
|
|
21
23
|
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.
|
|
@@ -32,6 +34,8 @@ Run `mcp__chrome-devtools__lighthouse_audit` on the same route with `{ "strategy
|
|
|
32
34
|
|
|
33
35
|
**Same pass bar: Accessibility, Best Practices, and SEO must all be 100.** Fix any failures and restart from step 2.
|
|
34
36
|
|
|
37
|
+
**If you need to visually inspect the layout at a mobile viewport width** (e.g. to debug a wrapping or overflow issue), use `mcp__chrome-devtools__emulate` with `viewport: "390x844x2,mobile,touch"` — not `resize_page`. The `emulate` tool reliably sets the viewport; `resize_page` does not reliably trigger responsive layout changes. Reset afterward with `emulate` using the desktop viewport: `"1440x900x1"`.
|
|
38
|
+
|
|
35
39
|
### 7. Performance
|
|
36
40
|
|
|
37
41
|
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.).
|
|
@@ -22,9 +22,27 @@ 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 unless a creative override applies (see below):
|
|
29
|
+
- Hero sections → `hero({ title, subtitle, actions, image, ... })`
|
|
30
|
+
- Product/service cards → `card({ title, body, image, footer, ... })`
|
|
31
|
+
- Image + text two-column layouts → `media({ image, content, reverse })`
|
|
32
|
+
- Horizontal strips/banners → `banner({ content, variant })`
|
|
33
|
+
- Feature tiles → `feature({ icon, title, description })`
|
|
34
|
+
- Testimonials → `testimonial({ quote, name, role, src })`
|
|
35
|
+
- CTAs → `cta({ title, subtitle, actions })`
|
|
36
|
+
- Navigation → `nav({ logo, links, actions })`
|
|
37
|
+
- Footers → `footer({ logo, links, columns, legal, ... })`
|
|
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
|
+
- State it explicitly in the build brief: *"Building component-free — raw HTML throughout for creative control."*
|
|
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: **100/100/100/100 Lighthouse on both desktop and mobile is the pass bar.** A component-free page that passes every audit is correct. A component-heavy page that fails accessibility is not.
|
|
44
|
+
|
|
45
|
+
- When reviewing a spec built under creative override, do not flag raw section HTML as a violation — check the Lighthouse results instead.
|
|
28
46
|
|
|
29
47
|
- **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.
|
|
30
48
|
|
|
@@ -110,7 +128,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
110
128
|
assert.equal(result.get('h1').text, mockProduct.name)
|
|
111
129
|
assert.equal(result.attr('img', 'src'), mockProduct.image)
|
|
112
130
|
```
|
|
113
|
-
Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`,
|
|
131
|
+
Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`, combinations (`button.primary[disabled]`), and **descendant combinators** (`result.count('.parent li')`, `result.find('tbody tr')`). Descendant selectors match the rightmost token within the ancestor context — they do not return 0 if the ancestor doesn't exist, they simply find no matching descendants.
|
|
114
132
|
|
|
115
133
|
### Markdown
|
|
116
134
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.14.1
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* andypixel.css — Andy Pixel blog article example
|
|
3
|
+
*
|
|
4
|
+
* Token definitions (hex values) + layout styles (var() only).
|
|
5
|
+
* Orange accent palette — editorial light theme.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ─── Token definitions ──────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
[data-theme="light"] {
|
|
11
|
+
--accent: #f97316;
|
|
12
|
+
--accent-hover: #ea6c0a;
|
|
13
|
+
--accent-dim: #fff3e8;
|
|
14
|
+
--surface-2: #f0f0f0;
|
|
15
|
+
--border: #e5e5e5;
|
|
16
|
+
--muted-bg: #f5f5f5;
|
|
17
|
+
|
|
18
|
+
/* Code block dark-shell tokens — defined here so app.css stays hex-free */
|
|
19
|
+
--ap-code-shell: #1e1e1e;
|
|
20
|
+
--ap-code-text: #d4d4d4;
|
|
21
|
+
--ap-btn-dark: #1a1a1a;
|
|
22
|
+
--ap-btn-dark-hover: #333333;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ─── Base ───────────────────────────────────────────────────── */
|
|
26
|
+
|
|
27
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
28
|
+
|
|
29
|
+
body {
|
|
30
|
+
font-family: var(--ui-font, system-ui, sans-serif);
|
|
31
|
+
line-height: 1.6;
|
|
32
|
+
background: var(--ui-bg);
|
|
33
|
+
color: var(--ui-text);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ─── Contrast fixes ─────────────────────────────────────────── */
|
|
37
|
+
|
|
38
|
+
/* Badge: muted on surface-2 fails WCAG AA at small size */
|
|
39
|
+
.ui-badge--default {
|
|
40
|
+
color: var(--ui-text);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Inline code: accent on surface-2 fails WCAG AA */
|
|
44
|
+
.ui-prose code {
|
|
45
|
+
color: var(--ui-text);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ─── Reading column ─────────────────────────────────────────── */
|
|
49
|
+
|
|
50
|
+
.ap-reading-col {
|
|
51
|
+
max-width: 72ch;
|
|
52
|
+
margin-left: auto;
|
|
53
|
+
margin-right: auto;
|
|
54
|
+
padding-left: var(--ui-space-6);
|
|
55
|
+
padding-right: var(--ui-space-6);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ─── Article header ─────────────────────────────────────────── */
|
|
59
|
+
|
|
60
|
+
.ap-article-header {
|
|
61
|
+
padding: var(--ui-space-12) 0 var(--ui-space-8);
|
|
62
|
+
border-bottom: 3px solid var(--ui-accent);
|
|
63
|
+
margin-bottom: var(--ui-space-10);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.ap-article-meta {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
gap: var(--ui-space-2);
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
margin-bottom: var(--ui-space-5);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.ap-article-title {
|
|
75
|
+
font-family: var(--ui-font-display, system-ui, serif);
|
|
76
|
+
font-size: clamp(2rem, 5vw, 3.25rem);
|
|
77
|
+
font-weight: 800;
|
|
78
|
+
line-height: 1.1;
|
|
79
|
+
letter-spacing: -0.02em;
|
|
80
|
+
color: var(--ui-text);
|
|
81
|
+
margin-bottom: var(--ui-space-4);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ap-article-subtitle {
|
|
85
|
+
font-size: var(--ui-text-xl);
|
|
86
|
+
color: var(--ui-muted);
|
|
87
|
+
line-height: 1.55;
|
|
88
|
+
margin-bottom: var(--ui-space-6);
|
|
89
|
+
max-width: 56ch;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.ap-article-author-strip {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: var(--ui-space-3);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ─── Article body ───────────────────────────────────────────── */
|
|
99
|
+
|
|
100
|
+
.ap-article-body {
|
|
101
|
+
padding-bottom: var(--ui-space-12);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ap-article-lead {
|
|
105
|
+
font-size: var(--ui-text-xl) !important;
|
|
106
|
+
line-height: 1.65 !important;
|
|
107
|
+
color: var(--ui-text) !important;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ─── Code block ─────────────────────────────────────────────── */
|
|
111
|
+
|
|
112
|
+
.ap-code-block {
|
|
113
|
+
margin: var(--ui-space-6) 0;
|
|
114
|
+
border-radius: var(--ui-radius);
|
|
115
|
+
overflow: hidden;
|
|
116
|
+
border: 1px solid var(--ui-border);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.ap-code-block__header {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: var(--ui-space-2);
|
|
123
|
+
padding: var(--ui-space-2) var(--ui-space-4);
|
|
124
|
+
background: var(--ui-text);
|
|
125
|
+
color: var(--ui-accent);
|
|
126
|
+
font-family: var(--ui-mono, monospace);
|
|
127
|
+
font-size: var(--ui-text-sm);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.ap-code-block__pre {
|
|
131
|
+
background: var(--ap-code-shell);
|
|
132
|
+
padding: var(--ui-space-5) var(--ui-space-6);
|
|
133
|
+
overflow-x: auto;
|
|
134
|
+
margin: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.ap-code-block__code {
|
|
138
|
+
font-family: var(--ui-mono, monospace);
|
|
139
|
+
font-size: var(--ui-text-sm);
|
|
140
|
+
line-height: 1.75;
|
|
141
|
+
color: var(--ap-code-text);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ─── Pullquote ──────────────────────────────────────────────── */
|
|
145
|
+
|
|
146
|
+
.ap-pullquote {
|
|
147
|
+
margin: var(--ui-space-8) 0;
|
|
148
|
+
padding: var(--ui-space-5) var(--ui-space-6);
|
|
149
|
+
border-left: 4px solid var(--ui-accent);
|
|
150
|
+
background: var(--accent-dim);
|
|
151
|
+
border-radius: 0 var(--ui-radius) var(--ui-radius) 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.ap-pullquote p {
|
|
155
|
+
font-family: var(--ui-font-display, system-ui, serif);
|
|
156
|
+
font-size: var(--ui-text-xl);
|
|
157
|
+
font-style: italic;
|
|
158
|
+
color: var(--ui-text);
|
|
159
|
+
margin: 0;
|
|
160
|
+
line-height: 1.55;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ─── Author bio ─────────────────────────────────────────────── */
|
|
164
|
+
|
|
165
|
+
.ap-author-bio-wrap {
|
|
166
|
+
border-top: 1px solid var(--ui-border);
|
|
167
|
+
border-bottom: 1px solid var(--ui-border);
|
|
168
|
+
padding: var(--ui-space-8) 0;
|
|
169
|
+
margin-bottom: var(--ui-space-10);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.ap-author-bio {
|
|
173
|
+
display: flex;
|
|
174
|
+
gap: var(--ui-space-4);
|
|
175
|
+
align-items: flex-start;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.ap-author-bio__name {
|
|
179
|
+
font-weight: 700;
|
|
180
|
+
font-size: var(--ui-text-base);
|
|
181
|
+
margin-bottom: var(--ui-space-1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ─── Newsletter ─────────────────────────────────────────────── */
|
|
185
|
+
|
|
186
|
+
.ap-newsletter-section {
|
|
187
|
+
max-width: 540px;
|
|
188
|
+
margin: 0 auto;
|
|
189
|
+
text-align: center;
|
|
190
|
+
padding: var(--ui-space-12) var(--ui-space-6);
|
|
191
|
+
border-top: 3px solid var(--ui-accent);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.ap-newsletter-eyebrow {
|
|
195
|
+
font-family: var(--ui-mono, monospace);
|
|
196
|
+
font-size: var(--ui-text-sm);
|
|
197
|
+
text-transform: uppercase;
|
|
198
|
+
letter-spacing: 0.1em;
|
|
199
|
+
color: var(--ui-text);
|
|
200
|
+
margin-bottom: var(--ui-space-2);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.ap-newsletter-title {
|
|
204
|
+
font-family: var(--ui-font-display, system-ui, serif);
|
|
205
|
+
font-size: clamp(1.5rem, 4vw, 2.25rem);
|
|
206
|
+
font-weight: 800;
|
|
207
|
+
letter-spacing: -0.02em;
|
|
208
|
+
color: var(--ui-text);
|
|
209
|
+
margin-bottom: var(--ui-space-3);
|
|
210
|
+
line-height: 1.15;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ap-newsletter-form {
|
|
214
|
+
display: flex;
|
|
215
|
+
gap: var(--ui-space-2);
|
|
216
|
+
margin-top: var(--ui-space-6);
|
|
217
|
+
flex-wrap: wrap;
|
|
218
|
+
justify-content: center;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Orange bg on small text fails WCAG AA — use near-black for contrast (15:1) */
|
|
222
|
+
.ap-newsletter-form .ui-btn--primary {
|
|
223
|
+
background: var(--ap-btn-dark);
|
|
224
|
+
}
|
|
225
|
+
.ap-newsletter-form .ui-btn--primary:hover {
|
|
226
|
+
background: var(--ap-btn-dark-hover);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.ap-newsletter-error {
|
|
230
|
+
color: var(--ui-error, #dc2626);
|
|
231
|
+
margin-top: var(--ui-space-3);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.ap-newsletter-success {
|
|
235
|
+
padding: var(--ui-space-12) var(--ui-space-6);
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|