synthesisui 0.16.409 → 0.16.411

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.
@@ -27,6 +27,7 @@
27
27
  * retyped ten-class string is where a `hover:` goes missing, and six components
28
28
  * lost their states to exactly that (audit, dono, 01/08).
29
29
  */
30
+ import { displayFromClasses } from "./display-from-class.js";
30
31
  import { frontierOf, rendersNothing, } from "./frontier-kind.js";
31
32
  /** An svg's insides are geometry, not anatomy: the glyph is the node. */
32
33
  const SVG_INTERNALS = new Set([
@@ -182,12 +183,23 @@ const NEUTRAL = new Set([
182
183
  "nav",
183
184
  "details",
184
185
  ]);
185
- /** `flex-col`, `grid` and `block` arrange DOWN; a bare `flex` arranges ACROSS. */
186
+ /**
187
+ * `flex-col`, `grid` and `block` arrange DOWN; a bare `flex` arranges ACROSS.
188
+ *
189
+ * ONE READING OF THE BOX, not two. This used to run its own regex over the class string
190
+ * while `transcribe` ran another, and the two answers disagreed by construction: the form
191
+ * is a lossy reduction of `display` - it says "across or down" and drops the difference
192
+ * between a grid and a column. Both now come off the same table, so the form is DERIVED
193
+ * from the declaration instead of being a second opinion about it.
194
+ */
186
195
  function arrangement(classes) {
187
- const list = ` ${classes ?? ""} `;
188
- if (/\s(flex-col|flex-col-reverse|grid|block|table)\s/.test(list))
196
+ const box = displayFromClasses(classes);
197
+ const direction = box.flexDirection;
198
+ if (direction === "column" || direction === "column-reverse")
189
199
  return "stack";
190
- if (/\s(flex|inline-flex|flex-row|flex-row-reverse)\s/.test(list))
200
+ if (direction === "row" || direction === "row-reverse")
201
+ return "row";
202
+ if (box.display === "flex" || box.display === "inline-flex")
191
203
  return "row";
192
204
  return "stack";
193
205
  }
@@ -1090,42 +1090,90 @@ export async function doctor(opts) {
1090
1090
  */
1091
1091
  const perApp = await readWiringPerApp(root, table.slug, config.pagesDir);
1092
1092
  const targets = perApp.length > 0 ? perApp : [{ app: null, wiring }];
1093
- console.log("");
1094
- console.log(body("Paste this into your agent - it does the setup for you:"));
1093
+ /**
1094
+ * OS APPS QUE PEDEM A MESMA COISA VIRAM UM BLOCO SÓ, nomeando os dois.
1095
+ *
1096
+ * O DEFEITO, e ele é meu, do PR de algumas horas atrás: no `codelevel-monorepo` os DOIS apps
1097
+ * resolvem para a MESMA folha (`packages/ui/src/styles/globals.css`, o pacote compartilhado),
1098
+ * então o comando imprimiu a mesma instrução de 40 linhas DUAS VEZES, com o mesmo caminho e o
1099
+ * mesmo prefixo. O dono leu a parede duas vezes (medido no repositório dele, 09/09).
1100
+ *
1101
+ * Eu tinha escrito o aviso no comentário abaixo - *"num monorepo ele apareceria em escala:
1102
+ * dois blocos idênticos"* - sobre o caso do app JÁ FIADO, e entreguei a duplicata no caso do
1103
+ * que ainda falta. Nomear cada app foi o conserto certo; repetir a instrução não.
1104
+ *
1105
+ * A CHAVE É A INSTRUÇÃO, não o app: dois apps com a mesma folha, o mesmo prefixo e os mesmos
1106
+ * passos pendentes recebem uma instrução só. Um monorepo onde cada app tem folha própria volta
1107
+ * a receber uma por app, porque ali as instruções DIFEREM - é a resposta derivada, e não uma
1108
+ * regra sobre quantos apps existem.
1109
+ */
1110
+ const blocks = new Map();
1095
1111
  for (const { app, wiring: w } of targets) {
1096
1112
  const sheet = app
1097
1113
  ? await globalSheetOf(root, `${app}/globals.css`)
1098
1114
  : null;
1099
1115
  /**
1100
1116
  * O APP QUE JÁ ESTÁ FIADO SAI DA LISTA, nomeado. Repetir o setup para quem já o fez é o
1101
- * defeito de 08/09 outra vez, e num monorepo ele apareceria em escala: dois blocos
1102
- * idênticos, um deles cobrando o que já está colado.
1117
+ * defeito de 08/09 outra vez.
1103
1118
  */
1104
1119
  const pending = !w.imported || !w.scoped || (w.fontsWritten && !w.fontsMapped);
1105
- if (targets.length > 1) {
1120
+ const text = pending
1121
+ ? setupPrompt(table.slug, {
1122
+ tokens: !w.imported,
1123
+ scope: !w.scoped,
1124
+ /** Só é passo quando o projeto TEM o arquivo de fontes - ver `readWiring`. */
1125
+ type: w.fontsWritten && !w.fontsMapped,
1126
+ ...(sheet
1127
+ ? { sheet: { path: sheet, prefix: prefixFrom(sheet) } }
1128
+ : {}),
1129
+ /**
1130
+ * DUAS LINHAS COM TAILWIND, UMA SEM - a mesma decisão que o `add` já toma pelo
1131
+ * projeto. Dizer "as duas linhas" a um projeto que precisa de uma manda o agente
1132
+ * procurar o que não existe.
1133
+ */
1134
+ imports: config.styles === "css" ? 1 : 2,
1135
+ }).split("\n")
1136
+ : [];
1137
+ const key = `${pending ? "p" : "ok"}|${text.join("\n")}`;
1138
+ const found = blocks.get(key);
1139
+ if (found)
1140
+ found.apps.push(app ?? "");
1141
+ else
1142
+ blocks.set(key, { apps: [app ?? ""], text, pending });
1143
+ }
1144
+ const anyPending = [...blocks.values()].some((b) => b.pending);
1145
+ if (anyPending) {
1146
+ console.log("");
1147
+ console.log(body("Paste this into your agent - it does the setup for you:"));
1148
+ }
1149
+ for (const { apps, text, pending } of blocks.values()) {
1150
+ const named = apps.filter(Boolean);
1151
+ /**
1152
+ * O CABEÇALHO SÓ APARECE QUANDO HÁ MAIS DE UM APP no projeto - com um só ele seria cerimônia
1153
+ * sobre uma lista de um, e a maioria dos projetos é assim. Com vários, ele diz de QUEM é a
1154
+ * instrução, e o plural sai naturalmente: "apps/landing/app and apps/web/app".
1155
+ */
1156
+ if (targets.length > 1 && named.length > 0) {
1157
+ /**
1158
+ * A LISTA SE LÊ EM VOZ ALTA, e o número dela não é fixo: "A and B" com dois,
1159
+ * "A, B and C" com três. `join(" and ")` daria "A and B and C", e `"both"` mentiria
1160
+ * no dia em que o monorepo tiver três apps na mesma folha - o mesmo erro de
1161
+ * dimensionar pela amostra, na escala de uma palavra.
1162
+ */
1163
+ const lista = named.length > 1
1164
+ ? `${named.slice(0, -1).join(", ")} and ${named[named.length - 1]}`
1165
+ : named[0];
1106
1166
  console.log("");
1107
- console.log(body(pending
1108
- ? ` ${app} - still needs it`
1109
- : ` ${app} - already wired, nothing to paste`));
1167
+ console.log(body(` ${lista} - ${pending
1168
+ ? named.length > 1
1169
+ ? "need it, and the steps are the same"
1170
+ : "still needs it"
1171
+ : "already wired, nothing to paste"}`));
1110
1172
  }
1111
1173
  if (!pending)
1112
1174
  continue;
1113
1175
  console.log("");
1114
- console.log(snippet(setupPrompt(table.slug, {
1115
- tokens: !w.imported,
1116
- scope: !w.scoped,
1117
- /** Só é passo quando o projeto TEM o arquivo de fontes - ver `readWiring`. */
1118
- type: w.fontsWritten && !w.fontsMapped,
1119
- ...(sheet
1120
- ? { sheet: { path: sheet, prefix: prefixFrom(sheet) } }
1121
- : {}),
1122
- /**
1123
- * DUAS LINHAS COM TAILWIND, UMA SEM - a mesma decisão que o `add` já toma pelo
1124
- * projeto. Dizer "as duas linhas" a um projeto que precisa de uma manda o agente
1125
- * procurar o que não existe.
1126
- */
1127
- imports: config.styles === "css" ? 1 : 2,
1128
- }).split("\n")));
1176
+ console.log(snippet(text));
1129
1177
  }
1130
1178
  }
1131
1179
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * O DISPLAY QUE O CLIENTE ESCREVEU, LIDO DE UM CLASS STRING - a tabela, num lugar só.
3
+ *
4
+ * O QUE O CLIENTE GANHA: o preview desenha a caixa que ele declarou, e não a que a
5
+ * plataforma teria adivinhado. `display` é a declaração que decide se o componente dele é
6
+ * uma linha, uma coluna, uma grade ou uma caixa em linha - e o desenho pergunta por ela
7
+ * para saber se o layout daquele nó é DELE. Sem ela, a forma inferida por nós vence a que
8
+ * ele escreveu, que é o inverso exato da lei de prioridade de layout.
9
+ *
10
+ * MEDIDO em 09/09 sobre três sistemas - dos componentes cuja raiz é estilizada por
11
+ * utilitário, quantos tinham `display` na receita: 2 de 58, 9 de 20 e 0 de 32; pela via de
12
+ * CSS Module, 48 de 196. A sintaxe que ele escolheu estava decidindo se a declaração dele
13
+ * sobrevivia, e ninguém escolhe uma sintaxe por esse motivo.
14
+ *
15
+ * A DIREÇÃO VIAJA JUNTO, ou `flex flex-col` vira uma linha: `display:flex` é `row` por
16
+ * padrão, e emitir o display sem a direção troca em silêncio toda coluna dele por uma
17
+ * linha - uma leitura pior que a anterior, porque parece deliberada.
18
+ *
19
+ * ESTE ARQUIVO TEM UM GÊMEO BYTE-IDÊNTICO em `libs/ds-contracts/src/display-from-class.ts`,
20
+ * garantido por spec. O CLI é publicado standalone e não pode importar os contratos; a
21
+ * plataforma precisa da mesma tabela para alcançar quem já importou sem pedir re-import.
22
+ * Editar um exige copiar para o outro.
23
+ */
24
+ /**
25
+ * Os valores de `display` do CSS, na grafia utilitária que os frameworks convergiram em
26
+ * usar para cada um. A tabela é sobre CSS: a chave é só como aquele valor costuma ser
27
+ * escrito quando não é escrito por CSS.
28
+ */
29
+ export const DISPLAY_UTILITIES = {
30
+ flex: { property: "display", value: "flex" },
31
+ grid: { property: "display", value: "grid" },
32
+ "inline-flex": { property: "display", value: "inline-flex" },
33
+ "inline-grid": { property: "display", value: "inline-grid" },
34
+ "inline-block": { property: "display", value: "inline-block" },
35
+ inline: { property: "display", value: "inline" },
36
+ block: { property: "display", value: "block" },
37
+ "flow-root": { property: "display", value: "flow-root" },
38
+ contents: { property: "display", value: "contents" },
39
+ table: { property: "display", value: "table" },
40
+ "inline-table": { property: "display", value: "inline-table" },
41
+ "list-item": { property: "display", value: "list-item" },
42
+ hidden: { property: "display", value: "none" },
43
+ "flex-col": { property: "flexDirection", value: "column" },
44
+ "flex-col-reverse": { property: "flexDirection", value: "column-reverse" },
45
+ "flex-row": { property: "flexDirection", value: "row" },
46
+ "flex-row-reverse": { property: "flexDirection", value: "row-reverse" },
47
+ "flex-wrap": { property: "flexWrap", value: "wrap" },
48
+ "flex-wrap-reverse": { property: "flexWrap", value: "wrap-reverse" },
49
+ "flex-nowrap": { property: "flexWrap", value: "nowrap" },
50
+ };
51
+ /**
52
+ * As declarações de caixa que um class string carrega, na ordem em que ele as escreveu.
53
+ *
54
+ * SÓ A CLASSE NUA CONTA. Um utilitário com condição na frente - `md:flex`, `hover:grid`,
55
+ * `dark:hidden` - descreve a caixa em OUTRO estado, e escrevê-lo na base afirmaria que
56
+ * aquele estado é o de repouso. Quem lê estado é quem lê estado.
57
+ */
58
+ export function displayFromClasses(classes) {
59
+ const out = {};
60
+ for (const raw of (classes ?? "").split(/\s+/)) {
61
+ if (!raw || raw.includes(":"))
62
+ continue;
63
+ const found = DISPLAY_UTILITIES[raw];
64
+ if (found)
65
+ out[found.property] = found.value;
66
+ }
67
+ return out;
68
+ }
@@ -42,6 +42,7 @@
42
42
  * hover:shadow-md → states.hover
43
43
  * data-[checked]:bg-ocean-50 → states.checked
44
44
  */
45
+ import { DISPLAY_UTILITIES } from "../display-from-class.js";
45
46
  import { tokenRefFor } from "../token-ref.js";
46
47
  import { frameworkDeclaration } from "./framework-palette.js";
47
48
  /**
@@ -384,9 +385,10 @@ const TYPE_KEYWORD = {
384
385
  * and without the position it stopped being a veil and became a block in the flow; the
385
386
  * chevron's `justify-between` is what puts it at the far edge of the header.
386
387
  *
387
- * `flex` and `flex-col` stay OUT on purpose - they travel as the node's own form
388
- * (`row`/`stack`), and declaring the same thing twice is how two sources of truth start
389
- * disagreeing. What comes in here is the DIMENSION (`flex-1`, `shrink-0`) and the PLACE.
388
+ * What comes in here is the DIMENSION (`flex-1`, `shrink-0`), the PLACE, and - since 09/09 -
389
+ * the DISPLAY in full. `flex` and `flex-col` used to be held out so the node's form
390
+ * (`row`/`stack`) would be the single carrier; the display block below says what that cost
391
+ * and why it was reversed.
390
392
  */
391
393
  const PLACE_KEYWORD = {
392
394
  // Position: what makes an overlay an overlay.
@@ -419,12 +421,27 @@ const PLACE_KEYWORD = {
419
421
  "overflow-y-hidden": { property: "overflowY", value: "hidden" },
420
422
  "overflow-x-scroll": { property: "overflowX", value: "scroll" },
421
423
  "overflow-y-scroll": { property: "overflowY", value: "scroll" },
422
- // Display, when it is not the arrangement we already carry as a form.
423
- "inline-flex": { property: "display", value: "inline-flex" },
424
- "inline-block": { property: "display", value: "inline-block" },
425
- inline: { property: "display", value: "inline" },
426
- block: { property: "display", value: "block" },
427
- hidden: { property: "display", value: "none" },
424
+ /**
425
+ * DISPLAY, ALL OF IT - and this reverses the decision written above.
426
+ *
427
+ * WHAT THE CLIENT SAW: their ThemeToggle is `relative grid place-items-center h-11 w-11
428
+ * rounded-full`. Everything survived except the word `grid`, and without it the preview
429
+ * drew a wide capsule with both icons stacked in the top-left corner instead of a round
430
+ * 2.75rem button - because `place-items-center` only does anything inside a grid or a
431
+ * flex box, and because `component-showcase.tsx` asks `base.display != null` to decide
432
+ * whether THEY declared the layout. With no display it answers no, and the shape WE infer
433
+ * beats the one they wrote - the exact inverse of the layout-priority law.
434
+ *
435
+ * `flex` and `flex-col` were held out on the reasoning that they already travel as the
436
+ * node's form (`row`/`stack`) and that declaring it twice starts a disagreement. The
437
+ * disagreement started anyway, because the form is a LOSSY reduction: it answers "across
438
+ * or down", so `grid` arrives as `stack` and `inline-grid` loses its inline-ness.
439
+ *
440
+ * The table itself lives in `display-from-class.ts` - ONE source, because the platform
441
+ * needs the same reading to reach whoever imported before this shipped, without asking
442
+ * them to re-import. The form stays derived from the same class string.
443
+ */
444
+ ...DISPLAY_UTILITIES,
428
445
  // Focus and the pointer: real decisions, and `outline-none` is 21 of theirs.
429
446
  "outline-none": { property: "outline", value: "none" },
430
447
  "cursor-pointer": { property: "cursor", value: "pointer" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.409",
3
+ "version": "0.16.411",
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": {