tokencheck

package
v0.18.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Dimension-token validation (dim-token chunk 1) — the sibling of the color side of this package. The embedded dim-catalog.json is generated by tools/dim-catalog.mjs (spacing/type defaults from web/src/app.css @theme, layout defaults from the JS-owned constants the generator tables), and the shapes are pinned together by TestDimCatalogShape the way TestCatalogShape pins catalog.json.

No color math runs here by design: a length has no hex form, no ΔEok and no contrast. The write-time rules are instead:

length   px lengths are non-negative integers or one-decimal values with
         a lowercase px suffix ("44px", "42.5px"); line-heights (unit
         none) are unitless with one or two decimals ("1.4", "1.35").
         Stricter-than-CSS on purpose (the parseHex precedent: stricter
         can only refuse, never misread). REFUSES — a value that cannot
         parse can never render (machine check).
range    every validated-range token carries min/max in the catalog;
         bounds are inclusive. Where the lower bound is a cross-token
         relation instead of a constant (row-excerpt, detail-max) the
         catalog omits min and the relation below owns it. WARNS — the
         range is tested territory, not a shape; the value is
         carried and still participates in the relations below, which
         judge it as it renders.
relation five cross-token rules from the chunk-1 spec (control-sm ≤
         control, row-excerpt ≥ row+8, detail-max ≥ detail-min,
         overlay-max ≤ shell-max, sidebar-narrow ≤ sidebar) plus the
         census type-step gap (neighbors ≥ 2px apart). Relations judge
         the EFFECTIVE set — defaults fill whatever the user did not
         override — and run only when a participant was overridden, like
         the color group rules. WARNS; type-axis warnings list
         the ladder that moves together.
locked   layout.docked-min is the derived dock floor
         (sidebar+list-min+detail-min). Writes are refused — a stored
         value would be overwritten by the recomputation of the three
         track tokens, so refusing is the honest answer; the message
         keeps teaching the three tokens to set instead.

Severity split (user decision 2026-08-25): length and locked refuse (machine check / derived); range and relation warn and carry — they are judgments about tested territory and visual rhythm, and "my look is mine" (docs/decisions/0003 has gadak forcing no taste on the user).

Input defense mirrors the color contract (GDK-769 axis 3): unknown axis ids and unknown token names produce warn-severity violations and are carried, never a panic and never a refused save — a config written by a newer gadak must keep loading.

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 gate and the load-time warning source for that surface.

Severity policy (user decision 2026-08-25: "난 대비는 워닝만 떠야지 거절은 아니라고 생각해. 대비 뿐 아니라 전반적으로" — judgment warns, only machine-checkable conditions refuse): a violation REFUSES when, and only when, a machine can see it without taste — a value that cannot parse (non-hex color). Everything judgment-shaped — tier, contrast, ΔEok, deuteranopia — WARNS and the value is carried: the look is the user's, and live filming measured agents burning turns working around refusals. The measurements stay in every warning (the diagnostics are unchanged; only the verdict moved), and the message teaches the next move instead of blocking it.

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 WARN and save; palette authoring stays
           the recommendation (custom-palette scope, GDK-789).
validated  12 tokens — accent 4, border 2, status inks 5, focus-ring.
           Open for override, judged at write time by the rules below
           (warn, not refuse).
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

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

func CatalogValue(name, palette string) (string, bool)

CatalogValue returns the catalog value of a token in one palette.

func Contrast

func Contrast(a, b string) float64

Contrast is the WCAG relative-luminance contrast ratio; the lighter side is numerator regardless of argument order, as in theme-check.

func DEok

func DEok(a, b string) float64

DEok is the OKLab Euclidean distance between two hex colors.

func Deut

func Deut(hex string) string

Deut maps a hex color through the deuteranopia simulation and returns the resulting hex (channels clamped and rounded, like theme-check rgb2hex).

func DimAxes added in v0.17.3

func DimAxes() []string

DimAxes returns the dimension axis ids in catalog order.

func TierOf

func TierOf(name string) (tier string, ok bool)

TierOf reports the tier of a token, by bare name ("accent") or CSS variable name ("--color-accent"). ok is false for names the catalog does not carry.

func ValidHex

func ValidHex(s string) bool

ValidHex reports whether s is an acceptable override value.

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 DimToken added in v0.17.3

type DimToken struct {
	CSSVar  string   `json:"cssVar"`
	Default string   `json:"default"`
	Tier    string   `json:"tier"`
	Unit    string   `json:"unit"`
	Min     *float64 `json:"min"`
	Max     *float64 `json:"max"`
}

DimToken is one entry of the generated dimension catalog. Min/Max are pointers so an omitted bound (relation-owned lower bounds, the locked token) stays absent rather than reading as zero.

func DimTokenOf added in v0.17.3

func DimTokenOf(axis, name string) (DimToken, bool)

DimTokenOf looks up one dimension token by bare name ("row") or its CSS variable form ("--spacing-row"). ok is false for names the catalog does not carry.

func (DimToken) ParseValue added in v0.17.3

func (t DimToken) ParseValue(v string) (float64, bool)

ParseValue parses v according to the token unit and reports the numeric value. px requires a positive number; unitless accepts the line-height shape. Use ok, not the value, as the verdict.

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.

const (
	SeverityReject Severity = "reject"
	SeverityWarn   Severity = "warn"
)

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 ValidateDimensions added in v0.17.3

func ValidateDimensions(axes map[string]map[string]string) []Violation

ValidateDimensions is the write gate for the dimension axes: tier, length format, range and cross-token relations. Input is axis id → token name → value; names may be bare ("row") or CSS variables ("--spacing-row"). Unknown axis ids and unknown names warn and are carried (GDK-769 axis 3). Of the failures, length format and the locked tier refuse (machine check / derived sum); range and relation violations warn and the value is carried (judgment, not shape).

func ValidateTokens

func ValidateTokens(overrides, base map[string]string) []Violation

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL