Repository navigation
fix(core): consistent achromatic hue handling and single transfer function - #207
Merged
Merged
Conversation
This was referenced Oct 6, 2026
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This was referenced Oct 6, 2026
Owner
Author
This was referenced Oct 6, 2026
|
This was referenced Oct 6, 2026
pbroom
force-pushed
the
feat/core-css-color-4-parsing
branch
from
October 7, 2026 23:28
1e39187 to
ebe8c51
Compare
pbroom
force-pushed
the
fix/core-achromatic-hue
branch
from
October 7, 2026 23:28
4e2c240 to
1b95ae6
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Oct 8, 2026
Owner
Author
pbroom
changed the base branch from
feat/core-css-color-4-parsing
to
graphite-base/207
October 10, 2026 18:59
…ction
Option-less mix() interpolated the raw hue of achromatic endpoints, so
mix(white, #0000ff) landed at h=312.03 while interpolate() (and
colorjs.io) give 264.05. Three different achromatic cutoffs were in use
(1e-4 in OKLab->OKLCH, 1e-3 in option-less interpolate, 4e-6 in the CSS
interpolation path), and the sRGB transfer function was written three
times with different sign handling.
- Add ACHROMATIC_CHROMA_THRESHOLD (0.000004, the CSS Color 4 OKLCH
epsilon) and isAchromatic() in utils, exported from the root. Every
8-bit/float gray converts with chroma ~1e-15 and the least chromatic
non-gray 8-bit color has chroma ~0.00106, so the cutoff separates them.
- Option-less mix()/mixInto() now delegate to the same OKLCH default as
interpolate()/generateScale(): a powerless endpoint borrows the other
endpoint's hue, both powerless gives h=0, and input hues are normalized
before the shortest-arc fix-up. mix, interpolate and generateScale now
agree, and match colorjs.io for gray endpoints in every space.
- OKLab->OKLCH conversion keeps reporting h=0 for achromatic results
(now at the shared threshold). Color.h stays a number: a rectangular
source has no hue to preserve, and atan2 of float residue is noise, so
0 is the canonical achromatic hue. Chroma is left as computed. Hues of
OKLCH inputs are never rewritten; interpolation treats them as
powerless when chroma <= threshold instead.
- linearToSrgbChannel()/srgbToLinearChannel() in utils are now the one
sign-preserving, extended-range transfer pair (CSS Color 4/colorjs.io),
used by conversion/p3, interpolation, interop kernels, srgb and gamut
linear bounds. In-range results are unchanged.
- Property tests (fast-check): hue in [0, 360), mix(a,b,0)~a,
mix(a,b,1)~b, option-less mix/mixInto/interpolate/generateScale
agreement, option-less vs { space: 'oklch' } agreement, and transfer
pair oddness/invertibility; plus colorjs.io checks for gray endpoints.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…olate Co-Authored-By: Claude Opus 5.5 <[email protected]>
pbroom
force-pushed
the
fix/core-achromatic-hue
branch
from
October 10, 2026 19:01
1b95ae6 to
11ee97e
Compare
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Option-less mix() interpolated the raw hue of achromatic endpoints, so
mix(white, #0000ff) landed at h=312.03 while interpolate() (and
colorjs.io) give 264.05. Three different achromatic cutoffs were in use
(1e-4 in OKLab->OKLCH, 1e-3 in option-less interpolate, 4e-6 in the CSS
interpolation path), and the sRGB transfer function was written three
times with different sign handling.
epsilon) and isAchromatic() in utils, exported from the root. Every
8-bit/float gray converts with chroma ~1e-15 and the least chromatic
non-gray 8-bit color has chroma ~0.00106, so the cutoff separates them.
interpolate()/generateScale(): a powerless endpoint borrows the other
endpoint's hue, both powerless gives h=0, and input hues are normalized
before the shortest-arc fix-up. mix, interpolate and generateScale now
agree, and match colorjs.io for gray endpoints in every space.
(now at the shared threshold). Color.h stays a number: a rectangular
source has no hue to preserve, and atan2 of float residue is noise, so
0 is the canonical achromatic hue. Chroma is left as computed. Hues of
OKLCH inputs are never rewritten; interpolation treats them as
powerless when chroma <= threshold instead.
sign-preserving, extended-range transfer pair (CSS Color 4/colorjs.io),
used by conversion/p3, interpolation, interop kernels, srgb and gamut
linear bounds. In-range results are unchanged.
mix(a,b,1)~b, option-less mix/mixInto/interpolate/generateScale
agreement, option-less vs { space: 'oklch' } agreement, and transfer
pair oddness/invertibility; plus colorjs.io checks for gray endpoints.
Part of a stacked set of PRs (#204 → #221) from the color-kit assessment. Validated per branch with
pnpm buildandpnpm test; lint, format, typecheck, size, artifact, preprod and agents checks pass at the top of the stack.🤖 Generated with Claude Code