synthesisui 0.16.285 → 0.16.289

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.
@@ -32,6 +32,7 @@
32
32
  import { readSxProps } from "./doctor/style-props.js";
33
33
  import { transcribe, } from "./doctor/transcribe.js";
34
34
  import { frontierKind, packageRoot } from "./frontier-kind.js";
35
+ import { withGlobal } from "./global-wear.js";
35
36
  /** The ten forms, closed. A renderer that accepts any tag draws anything. */
36
37
  const FORMS = new Set([
37
38
  "image",
@@ -252,7 +253,18 @@ sketch,
252
253
  * A escala de espaçamento que o projeto DELES declara para o `sx`. Ausente = o default
253
254
  * documentado do MUI, que é fato da biblioteca que eles escolheram - ver `spacingOf`.
254
255
  */
255
- sxSpacing) {
256
+ sxSpacing,
257
+ /**
258
+ * AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses` e `wearGlobal`.
259
+ *
260
+ * O QUE ISSO ALCANÇA QUE NADA MAIS ALCANÇAVA: uma utility que ele escreve num NÓ FILHO. O
261
+ * `LeaderboardRow` do `codelevel-ui` põe `text-grad-xp` num `<span>` dentro de um ternário, e o
262
+ * `QuestItem` põe `text-grad` numa tabela que pertence ao filho - as duas passam por aqui, e não
263
+ * pela raiz nem pela tabela de variantes do componente.
264
+ *
265
+ * Ausente é a resposta de quem não tem folha global, e nesse caso nada muda.
266
+ */
267
+ globals) {
256
268
  /**
257
269
  * THE PACKAGE THE RETURNED ELEMENT CAME FROM, measured rather than spelled.
258
270
  * Node 0 of the sketch IS what the component returns, and the reader stamped its
@@ -474,7 +486,7 @@ sxSpacing) {
474
486
  ? (sketch?.[node.at]?.classes ?? "")
475
487
  : "";
476
488
  const classes = raw.split(/\s+/).filter(Boolean);
477
- const t = transcribe(classes, declared);
489
+ const t = withGlobal(transcribe(classes, declared), classes, globals);
478
490
  /**
479
491
  * A PROP DE ESTILO DESTE NÓ, sob a classe dele.
480
492
  *
@@ -618,7 +630,7 @@ sxSpacing) {
618
630
  * otherwise. So a drawn root sends nothing here at all.
619
631
  */
620
632
  const rootStyle = rootPainted
621
- ? transcribe(rootPainted.split(/\s+/).filter(Boolean), declared)
633
+ ? withGlobal(transcribe(rootPainted.split(/\s+/).filter(Boolean), declared), rootPainted.split(/\s+/).filter(Boolean), globals)
622
634
  : null;
623
635
  const paints = rootStyle && Object.keys(rootStyle.base).length > 0
624
636
  ? { style: rootStyle.base }
@@ -710,13 +722,16 @@ sxSpacing) {
710
722
  * correctly, and the absence of a tree means the preview lays them out in a row
711
723
  * exactly as it did before.
712
724
  */
713
- export function resolveFlatParts(read, declared) {
725
+ export function resolveFlatParts(read, declared,
726
+ /** As classes da folha global dele - ver `wearGlobal`. */
727
+ globals) {
714
728
  const parts = {};
715
729
  for (const part of read) {
716
730
  const name = safePartName(String(part?.name ?? ""));
717
731
  if (!name || typeof part.classes !== "string")
718
732
  continue;
719
- const t = transcribe(part.classes.split(/\s+/).filter(Boolean), declared);
733
+ const partClasses = part.classes.split(/\s+/).filter(Boolean);
734
+ const t = withGlobal(transcribe(partClasses, declared), partClasses, globals);
720
735
  parts[name] = { base: t.base, dark: t.dark, states: t.states };
721
736
  }
722
737
  return { parts, tree: [], composes: [], external: [], notes: [], partAt: {} };
@@ -40,7 +40,9 @@ import { keyframesInSheets } from "../global-keyframes.js";
40
40
  import { withLibraryStructure } from "../library-structure.js";
41
41
  import { mergeCensus } from "../merge-census.js";
42
42
  import { claimName } from "../name-claim.js";
43
+ import { mergeNamespacePairs } from "../namespace-pairs.js";
43
44
  import { namingQueue } from "../naming-queue.js";
45
+ import { notExpressed } from "../not-expressed.js";
44
46
  import { body, paint, section } from "../output.js";
45
47
  import { outsideScope } from "../outside-scope.js";
46
48
  import { phase, startProgress } from "../progress.js";
@@ -367,6 +369,8 @@ function screenOf(rel) {
367
369
  const after = parts.slice(i + 1).filter((p) => !p.startsWith("("));
368
370
  return `${parts[i]}/${after.slice(0, 2).join("/")}`;
369
371
  }
372
+ /** Quantas classes da folha global viajam no censo. Ver o aviso em `takeCensus`. */
373
+ const MAX_GLOBAL_CLASSES = 300;
370
374
  export async function takeCensus(root, opts) {
371
375
  /**
372
376
  * O RELATÓRIO SÓ SAI QUANDO ALGUÉM O PEDIU - `say` cala quando o chamador é uma RE-medição.
@@ -450,6 +454,17 @@ export async function takeCensus(root, opts) {
450
454
  * porque é isso que o juiz do ledger precisa para chamar a classe e a regra de LIDAS.
451
455
  */
452
456
  const globalClasses = readGlobalClasses(globalSheets, declaredValues);
457
+ /**
458
+ * TETO DAS CLASSES GLOBAIS QUE VIAJAM NO CENSO - e quando ele corta, ele fala.
459
+ *
460
+ * Medido nas três populações: 24 classes na folha do `codelevel-ui`, 22 na nossa, 0 no
461
+ * `apps/web-dashboard` do repo real, que é CSS Modules. O teto é para a folha de um app que
462
+ * escreva utilitário por atacado, e existe porque um censo que dobra de tamanho em silêncio é o
463
+ * tipo de surpresa que ninguém pede.
464
+ */
465
+ if (globalClasses.size > MAX_GLOBAL_CLASSES) {
466
+ console.log(body(paint.faint(`${globalClasses.size} classes in your global sheet, and ${globalClasses.size - MAX_GLOBAL_CLASSES} of them are not travelling with this census - the first ${MAX_GLOBAL_CLASSES} are.`)));
467
+ }
453
468
  /**
454
469
  * OS KEYFRAMES DA FOLHA DELE, colhidos onde as folhas globais já estão na mão.
455
470
  *
@@ -732,9 +747,23 @@ export async function takeCensus(root, opts) {
732
747
  if (esize > 0 || etag || esketch.length > 0) {
733
748
  looks[extra.name] = {
734
749
  ...(esketch.length > 0 ? { sketch: esketch } : {}),
735
- /** Ver `CensusLook.aside` - o segundo componente do arquivo também. */
736
- ...(classifyAside(extra.name)
737
- ? { aside: classifyAside(extra.name) }
750
+ /**
751
+ * Ver `CensusLook.aside` - o segundo componente do arquivo também.
752
+ *
753
+ * E COM OS EIXOS QUE O TIPO DELE DECLARA. `transcribeVariants` roda para o primeiro
754
+ * componente do arquivo, então aqui não há tabela de variantes - mas `extra.axes` é a
755
+ * declaração da interface, lida por `scanDefinitions` para TODA definição do arquivo.
756
+ * Era `undefined`, e isso pôs de lado o `ToastIcon` dele: terceiro componente do
757
+ * `Toast.tsx`, cinco tons declarados no tipo, um gradiente para cada. Ver
758
+ * `classifyAside`.
759
+ */
760
+ ...(classifyAside({ name: extra.name, declaredAxes: extra.axes })
761
+ ? {
762
+ aside: classifyAside({
763
+ name: extra.name,
764
+ declaredAxes: extra.axes,
765
+ }),
766
+ }
738
767
  : {}),
739
768
  ...et,
740
769
  ...(etag ? { rootTag: etag } : {}),
@@ -770,7 +799,7 @@ export async function takeCensus(root, opts) {
770
799
  * for the result, which is the "valid and unread" failure this pipeline has
771
800
  * paid for twice already.
772
801
  */
773
- const v = transcribeVariants(src, declaredValues);
802
+ const v = transcribeVariants(src, declaredValues, globalClasses);
774
803
  /**
775
804
  * THE RESTING OPTION, from the definition itself. `cva` says it in
776
805
  * `defaultVariants`; a lookup-record component says it in the destructuring -
@@ -1025,9 +1054,25 @@ export async function takeCensus(root, opts) {
1025
1054
  if (size > 0 || tag) {
1026
1055
  looks[found[0].name] = {
1027
1056
  ...(sketch.length > 0 ? { sketch } : {}),
1028
- /** Ver `CensusLook.aside`: o fato viaja, a regra fica de um lado só. */
1029
- ...(classifyAside(found[0].name)
1030
- ? { aside: classifyAside(found[0].name) }
1057
+ /**
1058
+ * Ver `CensusLook.aside`: o fato viaja, a regra fica de um lado só.
1059
+ *
1060
+ * AS DUAS FONTES DE EIXO, e é a mesma decisão do autor nas duas. `found[0].axes` é o que
1061
+ * o TIPO declara (`interface ToastIconProps { tone?: "gold" | "cool" }`); `v.axes` é o
1062
+ * que a TABELA declara (`cva({ variants: … })`). Consultar só a segunda pôs de lado um
1063
+ * componente que pinta por `Record<tone, gradiente>` - forma que a tabela não vê.
1064
+ * Ver `classifyAside`.
1065
+ */
1066
+ ...(classifyAside({
1067
+ name: found[0].name,
1068
+ declaredAxes: { ...found[0].axes, ...v.axes },
1069
+ })
1070
+ ? {
1071
+ aside: classifyAside({
1072
+ name: found[0].name,
1073
+ declaredAxes: { ...found[0].axes, ...v.axes },
1074
+ }),
1075
+ }
1031
1076
  : {}),
1032
1077
  ...withoutDark,
1033
1078
  base,
@@ -1842,7 +1887,14 @@ export async function takeCensus(root, opts) {
1842
1887
  list.push(kebab(pair.child));
1843
1888
  companionsOf.set(pair.component, list);
1844
1889
  }
1845
- const components = merged.map((c) => ({
1890
+ /**
1891
+ * `Modal.Header` E `ModalHeader` CONTADOS UMA VEZ - ver `mergeNamespacePairs`.
1892
+ *
1893
+ * Antes do enriquecimento porque veredito, leis e companheiros são indexados por NOME: fundir
1894
+ * depois deixaria metade do que se sabe sobre a peça pendurado no nome que deixou de existir.
1895
+ */
1896
+ const paired = mergeNamespacePairs(merged, new Set(defined.map((d) => d.name)));
1897
+ const components = paired.map((c) => ({
1846
1898
  ...c,
1847
1899
  ...(verdicts.get(idOf(c)) ?? {}),
1848
1900
  ...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
@@ -2049,6 +2101,25 @@ export async function takeCensus(root, opts) {
2049
2101
  }
2050
2102
  : {}),
2051
2103
  ...(conventions.length > 0 ? { conventions } : {}),
2104
+ /**
2105
+ * AS CLASSES DA FOLHA GLOBAL DELE - ver `Census.globalClasses`.
2106
+ *
2107
+ * TODAS AS QUE A FOLHA DECLARA, e não só as que a raiz vestiu. A primeira tentativa levou apenas
2108
+ * as reivindicadas por `globalWear`, que só roda na raiz do componente - e as utilities usadas
2109
+ * numa tabela de variantes ou num nó filho, que são a maioria, ficavam de fora justamente do
2110
+ * campo que existe para alcançá-las. Duas de 24 chegavam.
2111
+ *
2112
+ * O CUSTO ESTÁ MEDIDO: a folha do `codelevel-ui` declara 24 classes, a nossa 22, e o
2113
+ * `apps/web-dashboard` do repo real declara 0 - ele é CSS Modules. Nas três populações isso é
2114
+ * ruído no tamanho do censo. O teto de `MAX_GLOBAL_CLASSES` existe para a folha de um app que
2115
+ * escreva utilitário por atacado, e quando ele corta, o número cortado é DITO em vez de
2116
+ * desaparecer.
2117
+ */
2118
+ ...(globalClasses.size > 0
2119
+ ? {
2120
+ globalClasses: Object.fromEntries([...globalClasses.entries()].slice(0, MAX_GLOBAL_CLASSES)),
2121
+ }
2122
+ : {}),
2052
2123
  classStyle,
2053
2124
  ...(Object.keys(looks).length > 0 ? { looks } : {}),
2054
2125
  ...(naming.components > 0 ? { naming } : {}),
@@ -2848,6 +2919,23 @@ export function ladderReach(declared) {
2848
2919
  }
2849
2920
  return { light, dark };
2850
2921
  }
2922
+ /**
2923
+ * QUEM DÁ NOME AO SISTEMA, e o cliente para de ganhar um sistema morto.
2924
+ *
2925
+ * O QUE ELE VIVEU EM 23/08: respondeu "CodeLevel" à pergunta do nome, o agente escreveu isso em
2926
+ * `reading.name` - o campo certo -, e o sistema nasceu como `repo-ui`, tirado do `@repo/ui` do
2927
+ * `package.json`. O slug não muda depois de criado, então corrigir custou um segundo import e ele
2928
+ * ficou com dois sistemas para uma biblioteca, um deles morto, e nenhum caminho de volta que não
2929
+ * seja apagar à mão.
2930
+ *
2931
+ * A ORDEM É A MESMA DO `scheme`, dez linhas abaixo de onde isto é chamado: a flag é uma decisão
2932
+ * digitada agora, a leitura é uma resposta que alguém já deu, e a pergunta é o último recurso.
2933
+ * `null` quer dizer "ninguém disse" - o ÚNICO caso em que se pergunta - e é o que fecha a porta:
2934
+ * não há como esta função devolver um palpite quando existe uma resposta.
2935
+ */
2936
+ export function chosenName(input) {
2937
+ return input.flag?.trim() || input.read?.trim() || null;
2938
+ }
2851
2939
  export function deriveName(pkgName, dirName) {
2852
2940
  const raw = (pkgName ?? "").trim();
2853
2941
  const scoped = /^@([^/]+)\/(.+)$/.exec(raw);
@@ -3127,6 +3215,16 @@ export function rootBehaviour(tag, sketch) {
3127
3215
  export async function resolveReadParts(census, root,
3128
3216
  /** `true` numa re-medição: o resumo desta etapa fica para o `sync --full` - ver `say`. */
3129
3217
  quiet = false) {
3218
+ /**
3219
+ * AS CLASSES DA FOLHA GLOBAL DELE, DO CENSO - ver `Census.globalClasses`.
3220
+ *
3221
+ * Esta etapa roda depois da medição, e também no `sync` e do lado da plataforma, sobre censo já
3222
+ * guardado - então ela não tem como voltar à folha no disco. Vindo do censo, uma utility que ele
3223
+ * escreve num NÓ FILHO chega ao look: o `LeaderboardRow` põe `text-grad-xp` num `<span>` dentro de
3224
+ * um ternário, e é aqui que aquele nó é lido.
3225
+ */
3226
+ const globalClasses = new Map(Object.entries(census
3227
+ .globalClasses ?? {}));
3130
3228
  const say = quiet
3131
3229
  ? () => { }
3132
3230
  : (line) => {
@@ -3239,13 +3337,13 @@ quiet = false) {
3239
3337
  ? resolveAnatomy(framed, declared, deps, resolveRef, entry?.root,
3240
3338
  // The component's own sketch, so a node naming itself by index reads
3241
3339
  // the exact class string the census measured (dono, 01/08).
3242
- looks[component]?.sketch, census.sxSpacing)
3340
+ looks[component]?.sketch, census.sxSpacing, globalClasses)
3243
3341
  : patched
3244
- ? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
3342
+ ? resolveAnatomy(patched.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
3245
3343
  : derived && derived.read.length > 0
3246
- ? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing)
3344
+ ? resolveAnatomy(derived.read, declared, deps, resolveRef, entry?.root, looks[component]?.sketch, census.sxSpacing, globalClasses)
3247
3345
  : Array.isArray(entry?.parts) && entry.parts.length > 0
3248
- ? resolveFlatParts(entry.parts, declared)
3346
+ ? resolveFlatParts(entry.parts, declared, globalClasses)
3249
3347
  : null;
3250
3348
  if (!resolved)
3251
3349
  continue;
@@ -3660,6 +3758,23 @@ export async function runImport(opts) {
3660
3758
  const out = opts.census ?? join(root, "_synthesisui", "census.json");
3661
3759
  await mkdir(dirname(out), { recursive: true });
3662
3760
  await writeFile(out, `${JSON.stringify(census, null, 2)}\n`, "utf8");
3761
+ /**
3762
+ * O QUE NÃO CHEGOU, DECLARADO PELA ESTEIRA - ver `notExpressed`.
3763
+ *
3764
+ * Escrito ao lado do censo e na mesma rodada, porque a lacuna e a medição são a mesma verdade
3765
+ * vista dos dois lados. Era o agente que escrevia este arquivo, instruído por uma frase dentro da
3766
+ * descrição de uma tool - e em 23/08 o mesmo repositório foi importado duas vezes, a primeira
3767
+ * declarando 37 linhas de lacuna e a segunda nenhuma, com as lacunas todas no lugar. Um pedido a
3768
+ * um modelo não é um artefato.
3769
+ *
3770
+ * O agente ainda ACRESCENTA o que só ele vê. O piso deixou de depender dele.
3771
+ */
3772
+ const gaps = notExpressed(census);
3773
+ if (gaps) {
3774
+ const gapFile = join(dirname(out), "not-expressed.md");
3775
+ await writeFile(gapFile, gaps, "utf8");
3776
+ console.log(body(`Gaps written to ${paint.strong(relative(root, gapFile))}`));
3777
+ }
3663
3778
  console.log("");
3664
3779
  console.log(body(`Written to ${paint.strong(relative(root, out))}`));
3665
3780
  if (opts.dry) {
@@ -3671,7 +3786,8 @@ export async function runImport(opts) {
3671
3786
  return;
3672
3787
  }
3673
3788
  const suggested = deriveName(census.project.name, basename(root) || "system");
3674
- const chosen = opts.name?.trim() || (await askName(suggested));
3789
+ const chosen = chosenName({ flag: opts.name, read: census.reading?.name }) ??
3790
+ (await askName(suggested));
3675
3791
  const reach = ladderReach(census.declared);
3676
3792
  // An explicit flag is a decision already made. Otherwise the person answers,
3677
3793
  // starting from the agent's reading if there is one and from the ladder's own
@@ -19,6 +19,8 @@
19
19
  */
20
20
  import { frontierKind } from "../frontier-kind.js";
21
21
  import { importMap } from "./imports.js";
22
+ /** Quantos lugares viajam por referência quebrada. Ver `BrokenRef.at`. */
23
+ const MAX_PLACES = 3;
22
24
  /** `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`. */
23
25
  const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
24
26
  /**
@@ -96,9 +98,20 @@ export function findBrokenRefs(sources, declared) {
96
98
  continue;
97
99
  if (runtime && (RUNTIME_ANCHOR.has(name) || RUNTIME_PREFIX.test(name)))
98
100
  continue;
99
- const hit = seen.get(name) ?? { count: 0, files: new Set() };
101
+ const hit = seen.get(name) ?? {
102
+ count: 0,
103
+ files: new Set(),
104
+ at: [],
105
+ };
100
106
  hit.count += 1;
101
107
  hit.files.add(file);
108
+ /** A linha contada do índice do match - ver `BrokenRef.at` para o teto. */
109
+ if (hit.at.length < MAX_PLACES) {
110
+ hit.at.push({
111
+ file,
112
+ line: source.slice(0, m.index ?? 0).split("\n").length,
113
+ });
114
+ }
102
115
  seen.set(name, hit);
103
116
  }
104
117
  }
@@ -111,6 +124,7 @@ export function findBrokenRefs(sources, declared) {
111
124
  count: hit.count,
112
125
  files: hit.files.size,
113
126
  ...(meant ? { meant } : {}),
127
+ ...(hit.at.length > 0 ? { at: hit.at } : {}),
114
128
  });
115
129
  }
116
130
  // A broken reference somebody typed nine times is worse than one typed once,
@@ -61,6 +61,8 @@ const PROVIDER_SUFFIX = /(Provider|Providers|Context|Boundary|Guard|Wrapper)$/;
61
61
  /** Any JSX at all in the file. A capitalised export with none is a constant, a
62
62
  * config object or a helper - `export const ROUTES = {…}` is not a component. */
63
63
  const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
64
+ /** `ICON_SIZE`, `AURORA_PALETTES`, `ARTICLE_TAB` - a convenção universal para valor, não para peça. */
65
+ const SCREAMING_SNAKE = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$/;
64
66
  /**
65
67
  * Any sign that this file decides how something LOOKS.
66
68
  *
@@ -71,7 +73,30 @@ const HAS_JSX = /<[A-Za-z][^>]*>|<>/;
71
73
  const HAS_STYLING = /className|class=|style=|styled[.(]|css`|cva\(|\btv\(|makeStyles|sx=|tw`/;
72
74
  /** `createContext` + a `.Provider` in the return is a provider whatever it is
73
75
  * called - `RootWrapper` and `CommonProviders` both read this way. */
74
- const IS_CONTEXT = /createContext\s*[<(]/;
76
+ /**
77
+ * QUEM RECEBE O `createContext` - o nome, e não o arquivo em volta dele.
78
+ *
79
+ * O QUE O CLIENTE PERDIA: o `Tabs` inteiro. `Tabs.tsx` declara `const TabsCtx = createContext(…)`, e
80
+ * a pergunta era se o ARQUIVO tem contexto - então `Tabs`, `TabsList`, `TabsTrigger` e `TabsPanel`
81
+ * saíram juntos, com `cn("flex flex-col gap-4")` escrito neles (`codelevel-ui`, 23/08). Composto com
82
+ * contexto é o padrão dominante de React - Tabs, Accordion, Select, Dropdown -, então recusar o
83
+ * arquivo recusa a família toda.
84
+ *
85
+ * É a mesma falha de forma que `HAS_JSX` já cometeu do outro lado: uma pergunta sobre o arquivo
86
+ * respondendo sobre o export. Um provider é UM nome; os vizinhos respondem por si.
87
+ *
88
+ * Medido em três populações - `codelevel-ui` recupera 4, o dashboard do `frontend-hub` recupera 7
89
+ * (o `Pagination` inteiro), e os 6 que terminam em `Provider` continuam fora pelo sufixo.
90
+ */
91
+ const CONTEXT_OWNER = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*(?::[^=]*)?=\s*(?:React\.)?createContext\s*[<(]/g;
92
+ /** Os nomes que ESTE arquivo atribui a um `createContext`. */
93
+ function contextNames(source) {
94
+ CONTEXT_OWNER.lastIndex = 0;
95
+ const out = new Set();
96
+ for (const m of source.matchAll(CONTEXT_OWNER))
97
+ out.add(m[1]);
98
+ return out;
99
+ }
75
100
  /** A lowercase JSX tag: an html element this file renders ITSELF. */
76
101
  const OWN_ELEMENT = /<(?:[a-z][a-z0-9]*)(\s|>|\/)/;
77
102
  /** Every capitalised JSX tag the file opens, dots included. */
@@ -205,7 +230,7 @@ export function gateComponent(input) {
205
230
  because: `\`${name}\` names a screen, and composes ${shape.composes} piece${shape.composes === 1 ? "" : "s"} across ${shape.nodes} element${shape.nodes === 1 ? "" : "s"} - too thin to be a template of yours. A design system is made of the pieces a screen is built FROM`,
206
231
  };
207
232
  }
208
- if (PROVIDER_SUFFIX.test(name) || IS_CONTEXT.test(source)) {
233
+ if (PROVIDER_SUFFIX.test(name) || contextNames(source).has(name)) {
209
234
  return {
210
235
  ok: false,
211
236
  why: "provider",
@@ -232,6 +257,38 @@ export function gateComponent(input) {
232
257
  */
233
258
  if (STYLED_FACTORY.test(source))
234
259
  return { ok: true };
260
+ /**
261
+ * UMA CONSTANTE NÃO É COMPONENTE, e o arquivo em volta dela não muda isso.
262
+ *
263
+ * O QUE O CLIENTE VIA: `ICON_SIZE` e `AURORA_PALETTES` ocupando vaga na vitrine do
264
+ * `codelevel-ui`, e uma delas com forma desenhada - um mapa de tamanhos renderizado como se fosse
265
+ * peça. Ele contava 59 componentes no design system dele e dois não existiam.
266
+ *
267
+ * O teste de markup logo abaixo já recusa constante, e ele pergunta pelo ARQUIVO INTEIRO:
268
+ * `ICON_SIZE` mora dentro do `Icon.tsx`, que tem JSX de sobra, então ela entrava pela porta do
269
+ * vizinho. Vem ANTES dele por isso - é a mesma falha de forma que o teste de contexto acima
270
+ * corrigiu do outro lado.
271
+ *
272
+ * MEDIDO EM QUATRO POPULAÇÕES, e as três encontradas são objeto literal no código real:
273
+ *
274
+ * codelevel-ui packages/ui 75 nomes · 2 (ICON_SIZE, AURORA_PALETTES)
275
+ * frontend-hub packages/ui 69 nomes · 0
276
+ * frontend-hub apps/web-dashboard 526 nomes · 1 (ARTICLE_TAB, um enum de aba)
277
+ * synthesisui apps/web · 0
278
+ *
279
+ * Nenhum componente das quatro usa este formato de nome - JSX escreve `<Button>` e nunca
280
+ * `<ICON_SIZE>` -, então a regra não recusa peça de ninguém. E ela só pôde entrar junto com o
281
+ * leitor de `export default` em linha própria: sozinha, ela deixava órfãs as 294 declarações de
282
+ * CSS Modules do único arquivo do dashboard onde a constante era a ÚNICA coisa que o leitor via.
283
+ * O dono daquele arquivo é o `CurateContentSection`, que agora é lido.
284
+ */
285
+ if (SCREAMING_SNAKE.test(name)) {
286
+ return {
287
+ ok: false,
288
+ why: "config",
289
+ because: `\`${name}\` is spelled as a constant - a value the code reads, not a component anything renders`,
290
+ };
291
+ }
235
292
  if (!HAS_JSX.test(source)) {
236
293
  return {
237
294
  ok: false,
@@ -293,10 +293,36 @@ export function axisOverlap(a, b) {
293
293
  */
294
294
  const IS_ICON = /(^Icon|Icon$)/;
295
295
  const IS_PROVIDER = /(Provider|Context)$/;
296
- export function classifyAside(name) {
297
- if (IS_ICON.test(name))
296
+ /**
297
+ * O QUE O CLIENTE GANHA: o componente dele para de desaparecer por causa do nome.
298
+ *
299
+ * Um glifo é modelado como biblioteca de ícones e não como receita - decisão de produto, e ela
300
+ * continua valendo. O que mudou é QUEM decide. O nome decidia sozinho, e em 23/08 isso custou o
301
+ * `IconButton` do `codelevel-ui`: três eixos declarados (`glow`, `size`, `variant`), a forma
302
+ * aninhada já derivada, e nenhuma linha no design system dele. Quem abrisse o Studio pediria um
303
+ * botão de ícone e ouviria que o sistema não tem - com o componente pronto no código.
304
+ *
305
+ * A lei 13 diz que a origem decide e não o nome, e um regex sobre o nome é exatamente o caso
306
+ * especial que ela proíbe. O discriminante estava no dado, e foi medido nas DUAS populações:
307
+ *
308
+ * frontend-hub 43 nomes casam o regex · 0 declaram eixos
309
+ * codelevel-ui 3 nomes casam o regex · 2 declaram eixos
310
+ *
311
+ * UM GLIFO NÃO DECLARA VARIAÇÃO - é um desenho, e desenho não tem `variant`. Quem declara eixos
312
+ * escreveu um contrato, e contrato é receita. Nenhum dos 43 do repositório real muda de lado, então
313
+ * a regra não afrouxou: ela passou a perguntar a coisa certa.
314
+ *
315
+ * `props` NÃO serve para isto, e a diferença é o caso do `Icon` dele: recebe `size` e `color` no uso
316
+ * e não declara nem um nem outro. Uso é o que alguém escolheu; declaração é o que o autor desenhou.
317
+ *
318
+ * A FORMA VEM INTEIRA, e não como segundo parâmetro opcional (§6): um argumento que pode ser
319
+ * esquecido é o defeito que este conserto está desfazendo, e não há como chamar isto pela metade.
320
+ */
321
+ export function classifyAside(shape) {
322
+ const declares = Object.keys(shape.declaredAxes ?? {}).length > 0;
323
+ if (IS_ICON.test(shape.name) && !declares)
298
324
  return "icon";
299
- if (IS_PROVIDER.test(name))
325
+ if (IS_PROVIDER.test(shape.name))
300
326
  return "provider";
301
327
  return null;
302
328
  }
@@ -429,7 +455,10 @@ opts) {
429
455
  }
430
456
  return mine
431
457
  .map((component) => {
432
- const aside = classifyAside(component.name);
458
+ const aside = classifyAside({
459
+ name: component.name,
460
+ declaredAxes: component.declaredAxes,
461
+ });
433
462
  if (aside) {
434
463
  return {
435
464
  component,