You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**Breaking:** the `format*` writers now take `s` / `l` / `t` on the 0–1 scale the Glaze converters return.
6
+
7
+
`formatOkhsl`, `formatOkhst`, `formatRgb`, `formatHsl`, and `formatOklch` took 0–100 percentages while `resolve()`, `variantToOkhsl`, `srgbToOkhsl`, `oklabToOkhsl`, and `okhslToSrgb` all return 0–1 — so composing a producer with a writer was off by 100x and failed silently, since `0.7` is a legal percentage and the result was a valid CSS string naming a near-black color. The library now speaks one scale end to end. (`toTone` / `fromTone` still speak the 0–100 tone axis the authoring API takes — divide by 100 before handing a tone to `formatOkhst`.)
8
+
9
+
Drop the `* 100` at the call site (`formatOkhst(v.h, v.s, v.t)`); a leftover one now warns instead of shifting the color quietly. Every export method — `css()`, `tokens()`, `tasty()`, `json()`, `dtcg()`, `tailwind()`, `glaze.format()` — emits the same colors as before: they were compensating internally, and dropping the redundant `×100 ÷100` round-trip only moves float noise (visible nowhere except the meaningless hue term of a fully-desaturated `hsl()` string).
Copy file name to clipboardExpand all lines: AGENTS.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,14 +43,15 @@ glaze/
43
43
|[src/shadow.ts](src/shadow.ts)| Shadow + mix def predicates (`isShadowDef`, `isMixDef`), default `ShadowTuning`, tuning merge, the actual `computeShadow` math (hue blend, saturation cap, lightness clamp, `tanh` alpha curve) operating on OKHSL lightness at the edge, and `circularLerp` for hue. |
44
44
|[src/hc-pair.ts](src/hc-pair.ts)| Tiny shared helpers: HC-pair selection (`pairNormal` / `pairHC`), HC-pair interpolation for the manual contrast level (`contrastFraction`, `hcMirrorsNormal` — the level-100 "HC slots are duplicates" predicate, `levelFraction`, `numberAt`, `parseToneValueAt`, `PAIR_SWITCH`), tone-value parsing (`parseToneValue` for absolute / relative / `'max'`/`'min'` extremes, `isExtremeTone`), root-tone discrimination (`isAbsoluteTone`, now incl. extremes), `clamp` / `lerp`, hue resolution (`resolveEffectiveHue`), relative-value parsing. Leaf module — types only, so every other module can import it. |
45
45
|[src/formatters.ts](src/formatters.ts)| Variant→string dispatch (`okhsl` / `rgb` / `hsl` / `oklch`) and the four token-map shapes Glaze emits: `buildTokenMap` ([Tasty](https://tasty.style)`#name` keys + state aliases), `buildFlatTokenMap` (per-variant maps), `buildJsonMap` (per-color JSON), `buildCssMap` (CSS custom-property declarations). For `splitHue` it emits the light hue declarations plus, via `darkHueDeclarations` in `channels.ts`, the whole dark set when a `darkHue` makes it differ. Also the `resolveModes` helper used everywhere. |
46
-
|[src/okhsl-color-math.ts](src/okhsl-color-math.ts)| OKHSL ↔ linear-sRGB ↔ gamma-sRGB ↔ OKLab conversions, hex parsing (3/6/8 digits), gamut clamping, and the `formatOkhsl` / `formatRgb` / `formatHsl` / `formatOklch` writers. The only file with the actual color science. |
46
+
|[src/okhsl-color-math.ts](src/okhsl-color-math.ts)| OKHSL ↔ linear-sRGB ↔ gamma-sRGB ↔ OKLab conversions, hex parsing (3/6/8 digits), gamut clamping, and the `formatOkhsl` / `formatOkhst` / `formatRgb` / `formatHsl` / `formatOklch` writers. Converters and writers share one scale — `h` 0–360, `s` / `l` / `t` 0–1 — so any converter composes with any writer (the 0–100 tone axis lives in `okhst.ts`'s `toTone` / `fromTone`, not here); `warnPercentScale` catches pre-2.0 percentage-scale input instead of emitting a plausible wrong color. The only file with the actual color science. |
47
47
|[src/config.ts](src/config.ts)| Global config singleton. `defaultConfig()` is the one source of truth for defaults; `configure()` mutates the live object and bumps a monotonic `configVersion` so theme caches invalidate. `snapshotConfig()` powers `glaze.getConfig()`. `freezeConfigForExport()` freezes resolve-relevant fields for authoring export (`pastel` is instance-only; `contrastLevel` is frozen only when instance-authored, since a global level is a live preference). `normalizeContrastLevel()` validates the manual contrast level. |
48
48
|[src/validation.ts](src/validation.ts)|`validateColorDefs` (missing references, shadow-bg-cannot-be-shadow, mix-cannot-target-shadow, contrast-without-base, relative-without-base, WCAG-vs-APCA metric switch in a `contrast` pair, etc.) + `topoSort` so the resolver processes each color after its dependencies. |
49
49
|[src/warnings.ts](src/warnings.ts)| Deduped contrast-unmet warnings. Caps cache at 256 entries to keep dev-server output bounded. |
50
50
|[src/types.ts](src/types.ts)| All public TypeScript types: `HCPair`, `MinContrast`, `ContrastSpec`, `RelativeValue`, `ExtremeValue` (`'max'`/`'min'`), `ToneValue`, `AdaptationMode`, `OkhstColor`, `ToneWindow` (`[lo,hi]`\|`{lo,hi,eps}`\|`false`), `ColorDef` discriminated union (with `tone` + `flip` + `darkHue` / `darkSaturation`), `GlazeConfig` (with `lightTone` / `darkTone` / `autoFlip` / `contrastLevel`), `GlazeThemeSeed` (`hue` / `saturation` + optional dark pair), `GlazeTheme`, `GlazePalette`, `GlazeColorToken`, the `*Export` shapes, etc. `ResolvedColorVariant` stores `{ h, s, t, alpha }`. |
51
51
|`src/glaze.test.ts`| Main test suite — covers the factory surface, resolver behavior, palette composition, shadow/mix algorithms, the manual contrast level, and the standalone color token round-trip. |
52
52
|`src/contrast-solver.test.ts`| Tests for the binary-search solver in isolation. |
53
53
|`src/hc-pair.test.ts`| Tests for the pure HC-pair selection and interpolation helpers. |
54
+
|`src/okhsl-color-math.test.ts`| Tests for the format writers' 0–1 scale contract, the percentage-scale guard, and producer→writer composition. |
A value above 1 can only be pre-2.0 percentage-scale input, so the writers
1948
+
`console.warn` about it once per writer rather than emit a wrong color quietly.
1949
+
1921
1950
To attach an alpha component, use `glaze.format(variant, format)` on a `ResolvedColorVariant` (which carries the `alpha` channel) instead of these raw writers.
Copy file name to clipboardExpand all lines: docs/migration.md
+36Lines changed: 36 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,6 +12,7 @@ If you're starting from scratch, see [methodology.md](methodology.md) first —
12
12
-[Wiring exports into the app](#wiring-exports-into-the-app)
13
13
-[Prefix map strategies](#prefix-map-strategies)
14
14
-[Migrating an existing color system](#migrating-from-an-existing-color-system)
15
+
-[Upgrading Glaze](#upgrading-glaze)
15
16
-[Common pitfalls](#common-pitfalls)
16
17
17
18
## Choosing an export
@@ -296,6 +297,40 @@ Glaze gives you light/dark/HC for free, but only the light mode is matched again
296
297
297
298
After migration, mark every default-only token (borders, shadows, disabled chip, code highlighting, etc.) `inherit: false`. Colored sibling themes only need the accent + tinted-surface chain — flagging the rest cuts the emitted token set per theme dramatically.
298
299
300
+
## Upgrading Glaze
301
+
302
+
### 2.0 — `format*` takes 0–1
303
+
304
+
The `format*` writers (`formatOkhsl`, `formatOkhst`, `formatRgb`, `formatHsl`,
305
+
`formatOklch`) used to take `s` / `l` / `t` on a 0–100 percentage scale while
306
+
everything that *produces* those values — `resolve()`, `variantToOkhsl`,
307
+
`srgbToOkhsl`, `oklabToOkhsl`, `okhslToSrgb` — returns them on 0–1. Composing
308
+
the two, which is the obvious thing to do, was off by 100x and failed silently:
309
+
`0.7` is a legal percentage, so you got a valid CSS string naming the wrong
310
+
(near-black) color.
311
+
312
+
The writers now take the 0–1 factors, so the whole library speaks one scale.
313
+
Drop the `* 100`:
314
+
315
+
```diff
316
+
- formatOkhsl(v.h, v.s * 100, l * 100);
317
+
+ formatOkhsl(v.h, v.s, l);
318
+
319
+
- formatOkhst(v.h, v.s * 100, v.t * 100);
320
+
+ formatOkhst(v.h, v.s, v.t);
321
+
```
322
+
323
+
Nothing else moves: `h` was always 0–360, `alpha` was always 0–1, and every
`tailwind()` / `glaze.format()`) emits the same colors — they were compensating
326
+
internally. Dropping the redundant `×100 ÷100` round-trip shifts float noise by
327
+
an ULP, which shows up nowhere except the (meaningless) hue term of a
328
+
fully-desaturated `hsl()` string.
329
+
330
+
A leftover `* 100` is not silent any more: a value above 1 cannot be a factor,
331
+
so the writers `console.warn` once per writer and you get an obviously-broken
332
+
`7040.68%` instead of a plausible wrong color.
333
+
299
334
## Common pitfalls
300
335
301
336
| Symptom | Cause | Fix |
@@ -307,6 +342,7 @@ After migration, mark every default-only token (borders, shadows, disabled chip,
307
342
| A relative `tone` like `'+48'` lands on the _wrong_ (darker) side of its base. | Overshooting offsets now mirror to the other side of the base by default (`autoFlip` inherits `autoFlip`). | Set `autoFlip: false` on the color to clamp to the boundary instead, or use `tone: 'max'`/`'min'` to force the extreme. |
308
343
|`palette.tokens()` emits unexpected unprefixed names. | A `primary` was set on the palette (or per-call) and is duplicating the theme's tokens without prefix. | Pass `primary: false` to disable for that export, or rename `glaze.palette(themes, { primary })`. |
309
344
|`console.warn: token "foo" collides with theme "bar"`. | Two themes resolved to the same output key under your prefix config. | Adjust the prefix map so each token is unique, or accept the first-write-wins behavior. |
345
+
|`console.warn: formatOkhsl() got a value above 1`. | Pre-2.0 percentage-scale input to a `format*` writer (a leftover `* 100`). | Pass the 0–1 factors the converters return — see [2.0 — `format*` takes 0–1](#20--format-takes-01). |
310
346
|`console.warn: color "X" cannot meet contrast`. | The requested contrast target is physically unreachable for the color's hue/saturation against its base. | Lower the floor, change the base, or accept the closest passing variant. Use the `name` override on standalone colors to make the warning identifiable. |
0 commit comments