Releases: BenSheridanEdwards/StyleProof
Release list
v4.6.2
Fixed
-
Content-length reflow no longer produces a blind visual-approval gate.
Captures now persist only each element's normalized own-text length, including
explicit zeroes (never its text), so the diff can distinguish content-driven
geometry drift from a real sizing-rule change. Geometry deltas paired with a
changed text length, or with a legacy map where length is unknown, now fail
closed asCERTIFICATION_FAILEDinstead of asking reviewers to approve an
empty or misleading CSS report; known same-length geometry remains reviewable. -
freezeClocknow pins the spec process's clock, not just the browser's.
A capture spec that computed a fixture at module level —
const GENERATED_AT = new Date().toISOString()— ran on the live Node clock,
outside the browser freeze, so each capture run baked its own wall clock into
the data it served. Any surface rendering that stamp as text drifted in width
between the base and head runs and was reported as computed-style changes on
PRs that never touched those surfaces. The in-run self-check cannot catch this
class: both of its captures share one spec process and therefore one stamp.
styleproof-mapnow setsSTYLEPROOF_FREEZE_SPEC_CLOCK=1for the Playwright
run it spawns, and importingstyleproofunder that env swapsglobalThis.Date
for a frozen twin (zero-argument construction,Date.now(), and bareDate()
report the fixed instant; explicit-argument construction,Date.parse/Date.UTC,
and instance methods stay real) before the spec's own constants evaluate.
The instant followsSTYLEPROOF_CLOCK_TIME(default2025-01-01T00:00:00Z,
matching the browser freeze);STYLEPROOF_FREEZE_SPEC_CLOCK=0or
freezeClock: falseopts out, and aclockTimethat disagrees with the
frozen instant is warned about rather than silently ignored. StyleProof's own
elapsed-time bookkeeping (settle windows, popup deadlines,createdAt
manifest stamps) reads the real clock throughout. -
styleproof-ci --spec-refnow overlays the spec's colocated test harness.
A head spec that imported a head-only fixture failed cold base capture with
Cannot find module, producing a degraded head-only receipt. The base render
now receives the head harness while application code and package metadata stay
pinned to the base commit, and every overlaid path is restored before head
capture. When the consumer command runs from a repository subdirectory, the
temporary harness allowance is normalized to repository-root coordinates so
git status --porcelaindoes not misclassify the intentional overlay as a
dirty base tree.
v4.6.1
Fixed
- Captures declare
prefers-reduced-motion: reducebefore navigation.
FREEZE_CSSonly reaches CSS-declared motion; JS animation libraries
(framer-motion, react-spring…) write inline styles from rAF loops that no
stylesheet can override — but they all honour reduced motion, and they read
it at mount. A short entrance animation (blur/opacity/transform) racing the
settle produced the "surface is non-deterministic — N computed-style
difference(s) between two captures of the same commit" self-check failure on
real consumer apps.captureSurfaceandcaptureUrlToDirnow emulate
reduced motion before the firstgo()/goto(persisting across every later
navigation in the flow), andcaptureStyleMapdeclares it defensively for
direct library users. Consumers whose components branch on reduced motion
will see a one-time baseline shift to the motionless final state — the
deterministic state a style gate should certify.
v4.6.0
Added
- Yarn Berry support in the cold path.
styleproof-cipinned yarn 1 for any
yarn.lock; on a Yarn 2+ repo that either refuses (thepackageManager
field) or cannot parse the lockfile, so every base-miss run failed. A
.yarnrc.ymlor apackageManager: "yarn@2+"pin now routes installs
through corepack (corepack yarn install --immutable), which provisions the
repo's own pinned release. styleproof.config.jsonwarns on unknown keys. A typo'ddirtyallowor
cache_branchwas silently dropped — exactly the "config the user wrote must
never be silently dropped" failure the validator forbids. Unknown top-level
andaffectedkeys now produce a loud stderr warning listing the known keys
(a warning, not an error, so a newer release's key doesn't brick older CLIs
during version skew).styleproof-map --helpalso now documents that
dirtyAllowaccumulates across config + env + flags instead of being
overridden.
Fixed
-
styleproof-cireads project config from the HEAD checkout, and each side
resolves its own spec. Config was loaded before the flow pinned the
consumer to--head— in the generated workflow that pre-checkout tree is
the PR merge commit — so a head that moved the spec via
styleproof.config.jsonran with the stale path and failed exit 2 with the
cause invisible. Config now loads after the head checkout, and when--spec
is not explicit, base-side probes and captures resolve the spec from the
BASE checkout's own config while head-side ones use the head's — a
config-only spec move now works end to end. -
styleproof-cifailure taxonomy tightened. A required upload's
map-store/network fault exited 2 — the code reserved for usage errors — so
exit-code triage retried the wrong thing; store faults now exit 5 (the same
retryable class as restore faults) while consumer-state preconditions (dirty
tree, missing manifest) keep 2, since retrying those can never succeed. A
failedstyleproof-map --restorespawn (ENOENT) now prints the underlying
error instead of "fault (exit null), re-run the job". Worktree residue from
hard kills is reclaimed: every session start runsgit worktree prune, and
SIGINT/SIGTERM now dispose live worktrees before exiting. -
styleproof-affected --rootworks from a monorepo.git diffprints
repo-root-relative paths, which the CLI resolved against--rootand matched
against a package-relative graph — every changed file was unplaceable, so
every monorepo invocation degraded to'all'. The--rootprefix is now
stripped when resolving git paths into the graph (files outside the package
still fail closed), andstyleproof.config.json,--graph, and
--surfacesnow load relative to--rootinstead of the invoking cwd. -
REPORT_PUBLICATION_FAILEDnow names its actual failure domain. The
Action's terminal trust step treated any non-successpublish outcome —
including a merely skipped step — as a publication failure, and conversely a
failed PR-comment or commit-status delivery after a successful publish
reported the diff verdict as if the reviewer had seen it. The trust state now
distinguishes publishfailure(→REPORT_PUBLICATION_FAILED), publish
skipped/cancelled (→ the diff verdict,CERTIFICATION_FAILEDwhen absent),
and delivery failure after a successful publish (→
REPORT_PUBLICATION_FAILED). The verdict'sDEGRADED_BASELINEcheck also
acceptsbase-capture-failedcase-insensitively, matching the downstream
GitHub-expression gate. -
--tolerate-surface-failuresno longer promotes unledgered failures. A
nonzero Playwright exit with maps on disk but NOTHING in the surface-failure
ledger (a self-check, nondeterminism, or harness failure) used to publish as
a "partial baseline"; promotion now requires at least one ledgered surface
failure, and anything else keeps the failing exit. Ledger filenames also
gained a content digest so two surface keys that sanitize to the same
filename can no longer clobber each other's entries. -
styleproof-cidiscards base-capture debris instead of keeping it as a
baseline. With ledgered failures now exiting 0, a nonzero base capture
exit means an UNtolerated failure — any maps left on disk have no
publishable manifest, and keeping them reported real regressions as
approvable "new surfaces". The base dir is cleared and the run proceeds as a
bare baseline withbase-capture-failed=true(DEGRADED_BASELINE). -
styleproof-init --check/--upgradenow treat both generated workflows as
marker-owned. The CI and approval workflow templates carry an ownership
marker (like the pre-push hook already did), so a consumer-authored
workflow at the same path reportsunmanagedand is never overwritten by
--upgrade, instead of being flagged stale and clobbered. -
The generated pre-push hook no longer bakes the default spec path. With
the default spec, the hook execs barestyleproof-prepush, which resolves
the spec at run time (flag > env >styleproof.config.json> built-in) — so
moving the spec via config alone can't strand a stale baked--spec. A
non-default--diris still baked explicitly. -
Cold-base captures resolve Playwright from the base worktree's own install.
styleproof-cibuilt PATH once from the consumer head'snode_modules/.bin,
so the base capture ran the head's Playwright CLI against the worktree's
@playwright/testlibrary — a version-mixing failure ("did not expect test()
to be called here") on exactly the PRs that bump rendering dependencies,
silently degrading them to a bare baseline. Base-side spawns now prepend the
cold-base worktree's ownnode_modules/.bin; head-side spawns keep the
consumer's. -
Symbolic
--spec-refvalues resolve in the consumer checkout. Inside the
detached base worktreeHEADis--base(so--spec-ref HEADsilently
overlaid the base's own spec — a no-op defeating the flag) and
FETCH_HEAD/MERGE_HEADare per-worktree pseudo-refs that don't resolve
there at all. The ref is now resolved to a commit SHA in the consumer
checkout before any worktree is entered, and an unresolvable ref fails loudly
up front even when the base restore would have hit. -
A derived-only change now renders as reviewable evidence instead of failing
closed with none. When a surface's only differences are size/position
longhands (width,inline-size,transform-origin, offsets…) — typically
content-length drift widening a text span, or a pure sizing rule change — the
noise-cleaning strip removed every finding, so 4.5.3's consistency guard
correctly refused to claim "identical" but classified the run
CERTIFICATION_FAILEDwith nothing for the reviewer to look at. Cleaning now
keeps a surface's findings when stripping would silence a gating change
(cleanFindingsForDisplay), titles them "size/position only, no styling
property changed (often content-length drift…)", and the truth contract counts
them reviewable — so the verdict isVISUAL_APPROVAL_REQUIREDwith rendered
evidence. The raw-onlyCERTIFICATION_FAILEDbackstop remains for shapes
that truly cannot render (state-strip-only deltas). Reflow casualties next to
a real driver fold exactly as before; exit codes are unchanged. -
Cached-map diffs now see the baseline's tolerated-failure ledger. The
no-arg/<baseRef>forms read the base manifest AFTER cached-restore cleanup
had deleted the directory, sosurfaceCaptureFailuresalways read empty and a
PARTIAL_BASELINErun silently degraded into approvable greenfield "new
surfaces" (exit 3). The manifest is now read while the restored dirs exist,
matching every other ledger read. -
A surface captured only on base renders as
REMOVED surface 🗑️, not "new
surface 🆕". The report/summary previously framed a disappearing surface as
an addition with approve-forward guidance; removals now get their own heading,
summary line ("approving accepts the disappearance"), andisRemovedin
report.json. -
A fresh capture clears any stale surface-capture-failures ledger in the
reused output dir (same hazard class as the browser-build sidecar): a failure
recorded by a prior run no longer stamps a phantom "partial baseline" into a
healthy recapture's manifest. -
"gateInventoryRemovals": falsenow reaches the verdict. The opt-out
previously only skipped the job-fail step while the classification and commit
status stayed an unclearable red; removals now downgrade to ordinary
approval-required changes when opted out. -
styleproof-diffandstyleproof-reportreadstyleproof.config.json
(spec,cacheBranch,remote) like every other CLI — without it, a repo
whose config moved the spec or store branch computed a different compatibility
key on the compare side than the capture side, and the no-arg diff (including
the pre-push advisory diff) always missed.
v4.5.3
Added
-
PARTIAL_BASELINEtrust state (#276). When the diff JSON reports
partialBaseline/explainedMissingBaselineSurfaces, the Action classifies
ledger-explained missing baseline surfaces as repair debt — after residue,
inventory, and certification failures, beforeDEGRADED_BASELINEand visual
approval.changedstays false for baseline-only gaps; mixed greenfield runs
still exit3butPARTIAL_BASELINEoutranksVISUAL_APPROVAL_REQUIRED
while explained gaps remain. Commit status and job block fail with repair-base
guidance; the approval box cannot clear this state. -
styleproof-ciephemeral worktrees (#277). Restore probes and cold base
install/capture run in detached git worktrees underRUNNER_TEMP(or the OS temp
dir), so the consumer checkout is never checked out to--base. Head capture may
still run in the consumer at--head. Worktrees are always torn down on success
and failure; invalid SHAs fail loudly before capture. -
Canonical comparison truth (
assessComparisonTruth). Diff JSON, durable
report, PR comment, and trust state share one assessment of raw vs reviewable
findings so they cannot invent disagreeing verdicts.
Fixed
-
Baseline
@autocrawl failures match head capture widths in diff/report (#276).
Viewport-detection failures recorded assurface@autonow classify head-only
surfaces as broken baseline capture (not greenfield new) for any width of that
exact surface key; width-specific ledger entries stay exact (about@1280does
not matchabout@900). Diff exit3applies only to genuinely new surfaces.
Report Markdown escapes inline injection in failure reasons; manifest JSON keeps
the original failure ledger keys. -
Partial baseline capture on the base branch (#276).
styleproof-map --tolerate-surface-failures(andstyleproof-cion cold base capture only)
records per-surface failures in the manifest, publishes a partial bundle when at
least one map succeeds, and leaves self-check / zero-map / head capture
fail-closed. Diff and report surface repair-base warnings instead of treating
ledger-explained surfaces as greenfield new. -
Report headline and global-chrome banners now count surface bases consistently.
The summary line reports unique changed product surface bases and, when more
capture keys exist than bases, adds a labeled variant count. Product bases prefer
authoritative capturemetadata.surfaceKey(live-state and popup expansions share
their declared base); older captures without metadata still strip only@width.
The global-chrome tier names captured surface bases (not raw variant keys). A
one-line glossary beside the headline explains@widthand state/popup keys as
variants of a base. -
Diff/report/verdict coherence for derived-only (reflow) deltas.
styleproof-diffcan report raw computed-style differences while
generateStyleMapReportstrips size/position longhands and previously claimed
"all surfaces identical" with no crops. That pair must never become
VISUAL_APPROVAL_REQUIRED(no reviewable evidence). Both CLIs now emit
reviewableCounts+reportConsistency; raw-only noise fails closed as
CERTIFICATION_FAILED(action gate + status), the report headline names the
consistency failure instead of "identical", and approval UI stays off.
v4.5.2
Fixed
styleproof-ci --spec-refnow works from a repo subdirectory. The overlay's
<rev>:<path>lookups resolved the spec from the repo root, so a workflow
running the CLI withworking-directory: hudand--spec tests/e2e/….spec.ts
failed with a false "missing at --spec-ref" even though the file exists (hit by
a consumer's 4.5.1 migration on its first cold capture). The lookups now use git's
cwd-relative<rev>:./<path>form, matching how the same spec path is used by
every pathspec call and filesystem write in the overlay.
v4.5.1
Added
styleproof-ci --spec-ref <ref>: on a cold base capture, when the base commit
already contains the configured--specpath, overlay that file's bytes from
<ref>:<spec>for the base render only (app source and lockfile stay at--base).
The overlay is hidden from the dirty-tree gate with a narrowly scoped
assume-unchangedindex flag and is always cleared before the head checkout,
including after a failed base capture. Omitted--spec-refpreserves 4.5.0 behavior.
Invalid refs, missing specs at the ref, or absolute/out-of-repo spec paths fail loudly.
v4.5.0
Added
-
styleproof.config.jsonis now the single project-config surface. Every
CLI readsspec,dirtyAllow,cacheBranch,remote, and
affected.{surfaces,graph,base}from it as the lowest-precedence default
layer (flag > env > file > built-in), alongside the Action's existing
gate-policy keys. A configured repo runsstyleproof-affectedbare, and
dirty-allow paths live in config instead of being threaded through hook and
workflow invocations. A malformed file or wrongly-typed key fails loudly. -
styleproof-init --upgradeand--check. The generated pre-push hook,
report workflow, and approval workflow are machine-owned thin wrappers;
--checkreports drift against the current release's templates without
writing (exit 1 — CI-able), and--upgraderefreshes them in place, never
touching the user-owned spec or playwright config. -
styleproof-ci— the cache-first CI orchestration as one command:
--base <sha> --head <sha>restores both exact-SHA bundles, captures just the
head on a head-only miss (HAR replay when the base recorded data), and rebuilds
the pair cold on a base miss under the head's exact StyleProof release, with
package-manager commands detected independently at each checkout. A failed
base capture now produces explicit head-only degraded evidence while head
capture remains fail-closed. The
init-generated workflow step is now a single invocation instead of ~80 lines of
copied bash, and emitsbase-hit/head-hit/capture-needed/
base-capture-failed. -
styleproof-prepush— the canonical pre-push capture→publish flow,
packaged. The generated hook is a two-line shim that execs it, so the rules
(pushed-refspec selection, docs-only skip, restore-before-capture) update with
each release instead of drifting in a copied hook file. The shim invokes the
installed local binary directly, so a missing install fails instead of falling
through to a package-registry download, and
styleproof-init --hookrefreshes a stale hook in place. -
styleproof-affected— the selective-remap verdict as a CLI over
affectedSurfaces: dependency-cruiser graph in, changed files from git (or
--changed), reviewer-checkable skip list and--jsonverdict out; exit0
scoped /3unbounded. -
--dirty-allow <path>onstyleproof-map(andSTYLEPROOF_DIRTY_ALLOW) —
tracked files a dev tool rewrites on every run (e.g.next devregenerating a
tsconfig.json) no longer mark a capture dirty, generalizing the built-in
next-env.d.tsallowance. -
The Action self-verifies its published report. After pushing the report
branch it reads the report back at the exact advertised commit and requires the
embedded receipt to name this run's head SHA, run id, and attempt — failing
closed instead of shipping a green run with a dead or stalereport-url, so
consumers can drop their own post-action read-back checks. -
Map-store dogfood runs the production roundtrip on every same-repo PR. A
real browser capture is published to a per-attempt scratch branch, restored by
exact SHA, certified byte-identical, and checked for the exit-4 miss contract.
Cleanup distinguishes an absent branch from an inspection or deletion fault,
so leaked scratch branches cannot hide behind a green run.
Fixed
-
Restore faults no longer masquerade as cache misses.
styleproof-prepush
exits with the persistent map-store/network failure instead of attempting an
unrelated capture, and fallback captures now preserve explicit map/base dirs. -
styleproof-init --upgradepreserves repository-owned pre-push hooks. It
only refreshes hooks carrying StyleProof's ownership marker; replacement of a
custom hook still requires the explicit--hookcommand. -
Action configuration now fails loudly through the shared config loader.
Malformed policy is no longer warned about and silently replaced with defaults. -
Generated pre-push hooks execute the installed local binary. Missing
installs can no longer fall through to a registry lookup for an unpublished
package name. -
Every Action run now publishes a durable report receipt. Clean comparisons
commit the no-change report and return its immutable URL, so consumers can
verify publication for every completed run instead of treating missing
evidence as a clean verdict. -
The consumer-checkout publish fallback now works from a partial isolated clone.
The isolated map-store checkout is a blob:none partial clone, and Git refuses to
lazy-fetch missing objects while serving a local fetch, so importing the new
commit into the consumer checkout failed with "protocol error: bad pack header"
whenever the store already held other bundles. The consumer now fetches the
branch tip through its own credentials first, which narrows the import to the
newly committed objects, and the fallback push fully qualifies its destination
ref so it can also create the branch on first publish. -
Explicit workflow authentication no longer duplicates the checkout header.
StyleProof clears any inherited GitHub HTTP authorization value before adding
STYLEPROOF_MAP_STORE_TOKEN, preventing GitHub from rejecting cold-cache
clones withDuplicate header: "Authorization"while retaining explicit-token
precedence over stale checkout credentials.
v4.4.22
Fixed
- The compatibility key no longer depends on how
cwdwas spelled. A
relativecwdmade the Playwright-version probe throw internally, silently
dropping that field from the key — so a publish and a restore in the same
environment could stamp different keys, and every cache lookup missed
(a silent full-recapture tax, never an error). The key inputs now resolve
cwdfirst, and a regression test pins relative/absolute equality.
v4.4.21
Fixed
- GitHub Actions map publication no longer sends duplicate authorization headers.
StyleProof now resets inherited checkout headers before applying the single
effective credential to isolated map-store clone and push operations.
v4.4.20
Fixed
- Map-store network operations can no longer wedge CI indefinitely. Remote
lookup, clone, and push commands now have a bounded timeout, and a stalled
isolated push falls through to the authenticated consumer checkout path.