@iamdangavin/claude-skill-vitepress-docs 3.0.2 → 3.1.0
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.
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitedocs:capture
|
|
3
|
+
description: Capture screenshots and record interaction videos for VitePress docs. Uploads videos to GitHub Releases.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Bash
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- AskUserQuestion
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# vitedocs:capture
|
|
15
|
+
|
|
16
|
+
This mode reads `.vitepress/docs-manifest.json`. Run `/vitedocs:generate` first if no manifest exists yet.
|
|
17
|
+
|
|
18
|
+
Handles two capture types from the manifest:
|
|
19
|
+
- **screenshot** (`"type": "screenshot"`) — static PNG via Playwright
|
|
20
|
+
- **video** (`"type": "video"`) — recorded interaction with step captions, uploaded to GitHub Releases
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step 1 — Gather capture details
|
|
25
|
+
|
|
26
|
+
**Q1 — Source:**
|
|
27
|
+
- header: "Capture source"
|
|
28
|
+
- question: "Where should I capture from?"
|
|
29
|
+
- options:
|
|
30
|
+
- "Local dev server — I'll capture from a running server"
|
|
31
|
+
- "Deployed URL — give me the base URL"
|
|
32
|
+
|
|
33
|
+
If deployed: ask as plain text — "What is the base URL?"
|
|
34
|
+
|
|
35
|
+
**Q2 — Stack type** (only if local):
|
|
36
|
+
- header: "Project type"
|
|
37
|
+
- question: "What type of project is this?"
|
|
38
|
+
- options:
|
|
39
|
+
- "Node/npm — you can optionally start the server for me"
|
|
40
|
+
- "WordPress, PHP, or other non-Node stack"
|
|
41
|
+
- "Static files"
|
|
42
|
+
|
|
43
|
+
**Q3 — Server status** (only if local Node):
|
|
44
|
+
- header: "Dev server"
|
|
45
|
+
- question: "Is the dev server already running?"
|
|
46
|
+
- options:
|
|
47
|
+
- "Already running"
|
|
48
|
+
- "Not running — please start it for me"
|
|
49
|
+
|
|
50
|
+
If starting: ask as plain text — "What command starts it? And what URL/port does it run on?"
|
|
51
|
+
|
|
52
|
+
**Q4 — Authentication:**
|
|
53
|
+
- header: "Login required?"
|
|
54
|
+
- question: "Does reaching the target screens require login?"
|
|
55
|
+
- options:
|
|
56
|
+
- "No — all target screens are public"
|
|
57
|
+
- "Yes — I'll provide credentials"
|
|
58
|
+
|
|
59
|
+
If yes: ask as plain text — "Please provide the username and password. ⚠️ Credentials are used only to drive the browser in this session — they will NEVER be written to any file, the manifest, or anywhere outside this conversation."
|
|
60
|
+
|
|
61
|
+
**Q5 — Scope:**
|
|
62
|
+
- header: "Capture scope"
|
|
63
|
+
- question: "Which items should I capture?"
|
|
64
|
+
- options:
|
|
65
|
+
- "All placeholders from the manifest (screenshots + videos)"
|
|
66
|
+
- "Screenshots only"
|
|
67
|
+
- "Videos only"
|
|
68
|
+
- "Specific items — I'll tell you which"
|
|
69
|
+
|
|
70
|
+
If specific: ask as plain text — "Which pages or capture names?"
|
|
71
|
+
|
|
72
|
+
**Q6 — GitHub repo** (ask only if manifest contains any video entries in scope):
|
|
73
|
+
- header: "GitHub repo"
|
|
74
|
+
- question: "Videos are uploaded to GitHub Releases. What is the GitHub repo?"
|
|
75
|
+
- options:
|
|
76
|
+
- "Same repo as the docs"
|
|
77
|
+
- "Different repo — I'll type it"
|
|
78
|
+
|
|
79
|
+
If different: ask as plain text — "What is the repo? (full URL or `owner/repo`)"
|
|
80
|
+
|
|
81
|
+
Wait for all answers before proceeding.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Step 2 — Find all pending captures
|
|
86
|
+
|
|
87
|
+
Read `.vitepress/docs-manifest.json`. Filter to:
|
|
88
|
+
- Screenshots where `"placeholder": true`
|
|
89
|
+
- Videos where `"videoUrl": ""`
|
|
90
|
+
|
|
91
|
+
Group by type. If no manifest exists, scan all markdown files for `VideoDemo` and image references in `public/screenshots/` or `public/videos/`.
|
|
92
|
+
|
|
93
|
+
Report what was found:
|
|
94
|
+
```
|
|
95
|
+
Found 4 pending captures:
|
|
96
|
+
Screenshots (2): timer-main.png, settings-overview.png
|
|
97
|
+
Videos (2): timer-start.webm, form-submit.webm
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Step 3 — Capture screenshots
|
|
103
|
+
|
|
104
|
+
Run ALL screenshots as a single batched heredoc — one bash approval covers the entire set.
|
|
105
|
+
|
|
106
|
+
**⛔ Credential rule — NEVER write credentials to any file.** All scripts run as inline bash heredocs. Credentials are passed directly as inline values within the heredoc and exist only in memory.
|
|
107
|
+
|
|
108
|
+
**For pages requiring auth:** drive a login flow before navigating to target URLs. If no credentials were given, skip auth-gated items and list them in the summary.
|
|
109
|
+
|
|
110
|
+
Batch all screenshots into one heredoc:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
node --input-type=module << 'SCRIPT'
|
|
114
|
+
import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
|
|
115
|
+
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
|
|
116
|
+
|
|
117
|
+
const captures = [
|
|
118
|
+
{ url: 'BASE_URL/PATH_1', path: 'OUTPUT_PATH_1', settle: SETTLE_MS },
|
|
119
|
+
{ url: 'BASE_URL/PATH_2', path: 'OUTPUT_PATH_2', settle: SETTLE_MS },
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const cap of captures) {
|
|
123
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
124
|
+
await page.goto(cap.url, { waitUntil: 'load', timeout: 60000 });
|
|
125
|
+
await page.waitForTimeout(cap.settle);
|
|
126
|
+
await page.screenshot({ path: cap.path, fullPage: false });
|
|
127
|
+
await page.close();
|
|
128
|
+
console.log('Captured', cap.path);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await browser.close();
|
|
132
|
+
SCRIPT
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Adjust `settle` based on stack: WordPress/non-SPA → `500`, Next.js/SPA → `2000`–`4000`.
|
|
136
|
+
|
|
137
|
+
After the batch completes, display each captured image inline with the Read tool for verification.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Step 4 — Capture videos
|
|
142
|
+
|
|
143
|
+
Capture each video one at a time (each needs its own heredoc due to the unique step sequence).
|
|
144
|
+
|
|
145
|
+
For each video entry, run a heredoc that:
|
|
146
|
+
1. Records the screen with `recordVideo`
|
|
147
|
+
2. Executes the steps from the manifest, tracking elapsed time for each
|
|
148
|
+
3. Captures a poster PNG at the final step
|
|
149
|
+
4. Moves the video from the temp dir to the final output path
|
|
150
|
+
5. Generates the `.vtt` caption file (written to disk — goes in git)
|
|
151
|
+
6. Prints the step timings as JSON
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
node --input-type=module << 'SCRIPT'
|
|
155
|
+
import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
|
|
156
|
+
import { writeFileSync, mkdirSync, readdirSync, renameSync } from 'fs';
|
|
157
|
+
import { dirname } from 'path';
|
|
158
|
+
|
|
159
|
+
const VIDEO_TMP = 'DOCS_FOLDER/public/videos/.tmp';
|
|
160
|
+
const VIDEO_OUT = 'DOCS_FOLDER/public/videos/CAPTURE_NAME.webm';
|
|
161
|
+
const VTT_OUT = 'DOCS_FOLDER/public/videos/CAPTURE_NAME.vtt';
|
|
162
|
+
const POSTER_OUT = 'DOCS_FOLDER/public/screenshots/CAPTURE_NAME-poster.png';
|
|
163
|
+
const BASE_URL = 'BASE_URL';
|
|
164
|
+
const CAPTURE_URL = 'CAPTURE_PATH';
|
|
165
|
+
|
|
166
|
+
mkdirSync(VIDEO_TMP, { recursive: true });
|
|
167
|
+
mkdirSync(dirname(VIDEO_OUT), { recursive: true });
|
|
168
|
+
mkdirSync(dirname(POSTER_OUT), { recursive: true });
|
|
169
|
+
|
|
170
|
+
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
|
|
171
|
+
const context = await browser.newContext({
|
|
172
|
+
viewport: { width: 1440, height: 900 },
|
|
173
|
+
recordVideo: { dir: VIDEO_TMP, size: { width: 1440, height: 900 } }
|
|
174
|
+
});
|
|
175
|
+
const page = await context.newPage();
|
|
176
|
+
|
|
177
|
+
const t0 = Date.now();
|
|
178
|
+
const elapsed = () => (Date.now() - t0) / 1000;
|
|
179
|
+
const stepTimings = [];
|
|
180
|
+
|
|
181
|
+
await page.goto(`${BASE_URL}${CAPTURE_URL}`, { waitUntil: 'load', timeout: 60000 });
|
|
182
|
+
await page.waitForTimeout(800);
|
|
183
|
+
|
|
184
|
+
// STEPS — substituted from manifest steps array:
|
|
185
|
+
// stepTimings.push({ caption: 'CAPTION', time: elapsed() }); await page.click('SELECTOR'); await page.waitForTimeout(MS);
|
|
186
|
+
// stepTimings.push({ caption: 'CAPTION', time: elapsed() }); await page.waitForTimeout(MS);
|
|
187
|
+
|
|
188
|
+
await page.screenshot({ path: POSTER_OUT });
|
|
189
|
+
const duration = elapsed();
|
|
190
|
+
|
|
191
|
+
await context.close();
|
|
192
|
+
await browser.close();
|
|
193
|
+
|
|
194
|
+
// Move recorded video to final path
|
|
195
|
+
const files = readdirSync(VIDEO_TMP).filter(f => f.endsWith('.webm'));
|
|
196
|
+
if (files.length) renameSync(`${VIDEO_TMP}/${files[0]}`, VIDEO_OUT);
|
|
197
|
+
|
|
198
|
+
// Generate VTT
|
|
199
|
+
const toVTT = s => {
|
|
200
|
+
const m = Math.floor(s / 60);
|
|
201
|
+
const sec = (s % 60).toFixed(3).padStart(6, '0');
|
|
202
|
+
return `${String(m).padStart(2,'0')}:${sec}`;
|
|
203
|
+
};
|
|
204
|
+
let vtt = 'WEBVTT\n\n';
|
|
205
|
+
stepTimings.forEach((step, i) => {
|
|
206
|
+
const end = i < stepTimings.length - 1 ? stepTimings[i + 1].time : duration;
|
|
207
|
+
vtt += `${toVTT(step.time)} --> ${toVTT(end)}\n${step.caption}\n\n`;
|
|
208
|
+
});
|
|
209
|
+
writeFileSync(VTT_OUT, vtt);
|
|
210
|
+
|
|
211
|
+
console.log(JSON.stringify({ stepTimings, duration }));
|
|
212
|
+
SCRIPT
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
After each video runs:
|
|
216
|
+
1. Display the poster image inline with the Read tool so the user can verify the final frame
|
|
217
|
+
2. Update the manifest entry with the step timings (replace the action-based steps from generate with timed steps)
|
|
218
|
+
3. The `.vtt` file is now on disk — it goes in git as-is
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Step 5 — GitHub Releases upload
|
|
223
|
+
|
|
224
|
+
After all videos are captured, provide upload instructions for each video file:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Videos are ready for upload to GitHub Releases.
|
|
228
|
+
|
|
229
|
+
1. Go to: https://github.com/OWNER/REPO/releases
|
|
230
|
+
2. Create a release tagged `docs-media` (or open the existing one)
|
|
231
|
+
— This is a dedicated release just for doc assets, not tied to code versions.
|
|
232
|
+
3. Attach these files:
|
|
233
|
+
public/videos/timer-start.webm
|
|
234
|
+
public/videos/form-submit.webm
|
|
235
|
+
4. Once uploaded, each file will have a URL like:
|
|
236
|
+
https://github.com/OWNER/REPO/releases/download/docs-media/timer-start.webm
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Then ask:
|
|
240
|
+
|
|
241
|
+
- header: "Upload complete?"
|
|
242
|
+
- question: "Have you uploaded the videos to GitHub Releases?"
|
|
243
|
+
- options:
|
|
244
|
+
- "Yes — update the manifest and docs"
|
|
245
|
+
- "I'll do it later — skip for now"
|
|
246
|
+
|
|
247
|
+
If yes: update each video entry in the manifest with the correct `videoUrl`. Also update any `<VideoDemo src="...">` placeholders in the markdown files with the real URL.
|
|
248
|
+
|
|
249
|
+
If skip: leave `videoUrl: ""` in the manifest. The VideoDemo component will still render the step list and poster — it just won't have a playable video until the URL is filled in.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Step 6 — Rewrite prose around captures
|
|
254
|
+
|
|
255
|
+
After all captures complete:
|
|
256
|
+
- For each screenshot: go back to the doc page and rewrite the surrounding paragraph to match what's actually in the screenshot
|
|
257
|
+
- For each video: verify the step captions in the doc match what was captured — update if anything looks off
|
|
258
|
+
|
|
259
|
+
Read each screenshot with the Read tool to inform the rewrite.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Step 7 — Update manifest
|
|
264
|
+
|
|
265
|
+
For each successfully captured item:
|
|
266
|
+
- Screenshots: set `"placeholder": false`, record capture timestamp
|
|
267
|
+
- Videos: set step timings, `vtt` path, `poster` path, and `videoUrl` (once uploaded)
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Step 8 — Summary
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Captured X screenshots, Y videos.
|
|
275
|
+
|
|
276
|
+
Screenshots:
|
|
277
|
+
✓ timer-main.png
|
|
278
|
+
✓ settings-overview.png
|
|
279
|
+
|
|
280
|
+
Videos:
|
|
281
|
+
✓ timer-start.webm — poster captured, VTT written, awaiting upload
|
|
282
|
+
✓ form-submit.webm — poster captured, VTT written, awaiting upload
|
|
283
|
+
|
|
284
|
+
Upload to: https://github.com/OWNER/REPO/releases/tag/docs-media
|
|
285
|
+
|
|
286
|
+
Prose rewritten in X doc pages.
|
|
287
|
+
Run /vitedocs:sync after future code changes to keep docs current.
|
|
288
|
+
```
|
|
@@ -40,10 +40,25 @@ Add this line if not already present:
|
|
|
40
40
|
"images": [
|
|
41
41
|
{
|
|
42
42
|
"path": "docs/public/screenshots/timer-main.png",
|
|
43
|
+
"type": "screenshot",
|
|
43
44
|
"placeholder": true,
|
|
44
45
|
"caption": "Timer in active interval state",
|
|
45
46
|
"captureUrl": "/workouts/123",
|
|
46
47
|
"captureNote": "Timer should be running with intervals visible"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"path": "docs/public/videos/timer-start.webm",
|
|
51
|
+
"type": "video",
|
|
52
|
+
"poster": "docs/public/screenshots/timer-start-poster.png",
|
|
53
|
+
"vtt": "docs/public/videos/timer-start.vtt",
|
|
54
|
+
"videoUrl": "",
|
|
55
|
+
"caption": "Starting a timer interval",
|
|
56
|
+
"captureUrl": "/workouts/123",
|
|
57
|
+
"steps": [
|
|
58
|
+
{ "caption": "Click Start to begin the interval", "selector": ".start-btn", "action": "click" },
|
|
59
|
+
{ "caption": "Timer counts down in real time", "action": "wait", "ms": 1500 },
|
|
60
|
+
{ "caption": "Pause freezes the current interval", "selector": ".pause-btn", "action": "click" }
|
|
61
|
+
]
|
|
47
62
|
}
|
|
48
63
|
],
|
|
49
64
|
"lastSynced": "ISO-8601 timestamp",
|
|
@@ -280,9 +295,72 @@ Present the proposed outline as plain text, then use AskUserQuestion to confirm:
|
|
|
280
295
|
|
|
281
296
|
If changes: ask as plain text — "What would you like to add, remove, or rename?" Then re-present the updated outline and ask again.
|
|
282
297
|
|
|
298
|
+
### Step 3b — Identify home page features
|
|
299
|
+
|
|
300
|
+
Based on your codebase analysis, identify 3–6 meaningful capabilities or selling points of the project that would work well as `features` entries on the VitePress home page (`index.md`).
|
|
301
|
+
|
|
302
|
+
Good features are things a user or developer would care about — core functionality, key integrations, notable technical traits, or workflow benefits. Avoid generic filler like "Easy to use" unless it's specifically supported by something concrete in the code.
|
|
303
|
+
|
|
304
|
+
For each feature, derive:
|
|
305
|
+
- `icon` — a single emoji that represents it
|
|
306
|
+
- `title` — 2–4 words
|
|
307
|
+
- `details` — 1–2 sentences drawn from what you actually read in the codebase
|
|
308
|
+
|
|
309
|
+
Present the proposed features as plain text before writing anything:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
Home page features I identified:
|
|
313
|
+
|
|
314
|
+
⚡️ Real-time Sync
|
|
315
|
+
Changes pushed from the dashboard are reflected in the app within 500ms via WebSockets.
|
|
316
|
+
|
|
317
|
+
🔒 Role-Based Access
|
|
318
|
+
Three permission tiers (admin, editor, viewer) enforced at the API and UI layer.
|
|
319
|
+
|
|
320
|
+
🧩 Block Builder
|
|
321
|
+
Drag-and-drop layout editor built on ACF Blocks — no PHP required for content authors.
|
|
322
|
+
...
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Then ask:
|
|
326
|
+
|
|
327
|
+
- header: "Home page features"
|
|
328
|
+
- question: "Do these features look right for the home page?"
|
|
329
|
+
- options:
|
|
330
|
+
- "Yes — use these"
|
|
331
|
+
- "I want to change some"
|
|
332
|
+
- "Skip the home page features section"
|
|
333
|
+
|
|
334
|
+
If changes: ask as plain text — "What would you like to add, remove, or reword?"
|
|
335
|
+
|
|
336
|
+
Store the confirmed features — they are written into `index.md` in Step 4.
|
|
337
|
+
|
|
283
338
|
### Step 4 — Write pages
|
|
284
339
|
|
|
285
|
-
Write pages one at a time. For
|
|
340
|
+
Write `index.md` first, then remaining pages one at a time. For `index.md`, use the VitePress home page layout with the confirmed features:
|
|
341
|
+
|
|
342
|
+
```md
|
|
343
|
+
---
|
|
344
|
+
layout: home
|
|
345
|
+
|
|
346
|
+
hero:
|
|
347
|
+
name: "App Name"
|
|
348
|
+
text: "One-line tagline"
|
|
349
|
+
tagline: Slightly longer supporting line drawn from Q2 app description.
|
|
350
|
+
actions:
|
|
351
|
+
- theme: brand
|
|
352
|
+
text: Get Started
|
|
353
|
+
link: /FIRST_GUIDE_PAGE
|
|
354
|
+
|
|
355
|
+
features:
|
|
356
|
+
- icon: ⚡️
|
|
357
|
+
title: Feature Title
|
|
358
|
+
details: Feature details sentence.
|
|
359
|
+
# ... remaining features
|
|
360
|
+
---
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Populate `name`, `text`, `tagline`, and the `link` in `actions` from what you know about the project. For each remaining page:
|
|
286
364
|
|
|
287
365
|
1. Read the relevant source files
|
|
288
366
|
2. Write the markdown based on what you can confidently determine
|
|
@@ -307,6 +385,27 @@ Write pages one at a time. For each page:
|
|
|
307
385
|
```
|
|
308
386
|
Populate `method`, `path`, `auth`, `body`, and `response` from what you read in the source files. If a request body or response shape is unclear, leave it as a gap comment.
|
|
309
387
|
|
|
388
|
+
6. For pages documenting interactions, flows, or button behaviors — anything where "what happens next" matters — suggest a video capture instead of a screenshot. Use `VideoDemo` like this:
|
|
389
|
+
|
|
390
|
+
```md
|
|
391
|
+
<VideoDemo
|
|
392
|
+
src="https://github.com/OWNER/REPO/releases/download/docs-media/timer-start.webm"
|
|
393
|
+
poster="/screenshots/timer-start-poster.png"
|
|
394
|
+
vtt="/videos/timer-start.vtt"
|
|
395
|
+
caption="Starting a timer interval"
|
|
396
|
+
:steps="[
|
|
397
|
+
{ caption: 'Click Start to begin the interval', time: 0 },
|
|
398
|
+
{ caption: 'Timer counts down in real time', time: 1.5 },
|
|
399
|
+
{ caption: 'Pause freezes the current interval', time: 4.2 }
|
|
400
|
+
]"
|
|
401
|
+
/>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Leave `src` with a placeholder GitHub Releases URL — it will be filled in after `/vitedocs:capture` runs. Record the entry in the manifest with `"type": "video"` and `"videoUrl": ""`.
|
|
405
|
+
|
|
406
|
+
Use a screenshot (not video) for: static UI states, settings screens, empty states, error messages.
|
|
407
|
+
Use a video for: multi-step flows, button interactions, animations, form submissions, drag-and-drop.
|
|
408
|
+
|
|
310
409
|
Do not stop to ask gap questions mid-page. Keep writing and accumulate all gaps.
|
|
311
410
|
|
|
312
411
|
### Step 5 — Gap review
|
|
@@ -484,6 +583,75 @@ const highlightedResponse = computed(() =>
|
|
|
484
583
|
</style>
|
|
485
584
|
```
|
|
486
585
|
|
|
586
|
+
**`.vitepress/components/VideoDemo.vue`** — write this file exactly:
|
|
587
|
+
|
|
588
|
+
```vue
|
|
589
|
+
<template>
|
|
590
|
+
<figure class="vd">
|
|
591
|
+
<div class="vd__player">
|
|
592
|
+
<video
|
|
593
|
+
ref="videoEl"
|
|
594
|
+
:src="src"
|
|
595
|
+
:poster="poster"
|
|
596
|
+
controls
|
|
597
|
+
preload="metadata"
|
|
598
|
+
@timeupdate="onTimeUpdate"
|
|
599
|
+
>
|
|
600
|
+
<track v-if="vtt" kind="subtitles" :src="vtt" default label="Steps" />
|
|
601
|
+
</video>
|
|
602
|
+
</div>
|
|
603
|
+
<ol v-if="steps && steps.length" class="vd__steps">
|
|
604
|
+
<li
|
|
605
|
+
v-for="(step, i) in steps"
|
|
606
|
+
:key="i"
|
|
607
|
+
class="vd__step"
|
|
608
|
+
:class="{ 'vd__step--active': activeStep === i }"
|
|
609
|
+
>
|
|
610
|
+
<span class="vd__num">{{ i + 1 }}</span>
|
|
611
|
+
{{ step.caption }}
|
|
612
|
+
</li>
|
|
613
|
+
</ol>
|
|
614
|
+
<figcaption v-if="caption" class="vd__caption">{{ caption }}</figcaption>
|
|
615
|
+
</figure>
|
|
616
|
+
</template>
|
|
617
|
+
|
|
618
|
+
<script setup>
|
|
619
|
+
import { ref } from 'vue'
|
|
620
|
+
|
|
621
|
+
const props = defineProps({
|
|
622
|
+
src: { type: String, required: true },
|
|
623
|
+
poster: { type: String, default: '' },
|
|
624
|
+
vtt: { type: String, default: '' },
|
|
625
|
+
caption: { type: String, default: '' },
|
|
626
|
+
steps: { type: Array, default: () => [] },
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
const videoEl = ref(null)
|
|
630
|
+
const activeStep = ref(-1)
|
|
631
|
+
|
|
632
|
+
const onTimeUpdate = () => {
|
|
633
|
+
if (!props.steps.length || !videoEl.value) return
|
|
634
|
+
const t = videoEl.value.currentTime
|
|
635
|
+
let active = -1
|
|
636
|
+
for (let i = 0; i < props.steps.length; i++) {
|
|
637
|
+
if (t >= props.steps[i].time) active = i
|
|
638
|
+
}
|
|
639
|
+
activeStep.value = active
|
|
640
|
+
}
|
|
641
|
+
</script>
|
|
642
|
+
|
|
643
|
+
<style scoped>
|
|
644
|
+
.vd { margin: 1.5rem 0 2.5rem; }
|
|
645
|
+
.vd__player { border-radius: 8px; overflow: hidden; border: 1px solid var(--vp-c-divider); background: #000; }
|
|
646
|
+
.vd__player video { width: 100%; display: block; max-height: 500px; }
|
|
647
|
+
.vd__steps { margin: 12px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
|
|
648
|
+
.vd__step { display: flex; align-items: baseline; gap: 8px; font-size: 0.82rem; color: var(--vp-c-text-2); padding: 6px 10px; border-radius: 6px; border: 1px solid transparent; transition: all 0.2s; }
|
|
649
|
+
.vd__step--active { color: var(--vp-c-text-1); background: var(--vp-c-bg-soft); border-color: var(--vp-c-divider); }
|
|
650
|
+
.vd__num { font-size: 0.65rem; font-weight: 700; color: var(--vp-c-brand-1); background: var(--vp-c-brand-soft); width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
651
|
+
.vd__caption { font-size: 0.75rem; color: var(--vp-c-text-3); margin-top: 8px; text-align: center; }
|
|
652
|
+
</style>
|
|
653
|
+
```
|
|
654
|
+
|
|
487
655
|
**Install dependencies** in the docs folder:
|
|
488
656
|
|
|
489
657
|
```bash
|
|
@@ -511,6 +679,7 @@ import { useRoute } from 'vitepress'
|
|
|
511
679
|
import mediumZoom from 'medium-zoom'
|
|
512
680
|
import './index.css'
|
|
513
681
|
import ApiEndpoint from '../components/ApiEndpoint.vue'
|
|
682
|
+
import VideoDemo from '../components/VideoDemo.vue'
|
|
514
683
|
|
|
515
684
|
export default {
|
|
516
685
|
extends: DefaultTheme,
|
|
@@ -522,6 +691,7 @@ export default {
|
|
|
522
691
|
},
|
|
523
692
|
enhanceApp({ app }) {
|
|
524
693
|
app.component('ApiEndpoint', ApiEndpoint)
|
|
694
|
+
app.component('VideoDemo', VideoDemo)
|
|
525
695
|
}
|
|
526
696
|
}
|
|
527
697
|
```
|
|
@@ -551,7 +721,7 @@ Add `docs-manifest.json` to `.vitepress/` entry in `.gitignore`.
|
|
|
551
721
|
|
|
552
722
|
```
|
|
553
723
|
Generated X pages (Y user-facing, Z developer).
|
|
554
|
-
Created N placeholder screenshots — run /vitedocs:
|
|
724
|
+
Created N placeholder screenshots — run /vitedocs:capture when ready to capture real images.
|
|
555
725
|
Filled M of P gaps — X remaining gap comments left in files for manual review.
|
|
556
726
|
```
|
|
557
727
|
|
|
@@ -15,7 +15,7 @@ Use AskUserQuestion to ask which mode to run:
|
|
|
15
15
|
- options:
|
|
16
16
|
- "setup — Wire up VitePress + GitHub Actions for Pages deployment"
|
|
17
17
|
- "generate — Analyze the codebase and write documentation pages"
|
|
18
|
-
- "
|
|
18
|
+
- "capture — Capture screenshots and record interaction videos"
|
|
19
19
|
- "sync — Detect code drift and update docs that are out of date"
|
|
20
20
|
- "brand — Configure colors, fonts, logo, and visual identity"
|
|
21
21
|
- "update — Retrofit an existing docs setup with the latest components"
|
|
@@ -24,7 +24,8 @@ Scan the current working directory and any known focus paths for `.vitepress/con
|
|
|
24
24
|
For each confirmed docs path, check what is already present:
|
|
25
25
|
|
|
26
26
|
- `.vitepress/components/ApiEndpoint.vue` — exists?
|
|
27
|
-
- `.vitepress/
|
|
27
|
+
- `.vitepress/components/VideoDemo.vue` — exists?
|
|
28
|
+
- `.vitepress/theme/index.js` — exists? includes `medium-zoom`? includes `ApiEndpoint`? includes `VideoDemo`?
|
|
28
29
|
- `.vitepress/theme/index.css` — exists? includes `.medium-zoom-overlay` z-index rules?
|
|
29
30
|
- `package.json` — are `medium-zoom` and `highlight.js` listed in dependencies?
|
|
30
31
|
|
|
@@ -32,9 +33,10 @@ Report findings as a checklist:
|
|
|
32
33
|
```
|
|
33
34
|
Path: docs/
|
|
34
35
|
[x] ApiEndpoint.vue — found (needs update)
|
|
36
|
+
[ ] VideoDemo.vue — missing
|
|
35
37
|
[ ] medium-zoom installed
|
|
36
38
|
[x] highlight.js installed
|
|
37
|
-
[x] theme/index.js — found (missing medium-zoom wiring)
|
|
39
|
+
[x] theme/index.js — found (missing medium-zoom wiring, missing VideoDemo)
|
|
38
40
|
[x] theme/index.css — found (missing z-index rules)
|
|
39
41
|
```
|
|
40
42
|
|
|
@@ -71,6 +73,8 @@ Apply only the items selected (or all missing/outdated items if "Everything" was
|
|
|
71
73
|
|
|
72
74
|
**`ApiEndpoint.vue`** — write the full component from the template in generate Step 6, substituting the correct tab list. If the file already exists, replace it entirely (it is a generated component, not hand-edited).
|
|
73
75
|
|
|
76
|
+
**`VideoDemo.vue`** — if missing, write the full component from the template in generate Step 6. If it already exists, replace it entirely (it is a generated component, not hand-edited).
|
|
77
|
+
|
|
74
78
|
**`medium-zoom` + `highlight.js`** — if either is missing from `package.json` dependencies, run:
|
|
75
79
|
```bash
|
|
76
80
|
cd DOCS_FOLDER && npm install medium-zoom highlight.js
|
|
@@ -101,6 +105,7 @@ import { useRoute } from 'vitepress'
|
|
|
101
105
|
import mediumZoom from 'medium-zoom'
|
|
102
106
|
import './index.css'
|
|
103
107
|
import ApiEndpoint from '../components/ApiEndpoint.vue'
|
|
108
|
+
import VideoDemo from '../components/VideoDemo.vue'
|
|
104
109
|
|
|
105
110
|
export default {
|
|
106
111
|
extends: DefaultTheme,
|
|
@@ -112,6 +117,7 @@ export default {
|
|
|
112
117
|
},
|
|
113
118
|
enhanceApp({ app }) {
|
|
114
119
|
app.component('ApiEndpoint', ApiEndpoint)
|
|
120
|
+
app.component('VideoDemo', VideoDemo)
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
```
|
package/install.js
CHANGED
|
@@ -18,6 +18,10 @@ if (existsSync(oldSkill)) {
|
|
|
18
18
|
const src = join(__dirname, 'commands', 'vitedocs');
|
|
19
19
|
const dest = join(home, '.claude', 'commands', 'vitedocs');
|
|
20
20
|
|
|
21
|
+
// Clean destination before installing to remove any stale files
|
|
22
|
+
if (existsSync(dest)) {
|
|
23
|
+
rmSync(dest, { recursive: true });
|
|
24
|
+
}
|
|
21
25
|
mkdirSync(dest, { recursive: true });
|
|
22
26
|
|
|
23
27
|
const files = readdirSync(src).filter(f => f.endsWith('.md'));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamdangavin/claude-skill-vitepress-docs",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Installs the vitedocs: Claude Code skill suite — VitePress docs setup, generate, screenshot, sync, brand, and update.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: vitedocs:screenshot
|
|
3
|
-
description: Capture real screenshots using Playwright and replace placeholders in docs.
|
|
4
|
-
allowed-tools:
|
|
5
|
-
- Read
|
|
6
|
-
- Write
|
|
7
|
-
- Edit
|
|
8
|
-
- Bash
|
|
9
|
-
- Glob
|
|
10
|
-
- Grep
|
|
11
|
-
- AskUserQuestion
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
This mode reads `.vitepress/docs-manifest.json`. Run `/vitedocs:generate` first if no manifest exists yet.
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Mode: screenshot
|
|
19
|
-
|
|
20
|
-
### Step 1 — Gather capture details
|
|
21
|
-
|
|
22
|
-
**Q1 — Source:**
|
|
23
|
-
- header: "Capture source"
|
|
24
|
-
- question: "Where should I capture screenshots from?"
|
|
25
|
-
- options:
|
|
26
|
-
- "Local dev server — I'll capture from a running server"
|
|
27
|
-
- "Deployed URL — give me the base URL"
|
|
28
|
-
|
|
29
|
-
If deployed: ask as plain text — "What is the base URL? (e.g. `https://docs.myapp.com`)"
|
|
30
|
-
|
|
31
|
-
**Q2 — Stack type** (only if local):
|
|
32
|
-
- header: "Project type"
|
|
33
|
-
- question: "What type of project is this?"
|
|
34
|
-
- options:
|
|
35
|
-
- "Node/npm — you can optionally start the server for me"
|
|
36
|
-
- "WordPress, PHP, or other non-Node stack"
|
|
37
|
-
- "Static files"
|
|
38
|
-
|
|
39
|
-
**Q3 — Server status** (only if local Node):
|
|
40
|
-
- header: "Dev server"
|
|
41
|
-
- question: "Is the dev server already running?"
|
|
42
|
-
- options:
|
|
43
|
-
- "Already running"
|
|
44
|
-
- "Not running — please start it for me"
|
|
45
|
-
|
|
46
|
-
If starting: ask as plain text — "What command starts it? And what URL/port does it run on?"
|
|
47
|
-
|
|
48
|
-
**Q4 — Authentication:**
|
|
49
|
-
- header: "Login required?"
|
|
50
|
-
- question: "Does reaching the screens that need screenshots require login?"
|
|
51
|
-
- options:
|
|
52
|
-
- "No — all target screens are public"
|
|
53
|
-
- "Yes — I'll provide credentials"
|
|
54
|
-
|
|
55
|
-
If yes: ask as plain text — "Please provide the username and password. ⚠️ Credentials are used only to drive the browser in this session — they will NEVER be written to any file, the manifest, or anywhere outside this conversation."
|
|
56
|
-
|
|
57
|
-
**Q5 — Scope:**
|
|
58
|
-
- header: "Capture scope"
|
|
59
|
-
- question: "Which screenshots should I capture?"
|
|
60
|
-
- options:
|
|
61
|
-
- "All placeholders from the manifest"
|
|
62
|
-
- "Specific pages only — I'll tell you which"
|
|
63
|
-
|
|
64
|
-
If specific: ask as plain text — "Which pages or screenshots?"
|
|
65
|
-
|
|
66
|
-
Wait for all answers.
|
|
67
|
-
|
|
68
|
-
### Step 2 — Find all placeholders
|
|
69
|
-
|
|
70
|
-
Read `.vitepress/docs-manifest.json`. Filter to all images where `"placeholder": true`. Group by the doc page they belong to.
|
|
71
|
-
|
|
72
|
-
If no manifest exists, scan all markdown files in the docs folder for images referencing `public/screenshots/` — treat all of them as needing capture.
|
|
73
|
-
|
|
74
|
-
### Step 3 — Capture screenshots
|
|
75
|
-
|
|
76
|
-
For each placeholder, refer to the manifest's `captureUrl` and `captureNote` fields to understand what state to capture.
|
|
77
|
-
|
|
78
|
-
Use Playwright via Homebrew for all captures:
|
|
79
|
-
```js
|
|
80
|
-
import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
**⛔ Credential rule — NEVER write credentials to any file.** Do not write them to a script file, `.env`, the manifest, or anywhere on disk. All Playwright scripts run as inline bash heredocs — credentials are passed directly as inline values to `page.fill()` calls within the heredoc and exist only in memory for the duration of the command. If Playwright needs credentials in a reusable way, use a saved storage state file — but prompt the user for credentials fresh each time rather than caching them.
|
|
84
|
-
|
|
85
|
-
**For pages requiring auth:** if credentials were provided, drive a login flow before navigating to the target URL. If no credentials were given, skip auth-gated pages and list them in the final summary so the user can run `/vitedocs:screenshot` again with credentials.
|
|
86
|
-
|
|
87
|
-
**Standard capture script template:**
|
|
88
|
-
|
|
89
|
-
Run inline via bash heredoc — no file is ever written to disk:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
node --input-type=module << 'SCRIPT'
|
|
93
|
-
import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
|
|
94
|
-
const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
|
|
95
|
-
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
96
|
-
await page.goto('BASE_URL + captureUrl', { waitUntil: 'load', timeout: 60000 });
|
|
97
|
-
await page.waitForTimeout(SETTLE_MS);
|
|
98
|
-
await page.screenshot({ path: 'OUTPUT_PATH', fullPage: false });
|
|
99
|
-
await browser.close();
|
|
100
|
-
SCRIPT
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Adjust `SETTLE_MS` based on stack type: WordPress/non-SPA → `500`, Next.js/SPA → `2000`–`4000`.
|
|
104
|
-
|
|
105
|
-
Never write the script to a file — always use the heredoc form above.
|
|
106
|
-
|
|
107
|
-
### Step 4 — Rewrite prose around captured images
|
|
108
|
-
|
|
109
|
-
After capturing an image, go back to the doc page that references it and rewrite the paragraph or section surrounding that image. The placeholder was written with generic framing — now that you can see the actual screenshot, make the description specific and accurate.
|
|
110
|
-
|
|
111
|
-
Read the screenshot with the Read tool to inform the rewrite.
|
|
112
|
-
|
|
113
|
-
### Step 5 — Update manifest
|
|
114
|
-
|
|
115
|
-
For each successfully captured image, update `"placeholder": false` in the manifest and record the capture timestamp.
|
|
116
|
-
|
|
117
|
-
### Step 6 — Summary
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
Captured X of Y screenshots.
|
|
121
|
-
Skipped: [list any skipped with reason]
|
|
122
|
-
|
|
123
|
-
Rewrote prose in X doc pages.
|
|
124
|
-
|
|
125
|
-
Run /vitedocs:sync after future code changes to keep docs current.
|
|
126
|
-
```
|