synthesisui 0.16.306 → 0.16.308

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.
@@ -43,6 +43,7 @@
43
43
  * data-[checked]:bg-ocean-50 → states.checked
44
44
  */
45
45
  import { tokenRefFor } from "../token-ref.js";
46
+ import { frameworkDeclaration } from "./framework-palette.js";
46
47
  /**
47
48
  * Utility prefix → the property name THE DOCUMENT USES, which is camelCase.
48
49
  *
@@ -291,33 +292,12 @@ const TAILWIND_COLOR = {
291
292
  */
292
293
  current: "currentColor",
293
294
  inherit: "inherit",
294
- // zinc and blue, because a real Select is styled entirely in them and every
295
- // reference read as nothing (test14, 01/08). Tailwind's own values, verbatim.
296
- "zinc-50": "#fafafa",
297
- "zinc-100": "#f4f4f5",
298
- "zinc-200": "#e4e4e7",
299
- "zinc-300": "#d4d4d8",
300
- "zinc-400": "#a1a1aa",
301
- "zinc-500": "#71717a",
302
- "zinc-600": "#52525b",
303
- "zinc-700": "#3f3f46",
304
- "zinc-800": "#27272a",
305
- "zinc-900": "#18181b",
306
- "zinc-950": "#09090b",
307
- "blue-400": "#60a5fa",
308
- "blue-500": "#3b82f6",
309
- "blue-600": "#2563eb",
310
- "neutral-50": "#fafafa",
311
- "neutral-100": "#f5f5f5",
312
- "neutral-200": "#e5e5e5",
313
- "neutral-300": "#d4d4d4",
314
- "neutral-400": "#a3a3a3",
315
- "neutral-500": "#737373",
316
- "neutral-600": "#525252",
317
- "neutral-700": "#404040",
318
- "neutral-800": "#262626",
319
- "neutral-900": "#171717",
320
- "neutral-950": "#0a0a0a",
295
+ /**
296
+ * A PALETA saiu daqui em 25/08: zinc, blue e neutral (as entradas do test14) moravam nesta
297
+ * tabela com os hex da v3, cravados - e um projeto v4 recebia o hex antigo. A paleta inteira
298
+ * mora em `framework-palette.ts`, por versão maior e com o carimbo `from` que o D1 exige;
299
+ * aqui ficam só os KEYWORDS de CSS, que não são paleta e não têm versão.
300
+ */
321
301
  };
322
302
  /**
323
303
  * TYPOGRAPHY, and the reason it is here at all.
@@ -649,8 +629,12 @@ export function parseClass(cls) {
649
629
  * `declared` maps a token NAME to its value, so a resolved colour can be
650
630
  * reported in their vocabulary rather than as a hex we looked up.
651
631
  */
652
- /** A hex, or a ref into their colour scale - the two shapes an alpha can wrap. */
653
- const COLOUR_VALUE = /^(#[0-9a-f]{3,8}|\{color\.[^}]+\})$/i;
632
+ /**
633
+ * A hex, a ref into their colour scale, OR a colour function - the shapes an alpha can wrap.
634
+ * As funções entraram com a paleta versionada (item 18): a v4 publica oklch, e sem elas
635
+ * `bg-emerald-500/10` resolvia a cor e PERDIA o alfa em silêncio - o scrim virava parede.
636
+ */
637
+ const COLOUR_VALUE = /^(#[0-9a-f]{3,8}|\{color\.[^}]+\}|(?:oklch|oklab|rgba?|hsla?|lab|lch)\([^)]*\))$/i;
654
638
  /**
655
639
  * `#ffffff` + `50` → `color-mix(in srgb, #ffffff 50%, transparent)`.
656
640
  *
@@ -922,6 +906,17 @@ function readUtilityCore(utility, declared) {
922
906
  if (declared.has(own)) {
923
907
  return { property: colorProp, value: refFor(rest), token: own };
924
908
  }
909
+ /**
910
+ * A PALETA PUBLICADA DO FRAMEWORK, por versão maior e COM carimbo (item 18 + D1).
911
+ *
912
+ * Depois do token dele (o vocabulário dele sempre vence) e antes da tabela de keywords:
913
+ * `text-rose-700` é decisão dele sobre um valor que o Tailwind publica, e morria em silêncio
914
+ * - 127 valores no diagnóstico de 25/08. O `from: "framework"` é o que separa isto de
915
+ * inventar: a métrica sabe que não é vocabulário DELE e o self-healing pode perguntar.
916
+ */
917
+ const published = frameworkDeclaration(colorProp, rest);
918
+ if (published)
919
+ return published;
925
920
  const builtin = TAILWIND_COLOR[rest];
926
921
  if (builtin)
927
922
  return { property: colorProp, value: builtin };
@@ -0,0 +1,153 @@
1
+ import { parseClass, readUtility } from "./transcribe.js";
2
+ /**
3
+ * AS PROPRIEDADES QUE SÃO ARRANJO - a definição de "estrutura", por propriedade resolvida.
4
+ *
5
+ * O critério do dono (D4): estrutura é onde "token é impossível por definição" - não existe
6
+ * decisão de vocabulário em `display: flex`. Espaçamento e tamanho NÃO entram: `gap`, `padding`
7
+ * e `height` são decisões de escala (a métrica de Spacing & Proportion existe para elas).
8
+ */
9
+ const STRUCTURAL_PROPERTIES = new Set([
10
+ "display",
11
+ "position",
12
+ "top",
13
+ "right",
14
+ "bottom",
15
+ "left",
16
+ "inset",
17
+ "insetInline",
18
+ "insetBlock",
19
+ "zIndex",
20
+ "overflow",
21
+ "overflowX",
22
+ "overflowY",
23
+ "flexDirection",
24
+ "flexWrap",
25
+ "alignItems",
26
+ "alignContent",
27
+ "alignSelf",
28
+ "justifyContent",
29
+ "justifyItems",
30
+ "justifySelf",
31
+ "placeItems",
32
+ "placeContent",
33
+ "order",
34
+ "flex",
35
+ "flexGrow",
36
+ "flexShrink",
37
+ "flexBasis",
38
+ "gridColumn",
39
+ "gridRow",
40
+ "gridTemplateColumns",
41
+ "gridTemplateRows",
42
+ "pointerEvents",
43
+ "userSelect",
44
+ "textAlign",
45
+ "verticalAlign",
46
+ "whiteSpace",
47
+ "objectFit",
48
+ "objectPosition",
49
+ "visibility",
50
+ "isolation",
51
+ "textOverflow",
52
+ "borderCollapse",
53
+ ]);
54
+ /**
55
+ * TOKENS DE ARRANJO QUE O RESOLVEDOR NÃO DEVOLVE COMO DECLARAÇÃO - a metade utilitária da mesma
56
+ * definição. `w-full` e `truncate` não são decisão de vocabulário; `hidden` e `sr-only` são
57
+ * visibilidade. Lista CONSERVADORA de propósito: o que não estiver aqui e não resolver cai em
58
+ * `unread`, que é o lado certo de errar - um arranjo cobrado como lacuna custa uma linha; uma
59
+ * decisão de estilo escondida como "estrutura" custa a confiança na métrica.
60
+ */
61
+ const STRUCTURAL_TOKENS = new Set([
62
+ /** Os valores de display crus - a mesma lista que o juiz de fragmento chama de STRUCTURE. */
63
+ "flex",
64
+ "inline-flex",
65
+ "grid",
66
+ "inline-grid",
67
+ "block",
68
+ "inline-block",
69
+ "inline",
70
+ "contents",
71
+ "flow-root",
72
+ "flex-row",
73
+ "flex-col",
74
+ "flex-row-reverse",
75
+ "flex-col-reverse",
76
+ "w-full",
77
+ "h-full",
78
+ "w-fit",
79
+ "h-fit",
80
+ "w-max",
81
+ "w-min",
82
+ "min-w-0",
83
+ "shrink",
84
+ "shrink-0",
85
+ "grow",
86
+ "grow-0",
87
+ "flex-1",
88
+ "flex-auto",
89
+ "flex-none",
90
+ "hidden",
91
+ "sr-only",
92
+ "not-sr-only",
93
+ "truncate",
94
+ "group",
95
+ "peer",
96
+ "isolate",
97
+ ]);
98
+ /**
99
+ * O destino de UM token de classe. Exportado para o julgamento por valor e a triagem lerem a
100
+ * MESMA resposta - duas cópias desta decisão é como o item 11 nasceu.
101
+ */
102
+ export function destinationOf(cls, declared) {
103
+ const { utility } = parseClass(cls);
104
+ if (STRUCTURAL_TOKENS.has(utility))
105
+ return "structure";
106
+ const read = readUtility(utility, declared);
107
+ if (read) {
108
+ return STRUCTURAL_PROPERTIES.has(read.property)
109
+ ? "structure"
110
+ : "interpreted";
111
+ }
112
+ return "unread";
113
+ }
114
+ /** A conta de uma lista de tokens - a soma SEMPRE fecha com `seen`, por construção. */
115
+ export function accountClasses(tokens, declared) {
116
+ const account = {
117
+ seen: 0,
118
+ interpreted: 0,
119
+ structure: 0,
120
+ unread: 0,
121
+ answered: 0,
122
+ unknown: 0,
123
+ };
124
+ for (const cls of tokens) {
125
+ account.seen += 1;
126
+ account[destinationOf(cls, declared)] += 1;
127
+ }
128
+ return account;
129
+ }
130
+ /** Soma de contas - o total do censo é a soma das contas por componente, nunca outra medição. */
131
+ export function sumAccounts(accounts) {
132
+ const out = {
133
+ seen: 0,
134
+ interpreted: 0,
135
+ structure: 0,
136
+ unread: 0,
137
+ answered: 0,
138
+ unknown: 0,
139
+ };
140
+ for (const a of accounts) {
141
+ out.seen += a.seen;
142
+ out.interpreted += a.interpreted;
143
+ out.structure += a.structure;
144
+ out.unread += a.unread;
145
+ out.answered += a.answered;
146
+ out.unknown += a.unknown;
147
+ }
148
+ return out;
149
+ }
150
+ /** A invariante do item 11, como pergunta - o spec das populações a assere, o portão a exige. */
151
+ export function invariantHolds(a) {
152
+ return (a.interpreted + a.structure + a.unread + a.answered + a.unknown === a.seen);
153
+ }
@@ -31,7 +31,7 @@
31
31
  * position in `layers`. They agree only if the reader inserts in the order it read.
32
32
  */
33
33
  import { wearGlobal, withGlobal } from "../global-wear.js";
34
- import { parseClass, transcribe } from "./transcribe.js";
34
+ import { parseClass, readUtility, transcribe, } from "./transcribe.js";
35
35
  /**
36
36
  * A Tailwind modifier, mapped to the condition it IS.
37
37
  *
@@ -1272,11 +1272,23 @@ globals) {
1272
1272
  ...t.base,
1273
1273
  ...t.dark,
1274
1274
  };
1275
+ /**
1276
+ * O HÍBRIDO DO D1, no nível da camada: `classes` é a grafia DELE, verbatim (a metade de
1277
+ * fidelidade - o codegen devolve o que ele escreveu); `from: "framework"` quando TODA
1278
+ * declaração resolvida veio da paleta publicada - numa camada mista o vocabulário dele
1279
+ * domina e o carimbo fica ausente, que é o caminho de sempre. Ver `framework-palette.ts`.
1280
+ */
1281
+ const reads = bare
1282
+ .map((utility) => readUtility(utility, declared))
1283
+ .filter((d) => d != null);
1284
+ const fromFramework = reads.length > 0 && reads.every((d) => d.from === "framework");
1275
1285
  return {
1276
1286
  ...(layer.when ? { when: layer.when } : {}),
1277
1287
  ...(layer.at ? { at: layer.at } : {}),
1278
1288
  ...(layer.onClasses ? { onClasses: layer.onClasses } : {}),
1279
1289
  style,
1290
+ classes: layer.classes,
1291
+ ...(fromFramework ? { from: "framework" } : {}),
1280
1292
  };
1281
1293
  })
1282
1294
  .filter((layer) => Object.keys(layer.style).length > 0);
@@ -196,7 +196,13 @@ export const MATERIALISER_SINCE = "0.16.306";
196
196
  * `var(--text-caption)` - um token de tipo numa posição de raio. Medido no repositório real: 290
197
197
  * sugestões com a categoria trocada, 66 delas graváveis por um `--fix --write`.
198
198
  */
199
- export const CHECKER_SINCE = "0.16.250";
199
+ /**
200
+ * 0.16.250 -> 0.16.308 em 25/08: o julgamento que o hook roda passa pela paleta versionada do
201
+ * framework (item 18). Um hook pinado antes lê o mesmo arquivo com `text-rose-700` e conta
202
+ * "não lido" sobre um valor que o de hoje resolve - e o número é o que a pessoa lê para decidir
203
+ * se adotou. O `!` de important (0.16.306) já tinha mudado a mesma contagem.
204
+ */
205
+ export const CHECKER_SINCE = "0.16.308";
200
206
  /**
201
207
  * A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
202
208
  *
@@ -288,7 +294,31 @@ export const CHECKER_SINCE = "0.16.250";
288
294
  *
289
295
  * Um censo medido antes carrega a poluição e a perda; quem remede ganha os três de uma vez.
290
296
  */
291
- export const READER_SINCE = "0.16.306";
297
+ /**
298
+ * 0.16.306 -> 0.16.307 em 25/08: o censo passa a carregar `values` - a CONTABILIDADE POR VALOR
299
+ * (item 11). Todo token de classe que veste um componente admitido tem um destino, e a soma fecha
300
+ * por construção: `interpreted + structure + unread + answered + unknown == seen`. É a
301
+ * reconciliação entre o ledger (que conta lugares) e a produção (que trabalha em valores) - 86%
302
+ * contra 50% no mesmo repositório, sem uma linha ligando os dois.
303
+ *
304
+ * Campo novo: um censo medido antes não o tem, e a tela de cobertura (D4: três colunas, estrutura
305
+ * visível fora da métrica) só pode ser honesta sobre quem remedir. É o que esta marca avisa.
306
+ */
307
+ /**
308
+ * 0.16.307 -> 0.16.308 em 25/08 (mvp-1128), o lote 1 fechado + o começo do lote 2:
309
+ *
310
+ * item 13 o censo carrega `exports` - o denominador antes do portão, com o resíduo em linhas
311
+ * item 12 cada grupo não lido carrega `forms` - a unidade da pergunta de self-healing
312
+ * item 17 a camada vira o híbrido do D1: `classes` verbatim + `from` de procedência
313
+ * item 18 a paleta publicada do framework resolve por versão maior (v4 oklch, v3 hex), com
314
+ * carimbo - 127 valores (21%) deixam de morrer em silêncio
315
+ * item 19 o vocabulário do pacote workspace IMPORTADO pelo escopo entra na colheita (genérico,
316
+ * nunca node_modules; o escopo vence) - medido no repo real: @frontend-hub/ui doa 94
317
+ * variáveis, e os 223 valores (37%) que as vestiam passam a resolver como token DELE
318
+ *
319
+ * Um censo medido antes não tem nada disso; quem remede ganha os cinco.
320
+ */
321
+ export const READER_SINCE = "0.16.308";
292
322
  /**
293
323
  * O QUE ESTÁ INSTALADO AQUI FICOU PARA TRÁS - e as DUAS condições que fazem isso ser verdade.
294
324
  *
@@ -0,0 +1,84 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { sep } from "node:path";
3
+ import { walkAll } from "./commands/doctor.js";
4
+ import { workspacePackages } from "./doctor/components-scan.js";
5
+ /**
6
+ * O VOCABULÁRIO QUE MORA NO PACOTE AO LADO (item 19, 25/08).
7
+ *
8
+ * A regra do Tailwind v4: só `--color-*` cria utilitário de cor. Num monorepo, essas variáveis
9
+ * costumam morar no PACOTE de design system - e quando o escopo medido é o app, a colheita de CSS
10
+ * só via a pasta apontada: o app do repositório real veste `border-lightgray-200` 213 vezes, e o
11
+ * `--color-lightgray-200` que cria esse utilitário mora em `packages/ui/src/styles.css`, um pacote
12
+ * ao lado. Resultado medido: 223 valores (37% do não-lido) morriam com o nome declarado a vinte
13
+ * linhas de distância do alcance do leitor.
14
+ *
15
+ * A regra é GENÉRICA por decisão do dono (25/08): qualquer pacote de WORKSPACE que o escopo
16
+ * importa - nunca um nome cravado, e nunca `node_modules`: o CSS de terceiro de verdade continua
17
+ * fora e declarado (população zero medida até hoje; quando aparecer, mede-se lá).
18
+ *
19
+ * O ESCOPO DELE SEMPRE VENCE: uma variável declarada na pasta medida nunca é sobrescrita por uma
20
+ * do pacote irmão - quem está mais perto do componente é a decisão mais específica.
21
+ */
22
+ /**
23
+ * `from "@acme/ui"` · `import("@acme/ui/x")` · `require("@acme/ui")` · `import "@acme/ui/css"` -
24
+ * o último é o side-effect import, e é EXATAMENTE como uma folha de tokens entra num app.
25
+ */
26
+ const SPECIFIER = /(?:from\s*|import\s*\(?\s*|require\s*\(\s*)["']([^"'./][^"']*)["']/g;
27
+ const CUSTOM_PROPERTY = /(--[a-zA-Z0-9_-]+)\s*:\s*([^;}]+)/g;
28
+ const SKIP_DIR = /(^|[/\\])(node_modules|dist|build|\.next|\.git|coverage)([/\\]|$)/;
29
+ /**
30
+ * Colhe as custom properties dos pacotes workspace que o ESCOPO importa.
31
+ *
32
+ * O pré-passe de especificadores relê os fontes do escopo (o laço de componentes só roda DEPOIS,
33
+ * e é ele que consome o `declared` que esta colheita completa) - custo medido em IO de leitura
34
+ * pura, centenas de ms num monorepo de mil arquivos, pago uma vez por medição.
35
+ */
36
+ export async function harvestWorkspaceCss(scopedRoot,
37
+ /** Nomes já declarados pelo escopo - a trava de "o escopo dele sempre vence". */
38
+ scopeDeclares) {
39
+ const imported = new Set();
40
+ for await (const file of walkAll([scopedRoot])) {
41
+ if (!/\.(tsx|jsx|ts|mjs|cjs|vue|svelte)$/i.test(file))
42
+ continue;
43
+ if (SKIP_DIR.test(file))
44
+ continue;
45
+ const source = await readFile(file, "utf8").catch(() => "");
46
+ SPECIFIER.lastIndex = 0;
47
+ for (const m of source.matchAll(SPECIFIER))
48
+ imported.add(m[1]);
49
+ }
50
+ if (imported.size === 0)
51
+ return { packages: [], declared: new Map() };
52
+ const declared = new Map();
53
+ const packages = [];
54
+ for (const pkg of await workspacePackages(scopedRoot)) {
55
+ /**
56
+ * IMPORTADO PELO ESCOPO - `@acme/ui` cobre `@acme/ui/tokens`. E nunca a própria pasta
57
+ * medida: o CSS dela já foi colhido pelo laço de sempre, e re-colher criaria a disputa
58
+ * que a trava abaixo existe para impedir.
59
+ */
60
+ const used = [...imported].some((s) => s === pkg.name || s.startsWith(`${pkg.name}/`));
61
+ if (!used)
62
+ continue;
63
+ if (scopedRoot === pkg.dir ||
64
+ scopedRoot.startsWith(pkg.dir + sep) ||
65
+ pkg.dir.startsWith(scopedRoot + sep))
66
+ continue;
67
+ let vars = 0;
68
+ for await (const file of walkAll([pkg.dir])) {
69
+ if (!/\.(css|scss|sass|less)$/i.test(file) || SKIP_DIR.test(file))
70
+ continue;
71
+ const body = await readFile(file, "utf8").catch(() => "");
72
+ CUSTOM_PROPERTY.lastIndex = 0;
73
+ for (const m of body.matchAll(CUSTOM_PROPERTY)) {
74
+ if (scopeDeclares(m[1]) || declared.has(m[1]))
75
+ continue;
76
+ declared.set(m[1], m[2].trim());
77
+ vars += 1;
78
+ }
79
+ }
80
+ if (vars > 0)
81
+ packages.push({ name: pkg.name, vars });
82
+ }
83
+ return { packages, declared };
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.306",
3
+ "version": "0.16.308",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {