Skip to content

diff: settle five over-reports the site pair shows - #1270

Merged
samoht merged 13 commits into
exact-coloursfrom
canonical-colour-residues
Sep 19, 2026
Merged

samoht merged 13 commits into
exact-coloursfrom
canonical-colour-residues

Conversation

@samoht

@samoht samoht commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Five pairs --diff=canonical reported that a browser paints alike, each with its spec section: a none colour axis in a custom property's stream (CSS Color 4 sec. 4.4), one angle under deg, rad and turn (CSS Values 4 sec. 6.1), a color-mix() or relative colour over light-dark() taken branch by branch (CSS Color 5 sec. 6), a declaration the reader refuses withholding no verdict since the browser drops it too (exit 0 when the rest is equivalent), and an @supports whose guard no reading answers yes dropped (CSS Conditional 3 sec. 6.1). Each pair has a pinned test and an entry.

@samoht
samoht added this pull request to stack #1271 September 18, 2026 18:48
@samoht
samoht removed this pull request from stack #1271 September 19, 2026 11:03
@samoht
samoht force-pushed the canonical-colour-residues branch from 9a82def to 3df9cea Compare September 19, 2026 11:03
@samoht
samoht changed the base branch from main to exact-colours September 19, 2026 11:03
@samoht
samoht added this pull request to stack #1273 September 19, 2026 11:03
@samoht
samoht force-pushed the canonical-colour-residues branch 3 times, most recently from 5d261ff to 176bffe Compare September 19, 2026 12:32
diff --diff=canonical reads oklab(0% none none/.5) and #80 as one
colour in a colour longhand, since CSS Color 4 sec. 4.4 makes a missing
component behave as zero for every purpose but interpolation, and holds
the two apart in a custom property. lightningcss writes Tailwind's
color-mix(in srgb, rgb(0 0 0) 50%, transparent) mask colour as the
OKLab with none chroma axes where tw writes the hex, so every mask
utility on tw's site sheet was reported. A colour function in a custom
stream is a colour wherever the stream substitutes, so its axes read as
the longhand's do, under the same carve-outs: a rule transitioning the
property, a keyframe and a @starting-style block keep them as written.
The canonical projection resolved a none axis only for a colour standing
as a whole longhand value, on the argument that a custom-property stream
may be substituted where sec. 13.3 of CSS Color 4 interpolates it. But
the stream's colour function already folds to the hex it is at that same
site, and that fold is as blind to where the stream lands as this one;
lightningcss writes Tailwind's mask colours as OKLab with none chroma
axes, so every mask utility on tw's site sheet reported against the hex
tw writes. The custom-property colour fold now takes normalize_color's
resolve_missing flag from the pass that owns it, so the stream's axes
read as the zeros CSS Color 4 sec. 4.4 makes them, under the same
carve-outs as a longhand: a keyframe, a @starting-style block and a rule
transitioning the property keep them as written. transition-property
never matched a custom property's own name, since the typed table reads
one as an unknown property; it now does, by the name CSS Variables 1
sec. 2 makes case-sensitive.
CSS Values 4 sec. 6.1 makes deg, grad, rad and turn units of one
dimension, and diff --diff=canonical reports .5turn against 180deg in a
custom stream and in rotate, and 1.5rad against the 85.9437deg
lightningcss writes, where a quotient already meets its
six-significant-figure fold. Tailwind writes a conic mask position in
turns and a rotation in radians, and each such utility on tw's site
sheet reported. The angle folds to its degrees in rotate, in a
transform function and in an unregistered custom property's stream, a
radian under the budget a quotient takes, and --lossless keeps the unit
as written.
The angle fold picked the shortest of the exactly interconvertible
spellings and kept the authored unit on a tie, so .5turn and 180deg
never met, and a radian was never converted at all since it goes
through pi. CSS Values 4 sec. 6.1 makes deg, grad, rad and turn units
of one dimension, and the conversion is Cascade's own digits, the same
kind a quotient's fold rounds to six significant figures. Under the
budget the canonical projection already sets, normalize_angle now
spells every concrete angle in degrees, a converted unit rounded to that
budget and an authored degree kept whole, so rotate: 1.5rad meets the
85.9437deg lightningcss writes. rotate and transform hand their calc
context down for it, and an unregistered custom property's stream folds
its angle tokens beside its time tokens, held off under --lossless as
the quotient fold is. The minifier's own spelling is unchanged: it
never sets the budget.
CSS Color 5 sec. 6 computes light-dark() to one of its two colours by
the element's used colour scheme, so a color-mix() or a relative colour
over a light-dark() argument is the light-dark() of that operation over
each branch, and a branch mixed with transparent at half is the colour
at half alpha, which the projection already folds for a plain colour.
Tailwind writes a scheme-aware colour at half alpha as the mix and
lightningcss as the light-dark() of the two hexes, and diff
--diff=canonical reports the pair. The fold is branch-wise and exact: a
mix a branch cannot fold stays as written and still reports, and
--lossless keeps the mix.
A color-mix() folds to the colour it names only when both arguments are
static, so a light-dark() argument kept the whole mix as written, and
diff --diff=canonical reported Tailwind's scheme-aware colour at half
alpha against the light-dark() of two hexes lightningcss writes for it.
CSS Color 5 sec. 6 computes light-dark() to one of its two colours by
the element's used colour scheme, the same scheme at every depth of the
value, so a mix or a relative colour over a light-dark() argument is the
light-dark() of that operation over each branch, both arguments
distributing at once. The fold is exact and stops where a branch cannot
fold: such a mix stays as written and still reports, and --lossless
holds the fold off as it holds the mix fold. --minify shares the fold
and writes the shorter light-dark() form.
Whether what the two readers dropped can still separate two sheets
that compare equal was decided in the binary, over the warnings the
library hands back, so nothing in test/ could hold it and the cram
tests were its only pin. The question is a property of the comparison
outcome: Css_compare.unread_separates answers it and the binary reads
that answer, with no change in what it does yet.
diff --diff=canonical exits 2 when a declaration the reader refuses is
all that separates two sheets, as Tailwind's compiled sheet does against
tw's site sheet: Tailwind writes filter: blur(<value>) for a docs
placeholder class and tw writes nothing. Parity is rendering, and the
reader is held to the browser's accept set by
test/spec/browser/accept_set, so a declaration it refuses is one the
browser drops from whichever sheet holds it and paints the same. The
verdict has to be 0 there, the warning kept as information, and 1 when a
real difference stands beside it. A rule the reader dropped is text the
comparison never saw, with nothing holding the rule reader to a browser,
so it still withholds the verdict unless both sides lost the same run.
cascade diff answered 2 whenever a declaration one reader refused had no
twin on the other side, on the argument that the comparison never saw
it. But the declaration reader is held to the browser's accept set by
test/spec/browser/accept_set, so a declaration it refuses is one every
browser drops from whichever sheet holds it, and the two sheets paint
the same. Parity is rendering: the verdict is now the one over what
remains, the parse warning kept as information since the reader can lag
the browser and the warning says where to look, and a difference beside
the refused declaration still exits 1. Tailwind's compiled sheet writes
filter: blur(<value>) for a docs placeholder class where tw writes
nothing, and that pair exited 2 over every comparison of the two. A
rule the reader dropped is text the comparison never saw, with nothing
holding the rule reader to a browser, so it still withholds the verdict
unless both sides lost the same run of text; exit 2 stays for that, for
a file that cannot be read at all, a driver failure and no browser.
diff --diff=canonical reports @supports (@media(width>=1px): var(--tw))
{ .x { display: flex } } against nothing as a difference, where the
browser applies nothing from the block. CSS Conditional 3 sec. 6.1
makes a parenthesised term that is neither a declaration nor a nested
condition a general-enclosed whose result is false, and @media(...) is
no property name, so the guard is false in every world. Tailwind writes
exactly that for supports-[@media(width>=1px)]:flex where tw writes
nothing, and the two render alike. The block goes under either reading,
--enforce-spec included, since no browser and no reading of the spec
answers the guard yes; a guard a browser can answer stays, as does a
function form cascade's own feature grammar may have refused.
The reader turns a parenthesised term that is neither a declaration nor
a nested condition into a general-enclosed leaf, and the projection kept
such a guard open as the optimizer does, so Tailwind's @supports
(@media(width>=1px): var(--tw)) block for supports-[@media(width>=1px)]
:flex was reported against the nothing tw writes, though the browser
applies nothing from it. CSS Conditional 3 sec. 6.1 makes a
general-enclosed term's result false, and @media(...) is no property
name, so the guard is false however a user agent answers its feature
tests. Supports.never_holds reads the term as that constant and the
projection drops the block, under --enforce-spec too, since no reading
of the spec and no browser answers the guard yes. A function form stays
a feature test, since cascade's own grammar may refuse an argument the
browser's selector() accepts, and simplify_under keeps the term open for
a sheet the optimizer emits, so --minify writes the guard as before.
canonicalize_rule_order's doc enumerates what the comparison-side pass
keys alike and names the custom-property stream among the positions a
none channel is kept at, which it no longer is. The list now carries
the stream, the angle in degrees, and the @supports guard no reading
answers yes, beside the keys it already had.
@samoht
samoht force-pushed the canonical-colour-residues branch from 176bffe to 0f12586 Compare September 19, 2026 12:40
@samoht
samoht merged commit 8fad292 into main Sep 19, 2026
12 checks passed
@samoht
samoht deleted the canonical-colour-residues branch September 19, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant