feat(configurator): WCAG color locks + OKLCH palette generator + richer preview - #297
Conversation
- suggestAccessiblePalette() gains a per-role lock contract: a locked role (base/neutral/action) is kept as a fixed anchor and only echoed back with its measured ratio, while unlocked roles are generated to clear WCAG against the (possibly locked) base surface. Lock one or two brand colors and have the rest derived compliant. Backward compatible. - New bestTextOnSurface() helper does an adaptive light/dark search so a locked dark base still yields a legible neutral + action. - WcagPanel: lock toggles per role, locked-aware result rows. - Preview: richer showcase (surfaces/elevation, tinted alerts, card with avatar/badges, form field, secondary/outline buttons) on real tokens. - Re-sync api-index.generated.json frameworkVersion 0.5.27 -> 0.5.28. - Tests cover lock passthrough, locked-dark-base generation, helper. Co-authored-by: Jack Granatowski <contact@codeslash.net>
… preview Addresses reviewer feedback that separate generators would drift. - New suggestPalette() composes the existing helpers into ONE coherent generator: a single surface (base), neutral body text at AAA, and the brand accents (primary/secondary/tertiary/action) spread into the widest hue gaps and cleared to AA — all against the same surface, so 'action' (which is both a brand and a foundation role) is handled once and nothing conflicts. - New suggestBrandPalette() + farthestHue() building blocks: lock one or two brand colors a client provides and generate the rest, distinct and WCAG-compliant. - WcagPanel: a single lock panel across all six roles, generating off the LIGHT source palette (own probe root) regardless of preview theme. - New 'Text on colors' preview: auto-contrasting on-color text on each brand & status color with its ratio, reflecting the current light/dark theme — the combinations that actually ship. - Tests for farthestHue, suggestBrandPalette and the unified suggestPalette.
The framework defines every main color in OKLCH (oklch(0.47 0.27 264)) and derives shades/dark-mode via oklch(from …); OKLab is only the color-mix interpolation space. The generator now searches the SAME perceptually-uniform space instead of HSL, so: - equal lightness steps look equally bright across hues (no muddy or uneven results, esp. in dark mode); - hue is preserved exactly and chroma held constant (gamut-reduced only where a lightness can't hold it) — the 'preserve hue, move L/C' method used by Leonardo/ColorBox and by the framework's own derivations; - generated values are emitted as oklch(L C H), matching token authoring. - New rgbToOklch / oklchToRgb (gamut-fitting) conversions. - bestTextOnSurface(hue,sat) -> bestOklchOnSurface(chroma,hue): searches L on a fixed OKLCH line for the softest value clearing the target. - suggestAccessiblePalette / suggestBrandPalette / suggestPalette now work in OKLCH (base surface oklch(0.96 …), hue gaps spread on OKLCH hue). - WCAG contrast maths unchanged (sRGB luminance). Tests updated.
Findings from an independent code review (CodeRabbit was out of org credits and produced no review): - color.js: bestOklchOnSurface now emits the oklch() string with the GAMUT-FITTED chroma actually used for the measured rgb (via new fitOklchToGamut), not the requested chroma — so a pair certified at 4.5/7 renders as the same color, no requested-vs-mapped gap. - color.js: suggestBrandPalette processes present roles before absent ones, so an absent leading role can't seed a phantom hue 0 that shoves the present roles off their own hues (partial-input path). - color.js: unify the search-direction measure on OKLab L (was WCAG luminance for direction, OKLab L for the in-direction test). - WcagPanel: restore an explicit 'no accessible value — left unchanged' row when a role can't reach its target (was silently dropped). - WcagPanel: drop the redundant Base matrix column (--c-surface == base). - Tests: chroma round-trip + single-non-primary-role hue guards.
Addresses CodeRabbit review on PR #295: USAGE_ROLES filtered only on `--sf-color-${role}`, but each chip also styles/measures `--sf-color-text--on-${role}`. In a partial catalogue a role with the color but no on-color token rendered a misleading chip (inherited text, no contrast result). Now require both tokens to exist.
|
Warning Review limit reached
More reviews will be available in 15 minutes and 5 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This pull request was created by @kiro-agent on behalf of @jackgranatowski 👻
Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro Web
Supersedes #295 (identical tree; only the first commit's subject was reworded so the
commitlintCI check passes — the sandbox git gateway rejects force-push, so the fixed history had to go on a fresh branch). Please close #295 in favor of this PR.What this adds
color.js: lock any subset ofbase / neutral / primary / secondary / tertiary / action; locked roles are kept as anchors, the rest are generated to clear WCAG against the (possibly locked) base surface, with brand hues spread into the widest gaps.core/tokens.cssdefines and derives every main color. Emitted asoklch(L C H).Review fixes already applied
Why a new branch
The only change vs #295 is rewording the first commit subject from
WCAG color locks…(flaggedsubject-case/upper-case by@commitlint/config-conventional) toadd WCAG color locks…. The file tree is identical.Testing
node --test: 42/42.svelte-check: 0 errors.npm run build: clean. All five commits passcommitlintlocally.