synthesisui 0.16.421 → 0.16.423

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.
@@ -12,25 +12,16 @@ import { appendEvent, COVERAGE_RULE, COVERAGE_RULE_REASON, readEvents, suggestio
12
12
  import { bindingsFromDocument, countComponents, findOverrides, } from "../doctor/overrides.js";
13
13
  import { checkableName, readRequests, verifyAndCloseRequests, } from "../doctor/requests.js";
14
14
  import { DEFAULT_ROOT_PX, rootSizeOf, saidOfRoot, } from "../doctor/root-size.js";
15
- import { diagnose, nameToWrite, scanSource, siblingTokens, } from "../doctor/scan.js";
15
+ import { diagnose, nameToWrite, ourNameOnly, scanSource, siblingTokens, } from "../doctor/scan.js";
16
16
  import { findSelfConflicts, forbiddenProps, isReset, propMatchesLabel, } from "../doctor/self-conflict.js";
17
17
  import { withTheirNames } from "../doctor/their-names.js";
18
18
  import { buildTable, EMPTY_TABLE, nearestToken, } from "../doctor/tokens.js";
19
19
  import { parseRules } from "../doctrine.js";
20
- /**
21
- * `detectAppDirs` SAIU DESTE IMPORT em 09/09: quem pergunta "quais pastas são app?" agora é
22
- * `readWiringPerApp`, que responde a fiação de cada uma. O comando não escolhe mais um app.
23
- */
24
- import { globalSheetOf, prefixFrom } from "../global-sheet.js";
25
20
  import { groupRole } from "../group-role.js";
26
21
  import { actingSlug, describeScope, measuredScope, scopePaths, } from "../measured-scope.js";
27
22
  import { body, paint, section, snippet } from "../output.js";
28
- import { setupPrompt } from "../setup-prompt.js";
29
- import { installedThemeCss, whatOnlyTheSheetResolves, } from "../sheet-needed.js";
30
23
  import { resolveDeps } from "../stack.js";
31
- import { projectTongue } from "../their-tongue.js";
32
24
  import { danglingTheirVars } from "../their-vars.js";
33
- import { readWiring, readWiringPerApp } from "../wiring-read.js";
34
25
  /**
35
26
  * `synthesisui doctor` - the check nobody else ships.
36
27
  *
@@ -617,17 +608,6 @@ async function runFix(root, d, writing) {
617
608
  console.log(body(paint.dim("Read the diff before committing - it is your code, and this replaced literals with the tokens your own system declares.")));
618
609
  }
619
610
  }
620
- /**
621
- * O NOME QUE ESTA TROCA ESCREVERIA É DELE? - a pergunta que a recusa do `--fix` nunca fez.
622
- *
623
- * `nameToWrite` devolve `theirToken ?? token`: quando o repositório dele nomeia aquele valor, é o
624
- * nome DELE que vai para o código. Um nome dele resolve no navegador dele HOJE, com ou sem a nossa
625
- * folha instalada - a folha só é necessária para os `--ds-*`, que são nossos.
626
- */
627
- const ownName = (f) => {
628
- const name = nameToWrite(f);
629
- return Boolean(name && !name.startsWith("--ds-"));
630
- };
631
611
  export async function doctor(opts) {
632
612
  const root = resolve(opts.dir ?? process.cwd());
633
613
  /**
@@ -743,15 +723,6 @@ export async function doctor(opts) {
743
723
  *
744
724
  * Nobody debugs from 0%. They conclude the product does not work.
745
725
  */
746
- const wiring = await readWiring(root, table.slug);
747
- /**
748
- * O QUE, NESTE REPOSITÓRIO, SÓ A NOSSA FOLHA RESOLVE - acumulado na varredura QUE JÁ ACONTECE.
749
- *
750
- * Uma segunda passada pelo disco para responder isto custaria o dobro num repositório de 178
751
- * arquivos, e a resposta está no mesmo texto que o scan já tem na mão. Ver `sheet-needed.ts`.
752
- */
753
- const themeCss = table.slug ? await installedThemeCss(root, table.slug) : "";
754
- let scannedSource = "";
755
726
  const skippedProjects = [];
756
727
  const tally = emptyTally();
757
728
  const internalSpecs = await internalSpecifiers(root);
@@ -763,9 +734,6 @@ export async function doctor(opts) {
763
734
  continue;
764
735
  const rel = relative(root, file);
765
736
  reports.push(scanSource(rel, src, table));
766
- /** A pasta que a plataforma escreve não conta: ela É a folha, não um consumidor dela. */
767
- if (!rel.startsWith("_synthesisui"))
768
- scannedSource += `\n${src}`;
769
737
  // Composition, alongside values: the contract check needs to know which
770
738
  // elements this file writes and with what options. Stories and tests are
771
739
  // excluded for the reason they are excluded everywhere else - they compose
@@ -916,304 +884,28 @@ export async function doctor(opts) {
916
884
  * distinção em vez de cada tela refazê-la. Ver `Reach` em `scan.ts`.
917
885
  */
918
886
  const measurable = d.reach.measured;
919
- // Before the number, because the number is the thing that misleads. An
920
- // installed-but-unwired system reads 0%, and 0% reads as "broken product"
921
- // rather than "one import missing".
922
- // Fonts only count as missing when `init` actually wrote the file - a
923
- // non-Next project has no fonts.ts and must not be nagged about one.
924
- const fontsPending = wiring.fontsWritten && !wiring.fontsMapped;
925
887
  /**
926
- * A FIAÇÃO É PORTÃO, e não um aviso ao lado de um número.
888
+ * A FIAÇÃO SAIU DAQUI EM 11/09, e com ela o portão inteiro - `INV-GERAL-13`.
927
889
  *
928
- * Sem o `@import` ou sem o `data-ds`, os tokens não chegam ao navegador. Até 06/08 a tela dizia isso
929
- * e, três linhas abaixo, imprimia uma barra de 0% e uma lista de cinco prioridades - se contradizendo
930
- * na mesma tela (dono: "fiquei perdido"). Ou o número não significa nada, ou existe uma lista de
931
- * prioridades; as duas não cabem juntas.
932
- */
933
- /**
934
- * A FIAÇÃO SÓ EXISTE PARA UM SISTEMA NOSSO - e usar "tem nomes" no lugar disso recusava o
935
- * `--fix` a quem nunca instalou nada (medido em 18/08, no caminho de quem começa do zero).
936
- *
937
- * Ela escreve os tokens dela no próprio `:root`, o doctor passa a dizer "7 dos seus valores
938
- * têm nome", e o `--fix` respondia "trocar agora apontaria para variáveis que o navegador não
939
- * resolve". Ele resolve: o CSS é dela e já está na página. A frase era literalmente falsa, e
940
- * ela aparece no primeiro comando que promete adotar.
941
- *
942
- * `table.source` é a distinção certa e ela já estava aqui, uma linha abaixo, em `unwired`:
943
- * `"yours"` e `"adopted"` são o vocabulário dela, e não há duas linhas para ligar.
944
- */
945
- const sheetNeed = whatOnlyTheSheetResolves({
946
- source: scannedSource,
947
- themeCss,
948
- theirNames: installed.theirs.byName.keys(),
949
- /**
950
- * A TERCEIRA FONTE NÃO É MEDIDA AQUI - ver o mesmo comentário em `component.ts` e o número em
951
- * `steps/11-fora-do-escopo.md`. Este comando decide `unwired` e `blocked` por `needed`, então
952
- * ligar a medição muda o veredito do `doctor` num repositório real: é etapa própria, e o vazio
953
- * deixa a omissão escrita em vez de escondida.
954
- */
955
- sheetCss: "",
956
- });
957
- /**
958
- * BLOQUEADO SÓ QUANDO A FOLHA É NECESSÁRIA - a mesma pergunta que decide `unwired`.
890
+ * O QUE ESTE TRECHO FAZIA, em 250 linhas: media se algo neste repositório ainda precisava da nossa
891
+ * folha, imprimia *"<sistema> is installed - but this project does not load it yet"*, listava os
892
+ * `✗` de `@import` e `data-ds`, colava um prompt de setup para o agente dele, e RECUSAVA o
893
+ * `--fix` enquanto aquilo não fosse feito.
959
894
  *
960
- * Sem esta condição, o comando parava de cobrar a fiação lá em cima e continuava recusando o
961
- * `--fix` aqui embaixo, fechando com *"esse número fica acionável no momento em que as duas forem
962
- * verdade"* - duas condições que ele mesmo tinha acabado de deixar de pedir. Um relatório que
963
- * retira a exigência numa seção e a mantém na outra ensina que nenhuma das duas é para valer.
964
- */
965
- const blocked = table.source === "installed" &&
966
- sheetNeed.needed &&
967
- (!wiring.imported || !wiring.scoped);
968
- /**
969
- * QUANTAS DAS DUAS FALTAM - e sem este número três frases mandavam consertar DUAS coisas quando
970
- * faltava UMA.
895
+ * O QUE O DONO LEU NA PRÓPRIA TELA (11/09): *"isso nunca deve acontecer, a gente havia combinado
896
+ * (...) garanta que nunca mais você irá falar desse import e nem solicitar e nem precisar nos
897
+ * projetos"*.
971
898
  *
972
- * `blocked` é um OU, e as frases que ele governa foram escritas assumindo o E: *"Until both are
973
- * true"*, *"--fix is refused while those two are false"* e *"Wire the two lines first"*. Visto na
974
- * máquina do dono em 04/09, num repositório onde `data-ds` já estava lá com um ✓ impresso três
975
- * linhas acima: a tela contradizia a própria lista, e a terceira frase é INSTRUÇÃO - a pessoa está
976
- * bloqueada, procurando o que escrever, e ela manda escrever duas linhas.
899
+ * E A PERGUNTA MORREU COM A CAUSA, não com o texto. Nada que a plataforma escreve no repositório
900
+ * dele carrega uma variável nossa (`their-tongue.ts`) nem um utilitário que dependa do nosso
901
+ * `@theme` (`theirThemeVars`), e o `--fix` só escreve nomes que o código dele declara
902
+ * (`nameToWrite`). Então não sobra nada para uma folha nossa resolver: não há fiação a medir, não
903
+ * há o que cobrar, e não há por que recusar o `--fix`.
977
904
  *
978
- * O caso de faltar UMA é o mais comum: uma instalação que parou no meio erra um dos dois passos,
979
- * não os dois. Derivado da mesma leitura que decide `blocked`, para as duas nunca discordarem.
905
+ * A RECUSA ERA O PIOR PEDAÇO. Medido no `codelevel` em 04/09: das 216 trocas com nome esperando,
906
+ * 156 (72%) escreviam o nome DELE e resolviam no navegador dele naquele dia - e o comando recusava
907
+ * as 156 para proteger as 60 que apontavam para nós. Essas 60 não existem mais.
980
908
  */
981
- const missingWiring = (wiring.imported ? 0 : 1) + (wiring.scoped ? 0 : 1);
982
- const bothMissing = missingWiring === 2;
983
- /**
984
- * A FIAÇÃO SÓ É COBRADA QUANDO ALGUMA COISA NESTE REPOSITÓRIO PRECISA DELA - ver `sheet-needed.ts`.
985
- *
986
- * O DEFEITO, apontado pelo dono em 07/09 sobre o próprio repositório: *"por que a gente precisa
987
- * desses imports, visto que o design do projeto dele já funciona? criar esse import gera
988
- * dependência ao design system, que é coisa que a gente não quer - a gente quer isolar os dois
989
- * pontos, sendo que o nosso deve ser apenas uma REFERÊNCIA para que qualquer agente que gera
990
- * código saiba como utilizar o design system"*.
991
- *
992
- * MEDIDO NO `codelevel` no mesmo dia: no repositório inteiro, apenas dois arquivos mencionam
993
- * `--ds-*` - um componente que a PLATAFORMA gerou com uma versão anterior à tradução, e a costura
994
- * de tipografia que o nosso próprio setup pediu. Zero linhas do código que ELE escreveu. Regerado
995
- * com o CLI de hoje, o componente sai com 0 referências à nossa folha, e as duas animações que ele
996
- * veste são declaradas pelo `globals.css` dele.
997
- *
998
- * Ou seja: o comando cobrava uma dependência de runtime que nada naquele repositório usava - e o
999
- * produto já promete o contrário em `their-tongue.ts` (*"nenhuma folha nossa precisa existir no
1000
- * repositório dele"*). As duas metades discordavam porque nenhuma perguntava.
1001
- *
1002
- * O ESCOPO (`data-ds`) SEGUE A MESMA REGRA e pelo mesmo motivo: ele existe para a folha aplicar
1003
- * dentro dele. Sem folha necessária, um atributo na árvore dele é a mesma dependência com outro
1004
- * nome.
1005
- *
1006
- * E QUANDO ALGO PRECISA, tudo continua exatamente como era - inclusive o roteiro para o agente. A
1007
- * mudança não é deixar de cobrar: é parar de cobrar sem ter perguntado.
1008
- */
1009
- const unwired = table.source === "installed" &&
1010
- sheetNeed.needed &&
1011
- (!wiring.imported || !wiring.scoped || fontsPending);
1012
- if (unwired) {
1013
- console.log("");
1014
- console.log(body(`${table.name ?? table.slug} is installed - but this project does not load it yet.`));
1015
- console.log("");
1016
- /**
1017
- * O QUE ESTÁ COBRANDO A FOLHA, POR NOME - senão a exigência lê como uma regra nossa.
1018
- *
1019
- * Ela não é: é um efeito do que ESTE repositório escreveu. Medido no `codelevel` (07/09), as
1020
- * duas coisas que a exigiam eram um componente gerado por uma versão anterior à tradução do CLI
1021
- * e a costura de tipografia que o nosso próprio setup pediu - nenhuma linha do código dele.
1022
- * Regerado com o CLI de hoje, o mesmo componente sai com zero referências e a exigência some.
1023
- *
1024
- * Sem esta linha, quem lê não tem como saber que a dependência é REMOVÍVEL, e a única saída
1025
- * visível é aceitar o import.
1026
- */
1027
- /**
1028
- * E A SAÍDA SÓ É OFERECIDA QUANDO ELA EXISTE - senão o comando manda consertar o que não conserta.
1029
- *
1030
- * "re-run `component` and it speaks your own names instead" só é verdade onde há MAPA: onde algum
1031
- * valor do sistema coincide com um nome que o repositório dele já declara. Sem mapa, a tradução
1032
- * roda e não tem o que traduzir, regerar devolve o mesmo arquivo, e a folha é o caminho legítimo.
1033
- *
1034
- * MEDIDO EM 07/09, validando o binário publicado num `create-next-app` que instalou o
1035
- * `codelevel`: `.lock` sem `tokenMap`, o componente saiu com 14 linhas `--ds-*`, e esta frase
1036
- * mandava regerar. No `codelevel-monorepo`, onde o `.lock` tem 33 pares, regerar É a saída - e o
1037
- * mesmo texto servia os dois. É o defeito que o `CLAUDE.md` registra em primeira pessoa: afirmar
1038
- * que um comando conserta o que ele não conserta.
1039
- */
1040
- const translatable = (await projectTongue(root, table.slug ?? "")) !== null;
1041
- const asks = sheetNeed.variables.length > 0
1042
- ? `${sheetNeed.variables.slice(0, 3).join(", ")}${sheetNeed.variables.length > 3 ? `, +${sheetNeed.variables.length - 3} more` : ""}${translatable
1043
- ? ` - re-run \`component\` on whatever writes ${sheetNeed.variables.length === 1 ? "it" : "them"} and it speaks your own names instead`
1044
- : ` - your code names no value this system also holds, so there is nothing to translate ${sheetNeed.variables.length === 1 ? "it" : "them"} into, and the sheet is the way`}`
1045
- : `the ${sheetNeed.classes.slice(0, 3).join(", ")}${sheetNeed.classes.length > 3 ? `, +${sheetNeed.classes.length - 3} more` : ""} ${sheetNeed.classes.length === 1 ? "utility" : "utilities"}, which only this system's @theme generates`;
1046
- console.log(body(` what asks for it: ${asks}`));
1047
- console.log("");
1048
- console.log(body(wiring.imported
1049
- ? ` ✓ some stylesheet imports _synthesisui/ds/${table.slug}/tokens.css`
1050
- : ` ✗ no stylesheet imports _synthesisui/ds/${table.slug}/tokens.css`));
1051
- console.log(body(wiring.scoped
1052
- ? ` ✓ data-ds="${table.slug}" found`
1053
- : ` ✗ no element carries data-ds="${table.slug}"`));
1054
- if (wiring.fontsWritten) {
1055
- console.log(body(wiring.fontsMapped
1056
- ? " ✓ the type from fonts.ts is mapped onto the system"
1057
- : " ✗ fonts.ts exists but nothing maps it - the system's type is not being used"));
1058
- }
1059
- console.log("");
1060
- // Three requirements now, and they fail differently. Missing the import or
1061
- // the scope means NOTHING reaches the browser and the number below is
1062
- // noise. Missing only the type mapping means colour and spacing are
1063
- // working and the faces are not - saying "nothing reaches the browser"
1064
- // there would be false, and a report that overstates is one nobody trusts
1065
- // the next time.
1066
- if (blocked) {
1067
- /**
1068
- * A FRASE SEGUE O QUE VEM DEPOIS DELA. Ela dizia "o número abaixo não pode significar nada"
1069
- * enquanto imprimia o número abaixo; agora não há número abaixo, e prometer um seria a mesma
1070
- * incoerência ao contrário.
1071
- */
1072
- console.log(body(bothMissing
1073
- ? `Until both are true, none of the ${table.byName.size} tokens reach the browser.`
1074
- : `Until that one is true, none of the ${table.byName.size} tokens reach the browser.`));
1075
- }
1076
- else {
1077
- console.log(body("Colour and spacing are working. Type is not: the system's faces"));
1078
- console.log(body("are declared and nothing points at them, so the page renders in"));
1079
- console.log(body("whatever the framework picked."));
1080
- }
1081
- /**
1082
- * A FIAÇÃO SAI DAQUI, e não de outro comando - achado do dono em 06/09, rodando o `upgrade`.
1083
- *
1084
- * O QUE ELE VIU: o doctor diz `installed - but not wired up yet` e *"none of the 66 tokens
1085
- * reach the browser"*, e a linha seguinte mandava ler *"the output of `init`"*. `init` é o
1086
- * comando de PRIMEIRA VEZ - ele instala um sistema em quem não tem nenhum. Quem chegou até
1087
- * aqui já importou, materializou e rodou upgrade: mandá-lo ao `init` é devolver ao começo
1088
- * alguém que está a um passo do fim.
1089
- *
1090
- * E O PASSO JÁ EXISTIA PRONTO. `setupPrompt` mora em `setup-prompt.ts`, exportado, e era
1091
- * usado só pelo `init`. Imprimi-lo aqui não é texto novo: é a MESMA fonte chegando na tela
1092
- * onde a pergunta nasce. Duas redações do mesmo passo divergiriam no primeiro conserto.
1093
- *
1094
- * O PROMPT LEVA A MEDIÇÃO, e até 07/09 ele era FIXO - o defeito que o dono leu na própria tela.
1095
- *
1096
- * As três linhas acima acabaram de imprimir `✗ ✓ ✓`: só o import faltava. O texto seguinte
1097
- * mandava *"Do the ONE-TIME SETUP it names, all of it"* e listava as três, então um agente
1098
- * obediente reescreve o escopo e o mapa de tipografia que já estavam corretos - contra a última
1099
- * linha do próprio texto, que pede para não mexer nos estilos dele. Um comando que mede e depois
1100
- * diz outra coisa gasta a medição.
1101
- *
1102
- * E A FOLHA TEM NOME, ao contrário do que este comentário dizia antes. `globalSheetOf` +
1103
- * `prefixFrom` são as mesmas funções que o `add` usa para achar a folha que os apps REALMENTE
1104
- * carregam - num monorepo não é o `globals.css` do app - e para contar o caminho relativo até a
1105
- * raiz. Elas existiam e este comando não as chamava; então ele descrevia o arquivo em vez de
1106
- * nomeá-lo, e o agente tinha que redescobrir o que a gente já sabia.
1107
- *
1108
- * Quando a derivação falha, `sheet` fica ausente e o texto volta a descrever - nenhum caminho é
1109
- * inventado.
1110
- */
1111
- if (table.slug) {
1112
- /**
1113
- * A INSTRUÇÃO É POR APP, e não do primeiro deles.
1114
- *
1115
- * O DEFEITO: esta linha era `appDirs[0]` - num monorepo com três apps servidos, dois recebiam
1116
- * a instrução do PRIMEIRO, com a folha e o prefixo relativo de outro app. É o mesmo defeito
1117
- * que `INV-VOLTA-12` fechou uma vez ("ele seguiu a instrução no arquivo CERTO e recebeu de nós
1118
- * o prefixo do arquivo errado"), voltando pela porta do plural: a invariante fala de "os apps
1119
- * dele", e o comando respondia por um.
1120
- *
1121
- * E A FIAÇÃO TAMBÉM É POR APP (`readWiringPerApp`): `readWiring` varre a raiz e responde
1122
- * "existe em algum lugar daqui", então o app fiado respondia pelo que não estava - e a pessoa
1123
- * abria o app sem nada e o comando dizia que estava tudo certo.
1124
- *
1125
- * COM UM APP, o texto não muda em byte nenhum - é a maioria dos projetos, e um cabeçalho
1126
- * por app ali seria cerimônia sobre uma lista de um.
1127
- */
1128
- const perApp = await readWiringPerApp(root, table.slug, config.pagesDir);
1129
- const targets = perApp.length > 0 ? perApp : [{ app: null, wiring }];
1130
- /**
1131
- * OS APPS QUE PEDEM A MESMA COISA VIRAM UM BLOCO SÓ, nomeando os dois.
1132
- *
1133
- * O DEFEITO, e ele é meu, do PR de algumas horas atrás: no `codelevel-monorepo` os DOIS apps
1134
- * resolvem para a MESMA folha (`packages/ui/src/styles/globals.css`, o pacote compartilhado),
1135
- * então o comando imprimiu a mesma instrução de 40 linhas DUAS VEZES, com o mesmo caminho e o
1136
- * mesmo prefixo. O dono leu a parede duas vezes (medido no repositório dele, 09/09).
1137
- *
1138
- * Eu tinha escrito o aviso no comentário abaixo - *"num monorepo ele apareceria em escala:
1139
- * dois blocos idênticos"* - sobre o caso do app JÁ FIADO, e entreguei a duplicata no caso do
1140
- * que ainda falta. Nomear cada app foi o conserto certo; repetir a instrução não.
1141
- *
1142
- * A CHAVE É A INSTRUÇÃO, não o app: dois apps com a mesma folha, o mesmo prefixo e os mesmos
1143
- * passos pendentes recebem uma instrução só. Um monorepo onde cada app tem folha própria volta
1144
- * a receber uma por app, porque ali as instruções DIFEREM - é a resposta derivada, e não uma
1145
- * regra sobre quantos apps existem.
1146
- */
1147
- const blocks = new Map();
1148
- for (const { app, wiring: w } of targets) {
1149
- const sheet = app
1150
- ? await globalSheetOf(root, `${app}/globals.css`)
1151
- : null;
1152
- /**
1153
- * O APP QUE JÁ ESTÁ FIADO SAI DA LISTA, nomeado. Repetir o setup para quem já o fez é o
1154
- * defeito de 08/09 outra vez.
1155
- */
1156
- const pending = !w.imported || !w.scoped || (w.fontsWritten && !w.fontsMapped);
1157
- const text = pending
1158
- ? setupPrompt(table.slug, {
1159
- tokens: !w.imported,
1160
- scope: !w.scoped,
1161
- /** Só é passo quando o projeto TEM o arquivo de fontes - ver `readWiring`. */
1162
- type: w.fontsWritten && !w.fontsMapped,
1163
- ...(sheet
1164
- ? { sheet: { path: sheet, prefix: prefixFrom(sheet) } }
1165
- : {}),
1166
- /**
1167
- * DUAS LINHAS COM TAILWIND, UMA SEM - a mesma decisão que o `add` já toma pelo
1168
- * projeto. Dizer "as duas linhas" a um projeto que precisa de uma manda o agente
1169
- * procurar o que não existe.
1170
- */
1171
- imports: config.styles === "css" ? 1 : 2,
1172
- }).split("\n")
1173
- : [];
1174
- const key = `${pending ? "p" : "ok"}|${text.join("\n")}`;
1175
- const found = blocks.get(key);
1176
- if (found)
1177
- found.apps.push(app ?? "");
1178
- else
1179
- blocks.set(key, { apps: [app ?? ""], text, pending });
1180
- }
1181
- const anyPending = [...blocks.values()].some((b) => b.pending);
1182
- if (anyPending) {
1183
- console.log("");
1184
- console.log(body("Paste this into your agent - it does the setup for you:"));
1185
- }
1186
- for (const { apps, text, pending } of blocks.values()) {
1187
- const named = apps.filter(Boolean);
1188
- /**
1189
- * O CABEÇALHO SÓ APARECE QUANDO HÁ MAIS DE UM APP no projeto - com um só ele seria cerimônia
1190
- * sobre uma lista de um, e a maioria dos projetos é assim. Com vários, ele diz de QUEM é a
1191
- * instrução, e o plural sai naturalmente: "apps/landing/app and apps/web/app".
1192
- */
1193
- if (targets.length > 1 && named.length > 0) {
1194
- /**
1195
- * A LISTA SE LÊ EM VOZ ALTA, e o número dela não é fixo: "A and B" com dois,
1196
- * "A, B and C" com três. `join(" and ")` daria "A and B and C", e `"both"` mentiria
1197
- * no dia em que o monorepo tiver três apps na mesma folha - o mesmo erro de
1198
- * dimensionar pela amostra, na escala de uma palavra.
1199
- */
1200
- const lista = named.length > 1
1201
- ? `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`
1202
- : named[0];
1203
- console.log("");
1204
- console.log(body(` ${lista} - ${pending
1205
- ? named.length > 1
1206
- ? "need it, and the steps are the same"
1207
- : "still needs it"
1208
- : "already wired, nothing to paste"}`));
1209
- }
1210
- if (!pending)
1211
- continue;
1212
- console.log("");
1213
- console.log(snippet(text));
1214
- }
1215
- }
1216
- }
1217
909
  /**
1218
910
  * A FOLHA APONTA PARA O VOCABULÁRIO DELE, E UM NOME SUMIU - ver `their-vars.ts`.
1219
911
  *
@@ -1234,112 +926,26 @@ export async function doctor(opts) {
1234
926
  console.log(body("Rename it back, or run `npx synthesisui add <slug>` to re-point the sheet at what you have now."));
1235
927
  }
1236
928
  /**
1237
- * E AQUI O RELATÓRIO PARA, quando a fiação está vermelha: uma instrução, e nada mais.
929
+ * O RELATÓRIO NÃO PARA MAIS - o `return` daqui foi embora com a fiação (`INV-GERAL-13`, 11/09).
1238
930
  *
1239
- * O que fica de fora é justamente o que não pode ser verdade ainda - a barra de cobertura, a lista de
1240
- * prioridades e a oferta do `--fix`. O `--fix` é o mais grave: no repo do dono ele teria trocado 217
1241
- * literais por `var(--ds-…)` que não resolvem em lugar nenhum, e o navegador descarta a declaração -
1242
- * ou seja, o comando que promete adotar o sistema quebraria a aparência do app.
1243
- *
1244
- * O ledger é gravado ANTES de sair: a corrida aconteceu, e o histórico não pode depender de o
1245
- * projeto estar bem configurado.
931
+ * Ele existia porque, sem o `@import`, a barra de cobertura lia 0% e o `--fix` teria escrito
932
+ * `var(--ds-…)` que o navegador descarta. As duas razões acabaram: a cobertura conta o vocabulário
933
+ * dele, e o `--fix` só escreve nomes que o código dele declara. Um relatório que parava aqui hoje
934
+ * esconderia a cobertura de quem nunca vai importar folha nenhuma - ou seja, de todo mundo.
1246
935
  */
1247
- if (blocked) {
1248
- if (fullRun)
1249
- await appendEvent(root, {
1250
- kind: "doctor",
1251
- at: new Date().toISOString(),
1252
- ...(d.reach.measured ? { coverage: d.reach.percent } : {}),
1253
- rule: COVERAGE_RULE,
1254
- named: d.named,
1255
- ...(d.findings.some((f) => f.crossFamily)
1256
- ? { crossFamily: d.findings.filter((f) => f.crossFamily).length }
1257
- : {}),
1258
- ...(d.repeats.some((r) => nameToWrite(r))
1259
- ? { matched: suggestionsFrom(d.repeats) }
1260
- : {}),
1261
- /** SEMPRE, inclusive vazio - ver `CheckEvent.unrepresentedMatches`: o vazio é a medição. */
1262
- unrepresentedMatches: unrepresentedFrom(d.findings, suggestionsFrom(d.repeats)),
1263
- }).catch(() => { });
1264
- console.log("");
1265
- if (opts.fix) {
1266
- /**
1267
- * A RECUSA PASSA A SER POR VALOR, E NÃO PELO COMANDO INTEIRO.
1268
- *
1269
- * NASCEU DE UMA PERGUNTA DO DONO (04/09): *"se o design system dele já está funcionando e são
1270
- * as mesmas referências, faz sentido esse ligamento? combinamos que não seria invasivo"*.
1271
- *
1272
- * A MEDIÇÃO DEU RAZÃO A ELE, pelo mesmo caminho que a tela usa (`loadSystem` + `scanSource` +
1273
- * `diagnose` sobre packages/ui + apps/landing + apps/web do codelevel): dos 216 valores com
1274
- * nome esperando, **156 (72%) escreveriam o nome DELE** - `--color-brand-violet`,
1275
- * `--color-tier-gold`, `--duration-base` - e esses resolvem no navegador dele HOJE, sem folha
1276
- * nenhuma instalada. Só 60 apontam para `--ds-*`, que são nossos e precisam da fiação.
1277
- *
1278
- * A recusa olhava `blocked` e nada mais. Ela nunca perguntava de quem era o nome que ia
1279
- * escrever, embora `nameToWrite` já responda isso (`theirToken ?? token`). O produto recusava
1280
- * 156 consertos que funcionariam para proteger 60 que quebrariam - e cobrava, em troca, uma
1281
- * folha inteira dentro do app de alguém que só queria governar o próprio vocabulário.
1282
- *
1283
- * ISSO ATINGE O CASO DE USO CENTRAL: quem importa um design system que já existe tem, por
1284
- * construção, a maioria dos nomes sendo dele.
1285
- *
1286
- * O QUE FICA DE FORA CONTINUA SENDO DITO, com o número e o motivo - Lei 8. O que muda é que a
1287
- * lacuna deixou de fechar a porta inteira.
1288
- */
1289
- const theirs = d.findings.filter(ownName).length;
1290
- const ours = d.findings.filter((f) => nameToWrite(f) && !ownName(f)).length;
1291
- if (theirs > 0) {
1292
- console.log(body(`${theirs} of your hand-written values ${theirs === 1 ? "carries" : "carry"} a name your own code declares, and ${theirs === 1 ? "it resolves" : "those resolve"} today - --fix will swap ${theirs === 1 ? "it" : "them"}.`));
1293
- if (ours > 0)
1294
- console.log(body(`${ours} more point at ${table.name ?? table.slug} tokens, which only resolve once the wiring above is done. Left alone.`));
1295
- console.log("");
1296
- await runFix(root, { ...d, findings: d.findings.filter(ownName) }, opts.write === true);
1297
- return;
1298
- }
1299
- /** Nenhum nome dele em jogo: aqui a recusa continua sendo a resposta certa, e inteira. */
1300
- const one = ours === 1;
1301
- console.log(body(`--fix is refused while ${bothMissing ? "those two are" : "that one is"} false. ${ours} of your hand-written values`));
1302
- console.log(body(`${one ? "does" : "do"} have a name in the system, and swapping ${one ? "it" : "them"} now would point ${one ? "it" : "them"} at`));
1303
- console.log(body(`variables the browser cannot resolve - the ${one ? "declaration" : "declarations"} would be dropped`));
1304
- console.log(body(`and the page would change. Wire ${bothMissing ? "the two lines" : "that line"} first.`));
1305
- }
1306
- else {
1307
- console.log(body(`${d.named} of the ${d.findings.length} hand-written values found have a name waiting in`));
1308
- console.log(body("your system, and that number becomes actionable the moment both are true."));
1309
- }
1310
- return;
1311
- }
1312
936
  /**
1313
- * SHADCN IS THE MOST LIKELY THING ALREADY IN THE PROJECT, AND THE BRIDGE WAS
1314
- * INVISIBLE.
937
+ * A PONTE DO SHADCN NÃO SE PEDE MAIS - ela era um terceiro `@import` da nossa folha.
1315
938
  *
1316
- * Two states, and both are worth a line. Unimported is a real finding: the
1317
- * components a person spends all day looking at are on shadcn's defaults while
1318
- * everything around them wears the system, which reads as "this product does
1319
- * not work here" rather than "one import missing".
939
+ * O QUE ELA ENTREGAVA: `shadcn.css` mapeia o contrato de variáveis do shadcn nos tokens do sistema,
940
+ * então os componentes shadcn dele parariam de usar os defaults do shadcn. O valor é real, e é por
941
+ * isso que o bloco existia - mas ele se cobrava colando `@import "_synthesisui/ds/<slug>/shadcn.css"`
942
+ * no CSS dele, que é exatamente o que `INV-GERAL-13` proíbe.
1320
943
  *
1321
- * Imported is worth saying too, and that is the unusual part. It is the only
1322
- * place we can answer "how do I know my shadcn is going through the system?"
1323
- * with something checked rather than claimed. A governance tool that cannot
1324
- * show its work is asking for trust it has not earned.
944
+ * A PONTE CONTINUA SENDO COMPILADA e continua caindo em `_synthesisui/ds/<slug>/`, como insumo
945
+ * nosso - o mesmo estatuto do `tokens.css`. O que sai é a cobrança, não o arquivo. Quando vestir
946
+ * os componentes shadcn dele passar a ser uma frente própria, ela se resolve como o resto do
947
+ * produto se resolve: escrevendo no vocabulário DELE, e não pedindo que ele carregue o nosso.
1325
948
  */
1326
- if (hasSystem && wiring.hasShadcn) {
1327
- console.log("");
1328
- if (wiring.bridged) {
1329
- console.log(body(`✓ shadcn is reading ${table.name ?? table.slug}, not its own`));
1330
- console.log(body(" defaults. Colour, charts, radius and the sidebar all resolve"));
1331
- console.log(body(" to this system's tokens, in both schemes."));
1332
- }
1333
- else {
1334
- console.log(body("This project has shadcn, and it is not wearing the"));
1335
- console.log(body("system yet - its components are still on shadcn's defaults."));
1336
- console.log("");
1337
- console.log(body(` @import "_synthesisui/ds/${table.slug}/shadcn.css";`));
1338
- console.log("");
1339
- console.log(body(" after the tokens.css import, in the same stylesheet. One line,"));
1340
- console.log(body(" and every shadcn component switches over."));
1341
- }
1342
- }
1343
949
  if (hasSystem && d.reach.measured) {
1344
950
  const { percent, uses, of } = d.reach;
1345
951
  console.log("");
@@ -1383,8 +989,16 @@ export async function doctor(opts) {
1383
989
  */
1384
990
  if (d.ownTokens > 0 && table.source === "installed") {
1385
991
  console.log(body(paint.dim(` ${d.ownTokens} token${d.ownTokens === 1 ? "" : "s"} of your own, used ${d.ownUses}x${d.ownMirrored > 0 ? ` - ${d.ownMirrored} hold a value ${systemName} also names` : ""}`)));
992
+ /**
993
+ * A GRAFIA SAIU DESTA LINHA EM 11/09 - `INV-GERAL-13`.
994
+ *
995
+ * Ela dizia *"point those at the `--ds-*` that holds it"*, e aquela variável só resolve com
996
+ * uma folha nossa carregada no app dele. O fato continua sendo dito - ele tem N tokens que
997
+ * seguram um valor que o sistema também nomeia -, e o que se faz com isso é decisão dele: os
998
+ * dois nomes são dele, e o segundo mora num sistema que nasceu do código dele.
999
+ */
1386
1000
  if (d.ownMirrored > 0)
1387
- console.log(body(paint.dim(` point those at the \`--ds-*\` that holds it: ${d.ownMirrored} lines, one file`)));
1001
+ console.log(body(paint.dim(` ${d.ownMirrored} line${d.ownMirrored === 1 ? "" : "s"} where the two agree - yours to keep as one or as two`)));
1388
1002
  }
1389
1003
  /**
1390
1004
  * O QUE UM COMANDO ALCANÇA, ao lado do que é verdade hoje - e são DUAS contas de propósito.
@@ -1815,8 +1429,16 @@ export async function doctor(opts) {
1815
1429
  for (const f of frozen) {
1816
1430
  say(body(`ds-${f.component} · ${f.where}`));
1817
1431
  say(` binds ${f.wrote}`);
1818
- /** A grafia que EXISTE no repo dele - o CSS compilado carrega esta variável; `{color.semantic.*}` é formato nosso e não sai. */
1819
- say(` var(--ds-color-semantic-${f.role}) holds that, and becomes ${f.becomes}`);
1432
+ /**
1433
+ * O PAPEL PELO NOME, e não pela nossa grafia de variável (`INV-GERAL-13`, 11/09).
1434
+ *
1435
+ * Esta linha dizia `var(--ds-color-semantic-<role>)`, com a justificativa de que era *"a
1436
+ * grafia que EXISTE no repo dele"*. Ela existe dentro da NOSSA folha, que o app dele não
1437
+ * carrega - então imprimi-la aqui ensina uma variável que ele não pode escrever. O que a
1438
+ * frase precisa dizer é qual papel do sistema segura aquele valor, e `role` já é o nome
1439
+ * daquele papel.
1440
+ */
1441
+ say(` the "${f.role}" surface colour of this system holds that, and becomes ${f.becomes}`);
1820
1442
  say("");
1821
1443
  }
1822
1444
  say(body("The value resolves and the CSS compiles, so nothing"));
@@ -2163,6 +1785,30 @@ export async function doctor(opts) {
2163
1785
  console.log(` ${i + 1}. ${what} ${paint.dim(`${j.size}${j.cheap ? " (cheap)" : ""}`)}`);
2164
1786
  });
2165
1787
  }
1788
+ /**
1789
+ * A4 · O VALOR QUE O SISTEMA NOMEIA E O CÓDIGO DELE NÃO - dito, nunca batizado.
1790
+ *
1791
+ * A METADE QUE SUMIRIA SEM ISTO. Desde que `nameToWrite` parou de devolver a nossa grafia
1792
+ * (11/09), um achado com nome só do nosso lado não entra na lista de trocas - e não entrava em
1793
+ * lista nenhuma. Ele saía das duas, e sumir é indistinguível de *"está tudo nomeado"* para quem
1794
+ * lê: o relatório dizia *"1 value by hand · 0 already have a name"* sobre um valor que o
1795
+ * sistema dele nomeia há meses.
1796
+ *
1797
+ * DIZER, E NÃO PROPOR. É a escolha dele de 10/09 entre as duas alternativas, literal: *"DIZER o
1798
+ * valor e onde ele está, sem propor nome nenhum"*. Nenhuma grafia nossa sai daqui - nem como
1799
+ * sugestão, nem entre parênteses - porque um relatório que roda sozinho é o pior lugar para
1800
+ * inventar vocabulário: o agente obedece, e a violação acontece sem ninguém decidir nada.
1801
+ */
1802
+ const onlyOurs = d.findings.filter(ourNameOnly);
1803
+ if (onlyOurs.length > 0) {
1804
+ console.log("");
1805
+ console.log(body(`${onlyOurs.length} value${onlyOurs.length === 1 ? "" : "s"} ${onlyOurs.length === 1 ? "has" : "have"} a name in ${systemName} and none in your own code:`));
1806
+ for (const f of onlyOurs.slice(0, 5))
1807
+ console.log(body(paint.dim(` ${f.file}:${f.line} ${f.literal}`)));
1808
+ if (onlyOurs.length > 5)
1809
+ console.log(body(paint.dim(` (${onlyOurs.length - 5} more)`)));
1810
+ console.log(body(paint.dim(" Naming one is yours to decide - it may be intentional. Once your code names it, `--fix` picks it up.")));
1811
+ }
2166
1812
  console.log("");
2167
1813
  console.log(body("synthesisui doctor --verbose every finding, file by file"));
2168
1814
  /**