@invisibleloop/pulse 0.14.2 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/verify.md +10 -4
- package/docs/.claude/pulse-checklist.md +5 -3
- package/docs/public/.pulse-ui-version +1 -1
- package/docs/public/docs.css +236 -0
- package/docs/public/home-brut.css +998 -0
- package/docs/public/menu.js +237 -6
- package/docs/public/pulse-ui.css +44 -0
- package/docs/public/theme.css +8 -0
- package/docs/server.js +24 -0
- package/docs/src/lib/layout.js +51 -3
- package/docs/src/pages/getting-started.js +1 -1
- package/docs/src/pages/home.js +231 -312
- package/docs/src/pages/how-it-works.js +67 -12
- package/package.json +1 -1
- package/public/.pulse-ui-version +1 -1
- package/scripts/build.js +127 -21
- package/src/agent/commands/verify.md +2 -6
- package/src/agent/guide-components.md +1 -1
- package/src/agent/guide-examples.md +310 -32
- package/src/agent/skills/verify/SKILL.md +2 -4
- package/src/agent/workflow.md +3 -1
- package/src/cli/dev.js +43 -0
- package/src/mcp/server.js +79 -27
- package/src/mcp/validate-worker.js +6 -0
- package/src/agent/guide.md +0 -1087
package/docs/public/menu.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pulse Docs — Mobile sidebar menu
|
|
2
|
+
* Pulse Docs — Mobile sidebar menu, search, and demo theme toggles
|
|
3
3
|
* Loaded as an external script so it passes the CSP (no unsafe-inline needed).
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
@@ -12,6 +12,225 @@
|
|
|
12
12
|
sidebar.scrollTop = sidebar.scrollTop + (linkRect.top - navRect.top) - sidebar.clientHeight / 2 + link.clientHeight / 2
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// ── Sidebar nav filter ────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function initSidebarSearch(sidebar) {
|
|
18
|
+
var input = sidebar && sidebar.querySelector('#sidebar-search')
|
|
19
|
+
var nav = sidebar && sidebar.querySelector('#sidebar-nav')
|
|
20
|
+
var noResult = sidebar && sidebar.querySelector('.sidebar-no-results')
|
|
21
|
+
if (!input || !nav || input._searchBound) return
|
|
22
|
+
input._searchBound = true
|
|
23
|
+
|
|
24
|
+
function filter(q) {
|
|
25
|
+
var query = q.trim().toLowerCase()
|
|
26
|
+
var sections = nav.querySelectorAll('.nav-section')
|
|
27
|
+
var anyVisible = false
|
|
28
|
+
|
|
29
|
+
if (!query) {
|
|
30
|
+
sections.forEach(function (s) {
|
|
31
|
+
s.hidden = false
|
|
32
|
+
s.querySelectorAll('.nav-link').forEach(function (l) { l.hidden = false })
|
|
33
|
+
})
|
|
34
|
+
if (noResult) noResult.hidden = true
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sections.forEach(function (section) {
|
|
39
|
+
var sectionLinks = section.querySelectorAll('.nav-link')
|
|
40
|
+
var sectionHasMatch = false
|
|
41
|
+
sectionLinks.forEach(function (link) {
|
|
42
|
+
var matches = link.textContent.toLowerCase().includes(query)
|
|
43
|
+
link.hidden = !matches
|
|
44
|
+
if (matches) { sectionHasMatch = true; anyVisible = true }
|
|
45
|
+
})
|
|
46
|
+
section.hidden = !sectionHasMatch
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (noResult) noResult.hidden = anyVisible
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
input.addEventListener('input', function () { filter(this.value) })
|
|
53
|
+
|
|
54
|
+
input.addEventListener('keydown', function (e) {
|
|
55
|
+
if (e.key === 'Escape') {
|
|
56
|
+
input.value = ''
|
|
57
|
+
filter('')
|
|
58
|
+
input.blur()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Full-text search overlay ──────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
var _searchIndex = null
|
|
66
|
+
|
|
67
|
+
function loadSearchIndex(cb) {
|
|
68
|
+
if (_searchIndex) { cb(_searchIndex); return }
|
|
69
|
+
fetch('/search-index.json')
|
|
70
|
+
.then(function (r) { return r.json() })
|
|
71
|
+
.then(function (data) { _searchIndex = data; cb(data) })
|
|
72
|
+
.catch(function () { cb([]) })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function searchIndex(query, index) {
|
|
76
|
+
var q = query.trim().toLowerCase()
|
|
77
|
+
if (!q) return []
|
|
78
|
+
return index.filter(function (item) {
|
|
79
|
+
return item.title.toLowerCase().includes(q) ||
|
|
80
|
+
item.section.toLowerCase().includes(q) ||
|
|
81
|
+
(item.body && item.body.toLowerCase().includes(q))
|
|
82
|
+
}).slice(0, 12)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function highlight(text, query) {
|
|
86
|
+
if (!query) return text
|
|
87
|
+
var re = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi')
|
|
88
|
+
return text.replace(re, '<mark>$1</mark>')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function snippet(body, query) {
|
|
92
|
+
if (!body || !query) return ''
|
|
93
|
+
var idx = body.toLowerCase().indexOf(query.toLowerCase())
|
|
94
|
+
if (idx === -1) return ''
|
|
95
|
+
var start = Math.max(0, idx - 40)
|
|
96
|
+
var end = Math.min(body.length, idx + query.length + 80)
|
|
97
|
+
var s = (start > 0 ? '…' : '') + body.slice(start, end) + (end < body.length ? '…' : '')
|
|
98
|
+
return highlight(s, query)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderResults(results, query, container, selectedIdx) {
|
|
102
|
+
while (container.firstChild) container.removeChild(container.firstChild)
|
|
103
|
+
|
|
104
|
+
if (!results.length) {
|
|
105
|
+
var empty = document.createElement('p')
|
|
106
|
+
empty.className = 'search-overlay-empty'
|
|
107
|
+
empty.textContent = 'No results for '
|
|
108
|
+
var strong = document.createElement('strong')
|
|
109
|
+
strong.textContent = query
|
|
110
|
+
empty.appendChild(strong)
|
|
111
|
+
container.appendChild(empty)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
results.forEach(function (item, i) {
|
|
116
|
+
var a = document.createElement('a')
|
|
117
|
+
a.href = item.href
|
|
118
|
+
a.className = 'search-result' + (i === selectedIdx ? ' search-result--selected' : '')
|
|
119
|
+
a.setAttribute('role', 'option')
|
|
120
|
+
if (i === selectedIdx) a.setAttribute('aria-selected', 'true')
|
|
121
|
+
|
|
122
|
+
var meta = document.createElement('div')
|
|
123
|
+
meta.className = 'search-result-meta'
|
|
124
|
+
var section = document.createElement('span')
|
|
125
|
+
section.className = 'search-result-section'
|
|
126
|
+
section.textContent = item.section
|
|
127
|
+
meta.appendChild(section)
|
|
128
|
+
a.appendChild(meta)
|
|
129
|
+
|
|
130
|
+
var title = document.createElement('div')
|
|
131
|
+
title.className = 'search-result-title'
|
|
132
|
+
setHighlighted(title, item.title, query)
|
|
133
|
+
a.appendChild(title)
|
|
134
|
+
|
|
135
|
+
var snip = snippet(item.body, query)
|
|
136
|
+
if (snip) {
|
|
137
|
+
var snipEl = document.createElement('div')
|
|
138
|
+
snipEl.className = 'search-result-snip'
|
|
139
|
+
setHighlighted(snipEl, snip, null) // snip already has highlight markers stripped
|
|
140
|
+
a.appendChild(snipEl)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
container.appendChild(a)
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function setHighlighted(el, text, query) {
|
|
148
|
+
if (!query) { el.textContent = text; return }
|
|
149
|
+
var lower = text.toLowerCase()
|
|
150
|
+
var q = query.toLowerCase()
|
|
151
|
+
var idx = lower.indexOf(q)
|
|
152
|
+
if (idx === -1) { el.textContent = text; return }
|
|
153
|
+
var before = document.createTextNode(text.slice(0, idx))
|
|
154
|
+
var mark = document.createElement('mark')
|
|
155
|
+
mark.textContent = text.slice(idx, idx + query.length)
|
|
156
|
+
var after = document.createTextNode(text.slice(idx + query.length))
|
|
157
|
+
el.appendChild(before)
|
|
158
|
+
el.appendChild(mark)
|
|
159
|
+
el.appendChild(after)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function initSearchOverlay() {
|
|
163
|
+
var overlay = document.getElementById('search-overlay')
|
|
164
|
+
var input = document.getElementById('search-overlay-input')
|
|
165
|
+
var results = document.getElementById('search-overlay-results')
|
|
166
|
+
var fullBtns = document.querySelectorAll('.sidebar-search-full')
|
|
167
|
+
if (!overlay || !input || !results) return
|
|
168
|
+
|
|
169
|
+
var selectedIdx = -1
|
|
170
|
+
var currentResults = []
|
|
171
|
+
var currentQuery = ''
|
|
172
|
+
|
|
173
|
+
function open() {
|
|
174
|
+
overlay.hidden = false
|
|
175
|
+
document.body.style.overflow = 'hidden'
|
|
176
|
+
setTimeout(function () { input.focus() }, 50)
|
|
177
|
+
selectedIdx = -1
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function close() {
|
|
181
|
+
overlay.hidden = true
|
|
182
|
+
document.body.style.overflow = ''
|
|
183
|
+
input.value = ''
|
|
184
|
+
results.innerHTML = ''
|
|
185
|
+
currentResults = []
|
|
186
|
+
currentQuery = ''
|
|
187
|
+
selectedIdx = -1
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function navigate(dir) {
|
|
191
|
+
selectedIdx = Math.max(-1, Math.min(currentResults.length - 1, selectedIdx + dir))
|
|
192
|
+
renderResults(currentResults, currentQuery, results, selectedIdx)
|
|
193
|
+
var sel = results.querySelector('.search-result--selected')
|
|
194
|
+
if (sel) sel.scrollIntoView({ block: 'nearest' })
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fullBtns.forEach(function (btn) { btn.addEventListener('click', open) })
|
|
198
|
+
|
|
199
|
+
overlay.querySelector('.search-overlay-backdrop').addEventListener('click', close)
|
|
200
|
+
overlay.querySelector('.search-overlay-close').addEventListener('click', close)
|
|
201
|
+
|
|
202
|
+
input.addEventListener('input', function () {
|
|
203
|
+
var q = this.value.trim()
|
|
204
|
+
currentQuery = q
|
|
205
|
+
selectedIdx = -1
|
|
206
|
+
if (!q) { results.innerHTML = ''; currentResults = []; return }
|
|
207
|
+
loadSearchIndex(function (index) {
|
|
208
|
+
currentResults = searchIndex(q, index)
|
|
209
|
+
renderResults(currentResults, q, results, selectedIdx)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
input.addEventListener('keydown', function (e) {
|
|
214
|
+
if (e.key === 'Escape') { close(); return }
|
|
215
|
+
if (e.key === 'ArrowDown') { e.preventDefault(); navigate(1); return }
|
|
216
|
+
if (e.key === 'ArrowUp') { e.preventDefault(); navigate(-1); return }
|
|
217
|
+
if (e.key === 'Enter') {
|
|
218
|
+
if (selectedIdx >= 0 && currentResults[selectedIdx]) {
|
|
219
|
+
window.location.href = currentResults[selectedIdx].href
|
|
220
|
+
close()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
document.addEventListener('keydown', function (e) {
|
|
226
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
227
|
+
e.preventDefault()
|
|
228
|
+
overlay.hidden ? open() : close()
|
|
229
|
+
}
|
|
230
|
+
if (e.key === 'Escape' && !overlay.hidden) close()
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
15
234
|
// ── Bind sidebar, mobile menu, and centre the active link ─────────────────
|
|
16
235
|
// Called on first load and after every client-side navigation, because
|
|
17
236
|
// #pulse-root is replaced on navigate — all element references go stale.
|
|
@@ -49,13 +268,25 @@
|
|
|
49
268
|
// Centre the active link
|
|
50
269
|
var activeLink = sidebar && sidebar.querySelector('.nav-link.active')
|
|
51
270
|
if (activeLink) centerNavLink(sidebar, activeLink)
|
|
271
|
+
|
|
272
|
+
// Sidebar filter
|
|
273
|
+
initSidebarSearch(sidebar)
|
|
274
|
+
|
|
275
|
+
// Full-text search overlay (only bind once — overlay persists across navigations)
|
|
276
|
+
if (!document.getElementById('search-overlay')?._searchBound) {
|
|
277
|
+
initSearchOverlay()
|
|
278
|
+
var o = document.getElementById('search-overlay')
|
|
279
|
+
if (o) o._searchBound = true
|
|
280
|
+
}
|
|
52
281
|
}
|
|
53
282
|
|
|
54
|
-
// On initial load —
|
|
55
|
-
if (
|
|
283
|
+
// On initial load — defer slightly to ensure layout paint before scrolling.
|
|
284
|
+
// Use both paths: if load already fired (common with defer scripts), run now;
|
|
285
|
+
// otherwise wait for it.
|
|
286
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
56
287
|
setTimeout(init, 50)
|
|
57
288
|
} else {
|
|
58
|
-
window.addEventListener('load', init)
|
|
289
|
+
window.addEventListener('load', function () { setTimeout(init, 50) })
|
|
59
290
|
}
|
|
60
291
|
|
|
61
292
|
// After client-side navigation — re-bind everything against the fresh DOM
|
|
@@ -88,9 +319,9 @@
|
|
|
88
319
|
|
|
89
320
|
document.addEventListener('pulse:navigate', bindDemoControls)
|
|
90
321
|
|
|
91
|
-
if (document.readyState === 'complete') {
|
|
322
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
92
323
|
setTimeout(bindDemoControls, 50)
|
|
93
324
|
} else {
|
|
94
|
-
window.addEventListener('load', bindDemoControls)
|
|
325
|
+
window.addEventListener('load', function () { setTimeout(bindDemoControls, 50) })
|
|
95
326
|
}
|
|
96
327
|
})()
|
package/docs/public/pulse-ui.css
CHANGED
|
@@ -150,6 +150,20 @@ html, body {
|
|
|
150
150
|
color: var(--ui-text);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/* ghost-light — use on dark section backgrounds (e.g. navy hero, section--dark).
|
|
154
|
+
Renders with light/white text and a subtle light border so it remains legible
|
|
155
|
+
regardless of whether the surrounding theme is light or dark. */
|
|
156
|
+
.ui-btn--ghost-light {
|
|
157
|
+
background: transparent;
|
|
158
|
+
color: rgba(255, 255, 255, 0.9);
|
|
159
|
+
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
160
|
+
}
|
|
161
|
+
.ui-btn--ghost-light:hover:not(.ui-btn--disabled):not([disabled]) {
|
|
162
|
+
background: rgba(255, 255, 255, 0.1);
|
|
163
|
+
border-color: rgba(255, 255, 255, 0.65);
|
|
164
|
+
color: #ffffff;
|
|
165
|
+
}
|
|
166
|
+
|
|
153
167
|
.ui-btn--danger {
|
|
154
168
|
background: var(--ui-red-dim);
|
|
155
169
|
color: var(--ui-red);
|
|
@@ -682,6 +696,36 @@ html, body {
|
|
|
682
696
|
.ui-section--alt { background: var(--ui-surface, var(--surface, #111116)); }
|
|
683
697
|
.ui-section--dark { background: var(--ui-surface-2, var(--surface-2, #18181f)); }
|
|
684
698
|
|
|
699
|
+
/* In light theme, --ui-surface-2 resolves to a light grey — not "dark".
|
|
700
|
+
Override so section--dark always renders a genuinely dark background that
|
|
701
|
+
contrasts with the page, regardless of the active theme. */
|
|
702
|
+
[data-theme="light"] .ui-section--dark,
|
|
703
|
+
.ui-theme-light .ui-section--dark {
|
|
704
|
+
background: var(--ui-dark-bg, #1a1a2e);
|
|
705
|
+
color: var(--ui-dark-text, #e2e2ea);
|
|
706
|
+
}
|
|
707
|
+
/* Ensure text children inherit the light colour in the dark section */
|
|
708
|
+
[data-theme="light"] .ui-section--dark .ui-section-title,
|
|
709
|
+
[data-theme="light"] .ui-section--dark .ui-section-eyebrow,
|
|
710
|
+
[data-theme="light"] .ui-section--dark .ui-section-subtitle,
|
|
711
|
+
.ui-theme-light .ui-section--dark .ui-section-title,
|
|
712
|
+
.ui-theme-light .ui-section--dark .ui-section-eyebrow,
|
|
713
|
+
.ui-theme-light .ui-section--dark .ui-section-subtitle {
|
|
714
|
+
color: inherit;
|
|
715
|
+
}
|
|
716
|
+
/* Ghost button inside a dark section on a light-theme page */
|
|
717
|
+
[data-theme="light"] .ui-section--dark .ui-btn--ghost,
|
|
718
|
+
.ui-theme-light .ui-section--dark .ui-btn--ghost {
|
|
719
|
+
color: var(--ui-dark-text, #e2e2ea);
|
|
720
|
+
border-color: rgba(226, 226, 234, 0.35);
|
|
721
|
+
}
|
|
722
|
+
[data-theme="light"] .ui-section--dark .ui-btn--ghost:hover:not(.ui-btn--disabled):not([disabled]),
|
|
723
|
+
.ui-theme-light .ui-section--dark .ui-btn--ghost:hover:not(.ui-btn--disabled):not([disabled]) {
|
|
724
|
+
background: rgba(226, 226, 234, 0.1);
|
|
725
|
+
border-color: rgba(226, 226, 234, 0.65);
|
|
726
|
+
color: var(--ui-dark-text, #e2e2ea);
|
|
727
|
+
}
|
|
728
|
+
|
|
685
729
|
/* diagonal — tapered bottom edge; follow with a section that starts flush */
|
|
686
730
|
.ui-section--diagonal {
|
|
687
731
|
background: var(--ui-surface, var(--surface, #111116));
|
package/docs/server.js
CHANGED
|
@@ -3,8 +3,17 @@ import { createServer } from '../src/server/index.js'
|
|
|
3
3
|
import { initLayoutManifest } from './src/lib/layout.js'
|
|
4
4
|
import { metrics } from './src/lib/stats.js'
|
|
5
5
|
import { metricsStore } from './src/lib/metrics-store.js'
|
|
6
|
+
import { NAV } from './src/lib/nav.js'
|
|
6
7
|
metricsStore.current = metrics
|
|
7
8
|
|
|
9
|
+
// Build search index once at startup from nav structure
|
|
10
|
+
const searchIndex = JSON.stringify(
|
|
11
|
+
NAV.flatMap(({ section, items }) =>
|
|
12
|
+
items.map(item => ({ title: item.label, href: item.href, section, body: '' }))
|
|
13
|
+
)
|
|
14
|
+
)
|
|
15
|
+
const searchIndexEtag = `"${Buffer.from(searchIndex).length}-search"`
|
|
16
|
+
|
|
8
17
|
// Populate hashed asset paths in layout.js before any page renders
|
|
9
18
|
try {
|
|
10
19
|
const raw = fs.readFileSync(new URL('./public/dist/manifest.json', import.meta.url), 'utf8')
|
|
@@ -142,5 +151,20 @@ await createServer(
|
|
|
142
151
|
'style-src': ['https://fonts.googleapis.com'],
|
|
143
152
|
'font-src': ['https://fonts.gstatic.com'],
|
|
144
153
|
},
|
|
154
|
+
onRequest(req, res) {
|
|
155
|
+
if (req.url !== '/search-index.json') return
|
|
156
|
+
if (req.headers['if-none-match'] === searchIndexEtag) {
|
|
157
|
+
res.writeHead(304)
|
|
158
|
+
res.end()
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
res.writeHead(200, {
|
|
162
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
163
|
+
'Cache-Control': 'public, max-age=3600',
|
|
164
|
+
'ETag': searchIndexEtag,
|
|
165
|
+
})
|
|
166
|
+
res.end(searchIndex)
|
|
167
|
+
return false
|
|
168
|
+
},
|
|
145
169
|
}
|
|
146
170
|
)
|
package/docs/src/lib/layout.js
CHANGED
|
@@ -34,7 +34,7 @@ function sidebar(currentHref) {
|
|
|
34
34
|
}).join('')
|
|
35
35
|
|
|
36
36
|
return `
|
|
37
|
-
<aside class="docs-sidebar" aria-label="Documentation navigation">
|
|
37
|
+
<aside class="docs-sidebar" id="docs-sidebar" aria-label="Documentation navigation">
|
|
38
38
|
<div class="sidebar-logo">
|
|
39
39
|
<a href="/" class="logo-link" aria-label="Pulse home">
|
|
40
40
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
@@ -44,10 +44,58 @@ function sidebar(currentHref) {
|
|
|
44
44
|
</a>
|
|
45
45
|
<span class="version-badge">v${version}</span>
|
|
46
46
|
</div>
|
|
47
|
-
<
|
|
47
|
+
<div class="sidebar-search-wrap">
|
|
48
|
+
<svg class="sidebar-search-icon" width="14" height="14" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
49
|
+
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/>
|
|
50
|
+
</svg>
|
|
51
|
+
<input
|
|
52
|
+
type="search"
|
|
53
|
+
id="sidebar-search"
|
|
54
|
+
class="sidebar-search"
|
|
55
|
+
placeholder="Filter pages…"
|
|
56
|
+
aria-label="Filter documentation pages"
|
|
57
|
+
autocomplete="off"
|
|
58
|
+
spellcheck="false"
|
|
59
|
+
>
|
|
60
|
+
<button class="sidebar-search-full" aria-label="Search all docs content" title="Search all docs">
|
|
61
|
+
<svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
62
|
+
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h7a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
|
63
|
+
</svg>
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
<nav class="sidebar-nav" id="sidebar-nav">
|
|
48
67
|
${sections}
|
|
68
|
+
<p class="sidebar-no-results" hidden>No pages found</p>
|
|
49
69
|
</nav>
|
|
50
|
-
</aside
|
|
70
|
+
</aside>
|
|
71
|
+
<div class="search-overlay" id="search-overlay" role="dialog" aria-modal="true" aria-label="Search documentation" hidden>
|
|
72
|
+
<div class="search-overlay-backdrop"></div>
|
|
73
|
+
<div class="search-overlay-panel">
|
|
74
|
+
<div class="search-overlay-header">
|
|
75
|
+
<div class="search-overlay-input-wrap">
|
|
76
|
+
<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
77
|
+
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"/>
|
|
78
|
+
</svg>
|
|
79
|
+
<input
|
|
80
|
+
type="search"
|
|
81
|
+
id="search-overlay-input"
|
|
82
|
+
class="search-overlay-input"
|
|
83
|
+
placeholder="Search all docs…"
|
|
84
|
+
aria-label="Search all documentation"
|
|
85
|
+
autocomplete="off"
|
|
86
|
+
spellcheck="false"
|
|
87
|
+
>
|
|
88
|
+
</div>
|
|
89
|
+
<button class="search-overlay-close" aria-label="Close search">ESC</button>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="search-overlay-results" id="search-overlay-results" role="listbox" aria-label="Search results"></div>
|
|
92
|
+
<div class="search-overlay-footer">
|
|
93
|
+
<span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
|
|
94
|
+
<span><kbd>↵</kbd> open</span>
|
|
95
|
+
<span><kbd>ESC</kbd> close</span>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>`
|
|
51
99
|
}
|
|
52
100
|
|
|
53
101
|
function prevNextBar(prev, next) {
|
|
@@ -22,7 +22,7 @@ export default {
|
|
|
22
22
|
${section('requirements', 'Requirements')}
|
|
23
23
|
<ul>
|
|
24
24
|
<li><strong>Node.js 22 or later</strong> — <a href="https://nodejs.org" target="_blank" rel="noopener" aria-label="nodejs.org (opens in new tab)">nodejs.org</a></li>
|
|
25
|
-
<li><strong>Google Chrome</strong> — used by the agent for screenshots and Lighthouse audits — <a href="https://www.google.com/chrome" target="_blank" rel="noopener" aria-label="
|
|
25
|
+
<li><strong>Google Chrome</strong> — used by the agent for screenshots and Lighthouse audits — <a href="https://www.google.com/chrome" target="_blank" rel="noopener" aria-label="google.com/chrome (opens in new tab)">google.com/chrome</a></li>
|
|
26
26
|
<li><strong>An AI agent</strong> — Claude Code or GitHub Copilot CLI (pick one):</li>
|
|
27
27
|
</ul>
|
|
28
28
|
<ul>
|