* fix(colour): clamp out-of-range text_color components instead of dropping them
_normalize_color returned None for a triple with a component outside 0..255,
and None means "not configured" to element_color -- so configuring
[300, 0, 20] silently handed the element its *default* colour rather than red.
Every scoreboard reads its per-element colours through this path, so the bug
reached all eight.
It is also the odd one out: sports_card.coerce_rgb and
SportsShared._coerce_rgb both clamp, and core's own test is named
test_coerce_rgb_clamps_rather_than_rejecting. The rejecting normaliser arrived
with the shared readers in 82a65ad2 (#425) while the eight plugins' colour
tests kept asserting the clamping behaviour they had before, so the two sides
have disagreed ever since.
Clamped inline rather than delegating to coerce_rgb: sports_card already
imports element_style, so importing back would be circular.
Adds the core assertion whose absence let this drift -- element_color had no
test covering an out-of-range component.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(element-style): cover ElementStyleResolver's own colour clamp path
CodeRabbit flagged that the new sports_card clamp regression test only
exercises element_color(); ElementStyleResolver._resolve() normalizes
configured colours through a separate call to the same _normalize_color,
comparing against a schema/classic reference to decide user_forced_color.
Add a resolver-level case so a future regression in that path (e.g. going
back to rejecting out-of-range components instead of clamping) is caught
too.
Mutation-checked: fails if _normalize_color rejects instead of clamps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP6kWxjUtJi72c56GaMmC8
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>