@invisibleloop/pulse 0.20.11 → 0.20.20
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/package.json +6 -5
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +26 -0
- package/scripts/release-version.js +17 -8
- package/scripts/strip-server.test.js +60 -0
- package/src/agent/checklist.md +8 -12
- package/src/agent/commands/pulse-start.md +2 -0
- package/src/agent/commands/verify.md +30 -8
- package/src/agent/guide-components.md +7 -25
- package/src/agent/guide-routing.md +0 -12
- package/src/agent/guide-server.md +102 -1
- package/src/agent/guide-spec.md +8 -1
- package/src/agent/guide-styles.md +23 -0
- package/src/agent/identity.md +9 -0
- package/src/agent/workflow.md +4 -23
- package/src/cli/check-bundles.js +113 -0
- package/src/cli/check-bundles.test.js +137 -0
- package/src/cli/cli.test.js +105 -0
- package/src/cli/dev.js +48 -3
- package/src/cli/diagnose.js +105 -0
- package/src/cli/diagnose.test.js +124 -0
- package/src/cli/discover.js +9 -2
- package/src/cli/index.js +171 -9
- package/src/cli/process-utils.js +57 -0
- package/src/cli/process-utils.test.js +114 -0
- package/src/cli/review.js +391 -0
- package/src/cli/review.test.js +143 -0
- package/src/cli/scaffold.js +9 -4
- package/src/cli/stamp.js +102 -0
- package/src/cli/stamp.test.js +149 -0
- package/src/cli/start.js +15 -2
- package/src/cli/validate.js +184 -0
- package/src/cli/validate.test.js +153 -0
- package/src/mcp/mcp.test.js +261 -30
- package/src/mcp/server.js +207 -515
- package/src/mcp/validate-worker.js +18 -1
- package/src/runtime/index.js +144 -11
- package/src/runtime/runtime.test.js +199 -0
- package/src/runtime/ssr.js +13 -6
- package/src/runtime/ssr.test.js +31 -0
- package/src/server/index.js +162 -9
- package/src/server/server.test.js +225 -2
- package/src/spec/schema.js +1 -1
- package/src/ui/index.js +0 -5
- package/src/ui/ui.test.js +0 -75
- package/types/ui.d.ts +0 -21
- package/.claude/commands/build-page.md +0 -63
- package/.claude/commands/new-doc-page.md +0 -45
- package/.claude/commands/pulse-start.md +0 -11
- package/.claude/commands/pulse-stop.md +0 -5
- package/.claude/commands/verify.md +0 -118
- package/.claude/pulse-checklist.md +0 -189
- package/.github/workflows/ci.yml +0 -25
- package/.github/workflows/publish.yml +0 -45
- package/.pulse/load-reports/home/1773432711417.json +0 -22
- package/.pulse-verified +0 -1
- package/AGENTS.md +0 -697
- package/CLAUDE.md +0 -697
- package/README.md +0 -95
- package/benchmark/public/dist/counter.boot-PO6SNSJZ.js +0 -8
- package/benchmark/public/dist/home.boot-YK6VAEQI.js +0 -6
- package/benchmark/public/dist/manifest.json +0 -5
- package/benchmark/public/dist/runtime-5Y7ESAIB.js +0 -2
- package/benchmark/public/dist/runtime-KO4BHUQ3.js +0 -49
- package/benchmark/results/2026-03-27-09-20.json +0 -188
- package/benchmark/results/2026-03-27-09-21.json +0 -188
- package/benchmark/results/2026-03-27-09-29.json +0 -188
- package/benchmark/results/2026-03-27-10-14.json +0 -188
- package/benchmark/results/2026-03-27-10-18.json +0 -232
- package/benchmark/src/pages/counter.js +0 -37
- package/benchmark/src/pages/home.js +0 -21
- package/docs/.claude/commands/pulse-build.md +0 -7
- package/docs/.claude/commands/pulse-dev.md +0 -7
- package/docs/.claude/commands/pulse-start.md +0 -9
- package/docs/.claude/commands/pulse-stop.md +0 -5
- package/docs/.claude/commands/verify.md +0 -84
- package/docs/.claude/coverage-check.js +0 -196
- package/docs/.claude/pulse-checklist.md +0 -189
- package/docs/public/.pulse-ui-version +0 -1
- package/docs/public/andypixel.css +0 -237
- package/docs/public/bookworms.css +0 -422
- package/docs/public/coutumes.css +0 -561
- package/docs/public/docs.css +0 -2388
- package/docs/public/favicon.svg +0 -4
- package/docs/public/home-brut.css +0 -982
- 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/img/heic1501a.jpg +0 -0
- package/docs/public/img/placeholder-landscape.svg +0 -18
- package/docs/public/img/placeholder-square.svg +0 -14
- package/docs/public/img/placeholder-tall.svg +0 -22
- package/docs/public/journey.css +0 -528
- package/docs/public/menu.js +0 -327
- package/docs/public/pulse-ui.css +0 -3028
- package/docs/public/pulse-ui.js +0 -257
- package/docs/public/retrowheels.css +0 -425
- package/docs/public/shop.css +0 -269
- package/docs/public/sparkys.css +0 -382
- package/docs/public/suzukibento.css +0 -517
- package/docs/public/swish.css +0 -394
- package/docs/public/theme.css +0 -52
- package/docs/public/themes/lumio.css +0 -50
- package/docs/public/themes/observatory.css +0 -104
- package/docs/server.js +0 -161
- package/docs/src/lib/component-page.js +0 -49
- package/docs/src/lib/highlight.js +0 -2
- package/docs/src/lib/layout.js +0 -190
- package/docs/src/lib/metrics-store.js +0 -6
- package/docs/src/lib/nav.js +0 -171
- package/docs/src/lib/stats.js +0 -81
- package/docs/src/pages/accessibility.js +0 -157
- package/docs/src/pages/actions.js +0 -194
- package/docs/src/pages/auth.js +0 -176
- package/docs/src/pages/caching.js +0 -95
- package/docs/src/pages/components/accordion.js +0 -48
- package/docs/src/pages/components/alert.js +0 -35
- package/docs/src/pages/components/app-badge.js +0 -41
- package/docs/src/pages/components/avatar.js +0 -35
- package/docs/src/pages/components/badge.js +0 -36
- package/docs/src/pages/components/breadcrumbs.js +0 -94
- package/docs/src/pages/components/button.js +0 -84
- package/docs/src/pages/components/card.js +0 -225
- package/docs/src/pages/components/charts.js +0 -283
- package/docs/src/pages/components/checkbox.js +0 -129
- package/docs/src/pages/components/cluster.js +0 -47
- package/docs/src/pages/components/container.js +0 -40
- package/docs/src/pages/components/cta.js +0 -54
- package/docs/src/pages/components/divider.js +0 -37
- package/docs/src/pages/components/empty.js +0 -36
- package/docs/src/pages/components/feature.js +0 -60
- package/docs/src/pages/components/fieldset.js +0 -65
- package/docs/src/pages/components/fileupload.js +0 -128
- package/docs/src/pages/components/footer.js +0 -58
- package/docs/src/pages/components/grid.js +0 -169
- package/docs/src/pages/components/heading.js +0 -107
- package/docs/src/pages/components/hero.js +0 -123
- package/docs/src/pages/components/icons.js +0 -410
- package/docs/src/pages/components/image.js +0 -71
- package/docs/src/pages/components/input.js +0 -51
- package/docs/src/pages/components/media.js +0 -51
- package/docs/src/pages/components/modal.js +0 -111
- package/docs/src/pages/components/nav.js +0 -136
- package/docs/src/pages/components/phone-frame.js +0 -74
- package/docs/src/pages/components/progress.js +0 -102
- package/docs/src/pages/components/prose.js +0 -111
- package/docs/src/pages/components/pullquote.js +0 -71
- package/docs/src/pages/components/radio.js +0 -194
- package/docs/src/pages/components/rating.js +0 -106
- package/docs/src/pages/components/search.js +0 -61
- package/docs/src/pages/components/section.js +0 -98
- package/docs/src/pages/components/segmented.js +0 -121
- package/docs/src/pages/components/select.js +0 -45
- package/docs/src/pages/components/slider.js +0 -115
- package/docs/src/pages/components/spinner.js +0 -73
- package/docs/src/pages/components/stack.js +0 -48
- package/docs/src/pages/components/stat.js +0 -55
- package/docs/src/pages/components/stepper.js +0 -66
- package/docs/src/pages/components/table.js +0 -45
- package/docs/src/pages/components/textarea.js +0 -31
- package/docs/src/pages/components/toggle.js +0 -84
- package/docs/src/pages/components/tooltip.js +0 -48
- package/docs/src/pages/components.js +0 -200
- package/docs/src/pages/config.js +0 -193
- package/docs/src/pages/constraints.js +0 -100
- package/docs/src/pages/deploy.js +0 -233
- package/docs/src/pages/error-pages.js +0 -99
- package/docs/src/pages/extending.js +0 -198
- package/docs/src/pages/faq.js +0 -99
- package/docs/src/pages/forms.js +0 -129
- package/docs/src/pages/getting-started.js +0 -147
- package/docs/src/pages/guard.js +0 -120
- package/docs/src/pages/home.js +0 -350
- package/docs/src/pages/how-it-works.js +0 -244
- package/docs/src/pages/hydration.js +0 -108
- package/docs/src/pages/images.js +0 -120
- package/docs/src/pages/markdown.js +0 -156
- package/docs/src/pages/meta.js +0 -193
- package/docs/src/pages/mutations.js +0 -107
- package/docs/src/pages/navigation.js +0 -85
- package/docs/src/pages/performance.js +0 -157
- package/docs/src/pages/persist.js +0 -89
- package/docs/src/pages/project-structure.js +0 -91
- package/docs/src/pages/prompt-examples.js +0 -186
- package/docs/src/pages/raw-responses.js +0 -122
- package/docs/src/pages/routing.js +0 -120
- package/docs/src/pages/server-api.js +0 -291
- package/docs/src/pages/server-data.js +0 -203
- package/docs/src/pages/slash-commands.js +0 -55
- package/docs/src/pages/spec.js +0 -207
- package/docs/src/pages/state.js +0 -102
- package/docs/src/pages/store.js +0 -203
- package/docs/src/pages/streaming.js +0 -107
- package/docs/src/pages/stripe.js +0 -194
- package/docs/src/pages/supabase.js +0 -324
- package/docs/src/pages/templates/andypixel.js +0 -260
- package/docs/src/pages/templates/blog-post.js +0 -226
- package/docs/src/pages/templates/bookworms.js +0 -227
- package/docs/src/pages/templates/coutumes.js +0 -237
- package/docs/src/pages/templates/journey.js +0 -186
- package/docs/src/pages/templates/mobile-app.js +0 -371
- package/docs/src/pages/templates/retrowheels.js +0 -355
- package/docs/src/pages/templates/shop.js +0 -268
- package/docs/src/pages/templates/sparkys.js +0 -264
- package/docs/src/pages/templates/suzukibento.js +0 -200
- package/docs/src/pages/templates/swish.js +0 -313
- package/docs/src/pages/templates.js +0 -534
- package/docs/src/pages/testing.js +0 -199
- package/docs/src/pages/validation.js +0 -138
- package/examples/.claude/commands/pulse-build.md +0 -7
- package/examples/.claude/commands/pulse-dev.md +0 -7
- package/examples/.claude/commands/pulse-start.md +0 -9
- package/examples/.claude/commands/pulse-stop.md +0 -5
- package/examples/.claude/commands/verify.md +0 -84
- package/examples/.claude/coverage-check.js +0 -196
- package/examples/.claude/pulse-checklist.md +0 -189
- package/examples/andypixel.css +0 -237
- package/examples/andypixel.js +0 -258
- package/examples/bookworms.css +0 -422
- package/examples/bookworms.js +0 -225
- package/examples/contact.js +0 -166
- package/examples/counter.js +0 -94
- package/examples/dev.server.js +0 -110
- package/examples/examples.test.js +0 -394
- package/examples/journey.css +0 -528
- package/examples/journey.js +0 -186
- package/examples/pricing.js +0 -262
- package/examples/products.js +0 -190
- package/examples/public/.pulse-ui-version +0 -1
- package/examples/public/pulse-ui.css +0 -3028
- package/examples/public/pulse-ui.js +0 -257
- package/examples/quiz.js +0 -208
- package/examples/retrowheels.css +0 -425
- package/examples/retrowheels.js +0 -353
- package/examples/shared.js +0 -84
- package/examples/suzukibento.css +0 -513
- package/examples/suzukibento.js +0 -200
- package/examples/swish.css +0 -394
- package/examples/swish.js +0 -311
- package/examples/todos.js +0 -162
- package/src/ui/fieldset.js +0 -37
- package/src/ui/phone-frame.js +0 -40
- package/src/ui/search.js +0 -78
- package/src/ui/segmented.js +0 -50
- package/src/ui/tooltip.js +0 -28
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { badge, cluster } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/cluster')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/cluster',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Cluster — Pulse Docs',
|
|
13
|
-
description: 'Cluster component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/cluster',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'cluster',
|
|
21
|
-
description: 'Flex row with wrapping. Groups inline elements horizontally — action buttons, badges, app store badges, stat rows.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
cluster({
|
|
25
|
-
gap: 'sm',
|
|
26
|
-
content: badge({ label: 'Performance' }) + badge({ label: 'Accessibility' }) + badge({ label: 'Zero JS', variant: 'success' }) + badge({ label: 'SSR', variant: 'info' }),
|
|
27
|
-
}),
|
|
28
|
-
`cluster({ gap: 'sm', justify: 'center', content:
|
|
29
|
-
badge({ label: 'Performance' }) +
|
|
30
|
-
badge({ label: 'Zero JS', variant: 'success' }) +
|
|
31
|
-
appBadge({ store: 'apple', href: url })
|
|
32
|
-
})`
|
|
33
|
-
)}
|
|
34
|
-
|
|
35
|
-
${table(
|
|
36
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
37
|
-
[
|
|
38
|
-
['<code>content</code>', 'string (HTML)', '—', 'Raw HTML slot'],
|
|
39
|
-
['<code>gap</code>', 'string', "'md'", "'xs' · 'sm' · 'md' · 'lg'"],
|
|
40
|
-
['<code>justify</code>', 'string', "'start'", "'start' · 'center' · 'end' · 'between'"],
|
|
41
|
-
['<code>align</code>', 'string', "'center'","'start' · 'center' · 'end'"],
|
|
42
|
-
['<code>wrap</code>', 'boolean', 'true', 'Set false to prevent wrapping'],
|
|
43
|
-
]
|
|
44
|
-
)}
|
|
45
|
-
`,
|
|
46
|
-
}),
|
|
47
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { container } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/container')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/container',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Container — Pulse Docs',
|
|
13
|
-
description: 'Container component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/container',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'container',
|
|
21
|
-
description: 'Max-width wrapper with horizontal padding. The four size presets cover the most common layout widths.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
container({ content: '<p style="background:var(--surface-2);border:1px dashed var(--border);padding:1rem;border-radius:6px;text-align:center;color:var(--muted)">Content constrained to 768px</p>', size: 'md' }),
|
|
25
|
-
`container({ size: 'md', content: \`
|
|
26
|
-
\${hero({ title: 'Hello' })}
|
|
27
|
-
\${grid({ cols: 3, content: features })}
|
|
28
|
-
\` })`
|
|
29
|
-
)}
|
|
30
|
-
|
|
31
|
-
${table(
|
|
32
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
33
|
-
[
|
|
34
|
-
['<code>content</code>', 'string (HTML)', '—', 'Raw HTML slot'],
|
|
35
|
-
['<code>size</code>', 'string', "'lg'", "'sm' (640px) · 'md' (768px) · 'lg' (1100px) · 'xl' (1280px)"],
|
|
36
|
-
]
|
|
37
|
-
)}
|
|
38
|
-
`,
|
|
39
|
-
}),
|
|
40
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { cta, button } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/cta')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/cta',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'CTA — Pulse Docs',
|
|
13
|
-
description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
state: {},
|
|
17
|
-
view: () => renderComponentPage({
|
|
18
|
-
currentHref: '/components/cta',
|
|
19
|
-
prev,
|
|
20
|
-
next,
|
|
21
|
-
name: 'cta',
|
|
22
|
-
description: 'Call-to-action block with eyebrow, heading, body text, and an actions slot. Sits inside <code>section()</code> + <code>container()</code> — adds no padding of its own.',
|
|
23
|
-
content: `
|
|
24
|
-
${demo(
|
|
25
|
-
cta({
|
|
26
|
-
eyebrow: 'Get started today',
|
|
27
|
-
title: 'Ready to build?',
|
|
28
|
-
subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
|
|
29
|
-
actions: button({ label: 'Start building →', href: '#', variant: 'primary', size: 'lg' }) +
|
|
30
|
-
button({ label: 'View on GitHub', href: '#', variant: 'ghost', size: 'lg' }),
|
|
31
|
-
}),
|
|
32
|
-
`cta({
|
|
33
|
-
eyebrow: 'Get started today',
|
|
34
|
-
title: 'Ready to build?',
|
|
35
|
-
subtitle: 'One spec file per page. SSR always on. Lighthouse 100 out of the box.',
|
|
36
|
-
actions: button({ label: 'Start building →', href: '/docs', variant: 'primary', size: 'lg' }) +
|
|
37
|
-
button({ label: 'View on GitHub', href: 'https://github.com/...', variant: 'ghost', size: 'lg' }),
|
|
38
|
-
})`
|
|
39
|
-
)}
|
|
40
|
-
|
|
41
|
-
${table(
|
|
42
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
43
|
-
[
|
|
44
|
-
['<code>eyebrow</code>', 'string', '—', 'Small label above the heading'],
|
|
45
|
-
['<code>title</code>', 'string', '—', 'Main heading'],
|
|
46
|
-
['<code>level</code>', 'number', '2', 'Heading tag for the title (1–6). Visual style is unchanged.'],
|
|
47
|
-
['<code>subtitle</code>', 'string', '—', 'Supporting paragraph'],
|
|
48
|
-
['<code>actions</code>', 'string (HTML)', '—', 'Raw HTML slot — typically button() calls'],
|
|
49
|
-
['<code>align</code>', 'string', "'center'", "'center' · 'left'"],
|
|
50
|
-
]
|
|
51
|
-
)}
|
|
52
|
-
`,
|
|
53
|
-
}),
|
|
54
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { divider, stack } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/divider')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/divider',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Divider — Pulse Docs',
|
|
13
|
-
description: 'Divider component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/divider',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'divider',
|
|
21
|
-
description: 'Horizontal rule for visual separation. With a <code>label</code>, the line splits either side of the text — the classic "or" pattern between login options.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
stack({ gap: 'lg', content: divider() + divider({ label: 'or continue with' }) }),
|
|
25
|
-
`divider()
|
|
26
|
-
divider({ label: 'or continue with' })`
|
|
27
|
-
)}
|
|
28
|
-
|
|
29
|
-
${table(
|
|
30
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
31
|
-
[
|
|
32
|
-
['<code>label</code>', 'string', '—', 'Optional centred text — renders as <code><div></code> with role="separator" when provided, <code><hr></code> otherwise'],
|
|
33
|
-
]
|
|
34
|
-
)}
|
|
35
|
-
`,
|
|
36
|
-
}),
|
|
37
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { empty } from '../../../../src/ui/index.js'
|
|
4
|
-
|
|
5
|
-
const { prev, next } = prevNext('/components/empty')
|
|
6
|
-
|
|
7
|
-
export default {
|
|
8
|
-
route: '/components/empty',
|
|
9
|
-
meta: {
|
|
10
|
-
theme: 'light',
|
|
11
|
-
title: 'Empty — Pulse Docs',
|
|
12
|
-
description: 'Empty state component for Pulse UI.',
|
|
13
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
14
|
-
},
|
|
15
|
-
view: () => renderComponentPage({
|
|
16
|
-
currentHref: '/components/empty',
|
|
17
|
-
prev,
|
|
18
|
-
next,
|
|
19
|
-
name: 'empty',
|
|
20
|
-
description: 'Empty state placeholder. Shows a centred title, optional description, and an optional call-to-action when there is nothing to display.',
|
|
21
|
-
content: `
|
|
22
|
-
${demo(
|
|
23
|
-
empty({
|
|
24
|
-
title: 'No results found',
|
|
25
|
-
description: 'Try adjusting your search or clearing the filters.',
|
|
26
|
-
action: { label: 'Clear filters', href: '#', variant: 'secondary' },
|
|
27
|
-
}),
|
|
28
|
-
`empty({
|
|
29
|
-
title: 'No results found',
|
|
30
|
-
description: 'Try adjusting your search or clearing the filters.',
|
|
31
|
-
action: { label: 'Clear filters', href: '/products', variant: 'secondary' },
|
|
32
|
-
})`
|
|
33
|
-
)}
|
|
34
|
-
`,
|
|
35
|
-
}),
|
|
36
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { feature } from '../../../../src/ui/index.js'
|
|
5
|
-
import { iconZap, iconLock, iconPhone } from '../../../../src/ui/icons.js'
|
|
6
|
-
|
|
7
|
-
const { prev, next } = prevNext('/components/feature')
|
|
8
|
-
|
|
9
|
-
export default {
|
|
10
|
-
route: '/components/feature',
|
|
11
|
-
meta: {
|
|
12
|
-
theme: 'light',
|
|
13
|
-
title: 'Feature — Pulse Docs',
|
|
14
|
-
description: 'Feature component for Pulse UI.',
|
|
15
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
16
|
-
},
|
|
17
|
-
view: () => renderComponentPage({
|
|
18
|
-
currentHref: '/components/feature',
|
|
19
|
-
prev,
|
|
20
|
-
next,
|
|
21
|
-
name: 'feature',
|
|
22
|
-
description: 'Icon + title + description block. Compose three or four in a CSS grid for the standard "why us" section.',
|
|
23
|
-
content: `
|
|
24
|
-
${demo(
|
|
25
|
-
`<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
|
|
26
|
-
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.' }) +
|
|
27
|
-
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.' }) +
|
|
28
|
-
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' }) +
|
|
29
|
-
`</div>`,
|
|
30
|
-
`import { iconZap, iconLock, iconPhone } from '@invisibleloop/pulse/ui'
|
|
31
|
-
|
|
32
|
-
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves.' })
|
|
33
|
-
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers, ever.' })
|
|
34
|
-
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.' })`
|
|
35
|
-
)}
|
|
36
|
-
|
|
37
|
-
${demo(
|
|
38
|
-
`<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:2rem">` +
|
|
39
|
-
feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: 'Streaming SSR ships HTML before data resolves. Fast by default.', center: true }) +
|
|
40
|
-
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: 'No trackers. No third-party scripts. Ever.', center: true }) +
|
|
41
|
-
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: 'Full functionality without a connection.', center: true }) +
|
|
42
|
-
`</div>`,
|
|
43
|
-
`feature({ icon: iconZap({ size: 20 }), title: 'Instant load', description: '...', center: true })
|
|
44
|
-
feature({ icon: iconLock({ size: 20 }), title: 'Private by default', description: '...', center: true })
|
|
45
|
-
feature({ icon: iconPhone({ size: 20 }), title: 'Works offline', description: '...', center: true })`
|
|
46
|
-
)}
|
|
47
|
-
|
|
48
|
-
${table(
|
|
49
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
50
|
-
[
|
|
51
|
-
['<code>icon</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG or emoji; displayed in an accent-tinted box'],
|
|
52
|
-
['<code>title</code>', 'string', '—', ''],
|
|
53
|
-
['<code>level</code>', 'number', '3', 'Heading tag for the title (1–6). Visual style is unchanged.'],
|
|
54
|
-
['<code>description</code>', 'string', '—', ''],
|
|
55
|
-
['<code>center</code>', 'boolean', 'false', 'Centre-align the icon, title, and description'],
|
|
56
|
-
]
|
|
57
|
-
)}
|
|
58
|
-
`,
|
|
59
|
-
}),
|
|
60
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { fieldset, input, textarea, grid, button } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/fieldset')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/fieldset',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Fieldset — Pulse Docs',
|
|
13
|
-
description: 'Semantic grouping of related form fields with an accessible legend.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/fieldset',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'fieldset',
|
|
21
|
-
description: 'Wraps related fields in a semantic <code><fieldset></code> with a styled <code><legend></code>. Screen readers announce the legend when focus enters the group — use it whenever fields belong together (address, card details, contact info). Works naturally inside a <code><form data-action="..."></code>.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
`<form class="u-flex u-flex-col u-gap-4">` +
|
|
25
|
-
fieldset({ legend: 'Your details', content:
|
|
26
|
-
grid({ cols: 2, gap: 'md', content:
|
|
27
|
-
input({ name: 'first', label: 'First name', required: true }) +
|
|
28
|
-
input({ name: 'last', label: 'Last name', required: true })
|
|
29
|
-
}) +
|
|
30
|
-
input({ name: 'email', label: 'Email address', type: 'email', required: true })
|
|
31
|
-
}) +
|
|
32
|
-
fieldset({ legend: 'Message', content:
|
|
33
|
-
textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })
|
|
34
|
-
}) +
|
|
35
|
-
button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true }) +
|
|
36
|
-
`</form>`,
|
|
37
|
-
`import { fieldset, grid, input, textarea, button } from '@invisibleloop/pulse/ui'
|
|
38
|
-
|
|
39
|
-
\`<form data-action="submit" class="u-flex u-flex-col u-gap-4">
|
|
40
|
-
\${fieldset({ legend: 'Your details', content: \`
|
|
41
|
-
\${grid({ cols: 2, gap: 'md', content: \`
|
|
42
|
-
\${input({ name: 'first', label: 'First name', required: true })}
|
|
43
|
-
\${input({ name: 'last', label: 'Last name', required: true })}
|
|
44
|
-
\` })}
|
|
45
|
-
\${input({ name: 'email', label: 'Email address', type: 'email', required: true })}
|
|
46
|
-
\` })}
|
|
47
|
-
\${fieldset({ legend: 'Message', content: \`
|
|
48
|
-
\${textarea({ name: 'message', label: 'Tell us about your project', rows: 4, required: true })}
|
|
49
|
-
\` })}
|
|
50
|
-
\${button({ label: 'Send message', type: 'submit', variant: 'primary', fullWidth: true })}
|
|
51
|
-
</form>\``
|
|
52
|
-
)}
|
|
53
|
-
|
|
54
|
-
${table(
|
|
55
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
56
|
-
[
|
|
57
|
-
['<code>legend</code>', 'string', '—', 'Group label — rendered as <code><legend></code>, announced by screen readers on focus'],
|
|
58
|
-
['<code>content</code>', 'string', '—', 'Raw HTML slot — input(), select(), textarea(), grid(), etc.'],
|
|
59
|
-
['<code>gap</code>', 'string', 'md', 'Vertical gap between fields: xs / sm / md / lg'],
|
|
60
|
-
['<code>class</code>', 'string', '—', 'Extra classes on the <code><fieldset></code> element'],
|
|
61
|
-
]
|
|
62
|
-
)}
|
|
63
|
-
`,
|
|
64
|
-
}),
|
|
65
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table, callout } from '../../lib/layout.js'
|
|
4
|
-
import { fileUpload } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/file-upload')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/file-upload',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'File Upload — Pulse Docs',
|
|
13
|
-
description: 'Drag-and-drop file upload zone component for Pulse UI.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
state: { fileName: '', fileSize: '' },
|
|
17
|
-
mutations: {
|
|
18
|
-
setFile: (state, e) => {
|
|
19
|
-
const file = e.target.files[0]
|
|
20
|
-
return file
|
|
21
|
-
? { fileName: file.name, fileSize: (file.size / 1024).toFixed(1) + ' KB' }
|
|
22
|
-
: { fileName: '', fileSize: '' }
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
view: (state) => renderComponentPage({
|
|
26
|
-
currentHref: '/components/file-upload',
|
|
27
|
-
prev,
|
|
28
|
-
next,
|
|
29
|
-
name: 'fileUpload',
|
|
30
|
-
description: 'Drag-and-drop upload zone with a hidden <code><input type="file"></code>. Clicking or dropping files opens the picker. Requires <code>pulse-ui.js</code> for drag highlighting and click-to-open behaviour.',
|
|
31
|
-
content: `
|
|
32
|
-
|
|
33
|
-
<h2 class="doc-h2" id="basic">Basic</h2>
|
|
34
|
-
${demo(
|
|
35
|
-
fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' }) +
|
|
36
|
-
(state.fileName ? `<p class="u-mt-3 u-text-sm">Selected: <strong>${state.fileName}</strong> (${state.fileSize})</p>` : ''),
|
|
37
|
-
`fileUpload({ name: 'file', label: 'Upload file', hint: 'PNG, JPG, PDF up to 10 MB', event: 'change:setFile' })`,
|
|
38
|
-
{ col: true }
|
|
39
|
-
)}
|
|
40
|
-
|
|
41
|
-
<h2 class="doc-h2" id="accept">Accepted file types</h2>
|
|
42
|
-
${demo(
|
|
43
|
-
fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' }),
|
|
44
|
-
`fileUpload({ name: 'avatar', label: 'Profile photo', accept: 'image/*', hint: 'PNG or JPG' })`,
|
|
45
|
-
{ col: true }
|
|
46
|
-
)}
|
|
47
|
-
|
|
48
|
-
<h2 class="doc-h2" id="multiple">Multiple files</h2>
|
|
49
|
-
${demo(
|
|
50
|
-
fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' }),
|
|
51
|
-
`fileUpload({ name: 'docs', label: 'Attachments', multiple: true, hint: 'Select one or more files' })`,
|
|
52
|
-
{ col: true }
|
|
53
|
-
)}
|
|
54
|
-
|
|
55
|
-
<h2 class="doc-h2" id="error">Error state</h2>
|
|
56
|
-
${demo(
|
|
57
|
-
fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' }),
|
|
58
|
-
`fileUpload({ name: 'file', label: 'Upload file', error: 'File must be under 10 MB' })`,
|
|
59
|
-
{ col: true }
|
|
60
|
-
)}
|
|
61
|
-
|
|
62
|
-
<h2 class="doc-h2" id="disabled">Disabled</h2>
|
|
63
|
-
${demo(
|
|
64
|
-
fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' }),
|
|
65
|
-
`fileUpload({ name: 'file', label: 'Upload file', disabled: true, hint: 'Uploads are currently unavailable' })`,
|
|
66
|
-
{ col: true }
|
|
67
|
-
)}
|
|
68
|
-
|
|
69
|
-
<h2 class="doc-h2" id="with-action">Reading the file in an action</h2>
|
|
70
|
-
<p class="u-mb-4 u-text-sm u-color-muted">Use the component inside a <code><form data-action="..."></code>. The file is available in FormData under <code>name</code>.</p>
|
|
71
|
-
${demo(
|
|
72
|
-
fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true }),
|
|
73
|
-
`// view
|
|
74
|
-
\`<form data-action="upload" class="u-flex u-flex-col u-gap-4">
|
|
75
|
-
\${fileUpload({ name: 'attachment', label: 'Attachment', accept: '.pdf,.doc,.docx', required: true })}
|
|
76
|
-
\${button({ label: 'Upload', type: 'submit', variant: 'primary' })}
|
|
77
|
-
</form>\`
|
|
78
|
-
|
|
79
|
-
// action
|
|
80
|
-
upload: {
|
|
81
|
-
onStart: (state) => ({ status: 'loading' }),
|
|
82
|
-
run: async (state, serverState, formData) => {
|
|
83
|
-
const file = formData.get('attachment') // File object
|
|
84
|
-
// process file...
|
|
85
|
-
},
|
|
86
|
-
onSuccess: (state) => ({ status: 'success' }),
|
|
87
|
-
onError: (state, err) => ({ status: 'error', message: err.message }),
|
|
88
|
-
}`,
|
|
89
|
-
{ col: true }
|
|
90
|
-
)}
|
|
91
|
-
|
|
92
|
-
<h2 class="doc-h2" id="with-mutation">Live file name via mutation</h2>
|
|
93
|
-
<p class="u-mb-4 u-text-sm u-color-muted">Use <code>event: 'change:mutationName'</code> to capture the selected filename in state immediately — without a form submission.</p>
|
|
94
|
-
${demo(
|
|
95
|
-
fileUpload({ name: 'photo', label: 'Photo', event: 'change:setFile', accept: 'image/*' }),
|
|
96
|
-
`// state
|
|
97
|
-
{ fileName: '' }
|
|
98
|
-
|
|
99
|
-
// mutation — e.target.files[0] gives the File object
|
|
100
|
-
setFile: (state, e) => ({ fileName: e.target.files[0]?.name ?? '' })
|
|
101
|
-
|
|
102
|
-
// view
|
|
103
|
-
fileUpload({ name: 'photo', label: 'Photo', accept: 'image/*', event: 'change:setFile' })
|
|
104
|
-
\${state.fileName ? \`<p class="u-mt-2 u-text-sm">\${state.fileName}</p>\` : ''}`,
|
|
105
|
-
{ col: true }
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
${callout('note', 'The file object itself cannot be stored in Pulse state — state must be serialisable. Store the filename or a preview URL (via <code>URL.createObjectURL</code>) instead, and upload the file in an action via FormData.')}
|
|
109
|
-
|
|
110
|
-
${table(
|
|
111
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
112
|
-
[
|
|
113
|
-
['<code>name</code>', 'string', '—', 'Field name — file available in FormData under this key'],
|
|
114
|
-
['<code>label</code>', 'string', '—', 'Visible label text'],
|
|
115
|
-
['<code>hint</code>', 'string', '—', 'Helper text, e.g. accepted types and max size'],
|
|
116
|
-
['<code>error</code>', 'string', '—', 'Validation error message'],
|
|
117
|
-
['<code>accept</code>', 'string', '—', 'Accepted MIME types or extensions, e.g. <code>image/*</code> or <code>.pdf,.docx</code>'],
|
|
118
|
-
['<code>multiple</code>', 'boolean', 'false', 'Allow selecting multiple files'],
|
|
119
|
-
['<code>required</code>', 'boolean', 'false', ''],
|
|
120
|
-
['<code>disabled</code>', 'boolean', 'false', ''],
|
|
121
|
-
['<code>event</code>', 'string', '—', 'data-event on the input — use <code>change:mutationName</code> to capture file selection in state'],
|
|
122
|
-
['<code>id</code>', 'string', '—', 'Override the generated <code>id</code>'],
|
|
123
|
-
['<code>class</code>', 'string', '—', ''],
|
|
124
|
-
]
|
|
125
|
-
)}
|
|
126
|
-
`,
|
|
127
|
-
}),
|
|
128
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { renderComponentPage, demo } from '../../lib/component-page.js'
|
|
2
|
-
import { prevNext } from '../../lib/nav.js'
|
|
3
|
-
import { table } from '../../lib/layout.js'
|
|
4
|
-
import { footer } from '../../../../src/ui/index.js'
|
|
5
|
-
|
|
6
|
-
const { prev, next } = prevNext('/components/footer')
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
route: '/components/footer',
|
|
10
|
-
meta: {
|
|
11
|
-
theme: 'light',
|
|
12
|
-
title: 'Footer — Pulse Docs',
|
|
13
|
-
description: 'Accessible site footer with logo slot, navigation links, and legal text.',
|
|
14
|
-
styles: ['/pulse-ui.css', '/theme.css', '/docs.css'],
|
|
15
|
-
},
|
|
16
|
-
view: () => renderComponentPage({
|
|
17
|
-
currentHref: '/components/footer',
|
|
18
|
-
prev,
|
|
19
|
-
next,
|
|
20
|
-
name: 'footer',
|
|
21
|
-
description: 'Accessible site footer with logo slot, navigation links, and legal text. Handles responsive stacking, hover states, and focus styles automatically.',
|
|
22
|
-
content: `
|
|
23
|
-
${demo(
|
|
24
|
-
footer({
|
|
25
|
-
logo: 'MyApp',
|
|
26
|
-
logoHref: '/',
|
|
27
|
-
links: [
|
|
28
|
-
{ label: 'Docs', href: '/docs' },
|
|
29
|
-
{ label: 'Pricing', href: '/pricing' },
|
|
30
|
-
{ label: 'Blog', href: '/blog' },
|
|
31
|
-
{ label: 'GitHub', href: 'https://github.com' },
|
|
32
|
-
],
|
|
33
|
-
legal: '© 2026 MyApp Ltd.',
|
|
34
|
-
}),
|
|
35
|
-
`footer({
|
|
36
|
-
logo: 'MyApp',
|
|
37
|
-
logoHref: '/',
|
|
38
|
-
links: [
|
|
39
|
-
{ label: 'Docs', href: '/docs' },
|
|
40
|
-
{ label: 'Pricing', href: '/pricing' },
|
|
41
|
-
{ label: 'GitHub', href: 'https://github.com' },
|
|
42
|
-
],
|
|
43
|
-
legal: '© 2026 MyApp Ltd.',
|
|
44
|
-
})`
|
|
45
|
-
)}
|
|
46
|
-
|
|
47
|
-
${table(
|
|
48
|
-
['Prop', 'Type', 'Default', 'Description'],
|
|
49
|
-
[
|
|
50
|
-
['<code>logo</code>', 'string (HTML)', '—', 'Raw HTML slot — SVG, img, or text'],
|
|
51
|
-
['<code>logoHref</code>', 'string', "'/'", 'Logo link destination'],
|
|
52
|
-
['<code>links</code>', 'array', '[]', '[{label, href}] — footer navigation links'],
|
|
53
|
-
['<code>legal</code>', 'string', '—', 'Copyright / legal text'],
|
|
54
|
-
]
|
|
55
|
-
)}
|
|
56
|
-
`,
|
|
57
|
-
}),
|
|
58
|
-
}
|