tuffgal 0.2.0-alpha.1 → 0.2.0-alpha.2
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 +32 -1
- package/dist/reporter/assets/report.css +178 -66
- package/dist/reporter/assets/report.js +286 -158
- package/dist/reporter/template.d.ts.map +1 -1
- package/dist/reporter/template.js +177 -26
- package/dist/reporter/template.js.map +1 -1
- package/dist/runner/runAction.d.ts.map +1 -1
- package/dist/runner/runAction.js +3 -0
- package/dist/runner/runAction.js.map +1 -1
- package/dist/schema/result.d.ts +19 -0
- package/dist/schema/result.d.ts.map +1 -1
- package/dist/schema/result.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,36 @@ this project uses [Pride Versioning](https://pridever.org) → `PROUD.DEFAULT.SH
|
|
|
8
8
|
|
|
9
9
|
_Nothing just yet_
|
|
10
10
|
|
|
11
|
+
## [0.2.0-alpha.2] — 2026-07-12
|
|
12
|
+
|
|
13
|
+
Report improvements. The HTML report picks up a breakpoint filter, sharper
|
|
14
|
+
status filtering, and an interactive viewer redesigned around
|
|
15
|
+
blink-comparing what changed.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Breakpoint filter pills in the report summary
|
|
20
|
+
- `npm run preview -- --interactive` renders sample report in interactive mode
|
|
21
|
+
- `sizeMismatch` on `results.json` action results
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- A status-filtered story now shows only its matching actions
|
|
26
|
+
- Candidate note names its referent
|
|
27
|
+
- Redesigned interactive mode so resting state is the selected variant
|
|
28
|
+
- Press and hold the screenshot to flip to its baseline counterpart
|
|
29
|
+
- The Baseline/Actual/Diff chips are always visible
|
|
30
|
+
- The Showing caption tracks whatever is displayed
|
|
31
|
+
- Resized-screenshot note folded into one accessible sentence
|
|
32
|
+
- Summary status filters restyled as colorful pills
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A press-to-native-image-drag no longer leaves the preview stuck
|
|
37
|
+
- Bulk expand/collapse no longer pre-expands hidden screenshot panels
|
|
38
|
+
- "Expanded changed in 1 stories" pluralization in bulk-toggle announcement
|
|
39
|
+
- `npm run preview` silently never rendered interactive mode
|
|
40
|
+
|
|
11
41
|
## [0.2.0-alpha.1] — 2026-07-10
|
|
12
42
|
|
|
13
43
|
CI-owned baselines. CI is now the sole writer of committed baselines. Local
|
|
@@ -249,7 +279,8 @@ styling or interactivity.
|
|
|
249
279
|
Initial public alpha. Tuffgal extracted from [Linklater](https://github.com/nschneble/linklater)'s
|
|
250
280
|
in-tree visual testing workspace.
|
|
251
281
|
|
|
252
|
-
[Unreleased]: https://github.com/nschneble/tuffgal/compare/v0.2.0-alpha.
|
|
282
|
+
[Unreleased]: https://github.com/nschneble/tuffgal/compare/v0.2.0-alpha.2...HEAD
|
|
283
|
+
[0.2.0-alpha.2]: https://github.com/nschneble/tuffgal/releases/tag/v0.2.0-alpha.2
|
|
253
284
|
[0.2.0-alpha.1]: https://github.com/nschneble/tuffgal/releases/tag/v0.2.0-alpha.1
|
|
254
285
|
[0.1.0-alpha.14]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.14
|
|
255
286
|
[0.1.0-alpha.13]: https://github.com/nschneble/tuffgal/releases/tag/v0.1.0-alpha.13
|
|
@@ -245,69 +245,118 @@ h2 {
|
|
|
245
245
|
color: var(--text-subtle);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
/* Each status total is a single-select filter styled as a
|
|
249
|
-
|
|
250
|
-
outside the button.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
(
|
|
248
|
+
/* Each status total is a single-select filter styled as a colorful, button-like
|
|
249
|
+
pill keyed to its status accent (aligned to the .chip idiom). Its sibling count
|
|
250
|
+
precedes it outside the button. Default (aria-pressed=false) is an OUTLINE pill:
|
|
251
|
+
transparent background, status-colored label + 1px status-colored border. Hover
|
|
252
|
+
adds a faint status tint; pressed becomes a FILLED pill (status background,
|
|
253
|
+
paired --*-fg label) with bold weight. The 1px solid status border on every
|
|
254
|
+
state guarantees a perceivable non-text edge (1.4.11); label/fg pairings clear
|
|
255
|
+
4.5:1 on --bg-elevated in both themes (1.4.3). Pressed is never color-alone
|
|
256
|
+
(1.4.1): it is the only filled + bold pill, readable without hue. Target size
|
|
257
|
+
≥24×24 via min-height/min-width (2.5.8). Stays a <button>: it filters in place,
|
|
258
|
+
never navigates. */
|
|
257
259
|
.summary-filter {
|
|
258
260
|
display: inline-flex;
|
|
259
261
|
align-items: center;
|
|
260
262
|
gap: 0.5rem;
|
|
261
263
|
min-height: 24px;
|
|
262
264
|
min-width: 24px;
|
|
263
|
-
padding: 0 0.
|
|
265
|
+
padding: 0.2rem 0.6rem;
|
|
264
266
|
line-height: 1.2;
|
|
265
|
-
|
|
266
|
-
border:
|
|
267
|
+
border: 1px solid var(--border-bright);
|
|
268
|
+
border-radius: 999px;
|
|
269
|
+
background: transparent;
|
|
267
270
|
color: var(--text);
|
|
268
271
|
font: inherit;
|
|
269
272
|
cursor: pointer;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
+
transition-property: scale, color, background-color, border-color;
|
|
274
|
+
transition-duration: 0.15s;
|
|
275
|
+
transition-timing-function: ease;
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
.summary-filter[aria-pressed='true'] {
|
|
276
279
|
cursor: default;
|
|
277
280
|
user-select: none;
|
|
281
|
+
font-weight: 600;
|
|
278
282
|
}
|
|
279
283
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
284
|
+
.summary-filter:active {
|
|
285
|
+
scale: 0.96;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Status pills: OUTLINE by default (status label + status border, transparent
|
|
289
|
+
bg), faint status tint on hover, FILLED on pressed (status bg + paired --*-fg
|
|
290
|
+
label). The "all" total stays NEUTRAL on every state: --accent equals --new, so
|
|
291
|
+
tinting "all" blue would falsely imply the "new" filter. Status label + border
|
|
292
|
+
clear their contrast floors on --bg-elevated in both themes; each status token
|
|
293
|
+
paired with its --*-fg fill clears 4.5:1. */
|
|
294
|
+
.summary-item[data-status='pass'] .summary-filter {
|
|
285
295
|
color: var(--pass);
|
|
296
|
+
border-color: var(--pass);
|
|
286
297
|
}
|
|
287
298
|
|
|
288
|
-
.summary-item[data-status='
|
|
289
|
-
|
|
299
|
+
.summary-item[data-status='pass'] .summary-filter:hover {
|
|
300
|
+
background: color-mix(in srgb, var(--pass) 15%, transparent);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.summary-item[data-status='pass'] .summary-filter[aria-pressed='true'] {
|
|
304
|
+
background: var(--pass);
|
|
305
|
+
color: var(--pass-fg);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.summary-item[data-status='changed'] .summary-filter {
|
|
290
309
|
color: var(--changed);
|
|
310
|
+
border-color: var(--changed);
|
|
291
311
|
}
|
|
292
312
|
|
|
293
|
-
.summary-item[data-status='
|
|
294
|
-
|
|
313
|
+
.summary-item[data-status='changed'] .summary-filter:hover {
|
|
314
|
+
background: color-mix(in srgb, var(--changed) 15%, transparent);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.summary-item[data-status='changed'] .summary-filter[aria-pressed='true'] {
|
|
318
|
+
background: var(--changed);
|
|
319
|
+
color: var(--changed-fg);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.summary-item[data-status='failed'] .summary-filter {
|
|
295
323
|
color: var(--failed);
|
|
324
|
+
border-color: var(--failed);
|
|
296
325
|
}
|
|
297
326
|
|
|
298
|
-
.summary-item[data-status='
|
|
299
|
-
|
|
327
|
+
.summary-item[data-status='failed'] .summary-filter:hover {
|
|
328
|
+
background: color-mix(in srgb, var(--failed) 15%, transparent);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.summary-item[data-status='failed'] .summary-filter[aria-pressed='true'] {
|
|
332
|
+
background: var(--failed);
|
|
333
|
+
color: var(--failed-fg);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.summary-item[data-status='new'] .summary-filter {
|
|
300
337
|
color: var(--new);
|
|
338
|
+
border-color: var(--new);
|
|
301
339
|
}
|
|
302
340
|
|
|
303
|
-
.summary-item[data-status='
|
|
304
|
-
|
|
341
|
+
.summary-item[data-status='new'] .summary-filter:hover {
|
|
342
|
+
background: color-mix(in srgb, var(--new) 15%, transparent);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.summary-item[data-status='new'] .summary-filter[aria-pressed='true'] {
|
|
346
|
+
background: var(--new);
|
|
347
|
+
color: var(--new-fg);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/* "all" stays NEUTRAL — never a status color, never blue. Pressed is a filled
|
|
351
|
+
neutral pill (it is pressed-by-default at load, so this is the resting view). */
|
|
352
|
+
.summary-item[data-status='all'] .summary-filter {
|
|
305
353
|
color: var(--text);
|
|
354
|
+
border-color: var(--border-bright);
|
|
306
355
|
}
|
|
307
356
|
|
|
308
|
-
.summary-filter
|
|
309
|
-
|
|
310
|
-
|
|
357
|
+
.summary-item[data-status='all'] .summary-filter[aria-pressed='true'] {
|
|
358
|
+
background: var(--bg);
|
|
359
|
+
color: var(--text);
|
|
311
360
|
}
|
|
312
361
|
|
|
313
362
|
.summary-filter:focus-visible {
|
|
@@ -315,6 +364,86 @@ h2 {
|
|
|
315
364
|
outline-offset: 2px;
|
|
316
365
|
}
|
|
317
366
|
|
|
367
|
+
/* Breakpoint filter — the SECOND, independent single-select filter dimension,
|
|
368
|
+
rendered below the status totals inside the summary region (only for
|
|
369
|
+
multi-breakpoint runs). A role="group" row of neutral pills; report.js ANDs it
|
|
370
|
+
with the status filter. The row sits on the page --bg with a top gap that also
|
|
371
|
+
guarantees ≥24px vertical spacing between the two pill rows (2.5.8). */
|
|
372
|
+
.breakpoint-filters {
|
|
373
|
+
display: flex;
|
|
374
|
+
flex-wrap: wrap;
|
|
375
|
+
gap: 0.5rem;
|
|
376
|
+
margin-top: 0.75rem;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Neutral pills — no status hue, so the two filter rows read as siblings by
|
|
380
|
+
geometry, not color. Same pill geometry/radius as the status filter.
|
|
381
|
+
Default is an OUTLINE pill: transparent bg, --text-muted border (≥3:1 vs --bg
|
|
382
|
+
in both themes → 1.4.11), --text label (≥4.5:1 → 1.4.3). Hover brightens the
|
|
383
|
+
border + adds a faint neutral tint. Pressed is a FILLED pill: --text fill,
|
|
384
|
+
--bg label, bold weight — the ONLY filled + bold pill, so the pressed state is
|
|
385
|
+
never conveyed by color alone (1.4.1); the inverse fill clears 4.5:1 in both
|
|
386
|
+
themes. Focus ring is --accent 2px / offset 2px, painted on the page --bg
|
|
387
|
+
outside the pill — identical to the status filter. Target size ≥24×24 via
|
|
388
|
+
min-height/min-width (2.5.8). Stays a <button>: it filters in place. */
|
|
389
|
+
.breakpoint-filter {
|
|
390
|
+
display: inline-flex;
|
|
391
|
+
align-items: center;
|
|
392
|
+
gap: 0.5rem;
|
|
393
|
+
min-height: 24px;
|
|
394
|
+
min-width: 24px;
|
|
395
|
+
padding: 0.2rem 0.6rem;
|
|
396
|
+
line-height: 1.2;
|
|
397
|
+
border: 1px solid var(--text-muted);
|
|
398
|
+
border-radius: 999px;
|
|
399
|
+
background: transparent;
|
|
400
|
+
color: var(--text);
|
|
401
|
+
font: inherit;
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
transition-property: scale, color, background-color, border-color;
|
|
404
|
+
transition-duration: 0.15s;
|
|
405
|
+
transition-timing-function: ease;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.breakpoint-filter .breakpoint-name {
|
|
409
|
+
color: inherit;
|
|
410
|
+
font-weight: inherit;
|
|
411
|
+
text-transform: none;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.breakpoint-filter .breakpoint-dimensions {
|
|
415
|
+
color: var(--text-subtle);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.breakpoint-filter:hover:not([aria-pressed='true']) {
|
|
419
|
+
border-color: var(--text);
|
|
420
|
+
background: color-mix(in srgb, var(--text) 8%, transparent);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.breakpoint-filter[aria-pressed='true'] {
|
|
424
|
+
cursor: default;
|
|
425
|
+
user-select: none;
|
|
426
|
+
font-weight: 600;
|
|
427
|
+
background: var(--text);
|
|
428
|
+
color: var(--bg);
|
|
429
|
+
border-color: var(--text);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* The pressed pill's dimensions ride the inverse fill: --bg on --text is the
|
|
433
|
+
same ≥4.5:1 pair as the label, so they stay legible. */
|
|
434
|
+
.breakpoint-filter[aria-pressed='true'] .breakpoint-dimensions {
|
|
435
|
+
color: color-mix(in srgb, var(--bg) 65%, var(--text));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.breakpoint-filter:active {
|
|
439
|
+
scale: 0.96;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.breakpoint-filter:focus-visible {
|
|
443
|
+
outline: 2px solid var(--accent);
|
|
444
|
+
outline-offset: 2px;
|
|
445
|
+
}
|
|
446
|
+
|
|
318
447
|
.chip {
|
|
319
448
|
display: inline-flex;
|
|
320
449
|
align-items: center;
|
|
@@ -801,7 +930,12 @@ summary.action-row:focus-visible {
|
|
|
801
930
|
sit in this fieldset, which lives OUTSIDE the clipped `.shot-stage`, so the
|
|
802
931
|
chip focus ring is never clipped by the stage overflow nor painted over
|
|
803
932
|
screenshot pixels. The chips reuse `.chip--toggle` (border `--border-bright`,
|
|
804
|
-
`--accent` focus ring)
|
|
933
|
+
`--accent` focus ring) and the same `.shot-radio-label` base styles as the
|
|
934
|
+
radio-tab viewer — permanently visible, no interactiveMode-specific chip
|
|
935
|
+
styling. Visible chips are the mouse/touch path to every variant (including
|
|
936
|
+
Diff, which the press gesture does not cover) and double as the viewer's
|
|
937
|
+
only visible affordance; the press-and-hold blink-compare in report.js is
|
|
938
|
+
enhancement on top. */
|
|
805
939
|
.shot-interactive {
|
|
806
940
|
display: flex;
|
|
807
941
|
flex-wrap: wrap;
|
|
@@ -812,38 +946,12 @@ summary.action-row:focus-visible {
|
|
|
812
946
|
border: 0;
|
|
813
947
|
}
|
|
814
948
|
|
|
815
|
-
/*
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
width: 1px;
|
|
822
|
-
height: 1px;
|
|
823
|
-
margin: -1px;
|
|
824
|
-
overflow: hidden;
|
|
825
|
-
clip: rect(0 0 0 0);
|
|
826
|
-
white-space: nowrap;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/* Reveal the whole chip group when a keyboard user focuses any radio.
|
|
830
|
-
:focus-within needs no :has() fallback and never fires for mouse/touch
|
|
831
|
-
(label clipped, input pointer-events:none → radios are keyboard-only
|
|
832
|
-
reachable). Specificity (0,3,0) beats the collapse rule (0,2,0). */
|
|
833
|
-
.shot-interactive:focus-within .shot-radio-label {
|
|
834
|
-
position: static;
|
|
835
|
-
width: auto;
|
|
836
|
-
height: auto;
|
|
837
|
-
margin: 0;
|
|
838
|
-
overflow: visible;
|
|
839
|
-
clip: auto;
|
|
840
|
-
white-space: normal;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/* Persistent caption naming the committed variant. Text on `--text` (≥4.5:1),
|
|
844
|
-
so the active variant is never conveyed by color alone — it pairs the checked
|
|
845
|
-
chip's accent border with this readable label. aria-hidden keeps it visual
|
|
846
|
-
only: the checked radio is the single accessible source of committed state. */
|
|
949
|
+
/* Persistent caption naming the DISPLAYED variant — the committed one, or the
|
|
950
|
+
press-and-hold preview while it is held (report.js keeps it in sync). Text on
|
|
951
|
+
`--text` (≥4.5:1), so the active variant is never conveyed by color alone —
|
|
952
|
+
it pairs the checked chip's accent border with this readable label.
|
|
953
|
+
aria-hidden keeps it visual only: the checked radio is the single accessible
|
|
954
|
+
source of committed state. */
|
|
847
955
|
.shot-caption {
|
|
848
956
|
margin: 0 0 0 0.25rem;
|
|
849
957
|
color: var(--text);
|
|
@@ -884,10 +992,14 @@ summary.action-row:focus-visible {
|
|
|
884
992
|
@media (prefers-reduced-motion: reduce) {
|
|
885
993
|
.skip-link,
|
|
886
994
|
summary.action-row::after,
|
|
887
|
-
.chip
|
|
995
|
+
.chip,
|
|
996
|
+
.summary-filter,
|
|
997
|
+
.breakpoint-filter {
|
|
888
998
|
transition: none;
|
|
889
999
|
}
|
|
890
|
-
.chip:active
|
|
1000
|
+
.chip:active,
|
|
1001
|
+
.summary-filter:active,
|
|
1002
|
+
.breakpoint-filter:active {
|
|
891
1003
|
scale: none;
|
|
892
1004
|
}
|
|
893
1005
|
}
|