Skip to content

feat(tint): add allowedHues and customColors options#134

Open
jimeh wants to merge 1 commit into
mainfrom
claude/custom-color-options-XjMr4
Open

feat(tint): add allowedHues and customColors options#134
jimeh wants to merge 1 commit into
mainfrom
claude/custom-color-options-XjMr4

Conversation

@jimeh

@jimeh jimeh commented Apr 5, 2026

Copy link
Copy Markdown
Owner

Add two new user-configurable tint options:

  • glaze.tint.allowedHues: array of integers (0-359) that restricts
    the computed base hue to the nearest allowed value using circular
    distance. Useful for clamping colors to a curated palette.

  • glaze.tint.customColors: array of hex colors that bypasses hue
    generation and color style entirely, selecting a color
    deterministically based on workspace identifier. Color harmony and
    theme blending still apply.

Precedence: baseHueOverride > customColors > allowedHues > default.

https://claude.ai/code/session_01GmcCNnBHQjufe4u8wd7Hw6

Add two new user-configurable tint options:

- `glaze.tint.allowedHues`: array of integers (0-359) that restricts
  the computed base hue to the nearest allowed value using circular
  distance. Useful for clamping colors to a curated palette.

- `glaze.tint.customColors`: array of hex colors that bypasses hue
  generation and color style entirely, selecting a color
  deterministically based on workspace identifier. Color harmony and
  theme blending still apply.

Precedence: baseHueOverride > customColors > allowedHues > default.

https://claude.ai/code/session_01GmcCNnBHQjufe4u8wd7Hw6
Copilot AI review requested due to automatic review settings April 5, 2026 15:53
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dd8ede01-f1e1-44b8-b85f-66c3d45d05c9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/custom-color-options-XjMr4
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/custom-color-options-XjMr4

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.

Copilot AI 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.

Pull request overview

Adds two new tint configuration options to support palette clamping (allowedHues) and deterministic selection from a user-provided color list (customColors), integrating them into the tint computation pipeline.

Changes:

  • Introduces config schema + runtime validation for glaze.tint.allowedHues and glaze.tint.customColors.
  • Extends computeTint with helpers to snap computed hues to an allowed set and to deterministically select a custom base color.
  • Wires new options into reconcile and adds unit tests for validators and tint behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/config/validate.ts Adds validators for allowed hues and custom hex colors.
src/config/index.ts Reads new settings and includes them in getTintConfig().
src/config/types.ts Extends TintConfig to include allowedHues and customColors.
src/color/tint.ts Implements snapToAllowedHue, selectCustomColor, and integrates both into computeTint.
src/reconcile/core.ts Passes allowedHues/customColors into computeTint when applying colors.
src/test/config/validate.test.ts Adds tests for the new config validators.
src/test/color/tint.test.ts Adds tests for snapping, custom selection, and computeTint precedence.
package.json Adds VS Code settings schema entries for the new options.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/config/validate.ts
Comment on lines +82 to +90
export function _validateAllowedHues(value: unknown[]): number[] {
const seen = new Set<number>();
const result: number[] = [];
for (const v of value) {
if (
typeof v === 'number' &&
Number.isInteger(v) &&
v >= 0 &&
v <= 359 &&

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

_validateAllowedHues/_validateCustomColors assume value is an array. If the user misconfigures the setting to a non-array (e.g. number/object), the for..of will throw at runtime. Consider changing the parameter type to unknown, guarding with Array.isArray(value), and returning [] when it isn't an array.

Copilot uses AI. Check for mistakes.
Comment thread src/color/tint.ts
export function snapToAllowedHue(
hue: number,
allowed: readonly number[]
): number {

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

snapToAllowedHue is exported but assumes allowed is non-empty (uses allowed[0]). If it’s ever called with an empty array, it will return undefined/produce incorrect results. Add an explicit guard (e.g. return hue or throw a clear error) to make the function safe as a public helper.

Suggested change
): number {
): number {
if (allowed.length === 0) {
throw new Error('snapToAllowedHue requires at least one allowed hue');
}

Copilot uses AI. Check for mistakes.
Comment thread src/color/tint.ts
Comment on lines +208 to +216
export function selectCustomColor(
identifier: string,
seed: number,
colors: readonly string[]
): OKLCH {
const workspaceHash = hashString(identifier);
const seedHash = seed !== 0 ? hashString(seed.toString()) : 0;
const index = ((workspaceHash ^ seedHash) >>> 0) % colors.length;
return hexToOklch(colors[index]);

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

selectCustomColor is exported but assumes colors.length > 0; modulo by 0 will yield NaN and colors[index] will be undefined. Add a guard for empty colors (throw a clear error or return a fallback) so misuse fails deterministically.

Copilot uses AI. Check for mistakes.
Comment thread src/color/tint.ts
Comment on lines +269 to +280
// Custom colors: select deterministically, extract hue
customBaseOklch = selectCustomColor(
options.workspaceIdentifier,
seed,
customColors
);
baseHue = customBaseOklch.h;
} else if (options.workspaceIdentifier !== undefined) {
baseHue = computeBaseHue(options.workspaceIdentifier, seed);
if (allowedHues.length > 0) {
baseHue = snapToAllowedHue(baseHue, allowedHues);
}

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

In customColors mode, baseHue is taken directly from hexToOklch(...).h, which is typically a fractional hue (unlike computeBaseHue/allowedHues/override which are integers). If downstream consumers expect integer degrees, consider normalizing/rounding (and wrapping to [0,360)) or updating the surrounding contract/docs so baseHue’s type/range is consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +580 to +584
const a = selectCustomColor('test', 0, colors);
const b = selectCustomColor('test', 42, colors);
// With 4 colors and different seeds, results are likely different
// (not guaranteed but extremely likely with SHA-256)
assert.notDeepStrictEqual(a, b);

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

This test is probabilistic: with colors.length === 4, different seeds still have a 25% chance of selecting the same index, making the assertion flaky. Prefer a deterministic approach (e.g. search for a seed that yields a different index, or assert a specific expected index for known inputs).

Suggested change
const a = selectCustomColor('test', 0, colors);
const b = selectCustomColor('test', 42, colors);
// With 4 colors and different seeds, results are likely different
// (not guaranteed but extremely likely with SHA-256)
assert.notDeepStrictEqual(a, b);
const baseline = selectCustomColor('test', 0, colors);
let changed: ReturnType<typeof selectCustomColor> | undefined;
for (let seed = 1; seed <= 100; seed++) {
const candidate = selectCustomColor('test', seed, colors);
if (
candidate.l !== baseline.l ||
candidate.c !== baseline.c ||
candidate.h !== baseline.h
) {
changed = candidate;
break;
}
}
assert.ok(changed, 'expected at least one seed to select a different color');
assert.notDeepStrictEqual(baseline, changed);

Copilot uses AI. Check for mistakes.
Comment thread src/reconcile/core.ts
Comment on lines 192 to 197
themeBlendFactor: themeConfig.blendFactor,
targetBlendFactors: themeConfig.targetBlendFactors,
seed: tintConfig.seed,
allowedHues: tintConfig.allowedHues,
customColors: tintConfig.customColors,
});

Copilot AI Apr 5, 2026

Copy link

Choose a reason for hiding this comment

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

applyTintColors now passes allowedHues/customColors into computeTint, but other call sites (e.g. status UI/state builders) compute baseHue directly and pass baseHue into computeTint, which will bypass these new options and can lead to mismatched displayed vs applied colors. Consider updating the other computeTint call sites to pass these options (or refactor base hue resolution into a shared helper).

Copilot uses AI. Check for mistakes.
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.

3 participants