fxcss 0.16.2__tar.gz → 0.18.0__tar.gz
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.
- {fxcss-0.16.2 → fxcss-0.18.0}/PKG-INFO +35 -6
- {fxcss-0.16.2 → fxcss-0.18.0}/README.md +34 -5
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/__init__.py +1 -1
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/cli.py +29 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/firefox-watch.yml +69 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/pr-preview.yml +49 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/tweaks.py +48 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/PKG-INFO +35 -6
- {fxcss-0.16.2 → fxcss-0.18.0}/tests/test_units.py +70 -2
- {fxcss-0.16.2 → fxcss-0.18.0}/LICENSE +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/__main__.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/adopt.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/audit.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/catalogue.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/compare.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/complete.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/core.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/fetch.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/install.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/probe.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/scaffold.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/sheets.py +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/pr-preview-cleanup.yml +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/pr-preview-publish.yml +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/readme-previews.yml +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/showcase.yml +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/starter/chrome/userChrome.css +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss/templates/starter/custom/accent-red.css +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/SOURCES.txt +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/dependency_links.txt +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/entry_points.txt +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/requires.txt +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/fxcss.egg-info/top_level.txt +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/pyproject.toml +0 -0
- {fxcss-0.16.2 → fxcss-0.18.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxcss
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Live-reload, inspect and screenshot-test Firefox userChrome.css themes
|
|
5
5
|
Author: AdamXweb
|
|
6
6
|
License: MIT
|
|
@@ -122,7 +122,7 @@ the [releases page](https://github.com/AdamXweb/fxcss/releases) has the latest.
|
|
|
122
122
|
CI runners' Pythons are not externally managed, so plain pip is fine there:
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
pip install "fxcss[images]==0.
|
|
125
|
+
pip install "fxcss[images]==0.18.0"
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Either gives you an `fxcss` command. To hack on it, clone and install editable:
|
|
@@ -322,9 +322,11 @@ screenshotting a `--combo` that cannot take effect.
|
|
|
322
322
|
> fighting over the same property. Two sheets that rearrange the same area
|
|
323
323
|
> through *different* selectors — WhiteSur's `tabs-swapclose` and
|
|
324
324
|
> `windows-swapclose` both move a close button and share no declarations at
|
|
325
|
-
> all — are invisible to it. `fxcss tweaks --combo a+b`
|
|
326
|
-
>
|
|
327
|
-
> is
|
|
325
|
+
> all — are invisible to it. `fxcss tweaks --combo a+b` proves those from
|
|
326
|
+
> the rendered pixels — and says so itself: a combo that renders identically
|
|
327
|
+
> to one of its parts alone is called out in TWEAKS.md as not a real
|
|
328
|
+
> combination on that Firefox. Silence from the static check is "nothing
|
|
329
|
+
> measurable", never "verified compatible"; the pixels are the judge.
|
|
328
330
|
|
|
329
331
|
When the theme's default branch has moved on since its newest release, that
|
|
330
332
|
choice is put to you as well — a tag can be a year behind a fix you are
|
|
@@ -369,6 +371,7 @@ Restart Firefox after either command; it reads `userChrome.css` at startup.
|
|
|
369
371
|
fxcss upgrade # take the newest version of what you have
|
|
370
372
|
fxcss upgrade --check # report only; exit code says what it found
|
|
371
373
|
fxcss upgrade --audit # check the new version against your Firefox first
|
|
374
|
+
fxcss upgrade --compare # see it before deciding: installed vs new, rendered
|
|
372
375
|
fxcss upgrade --ref v2.1.0 # somewhere specific
|
|
373
376
|
fxcss rollback # …and back again
|
|
374
377
|
```
|
|
@@ -391,6 +394,15 @@ It stops rather than surprise you, in three places:
|
|
|
391
394
|
which runs the same check as [`fxcss audit`](#fxcss-audit) against the
|
|
392
395
|
fetched copy *before* anything is installed.
|
|
393
396
|
|
|
397
|
+
`--compare` renders what is installed and what the upgrade would install —
|
|
398
|
+
the profile's own `chrome/`, local edits and carried-over sheets included, not
|
|
399
|
+
a re-fetch of what the manifest says — and diffs them before the confirmation
|
|
400
|
+
prompt, printing where the before/after images landed. It completes the three
|
|
401
|
+
questions an upgrade can answer about a theme with no API: do the selectors
|
|
402
|
+
still exist (`--audit`), has the user edited anything (always checked), and
|
|
403
|
+
what actually changes on screen (`--compare`). Needs Pillow and a local
|
|
404
|
+
Firefox, like `shot`.
|
|
405
|
+
|
|
394
406
|
`--check` changes nothing and answers with its exit code, for cron, launchd or
|
|
395
407
|
CI: **0** up to date, **1** an upgrade is available, **2** it cannot be told
|
|
396
408
|
(no install here, an unreachable repo, or a manifest too old to say what it
|
|
@@ -681,6 +693,16 @@ overwritten without `--force`, and the output tells you the things that
|
|
|
681
693
|
otherwise surprise people (the comment starts after the workflows reach your
|
|
682
694
|
default branch; first-time contributors need one approval click).
|
|
683
695
|
|
|
696
|
+
Each render captures the base branch and the pull request and diffs them. The
|
|
697
|
+
base half is cached: its captures are deterministic — that is the contract
|
|
698
|
+
fxcss's own CI asserts — so after a pull request's first push, later pushes
|
|
699
|
+
render only the change. The key names everything that could alter a pixel
|
|
700
|
+
(base commit, runner image, Firefox version, fxcss version, the workflow file
|
|
701
|
+
itself), and a miss on any of them re-renders rather than falling back to a
|
|
702
|
+
near match. GitHub scopes caches to the branch that made them, so this helps
|
|
703
|
+
the second push onward, not the first: a pull request that takes six pushes
|
|
704
|
+
to land renders the base once, not six times.
|
|
705
|
+
|
|
684
706
|
If you'd like people to know:
|
|
685
707
|
|
|
686
708
|
[](https://github.com/AdamXweb/fxcss)
|
|
@@ -699,6 +721,13 @@ renders the answer instead: the base theme, every optional stylesheet, and any
|
|
|
699
721
|
combination you bless with `--combo`, each with a labelled **before/after crop
|
|
700
722
|
of the region it actually changes** and how much of the chrome it touches.
|
|
701
723
|
|
|
724
|
+
A `--combo` is also judged against its own parts: if `a+b` renders
|
|
725
|
+
pixel-identically to `b` alone, then `a` did nothing in that combination, and
|
|
726
|
+
TWEAKS.md says so rather than presenting the pair as a real option. That is
|
|
727
|
+
the check the static conflict analysis in `install` cannot make — two sheets
|
|
728
|
+
can fight over the same pixels through entirely different rules — and the
|
|
729
|
+
rendered images settle it as a fact rather than a judgement.
|
|
730
|
+
|
|
702
731
|
The crop is built from the changed pixels, so it needs no per-option
|
|
703
732
|
configuration and cannot drift when Firefox moves something. It centres on the
|
|
704
733
|
busiest *cluster* of changes rather than the bounding box of all of them,
|
|
@@ -1013,7 +1042,7 @@ check out the base revision and the pull request revision, render both, compare,
|
|
|
1013
1042
|
and publish the result.
|
|
1014
1043
|
|
|
1015
1044
|
```yaml
|
|
1016
|
-
- run: pip install "fxcss[images]==0.
|
|
1045
|
+
- run: pip install "fxcss[images]==0.18.0" # pin: your CI, your upgrades
|
|
1017
1046
|
- run: fxcss shot --theme base --out shots/base
|
|
1018
1047
|
- run: fxcss shot --theme head --out shots/head
|
|
1019
1048
|
- run: fxcss compare --base shots/base --head shots/head --out out/ --platform ${{ runner.os }}
|
|
@@ -99,7 +99,7 @@ the [releases page](https://github.com/AdamXweb/fxcss/releases) has the latest.
|
|
|
99
99
|
CI runners' Pythons are not externally managed, so plain pip is fine there:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
pip install "fxcss[images]==0.
|
|
102
|
+
pip install "fxcss[images]==0.18.0"
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
Either gives you an `fxcss` command. To hack on it, clone and install editable:
|
|
@@ -299,9 +299,11 @@ screenshotting a `--combo` that cannot take effect.
|
|
|
299
299
|
> fighting over the same property. Two sheets that rearrange the same area
|
|
300
300
|
> through *different* selectors — WhiteSur's `tabs-swapclose` and
|
|
301
301
|
> `windows-swapclose` both move a close button and share no declarations at
|
|
302
|
-
> all — are invisible to it. `fxcss tweaks --combo a+b`
|
|
303
|
-
>
|
|
304
|
-
> is
|
|
302
|
+
> all — are invisible to it. `fxcss tweaks --combo a+b` proves those from
|
|
303
|
+
> the rendered pixels — and says so itself: a combo that renders identically
|
|
304
|
+
> to one of its parts alone is called out in TWEAKS.md as not a real
|
|
305
|
+
> combination on that Firefox. Silence from the static check is "nothing
|
|
306
|
+
> measurable", never "verified compatible"; the pixels are the judge.
|
|
305
307
|
|
|
306
308
|
When the theme's default branch has moved on since its newest release, that
|
|
307
309
|
choice is put to you as well — a tag can be a year behind a fix you are
|
|
@@ -346,6 +348,7 @@ Restart Firefox after either command; it reads `userChrome.css` at startup.
|
|
|
346
348
|
fxcss upgrade # take the newest version of what you have
|
|
347
349
|
fxcss upgrade --check # report only; exit code says what it found
|
|
348
350
|
fxcss upgrade --audit # check the new version against your Firefox first
|
|
351
|
+
fxcss upgrade --compare # see it before deciding: installed vs new, rendered
|
|
349
352
|
fxcss upgrade --ref v2.1.0 # somewhere specific
|
|
350
353
|
fxcss rollback # …and back again
|
|
351
354
|
```
|
|
@@ -368,6 +371,15 @@ It stops rather than surprise you, in three places:
|
|
|
368
371
|
which runs the same check as [`fxcss audit`](#fxcss-audit) against the
|
|
369
372
|
fetched copy *before* anything is installed.
|
|
370
373
|
|
|
374
|
+
`--compare` renders what is installed and what the upgrade would install —
|
|
375
|
+
the profile's own `chrome/`, local edits and carried-over sheets included, not
|
|
376
|
+
a re-fetch of what the manifest says — and diffs them before the confirmation
|
|
377
|
+
prompt, printing where the before/after images landed. It completes the three
|
|
378
|
+
questions an upgrade can answer about a theme with no API: do the selectors
|
|
379
|
+
still exist (`--audit`), has the user edited anything (always checked), and
|
|
380
|
+
what actually changes on screen (`--compare`). Needs Pillow and a local
|
|
381
|
+
Firefox, like `shot`.
|
|
382
|
+
|
|
371
383
|
`--check` changes nothing and answers with its exit code, for cron, launchd or
|
|
372
384
|
CI: **0** up to date, **1** an upgrade is available, **2** it cannot be told
|
|
373
385
|
(no install here, an unreachable repo, or a manifest too old to say what it
|
|
@@ -658,6 +670,16 @@ overwritten without `--force`, and the output tells you the things that
|
|
|
658
670
|
otherwise surprise people (the comment starts after the workflows reach your
|
|
659
671
|
default branch; first-time contributors need one approval click).
|
|
660
672
|
|
|
673
|
+
Each render captures the base branch and the pull request and diffs them. The
|
|
674
|
+
base half is cached: its captures are deterministic — that is the contract
|
|
675
|
+
fxcss's own CI asserts — so after a pull request's first push, later pushes
|
|
676
|
+
render only the change. The key names everything that could alter a pixel
|
|
677
|
+
(base commit, runner image, Firefox version, fxcss version, the workflow file
|
|
678
|
+
itself), and a miss on any of them re-renders rather than falling back to a
|
|
679
|
+
near match. GitHub scopes caches to the branch that made them, so this helps
|
|
680
|
+
the second push onward, not the first: a pull request that takes six pushes
|
|
681
|
+
to land renders the base once, not six times.
|
|
682
|
+
|
|
661
683
|
If you'd like people to know:
|
|
662
684
|
|
|
663
685
|
[](https://github.com/AdamXweb/fxcss)
|
|
@@ -676,6 +698,13 @@ renders the answer instead: the base theme, every optional stylesheet, and any
|
|
|
676
698
|
combination you bless with `--combo`, each with a labelled **before/after crop
|
|
677
699
|
of the region it actually changes** and how much of the chrome it touches.
|
|
678
700
|
|
|
701
|
+
A `--combo` is also judged against its own parts: if `a+b` renders
|
|
702
|
+
pixel-identically to `b` alone, then `a` did nothing in that combination, and
|
|
703
|
+
TWEAKS.md says so rather than presenting the pair as a real option. That is
|
|
704
|
+
the check the static conflict analysis in `install` cannot make — two sheets
|
|
705
|
+
can fight over the same pixels through entirely different rules — and the
|
|
706
|
+
rendered images settle it as a fact rather than a judgement.
|
|
707
|
+
|
|
679
708
|
The crop is built from the changed pixels, so it needs no per-option
|
|
680
709
|
configuration and cannot drift when Firefox moves something. It centres on the
|
|
681
710
|
busiest *cluster* of changes rather than the bounding box of all of them,
|
|
@@ -990,7 +1019,7 @@ check out the base revision and the pull request revision, render both, compare,
|
|
|
990
1019
|
and publish the result.
|
|
991
1020
|
|
|
992
1021
|
```yaml
|
|
993
|
-
- run: pip install "fxcss[images]==0.
|
|
1022
|
+
- run: pip install "fxcss[images]==0.18.0" # pin: your CI, your upgrades
|
|
994
1023
|
- run: fxcss shot --theme base --out shots/base
|
|
995
1024
|
- run: fxcss shot --theme head --out shots/head
|
|
996
1025
|
- run: fxcss compare --base shots/base --head shots/head --out out/ --platform ${{ runner.os }}
|
|
@@ -1048,6 +1048,31 @@ def cmd_upgrade(args):
|
|
|
1048
1048
|
"upgrade anyway.", file=sys.stderr)
|
|
1049
1049
|
return 1
|
|
1050
1050
|
|
|
1051
|
+
if args.compare:
|
|
1052
|
+
# Seeing beats being told: render what is installed and what the
|
|
1053
|
+
# upgrade would install, and diff them, before the question is
|
|
1054
|
+
# asked. The profile itself is the theme root for the installed
|
|
1055
|
+
# side -- its chrome/ IS the theme as the user actually has it,
|
|
1056
|
+
# local edits and carried-over sheets included, which no re-fetch
|
|
1057
|
+
# of the recorded ref could reproduce.
|
|
1058
|
+
try:
|
|
1059
|
+
from . import compare as compare_mod
|
|
1060
|
+
except ImportError as exc:
|
|
1061
|
+
return _needs_pillow("upgrade --compare", exc)
|
|
1062
|
+
import tempfile
|
|
1063
|
+
firefox = choose_firefox(args.firefox)
|
|
1064
|
+
outdir = Path(tempfile.mkdtemp(prefix="fxcss-upgrade-compare-"))
|
|
1065
|
+
print("\n rendering what is installed now …")
|
|
1066
|
+
with core.Session(Path(picked["path"]), firefox) as session:
|
|
1067
|
+
core.capture_views(session, outdir / "installed")
|
|
1068
|
+
print(f"\n rendering {why} …")
|
|
1069
|
+
with core.Session(theme_root, firefox) as session:
|
|
1070
|
+
core.capture_views(session, outdir / "new")
|
|
1071
|
+
print()
|
|
1072
|
+
compare_mod.run(outdir / "installed", outdir / "new",
|
|
1073
|
+
outdir / "diff", "upgrade")
|
|
1074
|
+
print(f" before/after images: {outdir / 'diff'}")
|
|
1075
|
+
|
|
1051
1076
|
if install.firefox_running(picked["path"]):
|
|
1052
1077
|
print("\n note: Firefox appears to be running — the change will "
|
|
1053
1078
|
"only show after a restart.")
|
|
@@ -1778,6 +1803,10 @@ def build_parser():
|
|
|
1778
1803
|
help="report only, changing nothing. Exit 0 when up to "
|
|
1779
1804
|
"date, 1 when an upgrade is available, 2 when it "
|
|
1780
1805
|
"cannot be told — for cron and CI")
|
|
1806
|
+
up.add_argument("--compare", action="store_true",
|
|
1807
|
+
help="render the installed theme and the new version, "
|
|
1808
|
+
"and show what changes, before asking (needs Pillow "
|
|
1809
|
+
"and a local Firefox)")
|
|
1781
1810
|
up.add_argument("--audit", action="store_true",
|
|
1782
1811
|
help="before installing, check the new version's "
|
|
1783
1812
|
"selectors against a real Firefox")
|
|
@@ -91,6 +91,74 @@ jobs:
|
|
|
91
91
|
echo "version=${version:-unknown}" >> "$GITHUB_OUTPUT"
|
|
92
92
|
if [ -s fxcss-fix.patch ]; then echo "patch=yes" >> "$GITHUB_OUTPUT"; fi
|
|
93
93
|
|
|
94
|
+
# The audit sees selectors; it does not see the capture pipeline. When
|
|
95
|
+
# Firefox 154 moved a module out from under fxcss, every preview render
|
|
96
|
+
# broke on release day while a (then healthy) audit-only watch would
|
|
97
|
+
# have stayed green -- beta had the breakage four days early and nothing
|
|
98
|
+
# could say so. One standard capture per channel is the canary for that:
|
|
99
|
+
# if this fails on beta, PR previews fail when that Firefox reaches
|
|
100
|
+
# release, and the usual fix is an fxcss update rather than a theme one.
|
|
101
|
+
- name: Capture smoke
|
|
102
|
+
id: smoke
|
|
103
|
+
if: ${{ github.event.inputs.channel == '' || github.event.inputs.channel == matrix.channel }}
|
|
104
|
+
run: |
|
|
105
|
+
set +e
|
|
106
|
+
fxcss shot --out "$RUNNER_TEMP/smoke" > smoke.txt 2>&1
|
|
107
|
+
status=$?
|
|
108
|
+
tail -40 smoke.txt
|
|
109
|
+
echo "status=$status" >> "$GITHUB_OUTPUT"
|
|
110
|
+
|
|
111
|
+
- name: Open or update the capture-pipeline issue
|
|
112
|
+
if: steps.smoke.outputs.status != '0'
|
|
113
|
+
env:
|
|
114
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
115
|
+
CHANNEL: ${{ matrix.channel }}
|
|
116
|
+
VERSION: ${{ steps.audit.outputs.version }}
|
|
117
|
+
run: |
|
|
118
|
+
set -euo pipefail
|
|
119
|
+
title="Firefox ${CHANNEL}: the capture pipeline broke"
|
|
120
|
+
{
|
|
121
|
+
echo "Rendering this theme against Firefox **${CHANNEL}** (**${VERSION}**) crashed"
|
|
122
|
+
echo "outright -- not a styling regression, the capture itself. When this"
|
|
123
|
+
echo "Firefox reaches release, the PR preview and README preview workflows"
|
|
124
|
+
echo "will fail the same way."
|
|
125
|
+
echo
|
|
126
|
+
echo "This is usually Firefox moving something out from under fxcss (as"
|
|
127
|
+
echo "Firefox 154 did), so the fix is typically a newer fxcss release and a"
|
|
128
|
+
echo "pin bump here -- check https://github.com/AdamXweb/fxcss/releases and"
|
|
129
|
+
echo "open an issue there if nothing newer exists."
|
|
130
|
+
echo
|
|
131
|
+
echo "The last lines of the failed capture:"
|
|
132
|
+
echo
|
|
133
|
+
echo '```'
|
|
134
|
+
tail -40 smoke.txt
|
|
135
|
+
echo '```'
|
|
136
|
+
echo
|
|
137
|
+
echo "_Updated automatically. This closes itself when the channel is clean._"
|
|
138
|
+
} > smoke-body.md
|
|
139
|
+
existing=$(gh issue list --state open --search "$title in:title" \
|
|
140
|
+
--json number,title --jq "[.[] | select(.title == \"$title\")][0].number")
|
|
141
|
+
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
|
|
142
|
+
gh issue edit "$existing" --body-file smoke-body.md
|
|
143
|
+
else
|
|
144
|
+
gh issue create --title "$title" --body-file smoke-body.md
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
- name: Close the capture-pipeline issue if this channel is clean again
|
|
148
|
+
if: steps.smoke.outputs.status == '0'
|
|
149
|
+
env:
|
|
150
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
151
|
+
CHANNEL: ${{ matrix.channel }}
|
|
152
|
+
run: |
|
|
153
|
+
set -euo pipefail
|
|
154
|
+
title="Firefox ${CHANNEL}: the capture pipeline broke"
|
|
155
|
+
existing=$(gh issue list --state open --search "$title in:title" \
|
|
156
|
+
--json number,title --jq "[.[] | select(.title == \"$title\")][0].number")
|
|
157
|
+
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
|
|
158
|
+
gh issue close "$existing" \
|
|
159
|
+
--comment "Firefox ${CHANNEL} captures cleanly again -- closing."
|
|
160
|
+
fi
|
|
161
|
+
|
|
94
162
|
- name: Open a pull request with the confident fixes
|
|
95
163
|
if: steps.audit.outputs.status != '0' && steps.audit.outputs.patch == 'yes'
|
|
96
164
|
env:
|
|
@@ -182,6 +250,7 @@ jobs:
|
|
|
182
250
|
name: audit-${{ matrix.channel }}
|
|
183
251
|
path: |
|
|
184
252
|
audit.txt
|
|
253
|
+
smoke.txt
|
|
185
254
|
fxcss-fix.patch
|
|
186
255
|
if-no-files-found: ignore
|
|
187
256
|
retention-days: 30
|
|
@@ -122,9 +122,58 @@ jobs:
|
|
|
122
122
|
- name: Environment
|
|
123
123
|
run: fxcss doctor --theme head
|
|
124
124
|
|
|
125
|
+
# The base branch renders identically every time -- that determinism is
|
|
126
|
+
# the contract fxcss's own CI asserts -- so after the first push to a
|
|
127
|
+
# pull request there is no reason to render it again. The key names
|
|
128
|
+
# everything that could change the pixels: the base commit, the runner
|
|
129
|
+
# image, the Firefox that happened to be current, the fxcss release and
|
|
130
|
+
# this workflow file. A miss on any of them re-renders; there is no
|
|
131
|
+
# restore-keys fallback, because a near-match is a different render.
|
|
132
|
+
#
|
|
133
|
+
# GitHub scopes caches to the branch that made them, so this helps the
|
|
134
|
+
# second and later pushes of a pull request, not its first.
|
|
135
|
+
- name: Base render cache key
|
|
136
|
+
id: basekey
|
|
137
|
+
shell: bash
|
|
138
|
+
run: |
|
|
139
|
+
firefox=$(python - <<'EOF'
|
|
140
|
+
import configparser, os, pathlib
|
|
141
|
+
binary = pathlib.Path(os.environ["FIREFOX_BIN"])
|
|
142
|
+
for ini in (binary.parent / "application.ini",
|
|
143
|
+
binary.parent.parent / "Resources" / "application.ini"):
|
|
144
|
+
if ini.is_file():
|
|
145
|
+
cfg = configparser.ConfigParser(); cfg.read(ini, encoding="utf-8")
|
|
146
|
+
print(cfg.get("App", "Version", fallback="unknown")); break
|
|
147
|
+
else:
|
|
148
|
+
print("unknown")
|
|
149
|
+
EOF
|
|
150
|
+
)
|
|
151
|
+
echo "firefox=$firefox" >> "$GITHUB_OUTPUT"
|
|
152
|
+
echo "image=${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT"
|
|
153
|
+
echo "base render key: firefox=$firefox image=${ImageVersion:-unknown}"
|
|
154
|
+
|
|
155
|
+
- name: Restore base render
|
|
156
|
+
id: basecache
|
|
157
|
+
uses: actions/cache/restore@v5
|
|
158
|
+
with:
|
|
159
|
+
path: shots/base
|
|
160
|
+
key: >-
|
|
161
|
+
base-render-${{ matrix.label }}-${{ github.event.pull_request.base.sha }}-ff${{ steps.basekey.outputs.firefox }}-img${{ steps.basekey.outputs.image }}-fxcss${{ env.FXCSS_VERSION }}-${{ hashFiles('head/.github/workflows/pr-preview.yml') }}
|
|
162
|
+
|
|
125
163
|
- name: Render base revision
|
|
164
|
+
if: steps.basecache.outputs.cache-hit != 'true'
|
|
126
165
|
run: fxcss shot --theme base --out shots/base --variants all
|
|
127
166
|
|
|
167
|
+
# Saved the moment the base render succeeds rather than when the job
|
|
168
|
+
# ends: a push that supersedes this run cancels it, and the base is
|
|
169
|
+
# usually done by then -- which is exactly the run the next push wants.
|
|
170
|
+
- name: Save base render
|
|
171
|
+
if: steps.basecache.outputs.cache-hit != 'true'
|
|
172
|
+
uses: actions/cache/save@v5
|
|
173
|
+
with:
|
|
174
|
+
path: shots/base
|
|
175
|
+
key: ${{ steps.basecache.outputs.cache-primary-key }}
|
|
176
|
+
|
|
128
177
|
- name: Render pull request revision
|
|
129
178
|
run: fxcss shot --theme head --out shots/head --variants all
|
|
130
179
|
|
|
@@ -200,6 +200,7 @@ def build(session, theme: Path, outdir: Path, variants, flags):
|
|
|
200
200
|
_shrink(base, FULL_WIDTH).save(outdir / "base.png", optimize=True)
|
|
201
201
|
|
|
202
202
|
entries = []
|
|
203
|
+
captures = {}
|
|
203
204
|
for slug, sheets in sorted(variants.items()):
|
|
204
205
|
for sheet in sheets:
|
|
205
206
|
session.m.script(core.LOAD_VARIANT_SHEET, [sheet.resolve().as_uri()])
|
|
@@ -218,15 +219,55 @@ def build(session, theme: Path, outdir: Path, variants, flags):
|
|
|
218
219
|
else:
|
|
219
220
|
(outdir / f"{slug}.png").unlink()
|
|
220
221
|
entries.append(entry)
|
|
222
|
+
captures[slug] = after
|
|
221
223
|
state = f"{pct:.2f}% of the chrome" if changed else "nothing on this Firefox"
|
|
222
224
|
print(f" {slug}: changes {state}", flush=True)
|
|
223
225
|
|
|
226
|
+
for entry in entries:
|
|
227
|
+
matched = combo_verdict(entry, captures)
|
|
228
|
+
if matched:
|
|
229
|
+
entry["same_as"] = matched
|
|
230
|
+
print(f" {entry['slug']}: renders identically to {matched} alone "
|
|
231
|
+
"— the other sheet(s) have no effect in this combination",
|
|
232
|
+
flush=True)
|
|
233
|
+
|
|
224
234
|
info = session.info()
|
|
225
235
|
markdown = render_markdown(theme, entries, flags, info["version"])
|
|
226
236
|
(outdir / "TWEAKS.md").write_text(markdown, encoding="utf-8")
|
|
227
237
|
return entries
|
|
228
238
|
|
|
229
239
|
|
|
240
|
+
def combo_verdict(entry, captures):
|
|
241
|
+
"""The single option a combo capture is pixel-identical to, or None.
|
|
242
|
+
|
|
243
|
+
Static analysis can say two sheets set the same declarations; it cannot
|
|
244
|
+
see two sheets that fight over the same pixels through different rules --
|
|
245
|
+
WhiteSur's tabs-swapclose and windows-swapclose both move the tab close
|
|
246
|
+
button while sharing no declarations at all. The rendered images settle
|
|
247
|
+
it: if `a+b` is identical to `b` alone, then `a` did nothing in that
|
|
248
|
+
combination, and that is a fact about pixels rather than a judgement.
|
|
249
|
+
Identical means what it means everywhere else in fxcss -- zero pixels
|
|
250
|
+
changed above diff_stats' noise threshold.
|
|
251
|
+
|
|
252
|
+
Only combinations are judged, and only against their own constituents:
|
|
253
|
+
`a+b` matching some unrelated option c would be a coincidence worth
|
|
254
|
+
nothing. Returns the constituent's slug, or None when the combo really is
|
|
255
|
+
more than any one of its parts.
|
|
256
|
+
"""
|
|
257
|
+
parts = entry["slug"].split("+")
|
|
258
|
+
if len(parts) < 2 or entry["slug"] not in captures:
|
|
259
|
+
return None
|
|
260
|
+
combo = captures[entry["slug"]]
|
|
261
|
+
for part in parts:
|
|
262
|
+
single = captures.get(part)
|
|
263
|
+
if single is None or single.size != combo.size:
|
|
264
|
+
continue
|
|
265
|
+
changed, _, _ = diff_stats(single, combo)
|
|
266
|
+
if changed == 0:
|
|
267
|
+
return part
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
|
|
230
271
|
def render_markdown(theme: Path, entries, flags, firefox_version):
|
|
231
272
|
"""The committable document. Pure, so it is unit-testable."""
|
|
232
273
|
lines = [
|
|
@@ -261,6 +302,13 @@ def render_markdown(theme: Path, entries, flags, firefox_version):
|
|
|
261
302
|
lines.append("")
|
|
262
303
|
lines.append(f"Full window: [{slug}.png]({slug}.png)")
|
|
263
304
|
lines.append("")
|
|
305
|
+
if entry.get("same_as"):
|
|
306
|
+
others = " + ".join(part for part in slug.split("+")
|
|
307
|
+
if part != entry["same_as"]) or "the rest"
|
|
308
|
+
lines.append(f"**Not a real combination on this Firefox:** it "
|
|
309
|
+
f"renders pixel-identically to `{entry['same_as']}` "
|
|
310
|
+
f"alone — `{others}` has no effect here.")
|
|
311
|
+
lines.append("")
|
|
264
312
|
enable = ", ".join(f"`{sheet_path_for_doc(theme, s)}`" for s in entry["sheets"])
|
|
265
313
|
lines.append(f"Enable by copying {enable} into your profile's `chrome/` "
|
|
266
314
|
f"folder (or with the installer flags below, if the theme "
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fxcss
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Live-reload, inspect and screenshot-test Firefox userChrome.css themes
|
|
5
5
|
Author: AdamXweb
|
|
6
6
|
License: MIT
|
|
@@ -122,7 +122,7 @@ the [releases page](https://github.com/AdamXweb/fxcss/releases) has the latest.
|
|
|
122
122
|
CI runners' Pythons are not externally managed, so plain pip is fine there:
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
pip install "fxcss[images]==0.
|
|
125
|
+
pip install "fxcss[images]==0.18.0"
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Either gives you an `fxcss` command. To hack on it, clone and install editable:
|
|
@@ -322,9 +322,11 @@ screenshotting a `--combo` that cannot take effect.
|
|
|
322
322
|
> fighting over the same property. Two sheets that rearrange the same area
|
|
323
323
|
> through *different* selectors — WhiteSur's `tabs-swapclose` and
|
|
324
324
|
> `windows-swapclose` both move a close button and share no declarations at
|
|
325
|
-
> all — are invisible to it. `fxcss tweaks --combo a+b`
|
|
326
|
-
>
|
|
327
|
-
> is
|
|
325
|
+
> all — are invisible to it. `fxcss tweaks --combo a+b` proves those from
|
|
326
|
+
> the rendered pixels — and says so itself: a combo that renders identically
|
|
327
|
+
> to one of its parts alone is called out in TWEAKS.md as not a real
|
|
328
|
+
> combination on that Firefox. Silence from the static check is "nothing
|
|
329
|
+
> measurable", never "verified compatible"; the pixels are the judge.
|
|
328
330
|
|
|
329
331
|
When the theme's default branch has moved on since its newest release, that
|
|
330
332
|
choice is put to you as well — a tag can be a year behind a fix you are
|
|
@@ -369,6 +371,7 @@ Restart Firefox after either command; it reads `userChrome.css` at startup.
|
|
|
369
371
|
fxcss upgrade # take the newest version of what you have
|
|
370
372
|
fxcss upgrade --check # report only; exit code says what it found
|
|
371
373
|
fxcss upgrade --audit # check the new version against your Firefox first
|
|
374
|
+
fxcss upgrade --compare # see it before deciding: installed vs new, rendered
|
|
372
375
|
fxcss upgrade --ref v2.1.0 # somewhere specific
|
|
373
376
|
fxcss rollback # …and back again
|
|
374
377
|
```
|
|
@@ -391,6 +394,15 @@ It stops rather than surprise you, in three places:
|
|
|
391
394
|
which runs the same check as [`fxcss audit`](#fxcss-audit) against the
|
|
392
395
|
fetched copy *before* anything is installed.
|
|
393
396
|
|
|
397
|
+
`--compare` renders what is installed and what the upgrade would install —
|
|
398
|
+
the profile's own `chrome/`, local edits and carried-over sheets included, not
|
|
399
|
+
a re-fetch of what the manifest says — and diffs them before the confirmation
|
|
400
|
+
prompt, printing where the before/after images landed. It completes the three
|
|
401
|
+
questions an upgrade can answer about a theme with no API: do the selectors
|
|
402
|
+
still exist (`--audit`), has the user edited anything (always checked), and
|
|
403
|
+
what actually changes on screen (`--compare`). Needs Pillow and a local
|
|
404
|
+
Firefox, like `shot`.
|
|
405
|
+
|
|
394
406
|
`--check` changes nothing and answers with its exit code, for cron, launchd or
|
|
395
407
|
CI: **0** up to date, **1** an upgrade is available, **2** it cannot be told
|
|
396
408
|
(no install here, an unreachable repo, or a manifest too old to say what it
|
|
@@ -681,6 +693,16 @@ overwritten without `--force`, and the output tells you the things that
|
|
|
681
693
|
otherwise surprise people (the comment starts after the workflows reach your
|
|
682
694
|
default branch; first-time contributors need one approval click).
|
|
683
695
|
|
|
696
|
+
Each render captures the base branch and the pull request and diffs them. The
|
|
697
|
+
base half is cached: its captures are deterministic — that is the contract
|
|
698
|
+
fxcss's own CI asserts — so after a pull request's first push, later pushes
|
|
699
|
+
render only the change. The key names everything that could alter a pixel
|
|
700
|
+
(base commit, runner image, Firefox version, fxcss version, the workflow file
|
|
701
|
+
itself), and a miss on any of them re-renders rather than falling back to a
|
|
702
|
+
near match. GitHub scopes caches to the branch that made them, so this helps
|
|
703
|
+
the second push onward, not the first: a pull request that takes six pushes
|
|
704
|
+
to land renders the base once, not six times.
|
|
705
|
+
|
|
684
706
|
If you'd like people to know:
|
|
685
707
|
|
|
686
708
|
[](https://github.com/AdamXweb/fxcss)
|
|
@@ -699,6 +721,13 @@ renders the answer instead: the base theme, every optional stylesheet, and any
|
|
|
699
721
|
combination you bless with `--combo`, each with a labelled **before/after crop
|
|
700
722
|
of the region it actually changes** and how much of the chrome it touches.
|
|
701
723
|
|
|
724
|
+
A `--combo` is also judged against its own parts: if `a+b` renders
|
|
725
|
+
pixel-identically to `b` alone, then `a` did nothing in that combination, and
|
|
726
|
+
TWEAKS.md says so rather than presenting the pair as a real option. That is
|
|
727
|
+
the check the static conflict analysis in `install` cannot make — two sheets
|
|
728
|
+
can fight over the same pixels through entirely different rules — and the
|
|
729
|
+
rendered images settle it as a fact rather than a judgement.
|
|
730
|
+
|
|
702
731
|
The crop is built from the changed pixels, so it needs no per-option
|
|
703
732
|
configuration and cannot drift when Firefox moves something. It centres on the
|
|
704
733
|
busiest *cluster* of changes rather than the bounding box of all of them,
|
|
@@ -1013,7 +1042,7 @@ check out the base revision and the pull request revision, render both, compare,
|
|
|
1013
1042
|
and publish the result.
|
|
1014
1043
|
|
|
1015
1044
|
```yaml
|
|
1016
|
-
- run: pip install "fxcss[images]==0.
|
|
1045
|
+
- run: pip install "fxcss[images]==0.18.0" # pin: your CI, your upgrades
|
|
1017
1046
|
- run: fxcss shot --theme base --out shots/base
|
|
1018
1047
|
- run: fxcss shot --theme head --out shots/head
|
|
1019
1048
|
- run: fxcss compare --base shots/base --head shots/head --out out/ --platform ${{ runner.os }}
|
|
@@ -2440,7 +2440,9 @@ class TemplateActionVersionTests(unittest.TestCase):
|
|
|
2440
2440
|
two to each other turns the next Dependabot bump into a failing test.
|
|
2441
2441
|
"""
|
|
2442
2442
|
|
|
2443
|
-
|
|
2443
|
+
# `actions/cache/restore@v5` is actions/cache: the sub-path is not a
|
|
2444
|
+
# separate action and is versioned with its parent.
|
|
2445
|
+
PIN = re.compile(r"uses:\s*actions/([a-z-]+)(?:/[a-z-]+)?@v(\d+)")
|
|
2444
2446
|
|
|
2445
2447
|
def versions(self, paths):
|
|
2446
2448
|
found = {}
|
|
@@ -2473,9 +2475,75 @@ class TemplateActionVersionTests(unittest.TestCase):
|
|
|
2473
2475
|
"""
|
|
2474
2476
|
root = Path(__file__).resolve().parent.parent
|
|
2475
2477
|
floors = {"checkout": 5, "setup-python": 6, "upload-artifact": 6,
|
|
2476
|
-
"download-artifact": 7, "github-script": 8}
|
|
2478
|
+
"download-artifact": 7, "github-script": 8, "cache": 5}
|
|
2477
2479
|
shipped = self.versions((root / "fxcss" / "templates").glob("*.yml"))
|
|
2478
2480
|
for name, floor in floors.items():
|
|
2479
2481
|
for major in shipped.get(name, ()):
|
|
2480
2482
|
self.assertGreaterEqual(
|
|
2481
2483
|
major, floor, f"actions/{name}@v{major} runs on Node 20")
|
|
2484
|
+
|
|
2485
|
+
|
|
2486
|
+
class ComboVerdictTests(unittest.TestCase):
|
|
2487
|
+
"""`a+b` rendering identically to `b` alone proves `a` did nothing there.
|
|
2488
|
+
|
|
2489
|
+
This is the pixel half of the sheet-conflict story: sheets.py sees two
|
|
2490
|
+
sheets fighting over the same declarations, and cannot see two sheets
|
|
2491
|
+
fighting over the same pixels through different rules (tabs-swapclose vs
|
|
2492
|
+
windows-swapclose share no declarations at all). The rendered images are
|
|
2493
|
+
the honest judge, with "identical" meaning the same thing it means in
|
|
2494
|
+
compare: zero pixels changed above the noise threshold.
|
|
2495
|
+
"""
|
|
2496
|
+
|
|
2497
|
+
def image(self, colour):
|
|
2498
|
+
from PIL import Image
|
|
2499
|
+
return Image.new("RGB", (24, 16), colour)
|
|
2500
|
+
|
|
2501
|
+
def entry(self, slug):
|
|
2502
|
+
return {"slug": slug, "sheets": [], "percent": 0.0, "image": None}
|
|
2503
|
+
|
|
2504
|
+
def test_a_combo_identical_to_one_part_names_that_part(self):
|
|
2505
|
+
from fxcss.tweaks import combo_verdict
|
|
2506
|
+
captures = {"a": self.image((200, 50, 50)),
|
|
2507
|
+
"b": self.image((100, 100, 100)),
|
|
2508
|
+
"a+b": self.image((100, 100, 100))}
|
|
2509
|
+
self.assertEqual(combo_verdict(self.entry("a+b"), captures), "b")
|
|
2510
|
+
|
|
2511
|
+
def test_noise_below_the_diff_threshold_still_counts_as_identical(self):
|
|
2512
|
+
from fxcss.tweaks import combo_verdict
|
|
2513
|
+
near = self.image((100, 100, 110)) # the DiffStats noise case
|
|
2514
|
+
captures = {"a": self.image((200, 50, 50)),
|
|
2515
|
+
"b": self.image((100, 100, 100)), "a+b": near}
|
|
2516
|
+
self.assertEqual(combo_verdict(self.entry("a+b"), captures), "b")
|
|
2517
|
+
|
|
2518
|
+
def test_a_combo_that_is_more_than_its_parts_matches_nothing(self):
|
|
2519
|
+
from fxcss.tweaks import combo_verdict
|
|
2520
|
+
captures = {"a": self.image((200, 50, 50)),
|
|
2521
|
+
"b": self.image((100, 100, 100)),
|
|
2522
|
+
"a+b": self.image((50, 200, 50))}
|
|
2523
|
+
self.assertIsNone(combo_verdict(self.entry("a+b"), captures))
|
|
2524
|
+
|
|
2525
|
+
def test_single_options_are_never_judged(self):
|
|
2526
|
+
from fxcss.tweaks import combo_verdict
|
|
2527
|
+
captures = {"a": self.image((1, 2, 3)), "b": self.image((1, 2, 3))}
|
|
2528
|
+
self.assertIsNone(combo_verdict(self.entry("a"), captures))
|
|
2529
|
+
|
|
2530
|
+
def test_a_window_that_changed_size_is_not_compared(self):
|
|
2531
|
+
from PIL import Image
|
|
2532
|
+
from fxcss.tweaks import combo_verdict
|
|
2533
|
+
captures = {"a": Image.new("RGB", (10, 10), (1, 2, 3)),
|
|
2534
|
+
"a+b": self.image((1, 2, 3))}
|
|
2535
|
+
self.assertIsNone(combo_verdict(self.entry("a+b"), captures))
|
|
2536
|
+
|
|
2537
|
+
def test_the_markdown_carries_the_verdict(self):
|
|
2538
|
+
from fxcss.tweaks import render_markdown
|
|
2539
|
+
with tempfile.TemporaryDirectory() as td:
|
|
2540
|
+
entries = [
|
|
2541
|
+
{"slug": "a+b", "sheets": [], "percent": 1.0,
|
|
2542
|
+
"image": None, "same_as": "b"},
|
|
2543
|
+
{"slug": "compact-tabs", "sheets": [], "percent": 1.0,
|
|
2544
|
+
"image": None},
|
|
2545
|
+
]
|
|
2546
|
+
text = render_markdown(Path(td), entries, [], "154.0")
|
|
2547
|
+
self.assertIn("renders pixel-identically to `b` alone", text)
|
|
2548
|
+
self.assertIn("`a` has no effect here", text)
|
|
2549
|
+
self.assertEqual(text.count("Not a real combination"), 1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|