@ksmv/ui-checks 0.1.2 → 0.1.4

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.4 - 2026-09-28
4
+
5
+ - A evidência calculada aceita tokens derivados ausentes ou vazios, que é como o navegador informa um token que o tema deixou no padrão, e usa o valor do token de origem. Na 0.1.3, um tema que confiava nos padrões documentados dos derivados nunca produzia evidência válida pela receita do README.
6
+ - Tokens de marca (`brandComposition`) também podem vir vazios na evidência, e a receita do README passa a capturá-los.
7
+ - Com evidência que traz o valor final de um token, `tokens/complete` deixa de marcar como não resolvidas as referências dele a tokens privados do consumidor. Na 0.1.3, uma ponte `--ui-*` que apontava para tokens do produto ficava com cobertura obrigatória pendente mesmo com evidência completa, e não podia entrar em baseline.
8
+ - Um token obrigatório vazio continua sendo recusado.
9
+
10
+ ## 0.1.3 - 2026-09-28
11
+
12
+ - Sem mudança nas verificações: regras, contratos, códigos de saída e README são os mesmos da 0.1.2.
13
+ - `bugs` passa a ser declarado vazio. Na 0.1.2 o campo foi omitido, e o npm o derivou do endereço de `repository` ao publicar, então a página do pacote continuou apontando para as issues do repositório privado, que respondem 404. Declarado vazio, o npm o descarta sem derivá-lo. Não há canal público de issues, e o README indica o canal de relato de segurança.
14
+
3
15
  ## 0.1.2 - 2026-09-28
4
16
 
5
17
  - `./package.json` passa a ser exportado, como já era em `@ksmv/ui-react`. Ler a versão instalada por `require.resolve("@ksmv/ui-checks/package.json")` terminava com `ERR_PACKAGE_PATH_NOT_EXPORTED`.
package/README.md CHANGED
@@ -84,6 +84,8 @@ A evidência não é uma exceção manual. O arquivo é recusado, com código `2
84
84
  - a identidade de um dos contratos diverge da instalada, ou seja, o pacote React mudou depois da captura;
85
85
  - falta algum token exigido, ou algum valor ainda contém `var(...)`.
86
86
 
87
+ 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.
88
+
87
89
  A captura usa a API pública deste pacote e um navegador à sua escolha. Com Playwright, e a aplicação servida localmente:
88
90
 
89
91
  ```js
@@ -109,6 +111,7 @@ const tokens = [...new Set([
109
111
  ...contracts.theme.required,
110
112
  ...Object.keys(contracts.theme.derived),
111
113
  ...Object.values(contracts.theme.derived),
114
+ ...contracts.theme.brandComposition,
112
115
  ...contracts.contrast.pairs.flatMap(({ background, foreground }) => [background, foreground]),
113
116
  ])];
114
117
 
package/dist/evidence.js CHANGED
@@ -38,11 +38,14 @@ function sameIdentity(actual, expected) {
38
38
  actual.contractVersion === expected.contractVersion &&
39
39
  actual.sha256 === expected.sha256;
40
40
  }
41
+ // Derived tokens are not required: a theme may leave them to their documented
42
+ // default, the source token, and the browser then reports them as empty.
41
43
  function requiredEvidenceTokens(contracts) {
44
+ const derived = contracts.theme.derived;
42
45
  return new Set([
43
46
  ...contracts.theme.required,
44
- ...Object.keys(contracts.theme.derived),
45
- ...Object.values(contracts.theme.derived),
47
+ // A source that is itself derived is optional too; its chain is followed.
48
+ ...Object.values(derived).filter((source) => !(source in derived)),
46
49
  ...contracts.contrast.pairs.flatMap(({ background, foreground }) => [
47
50
  background,
48
51
  foreground,
@@ -73,13 +76,20 @@ function validateEvidenceShape(value, contracts) {
73
76
  !isRecord(value.themes)) {
74
77
  return false;
75
78
  }
79
+ // A token the evidence must carry is never optional, even when the contract
80
+ // also lists it as derived or as a brand token.
81
+ const required = requiredEvidenceTokens(contracts);
82
+ const optional = new Set([...Object.keys(contracts.theme.derived), ...contracts.theme.brandComposition]
83
+ .filter((token) => !required.has(token)));
76
84
  return Object.values(value.themes).every((theme) => isRecord(theme) &&
77
85
  exactKeys(theme, ["selector", "tokens"]) &&
78
86
  typeof theme.selector === "string" &&
79
87
  isRecord(theme.tokens) &&
80
88
  Object.entries(theme.tokens).every(([token, tokenValue]) => tokenNamePattern.test(token) &&
81
89
  typeof tokenValue === "string" &&
82
- tokenValue.trim().length > 0 &&
90
+ // Empty means undeclared, which only an optional token may be: a derived
91
+ // token left to its default, or a brand token the theme does not use.
92
+ (tokenValue.trim().length > 0 || optional.has(token)) &&
83
93
  !/var\s*\(/i.test(tokenValue)));
84
94
  }
85
95
  export async function loadComputedEvidence({ contracts, evidenceByPath, now = new Date(), root, themes, }) {
@@ -121,7 +131,23 @@ export async function loadComputedEvidence({ contracts, evidenceByPath, now = ne
121
131
  throw new Error("tokens");
122
132
  }
123
133
  }
124
- result[theme.name] = { ...computedTheme.tokens };
134
+ const tokens = Object.fromEntries(Object.entries(computedTheme.tokens).filter(([, tokenValue]) => tokenValue.trim().length > 0));
135
+ // A derived token left to its default takes its source's value,
136
+ // following sources that are derived themselves. A chain that does not
137
+ // end in a real value makes the evidence invalid.
138
+ const derivedValue = (token, seen) => {
139
+ const value = tokens[token];
140
+ if (value !== undefined)
141
+ return value;
142
+ const source = contracts.theme.derived[token];
143
+ if (source === undefined || seen.has(token))
144
+ throw new Error("tokens");
145
+ return derivedValue(source, new Set([...seen, token]));
146
+ };
147
+ for (const token of Object.keys(contracts.theme.derived)) {
148
+ tokens[token] = derivedValue(token, new Set());
149
+ }
150
+ result[theme.name] = tokens;
125
151
  }
126
152
  }
127
153
  return result;
@@ -92,7 +92,13 @@ export function createTokenCompleteRule({ computed = {}, contract, themes, }) {
92
92
  tokens.set(name, value);
93
93
  }
94
94
  const reportedReferences = new Set();
95
- for (const declaration of theme.declarations.values()) {
95
+ for (const [name, declaration] of theme.declarations) {
96
+ // The evidence carries the value the browser computed for this very
97
+ // token, so whatever it referenced, private tokens included, has
98
+ // already been resolved.
99
+ if (computedTheme[name] !== undefined) {
100
+ continue;
101
+ }
96
102
  for (const reference of unresolvedTokenReferences(declaration.value, tokens)) {
97
103
  if (!reportedReferences.has(reference)) {
98
104
  reportedReferences.add(reference);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksmv/ui-checks",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
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",
@@ -29,5 +29,6 @@
29
29
  "engines": { "node": ">=22.14" },
30
30
  "repository": { "type": "git", "url": "https://github.com/AxisGov/ui.git", "directory": "packages/checks" },
31
31
  "homepage": "https://www.npmjs.com/package/@ksmv/ui-checks",
32
+ "bugs": {},
32
33
  "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
33
34
  }