Skip to content

feat(configurator): WCAG color locks + OKLCH palette generator + richer preview - #297

Merged
jackgranatowski merged 5 commits into
mainfrom
feat/wcag-oklch-color-locks-preview
Jun 10, 2026
Merged

feat(configurator): WCAG color locks + OKLCH palette generator + richer preview#297
jackgranatowski merged 5 commits into
mainfrom
feat/wcag-oklch-color-locks-preview

Conversation

@jackgranatowski

Copy link
Copy Markdown
Contributor

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 commitlint CI 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-lock palette generator in the shared, byte-identical color.js: lock any subset of base / 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.
  • OKLCH engine: generation now searches the OKLCH lightness axis (perceptually uniform, hue-preserving, sRGB-gamut-fitted) — matching how core/tokens.css defines and derives every main color. Emitted as oklch(L C H).
  • WcagPanel: one lock panel for all six roles (resolved off a light-mode probe root); curated text-on-background matrix; a "Text on colors" on-color usage preview that honors the preview theme.
  • Preview: richer token showcase (surfaces/elevation, tinted alerts, card, form field, button variants).

Review fixes already applied

  • gamut-fitted chroma is what gets emitted (no certified-vs-rendered gap); present-before-absent role ordering (no phantom hue 0); unified search-direction measure; explicit "no accessible value" row instead of silently dropping a role; removed the redundant Base matrix column; usage chips gated on both color + on-color tokens (CodeRabbit).

Why a new branch

The only change vs #295 is rewording the first commit subject from WCAG color locks… (flagged subject-case/upper-case by @commitlint/config-conventional) to add WCAG color locks…. The file tree is identical.

Testing

  • node --test: 42/42. svelte-check: 0 errors. npm run build: clean. All five commits pass commitlint locally.

kiro-agent and others added 5 commits June 10, 2026 08:06
- 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.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jackgranatowski, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17a78f3a-c639-4e6e-af51-702ce6dfb97d

📥 Commits

Reviewing files that changed from the base of the PR and between fd45b59 and 7ca016e.

📒 Files selected for processing (5)
  • configurator/src/components/Preview.svelte
  • configurator/src/components/WcagPanel.svelte
  • configurator/src/data/api-index.generated.json
  • configurator/src/lib/color.js
  • configurator/tests/color.test.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wcag-oklch-color-locks-preview

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jackgranatowski
jackgranatowski merged commit fffa4e0 into main Jun 10, 2026
12 checks passed
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.

2 participants