@ksmv/ui-checks 0.18.1 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.1 - 2026-10-14
4
+
5
+ - **A 0.20.0 não foi publicada.** A verificação da release parou por tempo, na suíte de navegadores, antes de a versão chegar ao npm, e nada mudou no pacote desde então: de 0.19.0, atualize direto para a 0.20.1. A tag `checks-v0.20.0` existe no repositório, mas não tem versão no registro.
6
+ - **Contratos de tema 3.1.0 e de contraste 9.0.0** empacotados. O de tema ganha 113 derivados opcionais e 2 tokens de forma; o de contraste ganha 88 pares (a superfície inversa, a paleta categórica, o aviso e o rótulo de seção, o item de menu e o cartão de rádio, o cabeçalho da tabela e o grupo de alternância, o selo cheio, a bolinha sobre a superfície inversa e a barra). Nenhum par sai, e cada par novo se reduz, com os padrões, a um par que já passava: um tema que não declara nada continua passando. Regras e CLI não mudam. Atualize o `@ksmv/ui-react` junto (ver o CHANGELOG dele).
7
+ - **Quem usa `computedEvidence` precisa capturar a evidência de novo:** os dois contratos mudaram.
8
+
9
+ ## 0.19.0 - 2026-10-13
10
+
11
+ - **Contratos de tema 3.0.0 e de contraste 8.0.0** empacotados, os dois em `schemaVersion: 2`; um contrato `schemaVersion: 1` passa a ser recusado. Atualize o `@ksmv/ui-react` junto (ver o CHANGELOG dele).
12
+ - **A forma é da biblioteca:** a regra nova `themes/no-shape` aponta, como violação, cada token da lista `shape` que um seletor de tema declarar. Quem migra pode carregar essas violações na baseline enquanto tira as linhas. A validação de densidade sai.
13
+ - **Derivados tipados:** um derivado aponta para outro token ou tem um padrão literal (`transparent` ou uma cor), usado quando o tema não o declara.
14
+ - **Fundo translúcido:** um par do manifesto pode dizer sobre qual superfície está (`over`); um fundo translúcido é composto sobre ela antes da medida. Sem `over`, ele continua cobertura pendente.
15
+ - **Quem usa `computedEvidence` precisa capturar a evidência de novo:** os dois contratos mudaram.
16
+
3
17
  ## 0.18.1 - 2026-10-11
4
18
 
5
19
  - **A 0.18.0 não foi publicada.** A verificação da release parou por tempo, antes de a versão chegar ao npm, e nada mudou no pacote desde então: de 0.17.0, atualize direto para a 0.18.1. A tag `checks-v0.18.0` existe no repositório, mas não tem versão no registro.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @ksmv/ui-checks
2
2
 
3
- Verificações de qualidade para o contrato semântico `--ui-*` de `@ksmv/ui-react`: completude dos temas, contraste dos pares do contrato (ações, estados, campos, controles e, desde o contrato de contraste `3.0.0`, o texto base sobre as superfícies e, desde o `4.0.0`, a cor de link e, desde o `5.0.0`, a borda das ações e, desde o `6.0.0`, as variantes de ação da 0.17.0 e o botão de texto e, desde o `7.0.0`, os botões da casa da 0.18.1 e o hover por tom do botão de texto), prefixo dos tokens, cores literais fora dos arquivos de tema e evidência calculada vinculada às fontes.
3
+ Verificações de qualidade para o contrato semântico `--ui-*` de `@ksmv/ui-react`: completude dos temas, contraste dos pares do contrato (ações, estados, campos, controles e, desde o contrato de contraste `3.0.0`, o texto base sobre as superfícies e, desde o `4.0.0`, a cor de link e, desde o `5.0.0`, a borda das ações e, desde o `6.0.0`, as variantes de ação da 0.17.0 e o botão de texto e, desde o `7.0.0`, os botões da casa da 0.18.1 e o hover por tom do botão de texto e, desde o `7.1.0`, o texto de hover dos botões suaves e do contorno de aviso e, desde o `9.0.0`, o texto, o limite de controle, o link, o botão de texto e o fantasma sobre a superfície inversa, a paleta categórica, a descrição do diálogo, o giro contra a superfície, o aviso e o rótulo de seção, o item de menu e o cartão de rádio, o cabeçalho da tabela e o grupo de alternância, e o selo cheio, a bolinha sobre a superfície inversa e a barra), prefixo dos tokens, cores literais fora dos arquivos de tema e evidência calculada vinculada às fontes.
4
4
 
5
5
  ## Antes de depender deste pacote
6
6
 
@@ -95,9 +95,9 @@ config Invalid computed theme evidence in artifacts/theme-evidence.json: the the
95
95
 
96
96
  Depois de atualizar o pacote React, a mensagem traz a versão do contrato da captura e a do contrato instalado; quando faltam tokens ou resta `var(...)`, traz os nomes dos tokens, em ordem, até dez, e a contagem dos demais. A mensagem não repete valores do produto: aparecem só o caminho da evidência e o nome do tema como estão na configuração, nomes de tokens e versões de contrato. Falha ao resolver os contratos também é relatada com a própria causa (`Unable to resolve the configured contract source.`, `Unsupported theme contract.` ou `Unsupported contrast manifest.`).
97
97
 
98
- Tokens opcionais, os derivados e os de marca, podem ficar vazios na captura: é assim que o navegador informa um token que o tema não declarou. Um derivado vazio assume o valor do token de origem, que é o padrão documentado no contrato. Quando a origem também é um derivado, como no peso do botão por tamanho (`--ui-button-font-weight-sm`, `-md` e `-lg` derivam de `--ui-button-font-weight`), a cadeia é seguida até um token declarado. Um derivado que é ponta de um par de contraste, como `--ui-control-checked-bg`, também pode ficar vazio; a captura precisa então do token em que a cadeia dele termina. Sem evidência, `tokens/contrast` segue a mesma cadeia: um derivado que o tema não declara é avaliado pelo valor da origem.
98
+ Tokens opcionais, os derivados e os de marca, podem ficar vazios na captura: é assim que o navegador informa um token que o tema não declarou. Um derivado vazio assume o valor do token de origem, que é o padrão documentado no contrato. Quando a origem também é um derivado, como no texto do hover do contorno de aviso (`--ui-action-warning-outline-text-hover` deriva de `--ui-action-warning-outline-text`, que deriva do contorno de perigo), a cadeia é seguida até um token declarado. Um derivado que é ponta de um par de contraste, como `--ui-control-checked-bg`, também pode ficar vazio; a captura precisa então do token em que a cadeia dele termina. Sem evidência, `tokens/contrast` segue a mesma cadeia: um derivado que o tema não declara é avaliado pelo valor da origem, ou pelo padrão literal do contrato (`transparent`, uma cor). Um par com `over` mede um fundo translúcido composto sobre aquela superfície; sem `over`, um fundo translúcido continua cobertura pendente.
99
99
 
100
- Os tokens de densidade têm o valor conferido por `tokens/complete`: comprimento maior que zero em `px`, `rem` ou `em` para fonte e altura; também `0` para respiro e espaçamento; número sem unidade maior que zero para a entrelinha; em todos esses, também `calc()`, `clamp()`, `min()` ou `max()`; inteiro de 1 a 1000, `normal` ou `bold` para o peso. Com evidência, vale o valor capturado no navegador. Sem evidência, um valor que chega por `var()` é conferido pelo que ele alcança: o token declarado no mesmo tema ou, se não houver, o fallback do próprio `var()`; uma referência que não chega a valor nenhum fica como cobertura pendente. Valor inválido é cobertura obrigatória e não entra em baseline.
100
+ Desde o contrato de tema `3.0.0`, a forma é da biblioteca: a regra `themes/no-shape` aponta, como violação, cada token da lista `shape` do contrato que um seletor de tema declarar. Quem migra de um tema que declarava forma pode carregar essas violações na baseline enquanto tira as linhas.
101
101
 
102
102
  A captura usa a API pública deste pacote e um navegador à sua escolha. Com Playwright, e a aplicação servida localmente:
103
103
 
package/dist/color.d.ts CHANGED
@@ -6,9 +6,17 @@ export type ColorResolution = {
6
6
  status: "uncovered";
7
7
  };
8
8
  export declare function isColorBearingProperty(property: string): boolean;
9
+ export declare function parsedColor(value: string): {
10
+ alpha: number | undefined;
11
+ b: number;
12
+ g: number;
13
+ r: number;
14
+ mode: "rgb";
15
+ } | undefined;
9
16
  export declare function containsUnsupportedColorFunction(value: string): boolean;
10
17
  export declare function containsLiteralColor(value: string): boolean;
11
18
  export declare function referencedTokens(value: string): string[];
12
19
  export declare function unresolvedTokenReferences(value: string, tokens: ReadonlyMap<string, string>, stack?: ReadonlySet<string>): string[];
13
20
  export declare function resolveColor(value: string | undefined, tokens: ReadonlyMap<string, string>, stack?: ReadonlySet<string>): ColorResolution;
21
+ export declare function composite(foreground: RgbColor, background: RgbColor): RgbColor;
14
22
  export declare function contrastRatio(foreground: RgbColor, background: RgbColor): number | undefined;
package/dist/color.js CHANGED
@@ -37,7 +37,7 @@ export function isColorBearingProperty(property) {
37
37
  cssProperty === "text-emphasis" ||
38
38
  cssProperty === "column-rule");
39
39
  }
40
- function parsedColor(value) {
40
+ export function parsedColor(value) {
41
41
  // culori throws, instead of answering "not a color", on some values Tailwind
42
42
  // allows in arbitrary classes, such as calc(100vw-1.5rem).
43
43
  let parsed;
@@ -174,7 +174,7 @@ export function resolveColor(value, tokens, stack = new Set()) {
174
174
  const color = parsedColor(value.trim());
175
175
  return color ? { color, status: "resolved" } : { status: "uncovered" };
176
176
  }
177
- function composite(foreground, background) {
177
+ export function composite(foreground, background) {
178
178
  const alpha = foreground.alpha ?? 1;
179
179
  return {
180
180
  b: foreground.b * alpha + background.b * (1 - alpha),
@@ -1,23 +1,41 @@
1
1
  export declare const tokenNamePattern: RegExp;
2
2
  export type ContractSource = "bundled" | "@ksmv/ui-react";
3
+ /** A derived token's default as the contract file writes it (schema 2). */
4
+ export type DerivedDefault = {
5
+ kind: "token";
6
+ token: string;
7
+ } | {
8
+ kind: "literal";
9
+ value: string;
10
+ };
11
+ /**
12
+ * The theme contract as the rules read it: `derived` maps a derived token to
13
+ * its source token, and `defaults` maps one whose default is a literal color
14
+ * to that value. `shape` lists the tokens the library declares itself: a
15
+ * theme does not declare them.
16
+ */
3
17
  export interface ThemeContract {
4
18
  brandComposition: string[];
5
19
  contractVersion: string;
20
+ defaults: Record<string, string>;
6
21
  derived: Record<string, string>;
7
22
  prefix: "--ui-";
8
23
  required: string[];
9
- schemaVersion: 1;
24
+ schemaVersion: 2;
25
+ shape: string[];
10
26
  }
11
27
  export interface ContrastPair {
12
28
  background: string;
13
29
  context: string;
14
30
  foreground: string;
15
31
  minimum: number;
32
+ /** The surface under a background that may be translucent (schema 2). */
33
+ over?: string;
16
34
  }
17
35
  export interface ContrastManifest {
18
36
  contractVersion: string;
19
37
  pairs: ContrastPair[];
20
- schemaVersion: 1;
38
+ schemaVersion: 2;
21
39
  }
22
40
  export interface ContractIdentity {
23
41
  contractVersion: string;
package/dist/contracts.js CHANGED
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { createRequire } from "node:module";
4
4
  import { resolve } from "node:path";
5
+ import { parsedColor, referencedTokens } from "./color.js";
5
6
  export const tokenNamePattern = /^--ui-[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
7
  /**
7
8
  * A failure whose message this package wrote, so a report may repeat it. The
@@ -16,6 +17,21 @@ function exactKeys(value, keys) {
16
17
  function isRecord(value) {
17
18
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
18
19
  }
20
+ // A literal default is `transparent` or a color the checker can read.
21
+ function isLiteralColor(value) {
22
+ return typeof value === "string" &&
23
+ (value === "transparent" || (!referencedTokens(value).length && parsedColor(value) !== undefined));
24
+ }
25
+ function isDerivedDefault(value) {
26
+ if (!isRecord(value))
27
+ return false;
28
+ if (value.kind === "token") {
29
+ return exactKeys(value, ["kind", "token"]) &&
30
+ typeof value.token === "string" &&
31
+ tokenNamePattern.test(value.token);
32
+ }
33
+ return value.kind === "literal" && exactKeys(value, ["kind", "value"]) && isLiteralColor(value.value);
34
+ }
19
35
  function isTokenList(value) {
20
36
  return Array.isArray(value) && value.every((token) => typeof token === "string" && tokenNamePattern.test(token));
21
37
  }
@@ -35,8 +51,9 @@ export function parseThemeContract(content) {
35
51
  "prefix",
36
52
  "required",
37
53
  "schemaVersion",
54
+ "shape",
38
55
  ]) ||
39
- value.schemaVersion !== 1 ||
56
+ value.schemaVersion !== 2 ||
40
57
  value.prefix !== "--ui-" ||
41
58
  typeof value.contractVersion !== "string" ||
42
59
  !semverPattern.test(value.contractVersion) ||
@@ -44,13 +61,30 @@ export function parseThemeContract(content) {
44
61
  new Set(value.required).size !== value.required.length ||
45
62
  !isTokenList(value.brandComposition) ||
46
63
  new Set(value.brandComposition).size !== value.brandComposition.length ||
64
+ !isTokenList(value.shape) ||
65
+ new Set(value.shape).size !== value.shape.length ||
47
66
  !isRecord(value.derived) ||
48
- Object.entries(value.derived).some(([token, fallback]) => !tokenNamePattern.test(token) ||
49
- typeof fallback !== "string" ||
50
- !tokenNamePattern.test(fallback))) {
67
+ Object.entries(value.derived).some(([token, fallback]) => !tokenNamePattern.test(token) || !isDerivedDefault(fallback))) {
51
68
  throw new CheckSetupError("Unsupported theme contract.");
52
69
  }
53
- return value;
70
+ const shape = new Set(value.shape);
71
+ const entries = Object.entries(value.derived);
72
+ const derived = {};
73
+ const defaults = {};
74
+ for (const [token, fallback] of entries) {
75
+ if (fallback.kind === "token")
76
+ derived[token] = fallback.token;
77
+ else
78
+ defaults[token] = fallback.value;
79
+ }
80
+ // A shape token is the library's, never the theme's: it cannot be required,
81
+ // derived, or the source of a derived token.
82
+ if (value.required.some((token) => shape.has(token)) ||
83
+ Object.entries(derived).some(([token, source]) => shape.has(token) || shape.has(source)) ||
84
+ Object.keys(defaults).some((token) => shape.has(token))) {
85
+ throw new CheckSetupError("Unsupported theme contract.");
86
+ }
87
+ return { ...value, defaults, derived };
54
88
  }
55
89
  export function parseContrastManifest(content) {
56
90
  let value;
@@ -62,7 +96,7 @@ export function parseContrastManifest(content) {
62
96
  }
63
97
  if (!isRecord(value) ||
64
98
  !exactKeys(value, ["contractVersion", "pairs", "schemaVersion"]) ||
65
- value.schemaVersion !== 1 ||
99
+ value.schemaVersion !== 2 ||
66
100
  typeof value.contractVersion !== "string" ||
67
101
  !semverPattern.test(value.contractVersion) ||
68
102
  !Array.isArray(value.pairs)) {
@@ -71,7 +105,9 @@ export function parseContrastManifest(content) {
71
105
  const identities = [];
72
106
  for (const pair of value.pairs) {
73
107
  if (!isRecord(pair) ||
74
- !exactKeys(pair, ["background", "context", "foreground", "minimum"]) ||
108
+ !(exactKeys(pair, ["background", "context", "foreground", "minimum"]) ||
109
+ (exactKeys(pair, ["background", "context", "foreground", "minimum", "over"]) &&
110
+ typeof pair.over === "string" && tokenNamePattern.test(pair.over))) ||
75
111
  typeof pair.background !== "string" ||
76
112
  !tokenNamePattern.test(pair.background) ||
77
113
  typeof pair.foreground !== "string" ||
@@ -83,7 +119,7 @@ export function parseContrastManifest(content) {
83
119
  pair.minimum <= 0) {
84
120
  throw new CheckSetupError("Unsupported contrast manifest.");
85
121
  }
86
- identities.push(`${pair.context}\u0000${pair.foreground}\u0000${pair.background}`);
122
+ identities.push(`${pair.context}\u0000${pair.foreground}\u0000${pair.background}\u0000${pair.over ?? ""}`);
87
123
  }
88
124
  if (new Set(identities).size !== identities.length) {
89
125
  throw new CheckSetupError("Unsupported contrast manifest.");
@@ -1,6 +1,6 @@
1
1
  {
2
- "contractVersion": "7.0.0",
3
- "schemaVersion": 1,
2
+ "contractVersion": "9.0.0",
3
+ "schemaVersion": 2,
4
4
  "pairs": [
5
5
  { "foreground": "--ui-action-primary-text", "background": "--ui-action-primary-bg", "minimum": 4.5, "context": "normal-text" },
6
6
  { "foreground": "--ui-action-primary-text", "background": "--ui-action-primary-bg-hover", "minimum": 4.5, "context": "normal-text-hover" },
@@ -12,8 +12,12 @@
12
12
  { "foreground": "--ui-action-danger-text", "background": "--ui-action-danger-bg-hover", "minimum": 4.5, "context": "normal-text-hover" },
13
13
  { "foreground": "--ui-action-danger-text", "background": "--ui-action-danger-bg-active", "minimum": 4.5, "context": "normal-text-active" },
14
14
  { "foreground": "--ui-action-ghost-text", "background": "--ui-surface", "minimum": 4.5, "context": "normal-text" },
15
- { "foreground": "--ui-action-ghost-text", "background": "--ui-action-ghost-bg-hover", "minimum": 4.5, "context": "normal-text-hover" },
16
- { "foreground": "--ui-action-ghost-text", "background": "--ui-action-ghost-bg-active", "minimum": 4.5, "context": "normal-text-active" },
15
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface", "minimum": 4.5, "context": "normal-text-hover" },
16
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface-raised", "minimum": 4.5, "context": "normal-text-hover-raised" },
17
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface-subtle", "minimum": 4.5, "context": "normal-text-hover-subtle" },
18
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface", "minimum": 4.5, "context": "normal-text-active" },
19
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface-raised", "minimum": 4.5, "context": "normal-text-active-raised" },
20
+ { "foreground": "--ui-action-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface-subtle", "minimum": 4.5, "context": "normal-text-active-subtle" },
17
21
  { "foreground": "--ui-status-success-text", "background": "--ui-status-success-bg", "minimum": 4.5, "context": "status-text" },
18
22
  { "foreground": "--ui-status-success", "background": "--ui-status-success-bg", "minimum": 3, "context": "status-indicator" },
19
23
  { "foreground": "--ui-status-warning-text", "background": "--ui-status-warning-bg", "minimum": 4.5, "context": "status-text" },
@@ -26,14 +30,12 @@
26
30
  { "foreground": "--ui-status-warning", "background": "--ui-surface", "minimum": 3, "context": "status-indicator-on-surface" },
27
31
  { "foreground": "--ui-status-danger", "background": "--ui-surface", "minimum": 3, "context": "status-indicator-on-surface" },
28
32
  { "foreground": "--ui-status-info", "background": "--ui-surface", "minimum": 3, "context": "status-indicator-on-surface" },
29
- { "foreground": "--ui-status-info", "background": "--ui-border", "minimum": 3, "context": "loading-indicator" },
33
+ { "foreground": "--ui-loading-indicator-color", "background": "--ui-surface", "minimum": 3, "context": "loading-indicator" },
30
34
  { "foreground": "--ui-status-danger-text", "background": "--ui-surface", "minimum": 4.5, "context": "field-error-text" },
31
35
  { "foreground": "--ui-status-danger", "background": "--ui-surface", "minimum": 3, "context": "invalid-control-boundary" },
32
36
  { "foreground": "--ui-status-info", "background": "--ui-surface-subtle", "minimum": 3, "context": "progress-indicator" },
33
37
  { "foreground": "--ui-text-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-text" },
34
38
  { "foreground": "--ui-border-control", "background": "--ui-surface", "minimum": 3, "context": "control-boundary" },
35
- { "foreground": "--ui-focus-ring", "background": "--ui-surface", "minimum": 3, "context": "focus-indicator" },
36
- { "foreground": "--ui-focus-ring", "background": "--ui-focus-ring-offset", "minimum": 3, "context": "focus-indicator" },
37
39
  { "foreground": "--ui-control-checked-bg", "background": "--ui-surface", "minimum": 3, "context": "checked-control-boundary" },
38
40
  { "foreground": "--ui-control-checked-fg", "background": "--ui-control-checked-bg", "minimum": 3, "context": "checked-indicator" },
39
41
  { "foreground": "--ui-text-primary", "background": "--ui-surface", "minimum": 4.5, "context": "body-text" },
@@ -46,12 +48,10 @@
46
48
  { "foreground": "--ui-link-text", "background": "--ui-surface", "minimum": 4.5, "context": "link-text" },
47
49
  { "foreground": "--ui-link-text", "background": "--ui-surface-raised", "minimum": 4.5, "context": "link-text-raised" },
48
50
  { "foreground": "--ui-link-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "link-text-subtle" },
49
- { "foreground": "--ui-action-primary-border", "background": "--ui-surface", "minimum": 3, "context": "action-primary-boundary" },
50
51
  { "foreground": "--ui-action-secondary-border", "background": "--ui-surface", "minimum": 3, "context": "action-secondary-boundary" },
51
- { "foreground": "--ui-action-danger-border", "background": "--ui-surface", "minimum": 3, "context": "action-danger-boundary" },
52
52
  { "foreground": "--ui-action-accent-text", "background": "--ui-surface", "minimum": 4.5, "context": "accent-text" },
53
- { "foreground": "--ui-action-accent-text", "background": "--ui-action-accent-bg-hover", "minimum": 4.5, "context": "accent-text-hover" },
54
- { "foreground": "--ui-action-accent-text", "background": "--ui-action-accent-bg-active", "minimum": 4.5, "context": "accent-text-active" },
53
+ { "foreground": "--ui-action-accent-text", "background": "--ui-action-accent-bg-hover", "over": "--ui-surface", "minimum": 4.5, "context": "accent-text-hover" },
54
+ { "foreground": "--ui-action-accent-text", "background": "--ui-action-accent-bg-active", "over": "--ui-surface", "minimum": 4.5, "context": "accent-text-active" },
55
55
  { "foreground": "--ui-action-danger-outline-text", "background": "--ui-surface", "minimum": 4.5, "context": "danger-outline-text" },
56
56
  { "foreground": "--ui-action-danger-outline-text", "background": "--ui-action-danger-outline-bg-hover", "minimum": 4.5, "context": "danger-outline-text-hover" },
57
57
  { "foreground": "--ui-action-danger-outline-text", "background": "--ui-action-danger-outline-bg-active", "minimum": 4.5, "context": "danger-outline-text-active" },
@@ -68,16 +68,15 @@
68
68
  { "foreground": "--ui-text-button-muted-text", "background": "--ui-surface-raised", "minimum": 4.5, "context": "text-button-muted-text-raised" },
69
69
  { "foreground": "--ui-text-button-muted-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "text-button-muted-text-subtle" },
70
70
  { "foreground": "--ui-action-danger-outline-border", "background": "--ui-surface", "minimum": 3, "context": "action-danger-outline-boundary" },
71
- { "foreground": "--ui-action-warning-border", "background": "--ui-surface", "minimum": 3, "context": "action-warning-boundary" },
72
- { "foreground": "--ui-action-soft-accent-text", "background": "--ui-action-soft-accent-bg", "minimum": 4.5, "context": "soft-accent-text" },
73
- { "foreground": "--ui-action-soft-accent-text", "background": "--ui-action-soft-accent-bg-hover", "minimum": 4.5, "context": "soft-accent-text-hover" },
74
- { "foreground": "--ui-action-soft-accent-text", "background": "--ui-action-soft-accent-bg-active", "minimum": 4.5, "context": "soft-accent-text-active" },
71
+ { "foreground": "--ui-action-soft-accent-text", "background": "--ui-action-soft-accent-bg", "over": "--ui-surface", "minimum": 4.5, "context": "soft-accent-text" },
72
+ { "foreground": "--ui-action-soft-accent-text-hover", "background": "--ui-action-soft-accent-bg-hover", "over": "--ui-surface", "minimum": 4.5, "context": "soft-accent-text-hover" },
73
+ { "foreground": "--ui-action-soft-accent-text-hover", "background": "--ui-action-soft-accent-bg-active", "over": "--ui-surface", "minimum": 4.5, "context": "soft-accent-text-active" },
75
74
  { "foreground": "--ui-action-soft-warning-text", "background": "--ui-action-soft-warning-bg", "minimum": 4.5, "context": "soft-warning-text" },
76
- { "foreground": "--ui-action-soft-warning-text", "background": "--ui-action-soft-warning-bg-hover", "minimum": 4.5, "context": "soft-warning-text-hover" },
77
- { "foreground": "--ui-action-soft-warning-text", "background": "--ui-action-soft-warning-bg-active", "minimum": 4.5, "context": "soft-warning-text-active" },
75
+ { "foreground": "--ui-action-soft-warning-text-hover", "background": "--ui-action-soft-warning-bg-hover", "minimum": 4.5, "context": "soft-warning-text-hover" },
76
+ { "foreground": "--ui-action-soft-warning-text-hover", "background": "--ui-action-soft-warning-bg-active", "minimum": 4.5, "context": "soft-warning-text-active" },
78
77
  { "foreground": "--ui-action-warning-outline-text", "background": "--ui-surface", "minimum": 4.5, "context": "warning-outline-text" },
79
- { "foreground": "--ui-action-warning-outline-text", "background": "--ui-action-warning-outline-bg-hover", "minimum": 4.5, "context": "warning-outline-text-hover" },
80
- { "foreground": "--ui-action-warning-outline-text", "background": "--ui-action-warning-outline-bg-active", "minimum": 4.5, "context": "warning-outline-text-active" },
78
+ { "foreground": "--ui-action-warning-outline-text-hover", "background": "--ui-action-warning-outline-bg-hover", "minimum": 4.5, "context": "warning-outline-text-hover" },
79
+ { "foreground": "--ui-action-warning-outline-text-hover", "background": "--ui-action-warning-outline-bg-active", "minimum": 4.5, "context": "warning-outline-text-active" },
81
80
  { "foreground": "--ui-text-button-strong-text", "background": "--ui-surface", "minimum": 4.5, "context": "text-button-strong-text" },
82
81
  { "foreground": "--ui-text-button-strong-text", "background": "--ui-surface-raised", "minimum": 4.5, "context": "text-button-strong-text-raised" },
83
82
  { "foreground": "--ui-text-button-strong-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "text-button-strong-text-subtle" },
@@ -95,6 +94,115 @@
95
94
  { "foreground": "--ui-text-button-strong-text-hover", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "text-button-strong-text-hover-subtle" },
96
95
  { "foreground": "--ui-action-soft-accent-border", "background": "--ui-surface", "minimum": 3, "context": "action-soft-accent-boundary" },
97
96
  { "foreground": "--ui-action-soft-warning-border", "background": "--ui-surface", "minimum": 3, "context": "action-soft-warning-boundary" },
98
- { "foreground": "--ui-action-warning-outline-border", "background": "--ui-surface", "minimum": 3, "context": "action-warning-outline-boundary" }
97
+ { "foreground": "--ui-action-warning-outline-border", "background": "--ui-surface", "minimum": 3, "context": "action-warning-outline-boundary" },
98
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-surface", "minimum": 3, "context": "icon-button-ghost-icon" },
99
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-surface-raised", "minimum": 3, "context": "icon-button-ghost-icon-raised" },
100
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-surface-subtle", "minimum": 3, "context": "icon-button-ghost-icon-subtle" },
101
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface", "minimum": 3, "context": "icon-button-ghost-icon-hover" },
102
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface-raised", "minimum": 3, "context": "icon-button-ghost-icon-hover-raised" },
103
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-hover", "over": "--ui-surface-subtle", "minimum": 3, "context": "icon-button-ghost-icon-hover-subtle" },
104
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface", "minimum": 3, "context": "icon-button-ghost-icon-active" },
105
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface-raised", "minimum": 3, "context": "icon-button-ghost-icon-active-raised" },
106
+ { "foreground": "--ui-icon-button-ghost-text-hover", "background": "--ui-action-ghost-bg-active", "over": "--ui-surface-subtle", "minimum": 3, "context": "icon-button-ghost-icon-active-subtle" },
107
+ { "foreground": "--ui-text-primary", "background": "--ui-dialog-header-bg", "minimum": 4.5, "context": "dialog-title" },
108
+ { "foreground": "--ui-text-primary", "background": "--ui-dialog-header-bg-danger", "minimum": 4.5, "context": "dialog-title-danger" },
109
+ { "foreground": "--ui-text-primary", "background": "--ui-dialog-header-bg-warning", "minimum": 4.5, "context": "dialog-title-warning" },
110
+ { "foreground": "--ui-dialog-description", "background": "--ui-dialog-header-bg", "minimum": 4.5, "context": "dialog-description" },
111
+ { "foreground": "--ui-dialog-description", "background": "--ui-dialog-header-bg-danger", "minimum": 4.5, "context": "dialog-description-danger" },
112
+ { "foreground": "--ui-dialog-description", "background": "--ui-dialog-header-bg-warning", "minimum": 4.5, "context": "dialog-description-warning" },
113
+ { "foreground": "--ui-dialog-icon", "background": "--ui-dialog-header-bg", "minimum": 3, "context": "dialog-icon" },
114
+ { "foreground": "--ui-dialog-icon-danger", "background": "--ui-dialog-header-bg-danger", "minimum": 3, "context": "dialog-icon-danger" },
115
+ { "foreground": "--ui-dialog-icon-warning", "background": "--ui-dialog-header-bg-warning", "minimum": 3, "context": "dialog-icon-warning" },
116
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-dialog-header-bg", "minimum": 3, "context": "dialog-close" },
117
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-dialog-header-bg-danger", "minimum": 3, "context": "dialog-close-danger" },
118
+ { "foreground": "--ui-icon-button-ghost-text", "background": "--ui-dialog-header-bg-warning", "minimum": 3, "context": "dialog-close-warning" },
119
+ { "foreground": "--ui-text-primary-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-body-text" },
120
+ { "foreground": "--ui-text-secondary-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-secondary-text" },
121
+ { "foreground": "--ui-border-control-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "inverse-control-boundary" },
122
+ { "foreground": "--ui-link-text-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-link-text" },
123
+ { "foreground": "--ui-text-button-text-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-text-button-text" },
124
+ { "foreground": "--ui-action-ghost-text-on-inverse", "background": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-ghost-text" },
125
+ { "foreground": "--ui-action-ghost-text-hover-on-inverse", "background": "--ui-action-ghost-bg-hover-on-inverse", "over": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-ghost-text-hover" },
126
+ { "foreground": "--ui-action-ghost-text-hover-on-inverse", "background": "--ui-action-ghost-bg-active-on-inverse", "over": "--ui-surface-inverse", "minimum": 4.5, "context": "inverse-ghost-text-active" },
127
+ { "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "inverse-icon-button-ghost-icon" },
128
+ { "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-action-ghost-bg-hover-on-inverse", "over": "--ui-surface-inverse", "minimum": 3, "context": "inverse-icon-button-ghost-icon-hover" },
129
+ { "foreground": "--ui-icon-button-ghost-text-on-inverse", "background": "--ui-action-ghost-bg-active-on-inverse", "over": "--ui-surface-inverse", "minimum": 3, "context": "inverse-icon-button-ghost-icon-active" },
130
+ { "foreground": "--ui-category-1", "background": "--ui-surface", "minimum": 3, "context": "category-1-mark" },
131
+ { "foreground": "--ui-category-1-text", "background": "--ui-category-1-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-1-text" },
132
+ { "foreground": "--ui-category-1-on-solid", "background": "--ui-category-1-solid", "minimum": 4.5, "context": "category-1-on-solid" },
133
+ { "foreground": "--ui-category-2", "background": "--ui-surface", "minimum": 3, "context": "category-2-mark" },
134
+ { "foreground": "--ui-category-2-text", "background": "--ui-category-2-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-2-text" },
135
+ { "foreground": "--ui-category-2-on-solid", "background": "--ui-category-2-solid", "minimum": 4.5, "context": "category-2-on-solid" },
136
+ { "foreground": "--ui-category-3", "background": "--ui-surface", "minimum": 3, "context": "category-3-mark" },
137
+ { "foreground": "--ui-category-3-text", "background": "--ui-category-3-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-3-text" },
138
+ { "foreground": "--ui-category-3-on-solid", "background": "--ui-category-3-solid", "minimum": 4.5, "context": "category-3-on-solid" },
139
+ { "foreground": "--ui-category-4", "background": "--ui-surface", "minimum": 3, "context": "category-4-mark" },
140
+ { "foreground": "--ui-category-4-text", "background": "--ui-category-4-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-4-text" },
141
+ { "foreground": "--ui-category-4-on-solid", "background": "--ui-category-4-solid", "minimum": 4.5, "context": "category-4-on-solid" },
142
+ { "foreground": "--ui-category-5", "background": "--ui-surface", "minimum": 3, "context": "category-5-mark" },
143
+ { "foreground": "--ui-category-5-text", "background": "--ui-category-5-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-5-text" },
144
+ { "foreground": "--ui-category-5-on-solid", "background": "--ui-category-5-solid", "minimum": 4.5, "context": "category-5-on-solid" },
145
+ { "foreground": "--ui-category-6", "background": "--ui-surface", "minimum": 3, "context": "category-6-mark" },
146
+ { "foreground": "--ui-category-6-text", "background": "--ui-category-6-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-6-text" },
147
+ { "foreground": "--ui-category-6-on-solid", "background": "--ui-category-6-solid", "minimum": 4.5, "context": "category-6-on-solid" },
148
+ { "foreground": "--ui-category-7", "background": "--ui-surface", "minimum": 3, "context": "category-7-mark" },
149
+ { "foreground": "--ui-category-7-text", "background": "--ui-category-7-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-7-text" },
150
+ { "foreground": "--ui-category-7-on-solid", "background": "--ui-category-7-solid", "minimum": 4.5, "context": "category-7-on-solid" },
151
+ { "foreground": "--ui-category-8", "background": "--ui-surface", "minimum": 3, "context": "category-8-mark" },
152
+ { "foreground": "--ui-category-8-text", "background": "--ui-category-8-bg", "over": "--ui-surface", "minimum": 4.5, "context": "category-8-text" },
153
+ { "foreground": "--ui-category-8-on-solid", "background": "--ui-category-8-solid", "minimum": 4.5, "context": "category-8-on-solid" },
154
+ { "foreground": "--ui-alert-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "alert-text" },
155
+ { "foreground": "--ui-alert-text-success", "background": "--ui-status-success-bg", "minimum": 4.5, "context": "alert-text-success" },
156
+ { "foreground": "--ui-alert-text-warning", "background": "--ui-status-warning-bg", "minimum": 4.5, "context": "alert-text-warning" },
157
+ { "foreground": "--ui-alert-text-danger", "background": "--ui-status-danger-bg", "minimum": 4.5, "context": "alert-text-danger" },
158
+ { "foreground": "--ui-alert-text-info", "background": "--ui-status-info-bg", "minimum": 4.5, "context": "alert-text-info" },
159
+ { "foreground": "--ui-alert-dismiss", "background": "--ui-surface-subtle", "minimum": 3, "context": "alert-dismiss" },
160
+ { "foreground": "--ui-alert-dismiss", "background": "--ui-alert-dismiss-bg-hover", "over": "--ui-surface-subtle", "minimum": 3, "context": "alert-dismiss-hover" },
161
+ { "foreground": "--ui-alert-dismiss-success", "background": "--ui-status-success-bg", "minimum": 3, "context": "alert-dismiss-success" },
162
+ { "foreground": "--ui-alert-dismiss-success", "background": "--ui-alert-dismiss-bg-hover", "over": "--ui-status-success-bg", "minimum": 3, "context": "alert-dismiss-success-hover" },
163
+ { "foreground": "--ui-alert-dismiss-warning", "background": "--ui-status-warning-bg", "minimum": 3, "context": "alert-dismiss-warning" },
164
+ { "foreground": "--ui-alert-dismiss-warning", "background": "--ui-alert-dismiss-bg-hover", "over": "--ui-status-warning-bg", "minimum": 3, "context": "alert-dismiss-warning-hover" },
165
+ { "foreground": "--ui-alert-dismiss-danger", "background": "--ui-status-danger-bg", "minimum": 3, "context": "alert-dismiss-danger" },
166
+ { "foreground": "--ui-alert-dismiss-danger", "background": "--ui-alert-dismiss-bg-hover", "over": "--ui-status-danger-bg", "minimum": 3, "context": "alert-dismiss-danger-hover" },
167
+ { "foreground": "--ui-alert-dismiss-info", "background": "--ui-status-info-bg", "minimum": 3, "context": "alert-dismiss-info" },
168
+ { "foreground": "--ui-alert-dismiss-info", "background": "--ui-alert-dismiss-bg-hover", "over": "--ui-status-info-bg", "minimum": 3, "context": "alert-dismiss-info-hover" },
169
+ { "foreground": "--ui-section-label-text", "background": "--ui-surface", "minimum": 4.5, "context": "section-label-text" },
170
+ { "foreground": "--ui-section-label-text", "background": "--ui-surface-raised", "minimum": 4.5, "context": "section-label-text-raised" },
171
+ { "foreground": "--ui-section-label-text", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "section-label-text-subtle" },
172
+ { "foreground": "--ui-section-label-text-strong", "background": "--ui-surface", "minimum": 4.5, "context": "section-label-text-strong" },
173
+ { "foreground": "--ui-section-label-text-strong", "background": "--ui-surface-raised", "minimum": 4.5, "context": "section-label-text-strong-raised" },
174
+ { "foreground": "--ui-section-label-text-strong", "background": "--ui-surface-subtle", "minimum": 4.5, "context": "section-label-text-strong-subtle" },
175
+ { "foreground": "--ui-section-label-text-danger", "background": "--ui-surface", "minimum": 4.5, "context": "section-label-text-danger" },
176
+ { "foreground": "--ui-section-label-text-warning", "background": "--ui-surface", "minimum": 4.5, "context": "section-label-text-warning" },
177
+ { "foreground": "--ui-menu-item-text-accent", "background": "--ui-surface-raised", "minimum": 4.5, "context": "menu-item-text-accent" },
178
+ { "foreground": "--ui-menu-item-text-brand", "background": "--ui-surface-raised", "minimum": 4.5, "context": "menu-item-text-brand" },
179
+ { "foreground": "--ui-menu-item-text-warning", "background": "--ui-surface-raised", "minimum": 4.5, "context": "menu-item-text-warning" },
180
+ { "foreground": "--ui-menu-item-text-danger", "background": "--ui-surface-raised", "minimum": 4.5, "context": "menu-item-text-danger" },
181
+ { "foreground": "--ui-menu-item-text-info", "background": "--ui-surface-raised", "minimum": 4.5, "context": "menu-item-text-info" },
182
+ { "foreground": "--ui-text-primary", "background": "--ui-radio-card-checked-bg", "over": "--ui-surface", "minimum": 4.5, "context": "radio-card-text-checked" },
183
+ { "foreground": "--ui-text-secondary", "background": "--ui-radio-card-checked-bg", "over": "--ui-surface", "minimum": 4.5, "context": "radio-card-description-checked" },
184
+ { "foreground": "--ui-text-primary", "background": "--ui-radio-card-checked-bg-danger", "over": "--ui-surface", "minimum": 4.5, "context": "radio-card-text-checked-danger" },
185
+ { "foreground": "--ui-text-secondary", "background": "--ui-radio-card-checked-bg-danger", "over": "--ui-surface", "minimum": 4.5, "context": "radio-card-description-checked-danger" },
186
+ { "foreground": "--ui-radio-card-checked-border-danger", "background": "--ui-surface", "minimum": 3, "context": "radio-card-border-danger" },
187
+ { "foreground": "--ui-control-checked-bg", "background": "--ui-radio-card-checked-bg", "over": "--ui-surface", "minimum": 3, "context": "radio-card-checked-border" },
188
+ { "foreground": "--ui-radio-card-checked-border-danger", "background": "--ui-radio-card-checked-bg-danger", "over": "--ui-surface", "minimum": 3, "context": "radio-card-checked-border-danger" },
189
+ { "foreground": "--ui-table-header-text", "background": "--ui-surface", "minimum": 4.5, "context": "table-header-text" },
190
+ { "foreground": "--ui-table-header-text", "background": "--ui-surface-raised", "minimum": 4.5, "context": "table-header-text-raised" },
191
+ { "foreground": "--ui-table-header-text", "background": "--ui-table-header-bg", "minimum": 4.5, "context": "table-header-text-subtle" },
192
+ { "foreground": "--ui-toggle-group-checked-text", "background": "--ui-toggle-group-checked-bg", "minimum": 4.5, "context": "toggle-group-checked-text" },
193
+ { "foreground": "--ui-toggle-group-checked-text", "background": "--ui-toggle-group-checked-bg-hover", "minimum": 4.5, "context": "toggle-group-checked-text-hover" },
194
+ { "foreground": "--ui-toggle-group-checked-border", "background": "--ui-surface", "minimum": 3, "context": "toggle-group-checked-border" },
195
+ { "foreground": "--ui-toggle-group-track-text", "background": "--ui-toggle-group-track-bg", "minimum": 4.5, "context": "toggle-group-track-text" },
196
+ { "foreground": "--ui-toggle-group-segment-text", "background": "--ui-toggle-group-segment-bg", "minimum": 4.5, "context": "toggle-group-segment-text" },
197
+ { "foreground": "--ui-toggle-group-segment-text", "background": "--ui-toggle-group-track-bg", "minimum": 4.5, "context": "toggle-group-segment-text-hover" },
198
+ { "foreground": "--ui-toggle-group-segment-border", "background": "--ui-toggle-group-segment-bg", "minimum": 3, "context": "toggle-group-segment-border" },
199
+ { "foreground": "--ui-badge-solid-accent-text", "background": "--ui-badge-solid-accent-bg", "minimum": 4.5, "context": "badge-solid-accent-text" },
200
+ { "foreground": "--ui-badge-solid-brand-text", "background": "--ui-badge-solid-brand-bg", "minimum": 4.5, "context": "badge-solid-brand-text" },
201
+ { "foreground": "--ui-status-success-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "status-indicator-success-on-inverse" },
202
+ { "foreground": "--ui-status-warning-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "status-indicator-warning-on-inverse" },
203
+ { "foreground": "--ui-status-danger-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "status-indicator-danger-on-inverse" },
204
+ { "foreground": "--ui-status-info-on-inverse", "background": "--ui-surface-inverse", "minimum": 3, "context": "status-indicator-info-on-inverse" },
205
+ { "foreground": "--ui-progress-accent", "background": "--ui-surface-subtle", "minimum": 3, "context": "progress-accent" },
206
+ { "foreground": "--ui-progress-brand", "background": "--ui-surface-subtle", "minimum": 3, "context": "progress-brand" }
99
207
  ]
100
208
  }
package/dist/evidence.js CHANGED
@@ -59,7 +59,11 @@ function isIdentity(value) {
59
59
  function requiredEvidenceTokens(contracts) {
60
60
  const derived = contracts.theme.derived;
61
61
  const required = new Set(contracts.theme.required);
62
+ const defaults = contracts.theme.defaults;
62
63
  const origin = (token, seen = new Set()) => {
64
+ // A token with a literal default needs no captured value.
65
+ if (token in defaults)
66
+ return undefined;
63
67
  const source = derived[token];
64
68
  if (required.has(token) || source === undefined || seen.has(token))
65
69
  return token;
@@ -68,11 +72,10 @@ function requiredEvidenceTokens(contracts) {
68
72
  return new Set([
69
73
  ...required,
70
74
  // A source that is itself derived is optional too; its chain is followed.
71
- ...Object.values(derived).filter((source) => !(source in derived)),
72
- ...contracts.contrast.pairs.flatMap(({ background, foreground }) => [
73
- origin(background),
74
- origin(foreground),
75
- ]),
75
+ // A source with a literal default needs no captured value either.
76
+ ...Object.values(derived).filter((source) => !(source in derived) && !(source in defaults)),
77
+ ...contracts.contrast.pairs.flatMap(({ background, foreground, over }) => [origin(background), origin(foreground), over === undefined ? undefined : origin(over)]
78
+ .filter((token) => token !== undefined)),
76
79
  ]);
77
80
  }
78
81
  // Structure only: whether the content agrees with the contracts, the
@@ -127,7 +130,7 @@ export async function loadComputedEvidence({ contracts, evidenceByPath, now = ne
127
130
  const requiredTokens = requiredEvidenceTokens(contracts);
128
131
  // A token the evidence must carry is never optional, even when the contract
129
132
  // also lists it as derived or as a brand token.
130
- const optionalTokens = new Set([...Object.keys(contracts.theme.derived), ...contracts.theme.brandComposition]
133
+ const optionalTokens = new Set([...Object.keys(contracts.theme.derived), ...Object.keys(contracts.theme.defaults), ...contracts.theme.brandComposition]
131
134
  .filter((token) => !requiredTokens.has(token)));
132
135
  for (const [path, group] of grouped) {
133
136
  const refused = (cause) => new CheckSetupError(`Invalid computed theme evidence in ${path}: ${cause}`);
@@ -227,13 +230,15 @@ export async function loadComputedEvidence({ contracts, evidenceByPath, now = ne
227
230
  const value = tokens[token];
228
231
  if (value !== undefined)
229
232
  return value;
233
+ if (token in contracts.theme.defaults)
234
+ return contracts.theme.defaults[token];
230
235
  const source = contracts.theme.derived[token];
231
236
  if (source === undefined || seen.has(token))
232
237
  return undefined;
233
238
  return derivedValue(source, new Set([...seen, token]));
234
239
  };
235
240
  const underivable = [];
236
- for (const token of Object.keys(contracts.theme.derived)) {
241
+ for (const token of [...Object.keys(contracts.theme.derived), ...Object.keys(contracts.theme.defaults)]) {
237
242
  const value = derivedValue(token, new Set());
238
243
  if (value === undefined)
239
244
  underivable.push(token);
@@ -2,6 +2,7 @@ import { createComponentOverrideRule } from "./componentOverride.js";
2
2
  import { createNoLiteralColorRule } from "./noLiteralColor.js";
3
3
  import { createTokenCompleteRule, loadThemeContract } from "./tokenComplete.js";
4
4
  import { createTokenContrastRule, loadContrastManifest, } from "./tokenContrast.js";
5
+ import { createThemeShapeRule } from "./themeShape.js";
5
6
  import { createTokenPrefixRule } from "./tokenPrefix.js";
6
7
  export async function createDefaultRules({ components, computed, contract, manifest, themes, }) {
7
8
  const [resolvedContract, resolvedManifest] = await Promise.all([
@@ -12,8 +13,10 @@ export async function createDefaultRules({ components, computed, contract, manif
12
13
  createNoLiteralColorRule({ themes }),
13
14
  createTokenPrefixRule(),
14
15
  createTokenCompleteRule({ computed, contract: resolvedContract, themes }),
16
+ createThemeShapeRule({ contract: resolvedContract, themes }),
15
17
  createTokenContrastRule({
16
18
  computed,
19
+ defaults: resolvedContract.defaults,
17
20
  derived: resolvedContract.derived,
18
21
  manifest: resolvedManifest,
19
22
  themes,
@@ -0,0 +1,8 @@
1
+ import type { ThemeContract } from "../contracts.js";
2
+ import type { CheckRule } from "../scan.js";
3
+ import type { ThemeConfig } from "../config.js";
4
+ export interface ThemeShapeOptions {
5
+ contract: ThemeContract;
6
+ themes: ThemeConfig[];
7
+ }
8
+ export declare function createThemeShapeRule({ contract, themes }: ThemeShapeOptions): CheckRule;
@@ -0,0 +1,35 @@
1
+ import { createThemeCollector } from "./theme.js";
2
+ // Shape comes from the library (theme contract 3.0.0): a theme declares color.
3
+ // A shape token declared in a theme selector is a violation, which a baseline
4
+ // can carry while a consumer removes the old declarations.
5
+ export function createThemeShapeRule({ contract, themes }) {
6
+ const collector = createThemeCollector(themes);
7
+ return {
8
+ name: "themes/no-shape",
9
+ begin: collector.begin,
10
+ check(file) {
11
+ collector.collect(file);
12
+ return { uncovered: [], violations: [] };
13
+ },
14
+ complete() {
15
+ const violations = [];
16
+ for (const theme of collector.themes) {
17
+ for (const token of contract.shape) {
18
+ const declaration = theme.declarations.get(token);
19
+ if (!declaration)
20
+ continue;
21
+ violations.push({
22
+ anchor: `${theme.name}:${token}`,
23
+ column: declaration.column,
24
+ line: declaration.line,
25
+ message: `Theme ${theme.name} declares shape token ${token}; shape comes from the library.`,
26
+ nodeKind: `theme-shape-token:${token}`,
27
+ path: declaration.path,
28
+ rule: "themes/no-shape",
29
+ });
30
+ }
31
+ }
32
+ return { uncovered: [], violations };
33
+ },
34
+ };
35
+ }
@@ -1,6 +1,5 @@
1
1
  import { unresolvedTokenReferences } from "../color.js";
2
2
  import { parseThemeContract, } from "../contracts.js";
3
- import { densityExpectation, densityTokenKinds, isValidDensityValue, resolveDensityReference, } from "../density.js";
4
3
  import { createThemeCollector } from "./theme.js";
5
4
  export async function loadThemeContract(url) {
6
5
  const { readFile } = await import("node:fs/promises");
@@ -78,7 +77,7 @@ export function createTokenCompleteRule({ computed = {}, contract, themes, }) {
78
77
  // A derived token falls back to its source, and to the source's own
79
78
  // source when that one is derived too, until a declared token.
80
79
  const hasFallback = (token, seen) => {
81
- if (theme.declarations.has(token))
80
+ if (theme.declarations.has(token) || token in contract.defaults)
82
81
  return true;
83
82
  const source = contract.derived[token];
84
83
  if (source === undefined || seen.has(token))
@@ -126,38 +125,6 @@ export function createTokenCompleteRule({ computed = {}, contract, themes, }) {
126
125
  }
127
126
  }
128
127
  }
129
- // Density values are checked on the value the browser resolved when the
130
- // evidence carries it; without evidence, a var() is checked through
131
- // what it resolves to in this theme or its own fallback, and one that
132
- // resolves to nothing stays with the reference check above. An invalid
133
- // value is mandatory coverage, so no baseline can absorb it.
134
- for (const [token, kind] of Object.entries(densityTokenKinds)) {
135
- if (!evidenceTokens.has(token))
136
- continue;
137
- const declaration = theme.declarations.get(token);
138
- // A derived token that is not declared here is validated through
139
- // its own source (the fallback token it derives from); reporting
140
- // it again from this theme would just duplicate that diagnostic.
141
- if (token in contract.derived && !declaration)
142
- continue;
143
- const raw = computedTheme[token] ?? declaration?.value;
144
- if (raw === undefined)
145
- continue;
146
- const value = /var\s*\(/iu.test(raw) ? resolveDensityReference(raw, tokens) : raw;
147
- if (value === undefined)
148
- continue;
149
- if (!isValidDensityValue(kind, value)) {
150
- result.uncovered.push({
151
- ...(declaration
152
- ? { column: declaration.column, line: declaration.line, path: declaration.path }
153
- : location),
154
- message: `Theme ${theme.name} has an invalid value for ${token}; expected ${densityExpectation(kind)}.`,
155
- nodeKind: "theme-density-value",
156
- rule: "tokens/complete",
157
- required: true,
158
- });
159
- }
160
- }
161
128
  }
162
129
  return result;
163
130
  },
@@ -4,9 +4,10 @@ import type { CheckRule } from "../scan.js";
4
4
  export type { ContrastManifest, ContrastPair } from "../contracts.js";
5
5
  export interface TokenContrastOptions {
6
6
  computed?: Record<string, Record<string, string>>;
7
+ defaults?: Record<string, string>;
7
8
  derived?: Record<string, string>;
8
9
  manifest: ContrastManifest;
9
10
  themes: ThemeConfig[];
10
11
  }
11
12
  export declare function loadContrastManifest(url?: URL): Promise<ContrastManifest>;
12
- export declare function createTokenContrastRule({ computed, derived, manifest, themes, }: TokenContrastOptions): CheckRule;
13
+ export declare function createTokenContrastRule({ computed, defaults, derived, manifest, themes, }: TokenContrastOptions): CheckRule;
@@ -1,5 +1,5 @@
1
1
  import { isComputedPropertyName, isIdentifier, isJsxAttribute, isJsxExpression, isObjectLiteralExpression, isPropertyAssignment, isJsxSpreadAttribute, isStringLiteralLikeNode, } from "typescript/unstable/ast";
2
- import { contrastRatio, isColorBearingProperty, resolveColor, } from "../color.js";
2
+ import { composite, contrastRatio, isColorBearingProperty, resolveColor, } from "../color.js";
3
3
  import { parseContrastManifest, } from "../contracts.js";
4
4
  import { createThemeCollector } from "./theme.js";
5
5
  export async function loadContrastManifest(url) {
@@ -84,7 +84,7 @@ function dynamicStyleResult(file) {
84
84
  sourceFile.forEachChild(visit);
85
85
  return result;
86
86
  }
87
- export function createTokenContrastRule({ computed = {}, derived = {}, manifest, themes, }) {
87
+ export function createTokenContrastRule({ computed = {}, defaults = {}, derived = {}, manifest, themes, }) {
88
88
  const collector = createThemeCollector(themes);
89
89
  return {
90
90
  name: "tokens/contrast",
@@ -138,9 +138,22 @@ export function createTokenContrastRule({ computed = {}, derived = {}, manifest,
138
138
  tokens.set(token, `var(${source})`);
139
139
  }
140
140
  }
141
+ // A literal default (theme contract schema 2) is the value itself.
142
+ for (const [token, value] of Object.entries(defaults)) {
143
+ if (!tokens.has(token))
144
+ tokens.set(token, value);
145
+ }
141
146
  for (const pair of manifest.pairs) {
142
- const background = resolveColor(tokens.get(pair.background), tokens);
147
+ let background = resolveColor(tokens.get(pair.background), tokens);
143
148
  const foreground = resolveColor(tokens.get(pair.foreground), tokens);
149
+ // A translucent background is measured as painted over the surface
150
+ // the pair names (schema 2); without one it stays uncovered below.
151
+ if (pair.over && background.status === "resolved" && (background.color.alpha ?? 1) < 1) {
152
+ const over = resolveColor(tokens.get(pair.over), tokens);
153
+ background = over.status === "resolved" && (over.color.alpha ?? 1) === 1
154
+ ? { color: composite(background.color, over.color), status: "resolved" }
155
+ : { status: "uncovered" };
156
+ }
144
157
  if (background.status === "uncovered" ||
145
158
  foreground.status === "uncovered") {
146
159
  result.uncovered.push({
@@ -1,6 +1,6 @@
1
1
  {
2
- "contractVersion": "2.5.0",
3
- "schemaVersion": 1,
2
+ "contractVersion": "3.1.0",
3
+ "schemaVersion": 2,
4
4
  "prefix": "--ui-",
5
5
  "required": [
6
6
  "--ui-surface-page",
@@ -18,8 +18,6 @@
18
18
  "--ui-border-strong",
19
19
  "--ui-border-control",
20
20
  "--ui-border-disabled",
21
- "--ui-focus-ring",
22
- "--ui-focus-ring-offset",
23
21
  "--ui-overlay",
24
22
  "--ui-action-primary-bg",
25
23
  "--ui-action-primary-bg-hover",
@@ -51,18 +49,196 @@
51
49
  "--ui-disabled-bg",
52
50
  "--ui-disabled-text",
53
51
  "--ui-disabled-border",
52
+ "--ui-shadow-sm",
53
+ "--ui-shadow-md",
54
+ "--ui-shadow-lg"
55
+ ],
56
+ "derived": {
57
+ "--ui-action-secondary-border": { "kind": "token", "token": "--ui-border-control" },
58
+ "--ui-status-success-border": { "kind": "token", "token": "--ui-status-success" },
59
+ "--ui-status-warning-border": { "kind": "token", "token": "--ui-status-warning" },
60
+ "--ui-status-danger-border": { "kind": "token", "token": "--ui-status-danger" },
61
+ "--ui-status-info-border": { "kind": "token", "token": "--ui-status-info" },
62
+ "--ui-control-checked-bg": { "kind": "token", "token": "--ui-action-primary-bg" },
63
+ "--ui-control-checked-fg": { "kind": "token", "token": "--ui-action-primary-text" },
64
+ "--ui-link-text": { "kind": "token", "token": "--ui-text-primary" },
65
+ "--ui-action-accent-text": { "kind": "token", "token": "--ui-action-ghost-text" },
66
+ "--ui-action-accent-bg-hover": { "kind": "token", "token": "--ui-action-ghost-bg-hover" },
67
+ "--ui-action-accent-bg-active": { "kind": "token", "token": "--ui-action-ghost-bg-active" },
68
+ "--ui-action-danger-outline-text": { "kind": "token", "token": "--ui-status-danger-text" },
69
+ "--ui-action-danger-outline-border": { "kind": "token", "token": "--ui-status-danger" },
70
+ "--ui-action-danger-outline-bg-hover": { "kind": "token", "token": "--ui-status-danger-bg" },
71
+ "--ui-action-danger-outline-bg-active": { "kind": "token", "token": "--ui-status-danger-bg" },
72
+ "--ui-action-warning-bg": { "kind": "token", "token": "--ui-action-danger-bg" },
73
+ "--ui-action-warning-bg-hover": { "kind": "token", "token": "--ui-action-danger-bg-hover" },
74
+ "--ui-action-warning-bg-active": { "kind": "token", "token": "--ui-action-danger-bg-active" },
75
+ "--ui-action-warning-text": { "kind": "token", "token": "--ui-action-danger-text" },
76
+ "--ui-text-button-text": { "kind": "token", "token": "--ui-link-text" },
77
+ "--ui-text-button-accent-text": { "kind": "token", "token": "--ui-text-button-text" },
78
+ "--ui-text-button-muted-text": { "kind": "token", "token": "--ui-text-secondary" },
79
+ "--ui-action-soft-accent-bg": { "kind": "token", "token": "--ui-action-accent-bg-hover" },
80
+ "--ui-action-soft-accent-bg-hover": { "kind": "token", "token": "--ui-action-accent-bg-active" },
81
+ "--ui-action-soft-accent-bg-active": { "kind": "token", "token": "--ui-action-accent-bg-active" },
82
+ "--ui-action-soft-accent-text": { "kind": "token", "token": "--ui-action-accent-text" },
83
+ "--ui-action-soft-accent-border": { "kind": "token", "token": "--ui-action-accent-text" },
84
+ "--ui-action-soft-warning-bg": { "kind": "token", "token": "--ui-status-warning-bg" },
85
+ "--ui-action-soft-warning-bg-hover": { "kind": "token", "token": "--ui-status-warning-bg" },
86
+ "--ui-action-soft-warning-bg-active": { "kind": "token", "token": "--ui-status-warning-bg" },
87
+ "--ui-action-soft-warning-text": { "kind": "token", "token": "--ui-status-warning-text" },
88
+ "--ui-action-soft-warning-border": { "kind": "token", "token": "--ui-status-warning" },
89
+ "--ui-action-warning-outline-text": { "kind": "token", "token": "--ui-action-danger-outline-text" },
90
+ "--ui-action-warning-outline-border": { "kind": "token", "token": "--ui-action-danger-outline-border" },
91
+ "--ui-action-warning-outline-bg-hover": { "kind": "token", "token": "--ui-action-danger-outline-bg-hover" },
92
+ "--ui-action-warning-outline-bg-active": { "kind": "token", "token": "--ui-action-danger-outline-bg-active" },
93
+ "--ui-text-button-strong-text": { "kind": "token", "token": "--ui-text-primary" },
94
+ "--ui-text-button-text-hover": { "kind": "token", "token": "--ui-text-button-text" },
95
+ "--ui-text-button-accent-text-hover": { "kind": "token", "token": "--ui-text-button-accent-text" },
96
+ "--ui-text-button-muted-text-hover": { "kind": "token", "token": "--ui-text-button-muted-text" },
97
+ "--ui-text-button-strong-text-hover": { "kind": "token", "token": "--ui-text-button-strong-text" },
98
+ "--ui-action-soft-accent-text-hover": { "kind": "token", "token": "--ui-action-soft-accent-text" },
99
+ "--ui-action-soft-warning-text-hover": { "kind": "token", "token": "--ui-action-soft-warning-text" },
100
+ "--ui-action-warning-outline-text-hover": { "kind": "token", "token": "--ui-action-warning-outline-text" },
101
+ "--ui-field-disabled-bg": { "kind": "token", "token": "--ui-surface-subtle" },
102
+ "--ui-field-disabled-text": { "kind": "token", "token": "--ui-text-muted" },
103
+ "--ui-button-shadow-color": { "kind": "literal", "value": "transparent" },
104
+ "--ui-action-danger-outline-bg": { "kind": "literal", "value": "transparent" },
105
+ "--ui-action-ghost-text-hover": { "kind": "token", "token": "--ui-action-ghost-text" },
106
+ "--ui-icon-button-ghost-text": { "kind": "token", "token": "--ui-action-ghost-text" },
107
+ "--ui-icon-button-ghost-text-hover": { "kind": "token", "token": "--ui-icon-button-ghost-text" },
108
+ "--ui-loading-indicator-color": { "kind": "token", "token": "--ui-status-info" },
109
+ "--ui-dialog-header-bg": { "kind": "token", "token": "--ui-surface-subtle" },
110
+ "--ui-dialog-header-bg-danger": { "kind": "token", "token": "--ui-status-danger-bg" },
111
+ "--ui-dialog-header-bg-warning": { "kind": "token", "token": "--ui-status-warning-bg" },
112
+ "--ui-dialog-icon": { "kind": "token", "token": "--ui-text-secondary" },
113
+ "--ui-dialog-icon-danger": { "kind": "token", "token": "--ui-status-danger" },
114
+ "--ui-dialog-icon-warning": { "kind": "token", "token": "--ui-status-warning" },
115
+ "--ui-dialog-footer-bg": { "kind": "token", "token": "--ui-surface-subtle" },
116
+ "--ui-dialog-footer-border": { "kind": "token", "token": "--ui-border" },
117
+ "--ui-text-primary-on-inverse": { "kind": "token", "token": "--ui-text-inverse" },
118
+ "--ui-text-secondary-on-inverse": { "kind": "token", "token": "--ui-text-primary-on-inverse" },
119
+ "--ui-text-muted-on-inverse": { "kind": "token", "token": "--ui-text-secondary-on-inverse" },
120
+ "--ui-border-on-inverse": { "kind": "token", "token": "--ui-border-control-on-inverse" },
121
+ "--ui-border-control-on-inverse": { "kind": "token", "token": "--ui-text-inverse" },
122
+ "--ui-link-text-on-inverse": { "kind": "token", "token": "--ui-text-primary-on-inverse" },
123
+ "--ui-text-button-text-on-inverse": { "kind": "token", "token": "--ui-link-text-on-inverse" },
124
+ "--ui-action-ghost-text-on-inverse": { "kind": "token", "token": "--ui-text-primary-on-inverse" },
125
+ "--ui-action-ghost-text-hover-on-inverse": { "kind": "token", "token": "--ui-action-ghost-text-on-inverse" },
126
+ "--ui-action-ghost-bg-hover-on-inverse": { "kind": "literal", "value": "transparent" },
127
+ "--ui-action-ghost-bg-active-on-inverse": { "kind": "token", "token": "--ui-action-ghost-bg-hover-on-inverse" },
128
+ "--ui-icon-button-ghost-text-on-inverse": { "kind": "token", "token": "--ui-action-ghost-text-on-inverse" },
129
+ "--ui-category-1": { "kind": "token", "token": "--ui-border-control" },
130
+ "--ui-category-1-bg": { "kind": "token", "token": "--ui-surface-subtle" },
131
+ "--ui-category-1-text": { "kind": "token", "token": "--ui-text-secondary" },
132
+ "--ui-category-1-border": { "kind": "token", "token": "--ui-border" },
133
+ "--ui-category-1-solid": { "kind": "token", "token": "--ui-surface-inverse" },
134
+ "--ui-category-1-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
135
+ "--ui-category-2": { "kind": "token", "token": "--ui-border-control" },
136
+ "--ui-category-2-bg": { "kind": "token", "token": "--ui-surface-subtle" },
137
+ "--ui-category-2-text": { "kind": "token", "token": "--ui-text-secondary" },
138
+ "--ui-category-2-border": { "kind": "token", "token": "--ui-border" },
139
+ "--ui-category-2-solid": { "kind": "token", "token": "--ui-surface-inverse" },
140
+ "--ui-category-2-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
141
+ "--ui-category-3": { "kind": "token", "token": "--ui-border-control" },
142
+ "--ui-category-3-bg": { "kind": "token", "token": "--ui-surface-subtle" },
143
+ "--ui-category-3-text": { "kind": "token", "token": "--ui-text-secondary" },
144
+ "--ui-category-3-border": { "kind": "token", "token": "--ui-border" },
145
+ "--ui-category-3-solid": { "kind": "token", "token": "--ui-surface-inverse" },
146
+ "--ui-category-3-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
147
+ "--ui-category-4": { "kind": "token", "token": "--ui-border-control" },
148
+ "--ui-category-4-bg": { "kind": "token", "token": "--ui-surface-subtle" },
149
+ "--ui-category-4-text": { "kind": "token", "token": "--ui-text-secondary" },
150
+ "--ui-category-4-border": { "kind": "token", "token": "--ui-border" },
151
+ "--ui-category-4-solid": { "kind": "token", "token": "--ui-surface-inverse" },
152
+ "--ui-category-4-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
153
+ "--ui-category-5": { "kind": "token", "token": "--ui-border-control" },
154
+ "--ui-category-5-bg": { "kind": "token", "token": "--ui-surface-subtle" },
155
+ "--ui-category-5-text": { "kind": "token", "token": "--ui-text-secondary" },
156
+ "--ui-category-5-border": { "kind": "token", "token": "--ui-border" },
157
+ "--ui-category-5-solid": { "kind": "token", "token": "--ui-surface-inverse" },
158
+ "--ui-category-5-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
159
+ "--ui-category-6": { "kind": "token", "token": "--ui-border-control" },
160
+ "--ui-category-6-bg": { "kind": "token", "token": "--ui-surface-subtle" },
161
+ "--ui-category-6-text": { "kind": "token", "token": "--ui-text-secondary" },
162
+ "--ui-category-6-border": { "kind": "token", "token": "--ui-border" },
163
+ "--ui-category-6-solid": { "kind": "token", "token": "--ui-surface-inverse" },
164
+ "--ui-category-6-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
165
+ "--ui-category-7": { "kind": "token", "token": "--ui-border-control" },
166
+ "--ui-category-7-bg": { "kind": "token", "token": "--ui-surface-subtle" },
167
+ "--ui-category-7-text": { "kind": "token", "token": "--ui-text-secondary" },
168
+ "--ui-category-7-border": { "kind": "token", "token": "--ui-border" },
169
+ "--ui-category-7-solid": { "kind": "token", "token": "--ui-surface-inverse" },
170
+ "--ui-category-7-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
171
+ "--ui-category-8": { "kind": "token", "token": "--ui-border-control" },
172
+ "--ui-category-8-bg": { "kind": "token", "token": "--ui-surface-subtle" },
173
+ "--ui-category-8-text": { "kind": "token", "token": "--ui-text-secondary" },
174
+ "--ui-category-8-border": { "kind": "token", "token": "--ui-border" },
175
+ "--ui-category-8-solid": { "kind": "token", "token": "--ui-surface-inverse" },
176
+ "--ui-category-8-on-solid": { "kind": "token", "token": "--ui-text-inverse" },
177
+ "--ui-dialog-description": { "kind": "token", "token": "--ui-text-secondary" },
178
+ "--ui-alert-text": { "kind": "token", "token": "--ui-text-primary" },
179
+ "--ui-alert-text-success": { "kind": "token", "token": "--ui-status-success-text" },
180
+ "--ui-alert-text-warning": { "kind": "token", "token": "--ui-status-warning-text" },
181
+ "--ui-alert-text-danger": { "kind": "token", "token": "--ui-status-danger-text" },
182
+ "--ui-alert-text-info": { "kind": "token", "token": "--ui-status-info-text" },
183
+ "--ui-alert-border": { "kind": "token", "token": "--ui-border" },
184
+ "--ui-alert-border-success": { "kind": "token", "token": "--ui-status-success-border" },
185
+ "--ui-alert-border-warning": { "kind": "token", "token": "--ui-status-warning-border" },
186
+ "--ui-alert-border-danger": { "kind": "token", "token": "--ui-status-danger-border" },
187
+ "--ui-alert-border-info": { "kind": "token", "token": "--ui-status-info-border" },
188
+ "--ui-alert-dismiss": { "kind": "token", "token": "--ui-alert-text" },
189
+ "--ui-alert-dismiss-success": { "kind": "token", "token": "--ui-alert-text-success" },
190
+ "--ui-alert-dismiss-warning": { "kind": "token", "token": "--ui-alert-text-warning" },
191
+ "--ui-alert-dismiss-danger": { "kind": "token", "token": "--ui-alert-text-danger" },
192
+ "--ui-alert-dismiss-info": { "kind": "token", "token": "--ui-alert-text-info" },
193
+ "--ui-alert-dismiss-bg-hover": { "kind": "literal", "value": "transparent" },
194
+ "--ui-section-label-text": { "kind": "token", "token": "--ui-text-secondary" },
195
+ "--ui-section-label-text-danger": { "kind": "token", "token": "--ui-status-danger-text" },
196
+ "--ui-section-label-text-warning": { "kind": "token", "token": "--ui-action-warning-outline-text" },
197
+ "--ui-section-label-text-strong": { "kind": "token", "token": "--ui-text-primary" },
198
+ "--ui-menu-item-text-accent": { "kind": "token", "token": "--ui-text-button-accent-text" },
199
+ "--ui-menu-item-text-brand": { "kind": "token", "token": "--ui-text-button-text" },
200
+ "--ui-menu-item-text-warning": { "kind": "token", "token": "--ui-text-primary" },
201
+ "--ui-menu-item-text-danger": { "kind": "token", "token": "--ui-text-primary" },
202
+ "--ui-menu-item-text-info": { "kind": "token", "token": "--ui-text-primary" },
203
+ "--ui-menu-item-icon-bg": { "kind": "token", "token": "--ui-surface-subtle" },
204
+ "--ui-radio-card-checked-bg": { "kind": "token", "token": "--ui-surface" },
205
+ "--ui-radio-card-checked-bg-danger": { "kind": "token", "token": "--ui-radio-card-checked-bg" },
206
+ "--ui-radio-card-checked-border-danger": { "kind": "token", "token": "--ui-status-danger" },
207
+ "--ui-table-header-text": { "kind": "token", "token": "--ui-text-secondary" },
208
+ "--ui-table-header-bg": { "kind": "token", "token": "--ui-surface-subtle" },
209
+ "--ui-table-divider": { "kind": "token", "token": "--ui-border" },
210
+ "--ui-toggle-group-checked-bg": { "kind": "token", "token": "--ui-action-primary-bg" },
211
+ "--ui-toggle-group-checked-bg-hover": { "kind": "token", "token": "--ui-action-primary-bg-hover" },
212
+ "--ui-toggle-group-checked-text": { "kind": "token", "token": "--ui-action-primary-text" },
213
+ "--ui-toggle-group-checked-border": { "kind": "token", "token": "--ui-control-checked-bg" },
214
+ "--ui-toggle-group-track-bg": { "kind": "token", "token": "--ui-surface-subtle" },
215
+ "--ui-toggle-group-track-text": { "kind": "token", "token": "--ui-text-secondary" },
216
+ "--ui-toggle-group-segment-bg": { "kind": "token", "token": "--ui-surface" },
217
+ "--ui-toggle-group-segment-text": { "kind": "token", "token": "--ui-text-primary" },
218
+ "--ui-toggle-group-segment-border": { "kind": "token", "token": "--ui-border-control" },
219
+ "--ui-badge-solid-accent-bg": { "kind": "token", "token": "--ui-surface-inverse" },
220
+ "--ui-badge-solid-accent-text": { "kind": "token", "token": "--ui-text-inverse" },
221
+ "--ui-badge-solid-brand-bg": { "kind": "token", "token": "--ui-action-primary-bg" },
222
+ "--ui-badge-solid-brand-text": { "kind": "token", "token": "--ui-action-primary-text" },
223
+ "--ui-status-indicator-muted": { "kind": "token", "token": "--ui-disabled-text" },
224
+ "--ui-status-success-on-inverse": { "kind": "token", "token": "--ui-border-control-on-inverse" },
225
+ "--ui-status-warning-on-inverse": { "kind": "token", "token": "--ui-border-control-on-inverse" },
226
+ "--ui-status-danger-on-inverse": { "kind": "token", "token": "--ui-border-control-on-inverse" },
227
+ "--ui-status-info-on-inverse": { "kind": "token", "token": "--ui-border-control-on-inverse" },
228
+ "--ui-progress-accent": { "kind": "token", "token": "--ui-text-button-accent-text" },
229
+ "--ui-progress-brand": { "kind": "token", "token": "--ui-text-button-text" }
230
+ },
231
+ "shape": [
54
232
  "--ui-font-body",
55
233
  "--ui-font-emphasis",
56
234
  "--ui-font-mono",
57
235
  "--ui-dialog-title-font",
236
+ "--ui-font-display",
58
237
  "--ui-dialog-title-weight",
59
238
  "--ui-radius-sm",
60
239
  "--ui-radius-md",
61
240
  "--ui-radius-lg",
62
241
  "--ui-radius-pill",
63
- "--ui-shadow-sm",
64
- "--ui-shadow-md",
65
- "--ui-shadow-lg",
66
242
  "--ui-font-size-xs",
67
243
  "--ui-font-size-sm",
68
244
  "--ui-font-size-md",
@@ -82,64 +258,21 @@
82
258
  "--ui-space-3",
83
259
  "--ui-space-4",
84
260
  "--ui-space-6",
85
- "--ui-space-8"
261
+ "--ui-space-8",
262
+ "--ui-field-height",
263
+ "--ui-dialog-title-font-size",
264
+ "--ui-button-font-weight",
265
+ "--ui-button-font-weight-sm",
266
+ "--ui-button-font-weight-md",
267
+ "--ui-button-font-weight-lg",
268
+ "--ui-icon-button-size-xs",
269
+ "--ui-text-button-font-size-xs",
270
+ "--ui-field-hint-font-size",
271
+ "--ui-font-weight-medium",
272
+ "--ui-font-weight-bold",
273
+ "--ui-alert-radius",
274
+ "--ui-alert-padding"
86
275
  ],
87
- "derived": {
88
- "--ui-action-primary-border": "--ui-action-primary-bg",
89
- "--ui-action-secondary-border": "--ui-border-control",
90
- "--ui-action-danger-border": "--ui-action-danger-bg",
91
- "--ui-status-success-border": "--ui-status-success",
92
- "--ui-status-warning-border": "--ui-status-warning",
93
- "--ui-status-danger-border": "--ui-status-danger",
94
- "--ui-status-info-border": "--ui-status-info",
95
- "--ui-field-height": "--ui-control-height-md",
96
- "--ui-dialog-title-font-size": "--ui-font-size-xl",
97
- "--ui-button-font-weight": "--ui-font-weight-strong",
98
- "--ui-button-font-weight-sm": "--ui-button-font-weight",
99
- "--ui-button-font-weight-md": "--ui-button-font-weight",
100
- "--ui-button-font-weight-lg": "--ui-button-font-weight",
101
- "--ui-control-checked-bg": "--ui-action-primary-bg",
102
- "--ui-control-checked-fg": "--ui-action-primary-text",
103
- "--ui-link-text": "--ui-text-primary",
104
- "--ui-action-accent-text": "--ui-action-ghost-text",
105
- "--ui-action-accent-bg-hover": "--ui-action-ghost-bg-hover",
106
- "--ui-action-accent-bg-active": "--ui-action-ghost-bg-active",
107
- "--ui-action-danger-outline-text": "--ui-status-danger-text",
108
- "--ui-action-danger-outline-border": "--ui-status-danger",
109
- "--ui-action-danger-outline-bg-hover": "--ui-status-danger-bg",
110
- "--ui-action-danger-outline-bg-active": "--ui-status-danger-bg",
111
- "--ui-action-warning-bg": "--ui-action-danger-bg",
112
- "--ui-action-warning-bg-hover": "--ui-action-danger-bg-hover",
113
- "--ui-action-warning-bg-active": "--ui-action-danger-bg-active",
114
- "--ui-action-warning-text": "--ui-action-danger-text",
115
- "--ui-action-warning-border": "--ui-action-danger-border",
116
- "--ui-text-button-text": "--ui-link-text",
117
- "--ui-text-button-accent-text": "--ui-text-button-text",
118
- "--ui-text-button-muted-text": "--ui-text-secondary",
119
- "--ui-icon-button-size-xs": "--ui-space-6",
120
- "--ui-action-soft-accent-bg": "--ui-action-accent-bg-hover",
121
- "--ui-action-soft-accent-bg-hover": "--ui-action-accent-bg-active",
122
- "--ui-action-soft-accent-bg-active": "--ui-action-accent-bg-active",
123
- "--ui-action-soft-accent-text": "--ui-action-accent-text",
124
- "--ui-action-soft-accent-border": "--ui-action-accent-text",
125
- "--ui-action-soft-warning-bg": "--ui-status-warning-bg",
126
- "--ui-action-soft-warning-bg-hover": "--ui-status-warning-bg",
127
- "--ui-action-soft-warning-bg-active": "--ui-status-warning-bg",
128
- "--ui-action-soft-warning-text": "--ui-status-warning-text",
129
- "--ui-action-soft-warning-border": "--ui-status-warning",
130
- "--ui-action-warning-outline-text": "--ui-action-danger-outline-text",
131
- "--ui-action-warning-outline-border": "--ui-action-danger-outline-border",
132
- "--ui-action-warning-outline-bg-hover": "--ui-action-danger-outline-bg-hover",
133
- "--ui-action-warning-outline-bg-active": "--ui-action-danger-outline-bg-active",
134
- "--ui-text-button-strong-text": "--ui-text-primary",
135
- "--ui-text-button-text-hover": "--ui-text-button-text",
136
- "--ui-text-button-accent-text-hover": "--ui-text-button-accent-text",
137
- "--ui-text-button-muted-text-hover": "--ui-text-button-muted-text",
138
- "--ui-text-button-strong-text-hover": "--ui-text-button-strong-text",
139
- "--ui-text-button-font-size-xs": "--ui-font-size-xs",
140
- "--ui-font-weight-medium": "--ui-font-weight-strong",
141
- "--ui-font-weight-bold": "--ui-font-weight-strong"
142
- },
143
276
  "brandComposition": [
144
277
  "--ui-brand-primary",
145
278
  "--ui-brand-on-primary",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksmv/ui-checks",
3
- "version": "0.18.1",
3
+ "version": "0.20.1",
4
4
  "description": "Static and computed quality checks for semantic UI token contracts.",
5
5
  "keywords": ["accessibility", "design-tokens", "quality", "static-analysis"],
6
6
  "license": "Apache-2.0",
package/dist/density.d.ts DELETED
@@ -1,22 +0,0 @@
1
- export type DensityKind = "control-height" | "font-size" | "font-weight" | "line-height" | "spacing";
2
- /**
3
- * The closed list of density tokens whose values the checker validates. It is
4
- * matched by exact name, never by pattern: `--ui-font-body` or
5
- * `--ui-shadow-sm` must not be read as lengths.
6
- */
7
- export declare const densityTokenKinds: Readonly<Record<string, DensityKind>>;
8
- export declare function isValidDensityValue(kind: DensityKind, raw: string): boolean;
9
- /**
10
- * Human-readable description of what a valid value looks like for the given
11
- * density kind, used in checker diagnostics. Never echoes the offending
12
- * value itself.
13
- */
14
- export declare function densityExpectation(kind: DensityKind): string;
15
- /**
16
- * Resolves a whole-value `var()` reference to the literal it ends in, using
17
- * the theme's own declarations (and the evidence) first and the reference's
18
- * fallback second. Returns undefined when the chain does not end in a literal
19
- * (the reference check reports that) or when it passes through another
20
- * density token, which is validated on its own.
21
- */
22
- export declare function resolveDensityReference(value: string, tokens: ReadonlyMap<string, string>, seen?: ReadonlySet<string>): string | undefined;
package/dist/density.js DELETED
@@ -1,103 +0,0 @@
1
- import valueParser from "postcss-value-parser";
2
- /**
3
- * The closed list of density tokens whose values the checker validates. It is
4
- * matched by exact name, never by pattern: `--ui-font-body` or
5
- * `--ui-shadow-sm` must not be read as lengths.
6
- */
7
- export const densityTokenKinds = Object.freeze({
8
- "--ui-font-size-xs": "font-size",
9
- "--ui-font-size-sm": "font-size",
10
- "--ui-font-size-md": "font-size",
11
- "--ui-font-size-lg": "font-size",
12
- "--ui-font-size-xl": "font-size",
13
- "--ui-font-weight-strong": "font-weight",
14
- "--ui-line-height-control": "line-height",
15
- "--ui-control-height-sm": "control-height",
16
- "--ui-control-height-md": "control-height",
17
- "--ui-control-height-lg": "control-height",
18
- "--ui-control-padding-inline-sm": "spacing",
19
- "--ui-control-padding-inline-md": "spacing",
20
- "--ui-control-padding-inline-lg": "spacing",
21
- "--ui-space-1": "spacing",
22
- "--ui-space-1-5": "spacing",
23
- "--ui-space-2": "spacing",
24
- "--ui-space-3": "spacing",
25
- "--ui-space-4": "spacing",
26
- "--ui-space-6": "spacing",
27
- "--ui-space-8": "spacing",
28
- "--ui-field-height": "control-height",
29
- "--ui-dialog-title-font-size": "font-size",
30
- "--ui-button-font-weight": "font-weight",
31
- "--ui-button-font-weight-sm": "font-weight",
32
- "--ui-button-font-weight-md": "font-weight",
33
- "--ui-button-font-weight-lg": "font-weight",
34
- });
35
- const lengthPattern = /^(?:\d+(?:\.\d+)?|\.\d+)(?:px|rem|em)$/iu;
36
- const math = /^(?:calc|clamp|min|max)\(.+\)$/iu;
37
- const unitless = /^(?:\d+(?:\.\d+)?|\.\d+)$/u;
38
- export function isValidDensityValue(kind, raw) {
39
- const value = raw.trim();
40
- switch (kind) {
41
- case "font-weight": {
42
- if (/^(?:normal|bold)$/iu.test(value))
43
- return true;
44
- if (!/^\d+$/u.test(value))
45
- return false;
46
- const weight = Number(value);
47
- return weight >= 1 && weight <= 1000;
48
- }
49
- case "line-height":
50
- return math.test(value) || (unitless.test(value) && Number(value) > 0);
51
- case "spacing":
52
- return value === "0" || math.test(value) || lengthPattern.test(value);
53
- case "control-height":
54
- case "font-size":
55
- return math.test(value) || (lengthPattern.test(value) && Number.parseFloat(value) > 0);
56
- }
57
- }
58
- /**
59
- * Human-readable description of what a valid value looks like for the given
60
- * density kind, used in checker diagnostics. Never echoes the offending
61
- * value itself.
62
- */
63
- export function densityExpectation(kind) {
64
- switch (kind) {
65
- case "control-height":
66
- case "font-size":
67
- return "a length above zero in px, rem or em, or calc()/clamp()/min()/max()";
68
- case "spacing":
69
- return "zero or a length in px, rem or em, or calc()/clamp()/min()/max()";
70
- case "line-height":
71
- return "a unitless number above zero, or calc()/clamp()/min()/max()";
72
- case "font-weight":
73
- return "an integer from 1 to 1000, normal or bold";
74
- }
75
- }
76
- /**
77
- * Resolves a whole-value `var()` reference to the literal it ends in, using
78
- * the theme's own declarations (and the evidence) first and the reference's
79
- * fallback second. Returns undefined when the chain does not end in a literal
80
- * (the reference check reports that) or when it passes through another
81
- * density token, which is validated on its own.
82
- */
83
- export function resolveDensityReference(value, tokens, seen = new Set()) {
84
- const trimmed = value.trim();
85
- const nodes = valueParser(trimmed).nodes;
86
- if (nodes.length !== 1)
87
- return undefined;
88
- const [node] = nodes;
89
- if (node.type !== "function" || node.value.toLowerCase() !== "var")
90
- return trimmed;
91
- const comma = node.nodes.findIndex((child) => child.type === "div" && child.value === ",");
92
- const name = valueParser.stringify(comma === -1 ? node.nodes : node.nodes.slice(0, comma)).trim();
93
- const fallback = comma === -1 ? undefined : valueParser.stringify(node.nodes.slice(comma + 1)).trim();
94
- if (name in densityTokenKinds || seen.has(name))
95
- return undefined;
96
- const next = new Set([...seen, name]);
97
- const declared = tokens.get(name);
98
- if (declared !== undefined)
99
- return resolveDensityReference(declared, tokens, next);
100
- if (fallback)
101
- return resolveDensityReference(fallback, tokens, next);
102
- return undefined;
103
- }