@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.
Files changed (181) hide show
  1. package/.claude/commands/build-page.md +5 -1
  2. package/.claude/commands/verify.md +31 -17
  3. package/.claude/pulse-checklist.md +21 -1
  4. package/.github/workflows/publish.yml +1 -0
  5. package/.pulse-verified +1 -0
  6. package/CLAUDE.md +56 -15
  7. package/docs/.claude/commands/verify.md +4 -0
  8. package/docs/.claude/pulse-checklist.md +20 -2
  9. package/docs/public/.pulse-ui-version +1 -1
  10. package/docs/public/andypixel.css +237 -0
  11. package/docs/public/bookworms.css +422 -0
  12. package/docs/public/coutumes.css +584 -0
  13. package/docs/public/images/bookworms/hero-books.jpg +0 -0
  14. package/docs/public/images/bookworms/how-we-read-now.jpg +0 -0
  15. package/docs/public/images/bookworms/lantern-keepers.jpg +0 -0
  16. package/docs/public/images/bookworms/provincial-bookseller.jpg +0 -0
  17. package/docs/public/images/journey/178.jpg +0 -0
  18. package/docs/public/images/journey/29.jpg +0 -0
  19. package/docs/public/images/retrowheels/hero.jpg +0 -0
  20. package/docs/public/images/shop/devbook-pro.jpg +0 -0
  21. package/docs/public/images/suzukibento/1015.jpg +0 -0
  22. package/docs/public/images/suzukibento/1025.jpg +0 -0
  23. package/docs/public/images/suzukibento/1043.jpg +0 -0
  24. package/docs/public/images/suzukibento/1059.jpg +0 -0
  25. package/docs/public/images/suzukibento/1062.jpg +0 -0
  26. package/docs/public/images/suzukibento/1084.jpg +0 -0
  27. package/docs/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  28. package/docs/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  29. package/docs/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  30. package/docs/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  31. package/docs/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  32. package/docs/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  33. package/docs/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  34. package/docs/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  35. package/docs/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  36. package/docs/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  37. package/docs/public/journey.css +528 -0
  38. package/docs/public/pulse-ui.css +1036 -22
  39. package/docs/public/retrowheels.css +402 -0
  40. package/docs/public/shop.css +235 -0
  41. package/docs/public/sparkys.css +349 -0
  42. package/docs/public/suzukibento.css +517 -0
  43. package/docs/public/swish.css +394 -0
  44. package/docs/server.js +19 -4
  45. package/docs/src/lib/nav.js +14 -3
  46. package/docs/src/pages/components/decorate.js +74 -0
  47. package/docs/src/pages/components/gallery.js +102 -0
  48. package/docs/src/pages/components/hero.js +1 -0
  49. package/docs/src/pages/components/marquee.js +76 -0
  50. package/docs/src/pages/components/photo-card.js +74 -0
  51. package/docs/src/pages/components/section.js +40 -1
  52. package/docs/src/pages/meta.js +26 -0
  53. package/docs/src/pages/templates/andypixel.js +260 -0
  54. package/docs/src/pages/templates/bookworms.js +227 -0
  55. package/docs/src/pages/templates/coutumes.js +226 -0
  56. package/docs/src/pages/templates/journey.js +186 -0
  57. package/docs/src/pages/templates/retrowheels.js +342 -0
  58. package/docs/src/pages/templates/shop.js +257 -0
  59. package/docs/src/pages/templates/sparkys.js +249 -0
  60. package/docs/src/pages/templates/suzukibento.js +200 -0
  61. package/docs/src/pages/templates/swish.js +313 -0
  62. package/docs/src/pages/templates.js +313 -2
  63. package/docs/src/pages/testing.js +2 -1
  64. package/examples/.claude/commands/pulse-build.md +7 -0
  65. package/examples/.claude/commands/pulse-dev.md +7 -0
  66. package/examples/.claude/commands/pulse-start.md +9 -0
  67. package/examples/.claude/commands/pulse-stop.md +5 -0
  68. package/examples/.claude/commands/verify.md +84 -0
  69. package/examples/.claude/coverage-check.js +127 -0
  70. package/examples/.claude/pulse-checklist.md +177 -0
  71. package/examples/andypixel.css +237 -0
  72. package/examples/andypixel.js +258 -0
  73. package/examples/bookworms.css +422 -0
  74. package/examples/bookworms.js +225 -0
  75. package/examples/contact.js +1 -0
  76. package/examples/counter.js +1 -0
  77. package/examples/dev.server.js +17 -0
  78. package/examples/journey.css +528 -0
  79. package/examples/journey.js +186 -0
  80. package/examples/pricing.js +1 -0
  81. package/examples/public/.pulse-ui-version +1 -0
  82. package/examples/public/pulse-ui.css +4031 -0
  83. package/examples/public/pulse-ui.js +257 -0
  84. package/examples/quiz.js +1 -0
  85. package/examples/retrowheels.css +402 -0
  86. package/examples/retrowheels.js +340 -0
  87. package/examples/shared.js +6 -0
  88. package/examples/suzukibento.css +513 -0
  89. package/examples/suzukibento.js +200 -0
  90. package/examples/swish.css +394 -0
  91. package/examples/swish.js +311 -0
  92. package/examples/todos.js +1 -0
  93. package/package.json +2 -3
  94. package/public/.pulse-ui-version +1 -1
  95. package/public/images/bookworms/hero-books.jpg +0 -0
  96. package/public/images/bookworms/how-we-read-now.jpg +0 -0
  97. package/public/images/bookworms/lantern-keepers.jpg +0 -0
  98. package/public/images/bookworms/provincial-bookseller.jpg +0 -0
  99. package/public/images/journey/178.jpg +0 -0
  100. package/public/images/journey/29.jpg +0 -0
  101. package/public/images/retrowheels/hero.jpg +0 -0
  102. package/public/images/shop/devbook-pro.jpg +0 -0
  103. package/public/images/suzukibento/1015.jpg +0 -0
  104. package/public/images/suzukibento/1025.jpg +0 -0
  105. package/public/images/suzukibento/1043.jpg +0 -0
  106. package/public/images/suzukibento/1059.jpg +0 -0
  107. package/public/images/suzukibento/1062.jpg +0 -0
  108. package/public/images/suzukibento/1084.jpg +0 -0
  109. package/public/images/swish/1469334031218-e382a71b716b.jpg +0 -0
  110. package/public/images/swish/1483985988355-763728e1935b.jpg +0 -0
  111. package/public/images/swish/1485518882345-15568b007407.jpg +0 -0
  112. package/public/images/swish/1490481651871-ab68de25d43d.jpg +0 -0
  113. package/public/images/swish/1496747611176-843222e1e57c.jpg +0 -0
  114. package/public/images/swish/1502716119720-b23a93e5fe1b.jpg +0 -0
  115. package/public/images/swish/1539109136881-3be0616acf4b.jpg +0 -0
  116. package/public/images/swish/1551803091-e20673f15770.jpg +0 -0
  117. package/public/images/swish/1558769132-cb1aea458c5e.jpg +0 -0
  118. package/public/images/swish/1572804013309-59a88b7e92f1.jpg +0 -0
  119. package/public/pulse-ui.css +1080 -22
  120. package/scripts/build.js +72 -18
  121. package/src/agent/checklist.md +22 -2
  122. package/src/agent/commands/verify.md +4 -0
  123. package/src/agent/guide-components.md +146 -8
  124. package/src/agent/guide-design-gallery.md +288 -0
  125. package/src/agent/guide-design-references.md +172 -0
  126. package/src/agent/guide-explore.md +313 -0
  127. package/src/agent/guide-styles.md +212 -11
  128. package/src/agent/guide-templates.md +223 -10
  129. package/src/agent/guide.md +9 -4
  130. package/src/agent/identity.md +2 -1
  131. package/src/agent/skills/verify/SKILL.md +64 -28
  132. package/src/agent/workflow.md +247 -61
  133. package/src/cli/dev.js +39 -14
  134. package/src/cli/fmt.js +113 -0
  135. package/src/cli/index.js +60 -33
  136. package/src/cli/logger.js +145 -0
  137. package/src/cli/scaffold.js +57 -3
  138. package/src/html.js +6 -1
  139. package/src/mcp/server.js +2561 -99
  140. package/src/mcp/validate-worker.js +49 -5
  141. package/src/runtime/ssr.js +4 -1
  142. package/src/server/index.js +29 -5
  143. package/src/spec/schema.js +21 -0
  144. package/src/spec/schema.test.js +28 -0
  145. package/src/testing/html.js +130 -4
  146. package/src/testing/testing.test.js +58 -0
  147. package/src/ui/banner.js +24 -9
  148. package/src/ui/brand-statement.js +58 -0
  149. package/src/ui/button.js +13 -2
  150. package/src/ui/card.js +20 -1
  151. package/src/ui/colophon.js +29 -0
  152. package/src/ui/contact-row.js +41 -0
  153. package/src/ui/cta.js +18 -4
  154. package/src/ui/decorate.js +55 -0
  155. package/src/ui/display-heading.js +44 -0
  156. package/src/ui/drop-cap.js +29 -0
  157. package/src/ui/editorial-hero.js +63 -0
  158. package/src/ui/entry-list.js +36 -0
  159. package/src/ui/feature.js +20 -3
  160. package/src/ui/footer.js +78 -9
  161. package/src/ui/gallery.js +56 -0
  162. package/src/ui/hero.js +68 -18
  163. package/src/ui/image-pair.js +72 -0
  164. package/src/ui/index.js +20 -0
  165. package/src/ui/input.js +1 -0
  166. package/src/ui/marquee.js +48 -0
  167. package/src/ui/nav.js +19 -9
  168. package/src/ui/photo-card.js +44 -0
  169. package/src/ui/product-card.js +65 -0
  170. package/src/ui/pullquote.js +14 -3
  171. package/src/ui/search.js +1 -0
  172. package/src/ui/section-label.js +38 -0
  173. package/src/ui/section.js +1 -1
  174. package/src/ui/service-card.js +47 -0
  175. package/src/ui/stat-grid.js +40 -0
  176. package/src/ui/stat.js +16 -2
  177. package/src/ui/testimonial.js +17 -6
  178. package/src/ui/textarea.js +1 -0
  179. package/src/ui/top-strip.js +47 -0
  180. package/src/ui/trust-list.js +46 -0
  181. 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. Validate the spec
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
- ### 3. Check the dev server
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
- ### 4. Screenshot
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
- ### 5. Lighthouse — desktop
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 2.
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
- ### 6. Lighthouse — mobile
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 2.
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
- ### 7. Performance
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
- ### 8. Console errors
49
+ ### 9. Console errors
40
50
 
41
51
  Use `mcp__chrome-devtools__list_console_messages` — report any errors or unexpected warnings.
42
52
 
43
- ### 9. Code review
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
- Check the spec against the checklist in `.claude/pulse-checklist.md`. Work through every item. Fix anything that fails before proceeding to the next step.
59
+ ### 11. Close extra browser tabs
46
60
 
47
- ### 10. Close the browser
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
- Use `mcp__chrome-devtools__list_pages` to get all open pages, then call `mcp__chrome-devtools__close_page` for every page ID returned. This closes the entire browser, not just the tab. `pageId` must be a number, not a string.
63
+ If there is only one page open, skip this step — there is nothing to close.
50
64
 
51
- ### 11. Write verification stamp
65
+ ### 12. Write verification stamp
52
66
 
53
- Run:
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 records that verify completed successfully. The stop hook checks this stamp — do not skip this step.
73
+ This ensures the stamp is present even if `pulse_review` was not called with a file path.
60
74
 
61
- ### 12. Report
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"]`, and combinations (`button.primary[disabled]`).
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
@@ -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` defines light theme variables under `[data-theme="light"]`, which has **higher specificity than `:root`**. Overrides written only to `:root` will be silently beaten by those rules.
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
- /* WRONG — loses to [data-theme="light"] rules in pulse-ui.css */
324
+ /* Dark theme — in :root */
327
325
  :root {
328
- --color-accent: #e25;
326
+ --accent: #9b8dff;
329
327
  }
330
328
 
331
- /* CORRECT */
329
+ /* Light theme — in [data-theme="light"] */
332
330
  [data-theme="light"] {
333
- --color-accent: #e25;
331
+ --accent: #e25; /* overrides the light default */
334
332
  }
335
333
  ```
336
334
 
337
- If a colour must apply in both themes, set it in both `:root` and `[data-theme="light"]`.
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, no raw colour values |
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 or raw colour values.** A lint hook enforces this. If you need to define or override a colour token, put it in `public/theme.css` and reference it via `var()` in `app.css`.
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 + Lighthouse desktop + mobile | 100/100/100 (Accessibility, Best Practices, SEO) both strategies |
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 Agent | Invoke review — **only after phases 4–6 all pass** | — |
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
- **The Review Agent is always last.** Never invoke it before validate, Lighthouse, and tests all pass.
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"]`, and combinations (`button.primary[disabled]`).
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.12.1
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
+ }