styleproof 1.1.0 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,41 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.0]
11
+
12
+ Visual-review approval gate. StyleProof can now act as a per-PR review gate
13
+ ("here is what changed visually — sign off if it's intentional") rather than only
14
+ a zero-diff refactor certifier.
15
+
16
+ ### Added
17
+
18
+ - **`require-approval` Action input.** Instead of failing the job on any diff, the
19
+ Action sets a `StyleProof` commit status: green when there are no visual changes,
20
+ red ("needs sign-off") until the changes are approved. The report comment gains
21
+ **one approval checkbox per change** — each distinct visual change is signed off
22
+ on its own, and the gate goes green only when **every** box is ticked.
23
+ - **`example/styleproof-approve.yml`** — a template `issue_comment` workflow (copy
24
+ to your default branch). As a write-access reviewer ticks the per-change boxes,
25
+ it updates the `StyleProof` status ("2 of 3 approved") and flips it green only at
26
+ full approval. Its trust model:
27
+ - acts only on a **human edit** of the **bot's own** report comment
28
+ (`comment.user.type == 'Bot'` and `sender.type == 'User'`), which excludes both
29
+ the Action's own comment upserts and any attacker-authored comment;
30
+ - **binds approval to the exact commit** the report was generated for (a
31
+ `<!-- styleproof-sha -->` marker), so a push after the report can never inherit
32
+ a green status — a new render re-opens the gate;
33
+ - **verifies write access** (`getCollaboratorPermissionLevel`, fails closed)
34
+ before moving the status. The marker is not the trust boundary — write access is.
35
+ - **`status-context` input** to rename the commit status (must match the approve
36
+ workflow + branch protection).
37
+
38
+ ### Changed
39
+
40
+ - The report no longer appends its own "regenerate the baseline" footer — the
41
+ consumer (the Action, or your own wrapper) owns the call to action. This also
42
+ removes the duplicate footer the Action used to add. `fail-on-diff` (legacy
43
+ refactor mode) is unchanged and still the default.
44
+
10
45
  ## [1.1.0]
11
46
 
12
47
  Report readability overhaul. No change to the capture format, the diff, or the
@@ -215,7 +250,8 @@ number)`), so each viewport band can capture at its own height. Default remains
215
250
  - `styleproof-diff` CLI: certifies a refactor (exit 0) or names the exact element,
216
251
  property, and state that drifted (exit 1).
217
252
 
218
- [Unreleased]: https://github.com/BenSheridanEdwards/styleproof/compare/v1.1.0...HEAD
253
+ [Unreleased]: https://github.com/BenSheridanEdwards/styleproof/compare/v1.2.0...HEAD
254
+ [1.2.0]: https://github.com/BenSheridanEdwards/styleproof/compare/v1.1.0...v1.2.0
219
255
  [1.1.0]: https://github.com/BenSheridanEdwards/styleproof/compare/v1.0.0...v1.1.0
220
256
  [1.0.0]: https://github.com/BenSheridanEdwards/styleproof/compare/v0.7.0...v1.0.0
221
257
  [0.7.0]: https://github.com/BenSheridanEdwards/styleproof/compare/v0.6.0...v0.7.0
package/README.md CHANGED
@@ -394,11 +394,11 @@ Numeric flags also accept the `--flag=value` form. The remaining `maxHeight` kno
394
394
 
395
395
  ## GitHub Action
396
396
 
397
- The repo ships a composite action that diffs against your committed baseline and, on
398
- changes, commits a compact report to an orphan branch and posts it to the PR. It
399
- fails the job while changes are unapproved; **approving = regenerating the committed
400
- baseline** and pushing it with the PR. The comment updates in place on every push and
401
- flips to ✓ when clean.
397
+ The repo ships a composite action that diffs two capture dirs and, on changes, commits a
398
+ compact report to an orphan branch and posts it to the PR. The comment updates in place on
399
+ every push and flips to when clean. It runs in one of two modes — **certify a refactor**
400
+ (default) or **review visual changes** with per-change sign-off (`require-approval`); see
401
+ [Two modes](#two-modes-certify-a-refactor-or-review-visual-changes) below.
402
402
 
403
403
  ```yaml
404
404
  - name: Capture style maps
@@ -416,14 +416,16 @@ flips to ✓ when clean.
416
416
 
417
417
  ### Inputs
418
418
 
419
- | Input | Required | Default | Description |
420
- | --------------- | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
421
- | `baseline-dir` | yes | — | Directory with the committed baseline captures (`.json.gz` + `.png`). |
422
- | `fresh-dir` | yes | — | Directory with the freshly captured maps to compare. |
423
- | `report-branch` | no | `styleproof-reports` | Orphan branch that stores reports (created on first run). One `pr-<n>/` folder per PR; never pruned. |
424
- | `inline-images` | no | `auto` | `auto` \| `always` \| `never`. `auto` embeds composites in the comment for public repos and links the report for private repos. |
425
- | `github-token` | no | `${{ github.token }}` | Token used to push the report branch and post the comment. |
426
- | `fail-on-diff` | no | `'true'` | Fail the job when differences are found. |
419
+ | Input | Required | Default | Description |
420
+ | ------------------ | -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
421
+ | `baseline-dir` | yes | — | Directory with the committed baseline captures (`.json.gz` + `.png`). |
422
+ | `fresh-dir` | yes | — | Directory with the freshly captured maps to compare. |
423
+ | `report-branch` | no | `styleproof-reports` | Orphan branch that stores reports (created on first run). One `pr-<n>/` folder per PR; never pruned. |
424
+ | `inline-images` | no | `auto` | `auto` \| `always` \| `never`. `auto` embeds composites in the comment for public repos and links the report for private repos. |
425
+ | `github-token` | no | `${{ github.token }}` | Token used to push the report branch and post the comment. |
426
+ | `fail-on-diff` | no | `'true'` | Legacy refactor mode: fail the job on any diff. Ignored when `require-approval` is `'true'`. |
427
+ | `require-approval` | no | `'false'` | Review-gate mode: set a `StyleProof` commit status (red until each change is approved) instead of failing the job. See below. |
428
+ | `status-context` | no | `StyleProof` | Name of the commit status set in review-gate mode (must match the approve workflow + branch protection). |
427
429
 
428
430
  ### Outputs
429
431
 
@@ -432,6 +434,46 @@ flips to ✓ when clean.
432
434
  | `changed` | `"true"` when any computed style, pseudo-element, or state changed. |
433
435
  | `report-url` | Blob URL of the committed report (when changed). |
434
436
 
437
+ ### Two modes: certify a refactor, or review visual changes
438
+
439
+ - **Certify (default, `fail-on-diff: true`).** Any computed-style change fails the job.
440
+ Right for a refactor you expect to be a no-op (a CSS-to-Tailwind migration); "approving"
441
+ a change means regenerating the committed baseline and pushing it.
442
+ - **Review gate (`require-approval: true`).** The report is the product: every PR shows
443
+ _what changed visually_, and a reviewer signs each change off against the PR's stated
444
+ intent. Instead of failing the job, the Action sets a **`StyleProof` commit status** —
445
+ green when nothing changed, red until approved — and posts **one approval checkbox per
446
+ change**. The gate goes green only when **every** box is ticked.
447
+
448
+ To enable the review gate:
449
+
450
+ 1. Set `require-approval: 'true'` on the Action, and grant the job `statuses: write`
451
+ (plus the existing `contents: write` / `pull-requests: write`).
452
+ 2. Copy [`example/styleproof-approve.yml`](example/styleproof-approve.yml) to
453
+ `.github/workflows/` **on your default branch** (`issue_comment` workflows only run
454
+ from the default branch).
455
+ 3. Add a branch-protection rule that **requires the `StyleProof` status** to pass.
456
+
457
+ A reviewer with write access walks the report change-by-change and ticks each box; the
458
+ status updates ("2 of 3 approved") and flips green at full approval. A new push that
459
+ changes styles re-opens the gate. The approval is bound to the exact reviewed commit and
460
+ acts only on a human edit of the bot's own comment, so it can't be flipped green by a
461
+ later push, by the bot's own comment updates, or by anyone without write access. Pair it
462
+ naturally with a head-vs-**base-branch** diff (capture the base branch into `baseline-dir`)
463
+ so each PR's report is exactly _what this PR changes_.
464
+
465
+ Two things to know about the trust model:
466
+
467
+ - **Single-reviewer by design.** Any write-access user can tick the boxes, **including the
468
+ PR author on their own PR** — the sign-off is "I confirm this is intentional," not
469
+ multi-party review. If you need a different person to approve, add a branch-protection
470
+ rule requiring a review from someone other than the author (CODEOWNERS); this gate
471
+ doesn't enforce that itself.
472
+ - **Use the default `GITHUB_TOKEN`.** The approve workflow only acts on a comment authored
473
+ by a bot (so an attacker-authored comment can never trigger it). If you post the report
474
+ with a personal access token (a `User`), the workflow won't fire and the gate can never
475
+ go green. A GitHub App token works; a PAT does not.
476
+
435
477
  ### Why two image modes (and why it's automatic)
436
478
 
437
479
  The two ways an image can appear on a PR have **opposite** privacy behaviour, so
package/dist/report.js CHANGED
@@ -579,9 +579,6 @@ export function generateStyleMapReport(opts) {
579
579
  md.push('', `### \`${p.sd.surface}\``, '', `⚠️ captured only in the **${p.sd.missing === 'before' ? 'after' : 'before'}** set — re-run both captures.`);
580
580
  json.push({ surface: p.sd.surface, missing: p.sd.missing });
581
581
  }
582
- if (surfaces.length) {
583
- md.push('', '---', '_To accept these changes, regenerate the committed baseline from this build and commit it with your diff._');
584
- }
585
582
  const reportMdPath = path.join(outDir, 'report.md');
586
583
  const reportJsonPath = path.join(outDir, 'report.json');
587
584
  fs.writeFileSync(reportMdPath, md.join('\n') + '\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "styleproof",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Certify CSS refactors with the browser's computed styles: capture every resolved longhand, pseudo-element, and forced hover/focus/active state, then diff before against after.",
5
5
  "keywords": [
6
6
  "playwright",