# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.0] - 2026-07-20

### Breaking Changes

- **Channel-range corrections** *(entry corrected 2026-07 — it originally claimed v2 normalized all spaces to 0–1; it did not: v2 already used conventional ranges — rgb 0–255, hsl 0–360/0–100/0–100, cmyk 0–100, ycbcr 16–235 — and those are unchanged)*. Five spaces rescaled to conventional form: `hsm` and `tsl` (were all-0–1), `hsi`/`hcy`/`hsp` third channel 0–255 → 0–100 (`hsp` also no longer rounds its outputs). Declared bounds made truthful where the v2 metadata understated the formula (`lab` a/b ±125, `luv` ±215, …) — metadata only, outputs identical. Spaces new in v3 adopt CSS conventions: predefined-RGB (P3, Rec.2020, A98-RGB, ProPhoto) are 0–1 per the CSS `color()` function; OKLab/OKLCh/OKLrab/OKLrch native 0–1 / ±0.4 (`oklch.rgb(0.65, 0.25, 180)` ≡ CSS `oklch(0.65 0.25 180)`); JzAzBz/JzCzHz and ICtCp native (Jz/I 0–1, az/bz/Ct/Cp ±0.5), matching colorjs.io / Safdar 2017 / BT.2100.
- **Flat channel arguments**: conversions take individual arguments — `rgb.lab(10, 20, 30)` — not an array `rgb.lab([10, 20, 30])`. (The array call returns as the batch form — see Added.)
- **Space object shape change**: `.min`, `.max`, `.channel`, and `.alias` properties removed from space objects; replaced by `.range`.
- **Lab/LCHab are now D50** (ICC/CSS convention; v2 lab was D65). `lab-d65` added for the v2-equivalent display-native Lab.
- **`ciecam` (`cam`) removed** — the v2 one-way stub is superseded by the full bidirectional `ciecam02`.
- **`cubehelix` removed** — a colormap (a single fraction painted to a color), not a color space you convert between; out of scope (README "What it isn't"). No replacement.
- rec2100-pq / rec2100-hlg ship hyphenated *(corrected 2026-07: previously listed as a rename — the unhyphenated files existed only in unreleased dev builds, never in a published v2)*.

See [docs/migration.md](docs/migration.md) for the v2→v3 upgrade path, verified against the v2.3.2 source.

### Added

- GLSL/WGSL shader chunks for all 161 graph spaces, including LUT-backed Munsell. A chunk is pure data (`{ name, deps, edges, code }`, like three.js `ShaderChunk`); `color-space/gl` is the engine that composes it — the engine imports the data, never the reverse. Import the spaces you convert and name the conversion: `glsl(oklch, 'rgb')` → `vec3 oklch_rgb(vec3 c)`, the scalar library's shape one level down (cf. `oklch.rgb(l, c, h)`). `glsl(oklch)` bundles both directions; `glsl(oklch, p3)` routes a pair through their shared ancestor.
- Lean by default: a per-space import brings only its own dependency chain (~5 kB minified), not the ~200 kB catalog — and stays pure data (`oklch.code` is its raw GLSL for hand-embedding), so importing a chunk never drags the composer along. Byte-identical to the full catalog.
- Full-catalog tier `color-space/gl/all` routes any space by name — `glsl('rgb', 'oklch')` — for runtime `from`/`to` strings.
- Multi-conversion shaders: `glsl([[a,b], [c,d], …])` emits every entry with shared chunks deduped — paint in one space, gamut-test in another, one source.
- WGSL for WebGPU, mechanically translated from the same chunks (`wgsl(oklch)` lean, `color-space/gl/wgsl` by name).
- GL differential suite: every edge evaluated as JS in float64 against the scalar library (`test/gl.js`), all 590 generated GLSL sources compiled on WebGL2 (`test/gl-gpu.html`), and all 590 translated WGSL sources grammar-parsed in CI (`test/wgsl.js`) plus live WebGPU validation when available.
- Complete reference coverage — all 161 graph spaces pinned to either the colorjs.io differential suite or a cited authoritative value (134 points), each entry carrying its source URL (`test/bonafide.js`).
- Integrity sweep: every composed pair callable as bare functions (catches `this`-bound conversion hops).
- `meta.wiki` — the canonical Wikipedia article per space (90 spaces; per-anchor verified), from a new `@wiki` JSDoc tag.
- `meta.year` / `meta.by` / `meta.use` — provenance (when the space was introduced, by whom) and usage domain + current status for 139 spaces, from new `@year`/`@by`/`@use` JSDoc tags (cross-checked against each file's cited references).
- `color-space/wasm` default export mirroring the scalar API: `space.oklch.rgb(l, c, h)` for scalars, `space.rgb.oklch(buf)` for whole buffers — an `alloc()`'d buffer converts in place (zero-copy), any other array-like returns a converted copy.
- `color-space/lut`: any conversion as a `.cube` LUT — `cube(space.slog3, space.rec709)` — for DaVinci Resolve, Premiere, Final Cut, OBS, ffmpeg. Channelwise pairs (pure transfer curves, e.g. rec709→rgb) auto-emit LUT_1D_SIZE 4096; cross-channel pairs emit LUT_3D_SIZE 33 by default (17/33/65 Resolve convention). Every header carries the measured deviation of the interpolated lattice against the direct conversion at random off-lattice points (median + max, fractions of full scale), so the file states its own accuracy.
- LUT differential suite against the scalar library plus an end-to-end ffmpeg check: the generated `.cube` applied to a float frame by ffmpeg itself must reproduce the library (`test/lut.js`, `test/lut-ffmpeg.js`).
- Shaper LUTs — `cube(from, to, { shaper: true })` prepends the conversion's own tone diagonal as a 1D shaper (Resolve-flavor combined LUT_1D+LUT_3D cube; Resolve/OCIO read it): measured, a shaped 33³ beats a plain 65³ for camera-log → display at ⅙ the size (logc4→rec2020 4.1e-3 vs 5.7e-3); super-range clips at the shaper.
- `color-space/icc` — `profile(space.p3)` emits compact ICC v2 matrix+TRC display profiles for representable RGB working spaces; other bounded 1–4 channel spaces use sampled AToB/BToA CLUT profiles. Matrix colorants/TRCs derive mechanically from the space conversions and Bradford-adapt to the D50 PCS; output is pinned to Lindbloom/IEC values and host-verified through macOS ColorSync (`test/icc.js`, `test/icc-colorsync.js`).
- `color-space/data.json` — the registry as one language-neutral artifact: per-space metadata + conventional ranges, conversion-graph edges, gamut primaries, CIE whitepoints, the CIE 1931 2° color-matching functions, and the cited conformance triples the suite pins to (`npm run data`; registry-drift test-pinned like meta.js).
- `color-space-mcp` CLI (shipped by the `color-space` package) — zero-dependency MCP server over stdio JSON-RPC exposing `convert`/`space`/`spaces`/`cube`; driven end-to-end by `test/mcp.js`.
- Source-only repo, generated site — the website stages into `_site/` (`npm run landing` → `scripts/build-site.js`: web/ sources + runtime modules with root-relative imports + prerendered catalog, a per-space reference page each, sitemap/robots/llms) and deploys via GitHub Actions (`pages.yml`, full suite as the deploy gate); `web/` holds the site source, `docs/` only markdowns. Generated package artifacts (`data.json`, `types/*.d.ts`, `dist/`, `wasm/binary.js`) are produced by `prepare`; `prepublishOnly` rebuilds and runs source plus packed-consumer checks before publication.
- **Batch calling form on the JS hubs** — every wired pair now also takes an interleaved array-like of pixels and returns a new `Float64Array`: `space.rgb.oklch(pixels)`, for all 161 spaces. Stride follows each space's channel count (`rgb→cmyk` maps 3n→4n, `kelvin→rgb` 1n→3n), trailing params (ycbcr `kb`/`kr`) reach every pixel, and a batch of one is exactly the v2 calling convention — `rgb.lab([50, 50, 50])` is legal again. Batch ≡ scalar pinned bit-for-bit across every pair (`test/batch.js`).
- `color-space/lite` — the compact hub: exactly the WASM-covered 27 spaces (rgb/lrgb/xyz, OKLab family, CIE Lab/Luv + DIN99, HDR, camera logs) in plain JS, ~9 kB gzipped vs the catalog's ~55 kB. Same registry shape, both calling forms, `register()` included — the three hubs (`color-space`, `/lite`, `/wasm`) interchange conversion calls freely (wasm is the bare kernel: `range`/metadata/`register` live on the JS hubs); parity with the wasm space list is test-pinned.
- **WASM scalar kernels are true multi-value exports** — each edge is `rgb_lrgb(r, g, b) → (r′, g′, b′)` (jz multi-value; i64 f64-bit lanes mapped by the `jz:i64exp` custom section), so the module's ABI mirrors the scalar API and scalar calls never touch the working buffer. Batch loops destructure the same kernels per pixel — each formula exists exactly once in `wasm/batch.js` — and the two forms are pinned bit-identical on every pair.
- Migration guide rewritten from verified `v2.3.2` source diffs ([docs/migration.md](docs/migration.md)); linked from the README. The previous guide's central claim — "v2 normalized everything to 0–1" — was false (see the channel-range correction above), and its ×255 conversion recipes would have double-scaled correct v2 code.
- Identity conversions — `space.rgb.rgb(…)`, scalar and batch, on all three hubs (MCP `convert` included).
- Machine-readable loss semantics — `data.spaces.<name>.loss` (`projective` / `lookup` / `quantized`) + `lossNote` tag the inherently non-invertible nodes: kelvin, cct-duv, maxwell, wavelength, gray, rg, cmyk, munsell, acesproxy.
- `cct-duv`, preserving both dimensions of near-white chromaticity as correlated colour temperature plus signed CIE 1960 uv distance; projective at conventional Y=100.
- Explicit legal-range Y′CbCr spaces for BT.601 525-line (SMPTE-C), BT.601 625-line (PAL/EBU), BT.709, and BT.2020 non-constant-luminance, fixing primaries, transfer, coefficients, and range in each id.
- Human-LMS `maxwell` graph space for fixed-observer trichromatic receptor-catch chromaticity.
- Exact TypeScript declarations for every public export: fixed per-space scalar arities, graph batch overloads, WASM, all GL/WGSL entry points, ICC, LUT, MCP, utilities, transfers, CIE constants, whitepoints, and gamuts. NodeNext and Bundler resolution are checked against the packed package.
- Graph-based conversion wiring: shortest-path routing means any space can reach any other; `din99o`, `oklrab`, `oklrch`, and `jzczhz` are now reachable from all other spaces.
- `lab-d65` space for D65-illuminant Lab (display-native, distinct from ICC/CSS D50 Lab).
- Full TypeScript types regenerated and verified tsc-strict clean.
- 29 spaces differentially validated against colorjs.io.
- NaN guards for achromatic/black edge cases across `hsi`, `osaucs`, `lchuv`, `cam16`, and more.

### Changed

- Trailing params on composed paths bind to the target edge when the source edge is not parametric — `space.hsl.ycbcr(h, s, l, kb, kr)` now honors `kb`/`kr` (they were silently dropped); source-side params behave as before.
- Hubs own shallow copies of the space objects — loading one hub no longer rewires another. `register(space, reverse)` now requires explicit forward and reverse connections, rejects duplicate/reserved/disconnected registrations, and copies instead of mutating either input.
- Wiring runs one BFS per source instead of one per pair — full-catalog import ~3× faster, peak memory roughly halved.
- `kelvin` inverse is the CCT definition itself (nearest Planckian-locus point in CIE 1960 uv, Krystek locus): exact round-trip over the whole 1000–25000 K range (the previous McCamy cubic drifted ~4800 K at 25000 K); JS and GLSL in lockstep.
- Scalar calls fail fast on missing channels — `space.rgb.hsl(255, 0)` throws instead of returning NaNs. WASM additionally validates names, arity, strides, integer counts, active allocations, and destination capacity before touching memory.
- `types/index.d.ts` no longer declares named per-space exports that never existed at runtime; `ColorSpace.range` is required, `Convert` accepts the real trailing-param types (numbers, an lms matrix, a uvw illuminant).

- `meta.js` folded into `data.json` — one metadata artifact instead of two overlapping ones: `data.spaces` carries everything `meta` did plus `neighbors`, and the file adds gamuts/whitepoints/CMFs/conformance. `import meta from 'color-space/meta.js'` → `import data from 'color-space/data.json' with { type: 'json' }`; the site fetches the same file (one generator, `npm run data`).
- Space modules moved from the repo root into `spaces/` (one file per space) — the root now holds only the hubs and tools (`index`/`lite`/`hub`/`lut`/`icc`/`mcp`/`wasm` + shared `util`/`transfers`/`whitepoints`/`cie`/`gamuts`). **Public paths are unchanged**: the exports map routes `color-space/oklch.js` (and `color-space/oklch`) to `spaces/oklch.js`, and every root module keeps its exact-key export — pinned by the exports-map integrity test.

- **icacb**: PQ now encodes against the 203 cd/m² media white (ITU-R BT.2100/BT.2408), matching ictcp/jzazbz/izazbz — SDR white lands at I ≈ 0.58 like ICtCp instead of deep in the PQ toe. colour-science's `XYZ_to_ICaCb` (1 unit = 1 cd/m²) differs by exactly that scaling.
- **macboyn**: `l` range tightened to [0.4, 1] — the spectrum locus spans l 0.433–0.963 (CVRL Smith-Pokorny MB table; s verified against the same table: peak 1.002 at 405 nm, so [0, 1] is the canonical peak-unity scaling).
- **macboyn, xyy, uv, rg**: achromatic (black) inputs now sit at the white/neutral chromaticity instead of a diagram corner — black carries no chromaticity, and every chromaticity inverts to black at zero luminance.

### Fixed

- Benchmark/test libraries are development-only rather than `optionalDependencies`; a normal install now has zero transitive packages.
- Packed-consumer checks cover every exact export plus NodeNext/Bundler types, and the tarball includes the changelog and docs without the stale nested `types/package.json`.
- `yuv`/`yiq` → rgb no longer clamp to 0–1 (JS and GLSL) — out-of-gamut values pass through, per the library-wide rule.
- README no longer claims alpha pass-through (channels only) or blanket lossless round-trips (lossy nodes are tagged).
- Benchmark fed color-space 0–1 rgb values where it expects 0–255 — competitors got the intended color, this library got near-black. RGB→HEX is now omitted for color-space instead of timing a no-op; timings warm up, consume outputs, and report the median of seven samples.
- MCP version and space counts derive from `package.json` and the live registry instead of hard-coded literals; initialization negotiates the protocol version the server actually implements instead of echoing unknown client versions. The CLI now resolves npm's `.bin` symlink before its direct-execution check, so the installed `color-space-mcp` executable actually starts.
- Composed conversions routed through `rgb.xyb` or `coloroid.xyy` threw (`this` lost in graph composition).
- 15 dead or mis-attributed `@see` citations: ACES encodings paths, Ottosson colorpicker anchors, DIN99d/Coloroid DOIs, Xerox YES attribution, colour-science doc pages, freieFarbe atlas, Leica L-Log manual, DIN99 wiki.
- **oklab**: corrected sRGB linearization (fix also propagates to oklch, okhsl, okhsv).
- **rec2020**: BT.2020 OETF now canonical.
- **hcy**: reimplemented as Chilliant luma-HCY (was incorrect).
- **tsl, hcl, labh, yuv, jzczhz, yiq, acescg, gray, uvw, cam16**: confirmed-correct conversions.
- Fixed `lrgb` → `sRGB` linearization bug (regression from 2.x; closes #65).

### Notes

- Coloroid's corrected Nemcsics limit-color table and A=70/T=70/V=60 reference value are pinned; fractional hue grades interpolate along the polygon and round-trip exactly in-domain.

## [2.3.2] - 2024-xx-xx

### Fixed

- Fixed bug in linear sRGB → sRGB conversion (#65).

## [2.3.1] - 2024-xx-xx

### Added

- TypeScript type declarations added (`index.d.ts`).
- Minimal `lrgb` (linear sRGB) space.
- `oklab` RGB conversions.

## [2.3.0] - 2024-xx-xx

### Added

- `hsm` (Hue–Saturation–Mix) space.
- Space metadata (`meta`) field.

### Fixed

- Removed rounding from conversion outputs.

## [2.2.0] - 2024-xx-xx

### Added

- Expanded TypeScript declarations across all spaces.
- Type declaration references in package exports.

## [2.1.2] - 2023-xx-xx

### Added

- Type declaration references on all modules.

## [2.1.1] - 2023-xx-xx

### Fixed

- Fixed `coloroid` typo.

## [2.1.0] - 2023-xx-xx

### Added

- JSDoc-generated TypeScript types (`color-space.d.ts`).

## [2.0.1] - 2022-xx-xx

### Fixed

- `tsl`: L now normalized to 1.
- Simplified HSL formula.
- Added tests.

## [2.0.0] - 2022-xx-xx

Initial 2.x release.

---

[Unreleased]: https://github.com/colorjs/color-space/compare/v3.0.0...HEAD
[3.0.0]: https://github.com/colorjs/color-space/compare/v2.3.2...v3.0.0
[2.3.2]: https://github.com/colorjs/color-space/compare/v2.3.1...v2.3.2
[2.3.1]: https://github.com/colorjs/color-space/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/colorjs/color-space/compare/v2.2.0...v2.3.0
[2.2.0]: https://github.com/colorjs/color-space/compare/v2.1.2...v2.2.0
[2.1.2]: https://github.com/colorjs/color-space/compare/v2.1.1...v2.1.2
[2.1.1]: https://github.com/colorjs/color-space/compare/v2.1.0...v2.1.1
[2.1.0]: https://github.com/colorjs/color-space/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/colorjs/color-space/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/colorjs/color-space/compare/v1.0.0...v2.0.0
