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 ¶
- 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 DimAxes() []string
- func TierOf(name string) (tier string, ok bool)
- func ValidHex(s string) bool
- type CatalogToken
- type DimToken
- 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).
func DimAxes ¶ added in v0.17.3
func DimAxes() []string
DimAxes returns the dimension axis ids in catalog order.
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
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.
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 ValidateDimensions ¶ added in v0.17.3
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 ¶
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.