Skip to content

feat(colors): give LumLocker a separate dark-mode lightness - #635

Merged
jackgranatowski merged 4 commits into
mainfrom
claude/configurator-controls-audit-zqi10q
Jul 19, 2026
Merged

feat(colors): give LumLocker a separate dark-mode lightness#635
jackgranatowski merged 4 commits into
mainfrom
claude/configurator-controls-audit-zqi10q

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

LumLocker pinned both themes to a single --sf-lumlocker L, so a lock tuned for a light surface read poorly in dark mode (the dark brand colors weren't suitable for a dark background). This adds a separate dark-mode target.

  • New token --sf-lumlocker-dark (PUBLIC-ADVANCED). Default clamp(0.5, calc(1.18 - var(--sf-lumlocker)), 0.92) — the light L mirrored around 0.59, the midpoint between the light (~0.96) and dark (~0.22) page backgrounds. Mirroring around the background midpoint (not 0.5) is what keeps the lock's contrast against the surface equal in both themes; a naive 1 - L would leave the dark lock too dark to read (L≈0.35, contrast ≈0.13 on a dark surface). Override it to break the mirror.
  • core/themes.css — the dark lock rules (light-dark() dark branch + [data-theme=dark]) now read --sf-lumlocker-dark; the light rules are unchanged.
  • Configurator LumLocker panel gains a second "L · dark" slider, and the locked-swatch preview resolves the D rows against the dark L (so the D swatches now differ from the L rows instead of being identical).

Verified in-browser: with light lock 0.65, light primary resolves to L=0.65 and dark primary to L=0.53 — both carrying contrast 0.31 against their respective surfaces.

Type

  • feat

Checklist

  • Conventional Commit messages — enforced by commitlint
  • npm run lint:css passes (stylelint)
  • npm run build rebuilds dist/ (bundles git-ignored; CI rebuilds/stamps)
  • Version references in sync (npm run check:version — 0.8.0, minor bump for the new public token)
  • LLM guide reviewed/updated (npm run check:llm-guide — token count 747)
  • Generated artifacts regenerated, not hand-edited (check:macros, check:registry, audit:check, check:doc-refs, check:release-add, check:layer-order)
  • CHANGELOG.md updated under ## Unreleased
  • Breaking changes include migration docs — n/a (additive; light-mode behaviour unchanged)

Notes

--sf-lumlocker keeps its meaning and default (0.65) as the light-mode target; only dark mode's lightness source changed. Existing configs that only set --sf-lumlocker now get a better-contrasting dark lock automatically; those wanting the old behaviour can set --sf-lumlocker-dark to the same value as --sf-lumlocker.

Configurator svelte-check and check:curation both pass.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added separate lightness controls for light and dark modes in LumLocker.
    • Added the --sf-lumlocker-dark token with automatic mirrored defaults and override support.
    • Improved dark-mode brand color previews and contrast preservation.
  • Documentation

    • Updated token references, counts, version information, and LumLocker guidance for version 0.8.0.

LumLocker pinned both themes to one shared --sf-lumlocker L, so a lock
tuned for a light surface read poorly on a dark one. Add
--sf-lumlocker-dark, defaulting to the light L mirrored around the
light/dark background midpoint — clamp(0.5, calc(1.18 - L), 0.92) — which
holds contrast against the surface in both themes. Dark theme's lock rule
now reads the dark token; light is unchanged.

The configurator LumLocker panel gains an "L · dark" slider and resolves
the D preview rows against the dark L.

New PUBLIC-ADVANCED token: docs, annotations, registry, api-index and the
llm-guide token count regenerated; minor version bump to 0.8.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BRVgKfEYocPCHz9KU96FN
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 70c189cd-dafe-4aa9-8a57-27dfc3b75275

📥 Commits

Reviewing files that changed from the base of the PR and between 546d4fa and fe061ea.

⛔ Files ignored due to path filters (1)
  • configurator/src/data/api-index.generated.json is excluded by !**/*.generated.*
📒 Files selected for processing (7)
  • demo/index.html
  • docs/api-index.json
  • docs/api-index.md
  • docs/token-index.json
  • docs/token-index.md
  • scripts/token-tiers.js
  • tests/token-api.snapshot.json
📝 Walkthrough

Walkthrough

Adds --sf-lumlocker-dark with mirrored defaults and override support, applies separate lightness targets to dark-mode brand colors, adds configurator controls and preview handling, and updates token registries, documentation, release versions, counts, and changelog entries.

Changes

LumLocker dark-mode support

Layer / File(s) Summary
Runtime lightness locking
core/tokens.css, core/themes.css
Defines the mirrored --sf-lumlocker-dark value and applies separate light- and dark-mode OKLCH lightness targets to locked brand colors.
Configurator dark-mode controls
configurator/src/components/panels/ColorsPanel.svelte, CHANGELOG.md
Derives or reads the dark lightness override, uses it for dark locked swatches, and adds separate light and dark LumLocker sliders.
Token registration and classification
token-registry.json, scripts/token-tiers.js, docs/api-index.*, docs/token-*, docs/registry.json, docs/token-annotations.json
Registers --sf-lumlocker-dark, classifies it as PUBLIC-ADVANCED, and refreshes generated token indexes, counts, and descriptions.
Release metadata and documentation
package.json, configurator/package.json, docs/roadmap.md, docs/llm-guide.md, README.md, badges/badge-optimal.json
Updates version, token-count, LumLocker guidance, changelog-facing metadata, and the gzip-size badge.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: codex

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: LumLocker now has a separate dark-mode lightness value.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/configurator-controls-audit-zqi10q

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.

@coderabbitai coderabbitai Bot added the codex label Jul 19, 2026
The token-coverage gate requires every token in docs/api-index.json to
appear in demo/index.html and the embedded #cov-data token list to match
the live API set exactly. Add the new --sf-lumlocker-dark cov-token row
and list entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BRVgKfEYocPCHz9KU96FN
@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds --sf-lumlocker-dark, a separate OKLCH lightness target for dark mode under [data-lumlocker], fixing the previous behavior where both themes shared a single light-tuned L value that rendered poorly on dark surfaces.

  • New token --sf-lumlocker-dark (PUBLIC-ADVANCED) defaults to clamp(0.5, calc(1.18 - var(--sf-lumlocker)), 0.92) — mirroring the light L around 0.59 (the midpoint of the light ~0.96 and dark ~0.22 page backgrounds), matching the same 1.18 constant already used in the base color's dark-source formula in themes.css.
  • core/themes.css — both the light-dark() dark branch and the [data-theme=\"dark\"] / .sf-theme-dark override now consume --sf-lumlocker-dark; the light branch is unchanged.
  • Configurator gains a second "L · dark" slider whose default value mirrors the light slider live (via Svelte $derived.by()), and the swatch preview correctly resolves dark rows against the dark L. All docs, generated indexes, and version references are consistently updated to 0.8.0.

Confidence Score: 5/5

Safe to merge — the change is purely additive, all existing light-mode behavior is preserved, and the dark-mode overrides are consistently applied across every affected selector site.

The midpoint math (1.18 = 2 × 0.59, already used by the base-color dark-source formula in the same file) is correct. The CSS cascade order in themes.css is sound: light-dark() handles OS-level scheme, and [data-theme] selectors correctly shadow it. The Svelte $derived.by() mirrors the CSS formula exactly, so the configurator preview matches real output. All generated artifacts, counts, and version references are consistently updated. The only note is a minor semantic tension in how --sf-lumlocker-dark is typed in the token taxonomy.

No files require special attention. The token taxonomy documentation (docs/token-index.md and mirrored files) has a minor role-classification nuance worth revisiting, but it does not affect runtime behavior.

Important Files Changed

Filename Overview
core/tokens.css Adds --sf-lumlocker-dark: clamp(0.5, calc(1.18 - var(--sf-lumlocker)), 0.92) to :root with thorough inline documentation explaining the midpoint math; no issues.
core/themes.css All dark-mode LumLocker override sites (light-dark() dark branch + [data-theme=dark] selector) consistently updated to --sf-lumlocker-dark; light overrides unchanged; cascade order is correct.
configurator/src/components/panels/ColorsPanel.svelte New lumlockerLDark $derived.by() correctly mirrors the light L with the same clamp formula as the CSS; swatch preview uses dark L for D rows; slider wiring and reset are correct.
scripts/token-tiers.js --sf-lumlocker-dark correctly added to the ADVANCED set alongside --sf-lumlocker.
docs/api-index.json New token entry for --sf-lumlocker-dark added with correct metadata; all counts updated (746 to 747 tokens, 52 to 53 PUBLIC-ADVANCED).
docs/token-index.md --sf-lumlocker-dark listed as consumption role while its description explicitly invites users to override it — minor semantic tension with the taxonomy definition.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User sets --sf-lumlocker (default 0.65)"] --> B["--sf-lumlocker-dark default\nclamp(0.5, 1.18 - L, 0.92)"]
    A --> C["User may override --sf-lumlocker-dark\n(breaks the mirror)"]
    C --> D["--sf-lumlocker-dark = explicit value"]
    B --> E{"Color scheme?"}
    D --> E
    E -->|"light-dark() light branch or [data-theme=light]"| F["oklch(from source var(--sf-lumlocker) c h)"]
    E -->|"light-dark() dark branch or [data-theme=dark]"| G["oklch(from source var(--sf-lumlocker-dark) c h)"]
    F --> H["--sf-color-primary / secondary / tertiary / action"]
    G --> H
    H --> I["Rendered under :root[data-lumlocker]"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["User sets --sf-lumlocker (default 0.65)"] --> B["--sf-lumlocker-dark default\nclamp(0.5, 1.18 - L, 0.92)"]
    A --> C["User may override --sf-lumlocker-dark\n(breaks the mirror)"]
    C --> D["--sf-lumlocker-dark = explicit value"]
    B --> E{"Color scheme?"}
    D --> E
    E -->|"light-dark() light branch or [data-theme=light]"| F["oklch(from source var(--sf-lumlocker) c h)"]
    E -->|"light-dark() dark branch or [data-theme=dark]"| G["oklch(from source var(--sf-lumlocker-dark) c h)"]
    F --> H["--sf-color-primary / secondary / tertiary / action"]
    G --> H
    H --> I["Rendered under :root[data-lumlocker]"]
Loading

Comments Outside Diff (1)

  1. docs/token-index.md, line 610 (link)

    P2 consumption role conflicts with user-override intent

    The taxonomy defines consumption as "Ready-to-use output you read; derived from other tokens via var(--sf-…)", and knob requires a literal primitive default. --sf-lumlocker-dark has a derived default, so consumption is technically correct under the taxonomy — but the token's own description says "Set explicitly to break the mirror," which is knob-like behavior. A user reading the token index and seeing consumption would reasonably conclude they should not set this property. This same tension appears in docs/api-index.md, docs/api-index.json, configurator/src/data/api-index.generated.json, and dist/css-custom-data.json. Worth either adding a note to the taxonomy definition (e.g. "consumption tokens with derived defaults may still be overridden") or extending the taxonomy with a third role like derived-knob.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "test(demo): add --sf-lumlocker-dark to c..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/token-tiers.js`:
- Around line 41-42: Add --sf-lumlocker-dark to the FORCED_KNOB set in
scripts/token-tiers.js, while preserving its existing ADVANCED classification,
so generated API and documentation output treats it as a knob instead of
consumption.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c5e7cac-6df8-40d3-966d-98a19d06fb74

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0e053 and 546d4fa.

⛔ Files ignored due to path filters (5)
  • configurator/package-lock.json is excluded by !**/package-lock.json
  • configurator/src/data/api-index.generated.json is excluded by !**/*.generated.*
  • configurator/src/data/token-registry.generated.json is excluded by !**/*.generated.*
  • dist/css-custom-data.json is excluded by !**/dist/**
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • CHANGELOG.md
  • README.md
  • badges/badge-optimal.json
  • configurator/package.json
  • configurator/src/components/panels/ColorsPanel.svelte
  • core/themes.css
  • core/tokens.css
  • docs/api-index.json
  • docs/api-index.md
  • docs/llm-guide.md
  • docs/registry.json
  • docs/roadmap.md
  • docs/token-annotations.json
  • docs/token-index.json
  • docs/token-index.md
  • docs/tokens.md
  • package.json
  • scripts/token-tiers.js
  • token-registry.json

Comment thread scripts/token-tiers.js
claude added 2 commits July 19, 2026 11:32
Its default references var(--sf-lumlocker), so the value-shape heuristic
in roleOf() mislabeled it as a consumption (read-only) token. It is a
user-settable input whose default merely happens to be a derived mirror —
exactly the case FORCED_KNOB exists for (cf. --sf-shadow-strength). Add it
there so the generated API/docs advertise it as a knob users can set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BRVgKfEYocPCHz9KU96FN
The locked token-surface snapshot (tests/token-api.spec.js, e2e suite)
must record intentional public-token additions. Add the new token; the
CHANGELOG already carries its ### Features entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BRVgKfEYocPCHz9KU96FN
@jackgranatowski
jackgranatowski merged commit 0deb2c3 into main Jul 19, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants