fix(brand): apply #497 to the source copy so check:assets passes again

#497 patched cli/assets/skills/brand/scripts/sync-brand-to-tokens.cjs and its
test, but sync-assets.mjs mirrors .claude/skills/ into cli/assets/, so the
source copy still carried the old adjustBrightness and check:assets reported
two stale asset files on main. That check is the first step of both CI
workflows, so every PR since then stopped there. Mirror the same two files
into the source copy; the diff is byte-identical to #497's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
notbucki 2026-09-15 10:54:29 +02:00
parent d8ce090f82
commit 975cba327e
2 changed files with 58 additions and 5 deletions

View File

@ -99,15 +99,34 @@ function generateColorScale(baseHex, darkHex, lightHex) {
}
/**
* Adjust hex color brightness
* Adjust hex color brightness.
*
* Blends each channel proportionally toward white (percent > 0) or toward
* black (percent < 0) instead of adding/subtracting a flat 255*percent to
* every channel. The flat-shift approach clamped all three channels to 0
* (or 255) whenever the base color's channels were already low (or high)
* relative to the shift e.g. darkening a dark brand color like #4A3228
* by -0.3/-0.45/-0.6 produced #000000 for all three, collapsing shades
* 700/800/900 into an identical, useless black.
*/
function adjustBrightness(hex, percent) {
if (typeof hex !== 'string') return '#000000';
const num = parseInt(hex.replace('#', ''), 16);
const r = Math.min(255, Math.max(0, (num >> 16) + Math.round(255 * percent)));
const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00FF) + Math.round(255 * percent)));
const b = Math.min(255, Math.max(0, (num & 0x0000FF) + Math.round(255 * percent)));
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase()}`;
const r = (num >> 16) & 0xFF;
const g = (num >> 8) & 0xFF;
const b = num & 0xFF;
const adjustChannel = (channel) => {
const adjusted = percent >= 0
? channel + (255 - channel) * percent
: channel * (1 + percent);
return Math.min(255, Math.max(0, Math.round(adjusted)));
};
const newR = adjustChannel(r);
const newG = adjustChannel(g);
const newB = adjustChannel(b);
return `#${((newR << 16) | (newG << 8) | newB).toString(16).padStart(6, '0').toUpperCase()}`;
}
/**

View File

@ -71,6 +71,40 @@ def test_sync_parses_bundled_starter_template(tmp_path):
assert css.exists() and css.stat().st_size > 0
def test_dark_base_color_does_not_collapse_shades_to_black(tmp_path):
"""adjustBrightness() used to add/subtract a flat 255*percent per channel.
For a dark base color (channels already close to 0), darkening by
-0.3/-0.45/-0.6 clamped every channel to 0, so shades 700, 800, and 900
all came back as the identical, useless #000000 instead of a graded dark
scale. This runs the sync against a dark, coffee-roastery-style brand
color and asserts the three shades stay distinct and non-black.
"""
(tmp_path / "docs").mkdir()
(tmp_path / "assets").mkdir()
shutil.copy(TOKENS_STARTER, tmp_path / "assets" / "design-tokens.json")
(tmp_path / "docs" / "brand-guidelines.md").write_text(
"## Quick Reference\n\n"
"| Element | Value |\n"
"|---------|-------|\n"
"| Primary Color | #4A3228 |\n"
"| Secondary Color | #C08A3E |\n"
"| Accent Color | #6B8F71 |\n"
)
result = _run(tmp_path)
assert result.returncode == 0, result.stderr + result.stdout
tokens = json.loads((tmp_path / "assets" / "design-tokens.json").read_text())
primary = tokens["primitive"]["color"]["primary"]
dark_shades = [primary[shade]["$value"] for shade in ("700", "800", "900")]
assert len(set(dark_shades)) == 3, (
f"expected three distinct dark shades, got {dark_shades}"
)
assert "#000000" not in dark_shades, dark_shades
def test_reports_missing_guidelines_without_breaking_the_harness(tmp_path):
"""The missing-guidelines path is the one that breaks a locale-decoded pipe.