@jesdi/skills 0.0.2 → 0.0.3
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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jesdi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-visual-diff
|
|
3
|
+
description: Capture before/after screenshots of UI views with Playwright and embed them in a PR. Use this skill whenever creating or preparing a pull request that touches frontend code (components, views, styles, layout, Tailwind classes) — even if the user just says "open a PR" or "create the PR" after UI work, and especially if they mention screenshots, visual diff, before/after, or "show what changed". Also use when the user asks to add screenshots to an existing PR.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PR Visual Diff
|
|
7
|
+
|
|
8
|
+
Produce before/after screenshots of the views affected by a branch's frontend
|
|
9
|
+
changes, commit them on the PR branch, and embed them in the PR body. Reviewers
|
|
10
|
+
should see what changed visually without checking out the branch.
|
|
11
|
+
|
|
12
|
+
The approach leans on the project's existing Playwright e2e harness with the
|
|
13
|
+
**API mocked**: because the data is stubbed and identical across both runs, any
|
|
14
|
+
pixel difference between "before" and "after" is caused by the code change
|
|
15
|
+
alone — that's the property that makes the comparison trustworthy. Never
|
|
16
|
+
screenshot against a live backend for this purpose.
|
|
17
|
+
|
|
18
|
+
Because every project wires this differently, the skill **discovers** the
|
|
19
|
+
project's setup once and caches it, then reuses the cache on later runs.
|
|
20
|
+
|
|
21
|
+
## Step 0. Load or build the per-project config
|
|
22
|
+
|
|
23
|
+
The config cache lives beside this skill at
|
|
24
|
+
`<skill-dir>/pr-visual-diff.<slug>.json`, where `<slug>` is the target repo's
|
|
25
|
+
directory name. It is gitignored — local to this machine. Ensure the cache file is ignored in the `.gitignore` of the repo where you install the skill directory (add an entry like `pr-visual-diff.*.json` if it isn't already).
|
|
26
|
+
|
|
27
|
+
- **If the cache exists**, read it and skip to "When NOT to run the capture".
|
|
28
|
+
If it says `{"supported": false}`, tell the user this project has no
|
|
29
|
+
mocked-API harness and create a plain PR (see Preconditions), then stop.
|
|
30
|
+
- **If it does not exist**, run the discovery pass below to build it.
|
|
31
|
+
|
|
32
|
+
### Discovery pass (first run only)
|
|
33
|
+
|
|
34
|
+
Inspect the target repo and record these values, then write them to the cache
|
|
35
|
+
file as JSON:
|
|
36
|
+
|
|
37
|
+
| Key | How to find it |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `frontendDir` | the dir holding the app + its `src/` (repo root or a subdir like `frontend/`) |
|
|
40
|
+
| `e2eDir`, `specsDir` | the Playwright spec/support tree (look for `playwright.config.*`, a `specs/` dir) |
|
|
41
|
+
| `authFixtureImport`, `authFixtureName`, `authedPageProp` | the support file exporting a `test` fixture that seeds auth; note its import path, the exported name, and the authed-page property (e.g. `authenticatedPage`) |
|
|
42
|
+
| `mockHelperImport`, `mockHelperName` | the helper that stubs `/api` routes with fixture data; its import path + exported name |
|
|
43
|
+
| `port` | the `webServer` port in the Playwright config |
|
|
44
|
+
| `deviceProjects` | the config's `projects` (default to a desktop + a mobile project) |
|
|
45
|
+
| `baseBranch` | the repo's default branch (`main`/`master`; `git symbolic-ref refs/remotes/origin/HEAD`) |
|
|
46
|
+
| `packageManager` | infer from lockfile: `pnpm-lock.yaml`→pnpm, `package-lock.json`→npm, `yarn.lock`→yarn, `bun.lockb`→bun |
|
|
47
|
+
| `screenshotDir` | default `docs/pr-screenshots` |
|
|
48
|
+
| `spinnerSelector` | the app's loading-indicator selector (search for a spinner/`role="status"`/`aria-busy` component) |
|
|
49
|
+
|
|
50
|
+
### Preconditions gate
|
|
51
|
+
|
|
52
|
+
The discovery pass is also the gate. **If there is no Playwright e2e harness
|
|
53
|
+
that mocks the API** (no mock helper, or no Playwright at all), write
|
|
54
|
+
`{ "supported": false, "reason": "<why>" }` to the cache, tell the user that a
|
|
55
|
+
trustworthy before/after diff isn't possible here (unmocked data would differ
|
|
56
|
+
between runs for reasons other than the code change), and **create a plain PR
|
|
57
|
+
instead** — no screenshots. Then stop.
|
|
58
|
+
|
|
59
|
+
## When NOT to run the capture
|
|
60
|
+
|
|
61
|
+
If `git diff --name-only $(git merge-base <baseBranch> HEAD)...HEAD` shows no
|
|
62
|
+
changes under `<frontendDir>/src/`, there is nothing visual to diff — create the
|
|
63
|
+
PR normally and say so. Test-only, docs-only, or backend-only diffs don't need
|
|
64
|
+
screenshots.
|
|
65
|
+
|
|
66
|
+
## Always run the capture in a subagent
|
|
67
|
+
|
|
68
|
+
Steps 2–6 (writing the spec, capturing, **reading every PNG back to eyeball
|
|
69
|
+
it**, committing, embedding) MUST run inside a single dispatched subagent — not
|
|
70
|
+
in the main context. Reading screenshots back is this skill's dominant token
|
|
71
|
+
cost, and those image bytes are worthless to the main thread; keeping them in a
|
|
72
|
+
subagent's throwaway context is the whole savings, with no loss of verification.
|
|
73
|
+
|
|
74
|
+
Main context does only: step 1 (map the diff, get the user's go-ahead), then
|
|
75
|
+
dispatch a subagent with the plan and the config values. The subagent returns a
|
|
76
|
+
compact report: the committed screenshot paths + pinned URLs, the PR-body
|
|
77
|
+
markdown, and any views it flagged. The main context creates/edits the PR from
|
|
78
|
+
that report — it never reads a PNG.
|
|
79
|
+
|
|
80
|
+
## Workflow
|
|
81
|
+
|
|
82
|
+
### 1. Map the diff to views, then get the user's go-ahead
|
|
83
|
+
|
|
84
|
+
List changed files against the merge base (`git merge-base <baseBranch> HEAD`).
|
|
85
|
+
For each changed component, trace upward (imports → page/route component → route
|
|
86
|
+
path) to find the route(s) where it renders, and note any interaction needed to
|
|
87
|
+
make it visible (open a modal, select a tab). Classify each view as **changed**
|
|
88
|
+
(existed at the merge base) or **new** (introduced by this branch) — new ones
|
|
89
|
+
get an after-only shot.
|
|
90
|
+
|
|
91
|
+
This capture is heavy (temp worktree, two Playwright passes, a commit pushed to
|
|
92
|
+
the branch, PR body rewritten). ALWAYS confirm with AskUserQuestion before
|
|
93
|
+
running any of it. The question must cover **both** (a) whether to run the whole
|
|
94
|
+
before/after capture at all, and (b) that it will commit the screenshots and
|
|
95
|
+
embed them in the PR. Include the proposed plan: which routes/states, and which
|
|
96
|
+
device projects — default to the config's `deviceProjects`; add more only when
|
|
97
|
+
the change is layout-sensitive. If they decline, create the PR normally and stop.
|
|
98
|
+
|
|
99
|
+
### 2. Write the capture spec
|
|
100
|
+
|
|
101
|
+
Copy `references/capture-spec-template.ts` from this skill into
|
|
102
|
+
`<specsDir>/__pr-capture__.spec.ts` and substitute the `__PVD_*__` markers from
|
|
103
|
+
the config:
|
|
104
|
+
|
|
105
|
+
- `__PVD_AUTH_FIXTURE_IMPORT__` → `authFixtureImport` (and, if the fixture isn't
|
|
106
|
+
named `test`, adjust the `import { <authFixtureName> as test }` accordingly)
|
|
107
|
+
- `__PVD_MOCK_HELPER_IMPORT__` → `mockHelperImport` (and `mockHelperName` if not
|
|
108
|
+
`mockApi`)
|
|
109
|
+
- `__PVD_SPINNER_SELECTOR__` → `spinnerSelector`
|
|
110
|
+
- the `{ authenticatedPage: page }` destructure → `{ <authedPageProp>: page }`
|
|
111
|
+
|
|
112
|
+
Then fill in the `VIEWS` array. This file is scaffolding: never commit it, and
|
|
113
|
+
delete it when done (it lives in the specs dir, so a forgotten copy would run in
|
|
114
|
+
every future e2e invocation).
|
|
115
|
+
|
|
116
|
+
### 3. Capture "after" (current branch)
|
|
117
|
+
|
|
118
|
+
From `<frontendDir>`. First make sure nothing is listening on `<port>`
|
|
119
|
+
(`lsof -nP -i :<port>`) and kill any stray server there: if the Playwright
|
|
120
|
+
config has `reuseExistingServer` on locally, a leftover server from another
|
|
121
|
+
worktree would be silently reused and you'd screenshot the *wrong code*.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
PR_SHOT_DIR=<scratchpad>/after PR_SHOT_LABEL=after \
|
|
125
|
+
<packageManager> exec playwright test <specsDir>/__pr-capture__.spec.ts \
|
|
126
|
+
--project=<deviceProject> [--project=<deviceProject>...]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Playwright boots its own server and tears it down. The template pins
|
|
130
|
+
`deviceScaleFactor: 1` and asserts no spinner is left on screen, so a
|
|
131
|
+
raced/under-mocked view fails the test instead of producing a misleading shot.
|
|
132
|
+
That assertion is a backstop, not a substitute for looking: the subagent still
|
|
133
|
+
Reads each PNG after capture — an empty state, error toast, or wrong content
|
|
134
|
+
passes the spinner check but is still wrong. On a genuine failure, fix the spec
|
|
135
|
+
(add mock stubs, a wait for a stable element, or correct the spinner selector)
|
|
136
|
+
rather than shipping a bad screenshot.
|
|
137
|
+
|
|
138
|
+
### 4. Capture "before" (merge base, temp worktree)
|
|
139
|
+
|
|
140
|
+
A "before" shot only makes sense for views that already existed. If a view is
|
|
141
|
+
introduced by this branch, capture only its "after" shot and present it as
|
|
142
|
+
**New** (step 6). If *every* captured view is new, skip this whole step.
|
|
143
|
+
|
|
144
|
+
For views that did exist, reconstruct the pre-change app from git:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
BASE=$(git merge-base <baseBranch> HEAD)
|
|
148
|
+
git worktree add <scratchpad>/pr-before-shots "$BASE"
|
|
149
|
+
cd <scratchpad>/pr-before-shots/<frontendDir> && <packageManager> install
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Copy the same filled `__pr-capture__.spec.ts` into that worktree's specs dir,
|
|
153
|
+
then run the same command with `PR_SHOT_DIR=<scratchpad>/before
|
|
154
|
+
PR_SHOT_LABEL=before`. Run it sequentially after the "after" pass — both configs
|
|
155
|
+
use `<port>`.
|
|
156
|
+
|
|
157
|
+
Edge cases:
|
|
158
|
+
- A route that doesn't exist at the base (new view): skip it and label it
|
|
159
|
+
**New** in the PR table.
|
|
160
|
+
- The e2e helpers (auth fixture, mock helper) missing or different at the base:
|
|
161
|
+
adapt the spec copy to what exists there; the goal is the same rendered view,
|
|
162
|
+
not identical spec code.
|
|
163
|
+
- `<packageManager> install` failing on a native binding: rerun once; if it
|
|
164
|
+
persists, report it rather than screenshotting a broken build.
|
|
165
|
+
|
|
166
|
+
Remove the worktree when done:
|
|
167
|
+
`git worktree remove --force <scratchpad>/pr-before-shots` (`--force` because
|
|
168
|
+
install left untracked artifacts).
|
|
169
|
+
|
|
170
|
+
### 5. Commit the images on the PR branch
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
<screenshotDir>/<branch-slug>/<view>--<project>--{before|after}.png
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`<branch-slug>` = branch name with `/` → `-`. Keep only the shots you'll
|
|
177
|
+
reference. Commit them, delete the temp spec, and push.
|
|
178
|
+
|
|
179
|
+
### 6. Embed in the PR body
|
|
180
|
+
|
|
181
|
+
After pushing, pin image URLs to the exact commit so they keep rendering after
|
|
182
|
+
later force-pushes or branch deletion:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
SHA=$(git rev-parse HEAD)
|
|
186
|
+
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
|
|
187
|
+
# → https://github.com/$REPO/blob/$SHA/<screenshotDir>/<slug>/<file>.png?raw=true
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Per view, one table. Put **Before on the left and After on the right** (one
|
|
191
|
+
device per row) so a reviewer sees the original and changed component side by
|
|
192
|
+
side:
|
|
193
|
+
|
|
194
|
+
```markdown
|
|
195
|
+
### <View name> (`/route`)
|
|
196
|
+
|
|
197
|
+
| | Before | After |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| **Desktop** |  |  |
|
|
200
|
+
| **Mobile** |  |  |
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
If the repo is private, GitHub's image proxy may refuse to inline blob URLs in
|
|
204
|
+
the PR body. Add a one-line note under the tables linking to the
|
|
205
|
+
`<screenshotDir>/<slug>/` directory on the branch as a fallback — the images
|
|
206
|
+
always render in the Files-changed tab and in blob view for authenticated
|
|
207
|
+
viewers.
|
|
208
|
+
|
|
209
|
+
The subagent's job ends here: it has pushed the commit and produced the PR-body
|
|
210
|
+
markdown, and returns both. The **main context** creates (or edits) the PR with
|
|
211
|
+
that body, then opens it (`gh pr view --web`) so the user can confirm the embeds
|
|
212
|
+
render.
|
|
213
|
+
|
|
214
|
+
For a **new** view there is no Before, so put a **New view** label in the left
|
|
215
|
+
cell instead of an image:
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
| | Before | After |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| **Desktop** | _New view_ |  |
|
|
221
|
+
| **Mobile** | _New view_ |  |
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Adding shots to an existing PR
|
|
225
|
+
|
|
226
|
+
Same flow; the only differences: diff against the PR's base branch
|
|
227
|
+
(`gh pr view --json baseRefName`) instead of `<baseBranch>`, and edit the body
|
|
228
|
+
with `gh pr edit --body-file` instead of creating.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pr-visual-diff capture spec — TEMPORARY SCAFFOLDING, never commit.
|
|
3
|
+
*
|
|
4
|
+
* This is a template. Before running, substitute every __PVD_*__ marker below
|
|
5
|
+
* with the value from this project's discovery cache
|
|
6
|
+
* (<skill-dir>/pr-visual-diff.<slug>.json). The markers exist because
|
|
7
|
+
* each project wires its Playwright auth fixture and API mock differently.
|
|
8
|
+
*
|
|
9
|
+
* Copy the filled result to <specsDir>/__pr-capture__.spec.ts, then run:
|
|
10
|
+
*
|
|
11
|
+
* PR_SHOT_DIR=<abs-out-dir> PR_SHOT_LABEL=after \
|
|
12
|
+
* <packageManager> exec playwright test <specsDir>/__pr-capture__.spec.ts \
|
|
13
|
+
* --project=<deviceProject> [--project=<deviceProject>...]
|
|
14
|
+
*
|
|
15
|
+
* Delete the copy when done — anything left in the specs dir runs on every e2e pass.
|
|
16
|
+
*/
|
|
17
|
+
import { expect, type Page } from '@playwright/test';
|
|
18
|
+
// __PVD_AUTH_IMPORT__: import the project's test fixture.
|
|
19
|
+
// From config: import { <authFixtureName> } from '<authFixtureImport>'
|
|
20
|
+
import { test } from '__PVD_AUTH_FIXTURE_IMPORT__';
|
|
21
|
+
// __PVD_MOCK_IMPORT__: import the project's API-mock helper.
|
|
22
|
+
// From config: import { <mockHelperName> } from '<mockHelperImport>'
|
|
23
|
+
import { mockApi } from '__PVD_MOCK_HELPER_IMPORT__';
|
|
24
|
+
|
|
25
|
+
const OUT = process.env.PR_SHOT_DIR;
|
|
26
|
+
const LABEL = process.env.PR_SHOT_LABEL ?? 'after';
|
|
27
|
+
if (!OUT) throw new Error('PR_SHOT_DIR must be set to an absolute output directory');
|
|
28
|
+
|
|
29
|
+
// Selector for this app's loading indicator. From config: <spinnerSelector>.
|
|
30
|
+
const SPINNER = '__PVD_SPINNER_SELECTOR__';
|
|
31
|
+
|
|
32
|
+
// Pin device-pixel-ratio to 1 for every project. Device descriptors (iPhone 14,
|
|
33
|
+
// Pixel 7, iPad) default to DPR 2-3, producing PNGs 4-9x larger for zero review
|
|
34
|
+
// value — and those bytes land in the main context when the shots get read back.
|
|
35
|
+
test.use({ deviceScaleFactor: 1 });
|
|
36
|
+
|
|
37
|
+
interface View {
|
|
38
|
+
/** Slug used in the filename, e.g. 'dashboard' or 'add-position-modal'. */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Route to open, e.g. '/dashboard'. */
|
|
41
|
+
path: string;
|
|
42
|
+
/** Optional interactions to reach the state (open a modal, pick a tab…). */
|
|
43
|
+
prepare?: (page: Page) => Promise<void>;
|
|
44
|
+
/** Full-page scroll capture (default) vs just the viewport. */
|
|
45
|
+
fullPage?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const VIEWS: View[] = [
|
|
49
|
+
// { name: 'dashboard', path: '/dashboard' },
|
|
50
|
+
// {
|
|
51
|
+
// name: 'add-position-modal',
|
|
52
|
+
// path: '/',
|
|
53
|
+
// prepare: async (page) => {
|
|
54
|
+
// await page.getByRole('button', { name: /add symbol/i }).click();
|
|
55
|
+
// await page.getByRole('dialog').waitFor();
|
|
56
|
+
// },
|
|
57
|
+
// },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
for (const view of VIEWS) {
|
|
61
|
+
// __PVD_PAGE_PROP__: destructure the project's authed-page fixture prop.
|
|
62
|
+
// From config: { <authedPageProp>: page }
|
|
63
|
+
test(`pr-capture: ${view.name}`, async ({ authenticatedPage: page }, testInfo) => {
|
|
64
|
+
await mockApi(page);
|
|
65
|
+
await page.goto(view.path);
|
|
66
|
+
await page.waitForLoadState('networkidle');
|
|
67
|
+
await view.prepare?.(page);
|
|
68
|
+
// Guard against shipping a misleading shot: a spinner still on screen means
|
|
69
|
+
// the mock is missing an endpoint or prepare() raced the render. Fail loudly
|
|
70
|
+
// here instead of committing a loading-state PNG.
|
|
71
|
+
await expect(
|
|
72
|
+
page.locator(SPINNER),
|
|
73
|
+
`${view.name}: a spinner is still visible — fix the mock/prepare before capturing`,
|
|
74
|
+
).toHaveCount(0);
|
|
75
|
+
await page.screenshot({
|
|
76
|
+
path: `${OUT}/${view.name}--${testInfo.project.name}--${LABEL}.png`,
|
|
77
|
+
fullPage: view.fullPage ?? true,
|
|
78
|
+
animations: 'disabled',
|
|
79
|
+
caret: 'hide',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
package/skills-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-07-
|
|
3
|
+
"generatedAt": "2026-07-16T10:07:19.685Z",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
6
6
|
"name": "backlog",
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
"version": "0.1.0",
|
|
32
32
|
"hash": "sha256-916ea397cd74d91c4b6060672088b9c54353c915b98bcaef3514815694842e5a",
|
|
33
33
|
"description": "Minimal example skill proving the distribution pipeline works. Responds to \"test my skills setup\" by confirming the skill is loaded."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "pr-visual-diff",
|
|
37
|
+
"version": "0.1.0",
|
|
38
|
+
"hash": "sha256-4e386dd175a34e55e89e0e29e37f5d0afd927560b7e3ba4c5f3ef482fb73f722",
|
|
39
|
+
"description": "Capture before/after screenshots of UI views with Playwright and embed them in a PR. Use this skill whenever creating or preparing a pull request that touches frontend code (components, views, styles, layout, Tailwind classes) — even if the user just says \"open a PR\" or \"create the PR\" after UI work, and especially if they mention screenshots, visual diff, before/after, or \"show what changed\". Also use when the user asks to add screenshots to an existing PR."
|
|
34
40
|
}
|
|
35
41
|
]
|
|
36
42
|
}
|