Documentation
¶
Overview ¶
Package tokencheck validates user color-token overrides (GDK-769 wave, GDK-785) with the same color math tools/theme-check.mjs asserts the shipped palettes with at build time. User colors arrive at runtime, where the build gate cannot see them; this package is the write-time refusal point and the load-time warning source for that surface.
The two implementations are pinned together by golden vectors: tools/theme-check.mjs --emit-vectors writes testdata/token-vectors.json from the exact formulas below, and the tests consume the full file. A formula that drifts here shows up as a vector mismatch, not as a silently different acceptance boundary.
Tier policy (single source: the embedded catalog.json, generated by tools/token-catalog.mjs from web/src/app.css — GDK-787):
locked 10 tokens — grounds (bg-*), text tiers, search-match, shell.
Overrides are refused; palette-level opening is the
custom-palette scope (GDK-789).
validated 12 tokens — accent 4, border 2, status inks 5, focus-ring.
Open for override, checked at write time by the rules below.
free 22 tokens — lozenge/avatar/dept chips, scrim, scrollbar-hover.
Any valid hex passes.
Rule derivations — every floor is a current theme-check assertion, carried over verbatim (no floor is invented in this package):
status-pair pairwise status-ink ΔEok ≥ 0.05
(theme-check.mjs PAIR_N, checkPairwise)
status-pair-deuteranopia the same pairs after the Machado 2009
severity-1.0 simulation, ΔEok ≥ 0.04
(theme-check.mjs PAIR_D, DEUT_M — the matrix
must not be substituted or transposed)
status-role-floor overridden status ink on each ground:
≥ 4.5 on bg-base/panel/elevated (text role),
≥ 3 on bg-hover/active (dot role), compared at
two-decimal rounding like theme-check cr()
("status ink floors" + "per-role ink contrast
floors" sections)
accent-text-contrast accent-text on accent-subtle and on each
ground ≥ 4.5 — the text-role floor theme-check
applies to ink-on-ground pairs, applied to the
surfaces accent-text is actually painted on
(badges on accent-subtle, keys and links on the
grounds; measured: every shipped palette clears
it on all six, minimum 5.27)
Deliberate narrowing (three-part justification, per the GDK-769 investigation Q2 recommendation adopted 2026-08-24): theme-check also holds status-ink loudness RANK invariant across palettes (its "rank" section). The runtime does not require rank of an override — rank is taste, not legibility; the build assertion stays in force for app.css palettes. Same formulas and same thresholds are kept everywhere rank IS legibility.
For accent, accent-hover, accent-subtle, border-subtle, border-strong and focus-ring, theme-check carries no named assertion today, so no floor is carried for them either — their carried rule is hex validity only, which is what their empty extra rule lists in catalog.json record. Adding a real floor means theme-check gains the assertion first, then the catalog rules lists and this package grow together (GDK-786 hand-off).
Input defense (GDK-769 axis 3): unknown token names are IGNORED with a warn-severity violation — a stale schema (a token renamed or removed in a newer catalog) must never panic or brick a config write. Malicious or corrupt values (huge strings, non-color payloads, truncated hex) are rejected as invalid hex; echoed values are truncated so an error message cannot be turned into a paste bomb.
Index ¶
- func CatalogPalettes() []string
- func CatalogValue(name, palette string) (string, bool)
- func Contrast(a, b string) float64
- func DEok(a, b string) float64
- func Deut(hex string) string
- func TierOf(name string) (tier string, ok bool)
- func ValidHex(s string) bool
- type CatalogToken
- type Severity
- type Violation
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CatalogPalettes ¶
func CatalogPalettes() []string
CatalogPalettes returns the palette axis (light first, then data-theme blocks in sorted order).
func CatalogValue ¶
CatalogValue returns the catalog value of a token in one palette.
func Contrast ¶
Contrast is the WCAG relative-luminance contrast ratio; the lighter side is numerator regardless of argument order, as in theme-check.
func Deut ¶
Deut maps a hex color through the deuteranopia simulation and returns the resulting hex (channels clamped and rounded, like theme-check rgb2hex).
Types ¶
type CatalogToken ¶
type CatalogToken struct {
Name string `json:"name"`
CSSVar string `json:"cssVar"`
Tier string `json:"tier"`
Description string `json:"description"`
Rules []string `json:"rules"`
Values map[string]string `json:"values"`
}
CatalogToken is one entry of the generated token catalog.
func CatalogTokens ¶
func CatalogTokens() []CatalogToken
CatalogTokens returns the embedded catalog in file (app.css) order.
type Severity ¶
type Severity string
Severity of a violation. Reject means the write must be refused; warn means the input is carried but the user must be told.
type Violation ¶
type Violation struct {
Token string `json:"token"`
Rule string `json:"rule"`
Severity Severity `json:"severity"`
Measured string `json:"measured,omitempty"`
Floor string `json:"floor,omitempty"`
Message string `json:"message"`
}
Violation is one rule outcome. Measured and Floor carry the real numbers so an agent can fix its own color without a round trip.
func ValidateTokens ¶
ValidateTokens checks a partial set of color-token overrides against the tier policy. Keys are bare token names ("accent") or CSS variable names ("--color-accent"); values are hex colors. base is the palette the overrides will be applied to (name → raw value, hex for everything the rules read); tokens the caller did not override are filled from it, so a one-token override is checked in the context it will actually render in.
The result is deterministic for identical inputs: per-token checks run in sorted name order, group rules in STATS/GROUNDS order. Duplicate keys that normalize to the same name (both "--color-x" and "x") resolve last-wins in sorted key order, like CSS. Unknown names never panic: they produce a warn violation and are ignored.