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.
package/dist/claude-md.js CHANGED
@@ -158,34 +158,21 @@ best reasoning you produce.`;
158
158
  * agent is pointed at the harmless half and told to ask for the other.
159
159
  */
160
160
  /**
161
- * THE ONE THING MOST LIKELY TO ALREADY BE IN THE PROJECT.
161
+ * A PONTE DO SHADCN SAIU DAQUI EM 11/09 - `INV-GERAL-13`.
162
162
  *
163
- * Every system compiles a `shadcn.css` mapping shadcn's whole variable contract
164
- * onto its own tokens. It has been generated into every project since it was
165
- * built and nothing ever mentioned it, so the feature that answers "why not just
166
- * use shadcn" was sitting unimported in the repo of the person who asked.
163
+ * O QUE ESTE PARÁGRAFO FAZIA: mandava o agente dele colar `@import "_synthesisui/ds/<slug>/shadcn.css"`
164
+ * na folha dele, para que os componentes shadcn do projeto vestissem o sistema em vez dos defaults
165
+ * do shadcn. O valor é real - era a resposta para *"por que não só usar shadcn?"* -, e a forma
166
+ * estava errada: um bloco escrito no `CLAUDE.md` dele, que ele a cada sessão, pedindo um import
167
+ * da nossa folha.
167
168
  *
168
- * Told to the AGENT and not only to the person, because the agent is who reaches
169
- * for a shadcn block. Left to guess, it installs `dashboard-01` and gets a
170
- * sidebar on shadcn's defaults next to components wearing the system, which
171
- * reads as a broken product rather than a missing import.
169
+ * ERA O PIOR LUGAR PARA A INSTRUÇÃO EXISTIR. Um comentário no terminal rola; este bloco é lido por
170
+ * todo agente em toda sessão, e um agente obediente cola a linha sem que uma pessoa decida nada.
172
171
  *
173
- * Conditional on `components.json`: a project without shadcn must not carry a
174
- * paragraph about it.
172
+ * A ponte continua sendo compilada e continua caindo em `_synthesisui/ds/<slug>/`, como insumo
173
+ * nosso. Vestir os componentes shadcn dele é uma frente própria, e ela se resolve como o resto do
174
+ * produto: escrevendo no vocabulário DELE, não pedindo que ele carregue o nosso.
175
175
  */
176
- const SHADCN = (slug) => `
177
-
178
- **This project has shadcn, and this system already speaks its language.** A
179
- generated bridge maps shadcn's whole variable contract - colour, charts, radius,
180
- the sidebar family - onto this system's tokens, in both schemes:
181
-
182
- @import "_synthesisui/ds/${slug}/shadcn.css";
183
-
184
- after the tokens.css import, in the same stylesheet. With that line in place,
185
- shadcn components wear this system and need no edits from you. Without it they
186
- stay on shadcn's defaults, which looks like the design system failing rather than
187
- one import missing - so check for it before you add a shadcn component, and add
188
- it if it is not there.`;
189
176
  /**
190
177
  * A REGRA QUE TIRA O "O DEV FAZ ALGO", e ela vale para todo agente que lê este bloco.
191
178
  *
@@ -462,9 +449,9 @@ is no component index for an adopted system: the tokens ARE the contract.
462
449
  \`system_doctrine\` for the rules and the voice, \`find_token\` for a value you are about to write.${memoryLine}${selfCheck}`
463
450
  : `**These are true without asking anyone:**
464
451
 
465
- - Semantic tokens only - \`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`. No raw values outside this
466
- system's scale.
467
- - Scope the UI with \`data-ds="<slug>"\` and reuse the \`.ds-*\` classes.
452
+ - The system's vocabulary only - and it is written in the names THIS project declares. \`find_token\`
453
+ answers with the exact name for a value you are about to write. No raw values outside the scale,
454
+ and never a name from a stylesheet this project does not have.
468
455
  - To override a style a component already sets, use this system's semantic role class - never \`!\`.
469
456
  If the override is ignored, regenerate that component: older ones predate the resolver.
470
457
  - Motion is selection, not improvisation. The base is quiet: nothing moves until a person asks, and
@@ -489,9 +476,10 @@ project's OWN custom properties, exactly as the guide lists them. Do not write r
489
476
  spacings or radii that a token already covers, and do not invent a new token silently - say so
490
477
  instead, because a new token is a decision for a person to make. There is no component
491
478
  manifest for an adopted system - the tokens ARE the contract.${selfCheck}`
492
- : `**When creating or editing components, read the system's GUIDE.md and follow it:** use only semantic tokens
493
- (\`var(--ds-color-semantic-*)\`, \`--ds-spacing-*\`, etc.), scope the UI with \`data-ds="<slug>"\`,
494
- and reuse the \`.ds-*\` classes. Do not use raw values outside the system's scale.
479
+ : `**When creating or editing components, read the system's GUIDE.md and follow it:** use the
480
+ vocabulary it lists - every name in it is a name THIS project already declares, written exactly as
481
+ you would write it. Do not use raw values outside the system's scale, and never a name from a
482
+ stylesheet this project does not have.
495
483
 
496
484
  **Before creating any UI element, look it up in the manifest below.** If something there
497
485
  already covers the purpose, do not write it from scratch - run
@@ -539,16 +527,9 @@ element. Write every user-facing string in that language - labels, empty states,
539
527
  \`alt\`. A screen reader pronounces \`aria-label\` using \`lang\`, so a mixed-language interface is
540
528
  worse than an untranslated one. If the attribute is wrong, change it rather than writing against
541
529
  it.`;
542
- // Named for the first installed system: the bridge is per-system, and a
543
- // project with two of them has already chosen which one dresses the app.
544
- const bridgeable = installed.find((d) => !d.adopted);
545
- const shadcn = bridgeable &&
546
- (await readFile(join(projectRoot, "components.json"), "utf8").then(() => true, () => false))
547
- ? SHADCN(bridgeable.slug)
548
- : "";
549
530
  const body = `## Design Systems (via SynthesisUI)
550
531
 
551
- This project uses design system(s) tracked by the \`synthesisui\` CLI. ${rule}${language}${shadcn}
532
+ This project uses design system(s) tracked by the \`synthesisui\` CLI. ${rule}${language}
552
533
 
553
534
  ${sections.join("\n")}
554
535
 
@@ -5,7 +5,7 @@ import { syncClaudeMd } from "../claude-md.js";
5
5
  import { readProjectConfig, readToken, resolveRegistry } from "../config.js";
6
6
  import { parseRules } from "../doctrine.js";
7
7
  import { customFontFamilies, googleFontsHref, nextFontSnippet, } from "../fonts.js";
8
- import { detectAppDirs, globalSheetOf, prefixFrom } from "../global-sheet.js";
8
+ import { detectAppDirs } from "../global-sheet.js";
9
9
  import { lockReference } from "../group-role.js";
10
10
  import { buildGuide } from "../guide.js";
11
11
  import { censusScope } from "../measured-scope.js";
@@ -17,6 +17,7 @@ import { repoStateOf } from "../repo-state.js";
17
17
  import { describeFiltered, ruleApplies, rulesForProject, } from "../rule-filter.js";
18
18
  import { detectStack } from "../stack.js";
19
19
  import { onlyWhatMatched } from "../their-theme.js";
20
+ import { tongueOf } from "../their-tongue.js";
20
21
  import { pointTokensAtTheirNames } from "../their-vars.js";
21
22
  import { tracksAnyOf } from "../tracked.js";
22
23
  /**
@@ -225,6 +226,19 @@ export async function add(slug, opts) {
225
226
  * componente em vez de por arquivo.
226
227
  */
227
228
  await writeFile(join(versionDir, "design-system.json"), `${JSON.stringify(payload.document)}\n`, "utf8");
229
+ /**
230
+ * E A CÓPIA DO QUE FOI INSTALADO, ao lado - é ela que faz o `sync` saber o que o agente dele
231
+ * mudou depois.
232
+ *
233
+ * O que o cliente ganha: o ciclo fecha sem uma ida à rede. Ele cola o prompt que a plataforma
234
+ * montou, o agente edita `design-system.json`, roda o `sync` - e o comando compara os dois
235
+ * arquivos aqui mesmo. Sem este baseline, a única forma de saber o que mudou seria perguntar
236
+ * ao servidor a cada `sync`, e um `sync` offline não conseguiria nem isso.
237
+ *
238
+ * O PONTO NA FRENTE não é estética: `.installed.json` é registro nosso desta pasta, como o
239
+ * `.lock`, e não um artefato que ele lê. Ver `local-edits.ts`.
240
+ */
241
+ await writeFile(join(versionDir, ".installed.json"), `${JSON.stringify(payload.document)}\n`, "utf8");
228
242
  /**
229
243
  * 3. O GUIA - e o catálogo só é COPIADO onde não há caminho de volta.
230
244
  *
@@ -245,7 +259,16 @@ export async function add(slug, opts) {
245
259
  * certa, não uma falha - lá não existe `next/font` para ter peso nenhum.
246
260
  */
247
261
  const fontFacts = await nextFontFacts(projectRoot);
248
- await writeFile(join(versionDir, "GUIDE.md"), buildGuide(payload, tools.available, fontFacts), "utf8");
262
+ /**
263
+ * O GUIA FALA A LÍNGUA DELE - `INV-GERAL-13`, e a folha REESCRITA é a fonte.
264
+ *
265
+ * `theirVars.css` é o `tokens.css` já apontado para o vocabulário dele nesta máquina, que é
266
+ * exatamente o que caiu na pasta um passo acima. Ler dali, e não do payload cru, é o que garante
267
+ * que o guia e o componente materializado digam o MESMO nome para o mesmo valor - duas leituras
268
+ * da mesma folha, uma delas livre para discordar, é a forma de dois textos nossos ensinarem dois
269
+ * vocabulários.
270
+ */
271
+ await writeFile(join(versionDir, "GUIDE.md"), buildGuide(payload, tools.available, fontFacts, tongueOf(theirVars.pairs, theirVars.css)), "utf8");
249
272
  // 4. stable root re-exports for each CSS artifact → always the active version,
250
273
  // so the consumer's @import path never changes across updates
251
274
  const cssArtifacts = Object.keys(payload.artifacts).filter((f) => f.endsWith(".css") && !(f === "shadcn.css" && !wantsShadcn));
@@ -461,8 +484,18 @@ export async function add(slug, opts) {
461
484
  ...Object.keys(payload.artifacts).filter((f) => !(f === "shadcn.css" && skipped)),
462
485
  "design-system.json",
463
486
  "GUIDE.md",
487
+ ".installed.json",
464
488
  ];
465
489
  console.log(` v${v}/: ${files.join(", ")}`);
490
+ /**
491
+ * O ARQUIVO NOVO É ANUNCIADO, e não plantado em silêncio - lei 8.
492
+ *
493
+ * Ele é versionado junto com o resto da pasta de propósito: assim um colega que clona tem o
494
+ * baseline e o `sync` dele funciona. Mas um arquivo de ponto que nasce sem aviso e reaparece
495
+ * no `git status` a cada sync é o atrito que a régua do `census.json` foi desenhada para
496
+ * evitar - e a diferença entre os dois é uma frase.
497
+ */
498
+ console.log(` (.installed.json records what was installed, so \`sync\` can tell what you changed)`);
466
499
  /**
467
500
  * O QUE NÃO FOI ESCRITO, DITO EM VOZ ALTA - lei 8, e ela vale nos dois sentidos.
468
501
  *
@@ -585,106 +618,40 @@ export async function add(slug, opts) {
585
618
  : ` CLAUDE.md updated - your file is intact, with our block inside it (${claudeMd.count} system(s) indexed)`);
586
619
  if (opts.setupHints === false)
587
620
  return;
588
- const hasTheme = cssArtifacts.includes("theme.css");
589
621
  // ── DX: concrete paths + copy-pasteable snippets, with breathing room ──
590
- // Where the app actually lives (app/ vs src/app/) drives every printed
591
- // path: the @import depth, the layout example, and where fonts.ts lands.
622
+ // Where the app actually lives (app/ vs src/app/) still drives where `fonts.ts`
623
+ // lands and which layout the snippet names. The @import depth went with the
624
+ // setup block - see the comment below.
592
625
  const projectConfig = await readProjectConfig(projectRoot);
593
626
  const appDirs = await detectAppDirs(projectRoot, projectConfig.pagesDir);
594
627
  /** Nenhuma pasta encontrada: os caminhos viram exemplo, e o texto abaixo diz isso. */
595
628
  const appDir = appDirs[0] ?? projectConfig.pagesDir;
596
629
  const appDirFound = appDirs.length > 0;
597
630
  /**
598
- * A FOLHA GLOBAL QUE ELE DE FATO CARREGA - ver `globalSheetOf`.
631
+ * O SETUP SAIU DAQUI EM 11/09 - `INV-GERAL-13`, e eram os dois `@import` mais o `data-ds`.
599
632
  *
600
- * A instrução mandava editar `<appDir>/globals.css`. MEDIDO em 01/09 no `codelevel`: os dois apps
601
- * dele fazem `@import "@repo/ui/styles/globals.css"`, e o `globals.css` do app diz em comentário
602
- * *"Do not redeclare tokens or fonts here"*. O arquivo certo era o do pacote, e a gente apontava
603
- * para o do app - com o caminho relativo do app, que dali não resolve.
604
- */
605
- const sheet = await globalSheetOf(projectRoot, `${appDir}/globals.css`);
606
- const importPrefix = prefixFrom(sheet);
607
- console.log(section("One-time setup (once per app)"));
608
- /**
609
- * QUANDO O SETUP É NECESSÁRIO, E QUANDO ELE É UMA ESCOLHA - a última porta onde a pergunta faltava.
633
+ * O QUE O DONO VIU, e é o que abriu esta etapa: o `upgrade` imprimiu
610
634
  *
611
- * O `doctor` e o `component` passaram a perguntar antes de cobrar (ver `sheet-needed.ts`, 07/09,
612
- * a partir da objeção do dono: *"esse import gera dependência ao design system, e a gente quer
613
- * isolar os dois pontos - o nosso deve ser apenas uma REFERÊNCIA"*). Aqui a pergunta não tem como
614
- * ser respondida da mesma forma: o sistema acabou de ser instalado e não há código gerado para
615
- * medir.
635
+ * @import "../../../../_synthesisui/ds/codelevel/tokens.css";
636
+ * @import "../../../../_synthesisui/ds/codelevel/theme.css";
616
637
  *
617
- * O QUE MUDA É A HONESTIDADE DO ENUNCIADO. "One-time setup" lido logo após a instalação soa como
618
- * obrigação, e para metade dos casos ele não é: quem vai trazer componentes como CÓDIGO SEU não
619
- * precisa de folha nenhuma - o `component` traduz para o vocabulário do próprio repositório e diz,
620
- * arquivo a arquivo, se sobrou algo. Quem vai ESCREVER as classes do sistema (`bg-brand`,
621
- * `rounded-lg` no valor dele) precisa, porque só o `@theme` do adaptador as gera.
638
+ * e a resposta dele foi *"não! isso nunca deve acontecer, a gente havia combinado isso"*.
622
639
  *
623
- * Então a instrução continua inteira - suprimi-la entregaria um projeto sem as classes - e ganha a
624
- * linha que diz QUAL dos dois casos exige cada coisa. A escolha volta a ser dele, informada.
625
- */
626
- console.log(line("(needed if you will WRITE this system's utility classes - only its @theme generates them."));
627
- console.log(line(" Bringing components in as YOUR code needs none of it: `component` translates them into"));
628
- console.log(line(" your own vocabulary and tells you, per file, if anything is left.)"));
629
- console.log("");
630
- /**
631
- * "ONCE PER APP" É LITERAL NUM MONOREPO, e calar os outros apps entrega metade da fiação. Os
632
- * caminhos abaixo são de um só; sem esta linha o cliente conclui que o repositório inteiro está
633
- * ligado quando só o primeiro está.
640
+ * A INSTRUÇÃO EXISTIA POR UMA RAZÃO REAL, e a razão acabou. Sem a folha carregada, o
641
+ * `var(--ds-*)` que a plataforma escrevia não resolvia e o componente chegava sem cor. Nada que
642
+ * a plataforma escreve carrega mais uma variável nossa (`their-tongue.ts`) nem um utilitário que
643
+ * dependa do nosso `@theme` (`theirThemeVars`), então não nada para o app dele carregar.
644
+ *
645
+ * O QUE A PASTA PASSA A SER, e é o que ele descreveu em 07/09: *"só pra gente ter uma base para
646
+ * interpretar o design da pessoa e replicar no nosso lado"*. `tokens.css` e `theme.css` continuam
647
+ * caindo em `_synthesisui/ds/<slug>/` como INSUMO - o hook, o doctor e o MCP os leem para
648
+ * interpretar o design dele. O que muda é que aquilo deixou de ser folha que o app dele importa.
634
649
  */
635
- if (appDirs.length > 1)
636
- console.log(line(`(${appDirs.length} apps here: ${appDirs.join(", ")} - the paths below are for ${appDir}; repeat for the others.)`));
637
- console.log(line(sheet === `${appDir}/globals.css`
638
- ? `1. Import the system in your GLOBAL stylesheet, e.g. ${sheet}`
639
- : `1. Import the system in ${sheet} - that is the sheet your apps load (${appDir}/globals.css only re-exports it, so tokens put there would not reach the package's own components):`));
640
- console.log(line(` (the path is relative to that file - hence the leading ${importPrefix}):`));
641
- console.log("");
642
- // THE BRIDGE ONLY EXISTS FOR PEOPLE WHO ALREADY HAVE SHADCN, so it only
643
- // appears for them. Every system compiles a `shadcn.css` mapping shadcn's
644
- // whole variable contract onto its own tokens, and until 28/07 nothing said
645
- // so - not this output, not the managed block, not the GUIDE beyond a filename
646
- // in a list. The person it mattered most to asked "I don't get the use case, I
647
- // can just build a design system with shadcn", with the answer sitting
648
- // unimported in his own repo. Naming it to everybody would be noise; naming it
649
- // to whoever has `components.json` is the whole feature arriving.
650
- /** A MESMA pergunta que decide a ESCRITA - ver `wantsShadcn`. Duas leituras seriam duas regras. */
651
- const hasShadcn = await wearsShadcn(projectRoot);
652
- console.log(snippet([
653
- ...(hasTheme ? [`@import "tailwindcss";`] : []),
654
- `@import "${importPrefix}_synthesisui/ds/${payload.slug}/tokens.css";`,
655
- ...(hasTheme
656
- ? [
657
- `@import "${importPrefix}_synthesisui/ds/${payload.slug}/theme.css"; /* Tailwind utilities on your tokens */`,
658
- ]
659
- : []),
660
- ...(hasShadcn
661
- ? [
662
- `@import "${importPrefix}_synthesisui/ds/${payload.slug}/shadcn.css"; /* your shadcn components, wearing this system */`,
663
- ]
664
- : []),
665
- ]));
666
- if (hasShadcn) {
667
- console.log("");
668
- console.log(line(` You have shadcn here. That third line maps its whole variable contract`));
669
- console.log(line(` onto this system - colour, charts, radius and the sidebar - so its`));
670
- console.log(line(` components stop using shadcn's defaults. Both schemes included.`));
671
- }
672
- console.log("");
673
- console.log(line(`2. Scope your app: add data-ds="${payload.slug}" to a ROOT element, e.g. ${appDir}/layout.tsx:`));
674
- console.log("");
675
- console.log(snippet([
676
- `<body data-ds="${payload.slug}" className="bg-canvas">{children}</body>`,
677
- ]));
678
- // THE SCOPE DELIBERATELY DOES NOT PAINT A BACKGROUND, and every project hits
679
- // it. `[data-ds]` sets ink and body family only, because scopes NEST: a dark
680
- // contrast section over a photograph carries its own nested `data-ds` so its
681
- // tokens remap, and a background on every scope would paint over the picture.
682
- // So the page background is the root element's job, and saying so here is the
683
- // fix - an agent found this by reading rendered CSS on 28/07 and had to
684
- // deduce it.
685
- console.log(line(` \`bg-canvas\` is not optional: the scope sets text colour and type, not the page`));
686
- console.log(line(` background. Scopes nest (a dark section carries its own), so painting every one`));
687
- console.log(line(` would cover whatever is behind it.`));
650
+ console.log(section("What landed, and what it is for"));
651
+ console.log(line(`_synthesisui/ds/${payload.slug}/ is ours to READ - the check, the doctor and the MCP`));
652
+ console.log(line(`server use it to interpret your design. Nothing in your app imports it, and nothing`));
653
+ console.log(line(`we write into your repository needs it at runtime: a component arrives speaking the`));
654
+ console.log(line(`names YOUR code declares, or carrying the value itself.`));
688
655
  // 3. Load the type - the DS ships token NAMES, not the fonts themselves.
689
656
  // Next apps get fonts.ts MATERIALIZED (deterministic does, not teaches):
690
657
  // next/font = self-hosted + preloaded + adjusted fallback, no FOUT
@@ -700,9 +667,7 @@ export async function add(slug, opts) {
700
667
  const fontPlan = projectConfig.target === "next"
701
668
  ? nextFontSnippet({
702
669
  families,
703
- slug: payload.slug,
704
670
  appDir,
705
- seam: payload.fontSeam,
706
671
  facts: fontFacts,
707
672
  declaredWeights: payload.document.foundations.typography.weights,
708
673
  })
@@ -778,9 +743,7 @@ export async function add(slug, opts) {
778
743
  continue;
779
744
  const plan = nextFontSnippet({
780
745
  families,
781
- slug: payload.slug,
782
746
  appDir: dir,
783
- seam: payload.fontSeam,
784
747
  facts: fontFacts,
785
748
  declaredWeights: payload.document.foundations.typography.weights,
786
749
  });