synthesisui 0.16.512 → 0.16.514

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.
@@ -17,6 +17,8 @@ import { readMode } from "../mode.js";
17
17
  import { namesRemoved, previousText, rulesTouchedBy, } from "../rule-touched.js";
18
18
  import { loadSystem } from "./doctor.js";
19
19
  import { takeGrowthNote } from "../what-grew.js";
20
+ import { readProjectConfig } from "../config.js";
21
+ import { generatedTells, tellsText } from "../generated-tells.js";
20
22
  const pass = () => ({ continue: true });
21
23
  const speak = (context) => ({
22
24
  continue: true,
@@ -765,6 +767,26 @@ async function endOfTurn(root, input) {
765
767
  ? await checkChanged(root, changed, mode, "turn")
766
768
  : { said: [] };
767
769
  /** O que o sync destacado do turno anterior mandou para o rascunho - dito uma vez (re-rodada, 28/09). */
770
+ /**
771
+ * OS SINAIS DE PÁGINA FEITA POR IA (jornada "as camadas de design com fonte", E04, 24/10): só os que as fontes
772
+ * concordam, cada um com a fonte e o que fazer no lugar - ver `generated-tells.ts`. Um sistema que veio do código dela
773
+ * (o `intent` do config) não ouve: o hábito do código dela vence a regra de fora.
774
+ */
775
+ const imported = Boolean((await readProjectConfig(root).catch(() => null))?.intent);
776
+ if (!imported && changed.length > 0) {
777
+ const found = [];
778
+ for (const { rel } of changed.filter((c) => SCREEN.test(c.rel)).slice(0, REFUSAL_CEILING)) {
779
+ const src = await readFile(join(root, rel), "utf8").catch(() => null);
780
+ if (src == null)
781
+ continue;
782
+ const tells = generatedTells(src);
783
+ if (tells.length > 0)
784
+ found.push({ rel, tells });
785
+ }
786
+ const text = tellsText(found);
787
+ if (text)
788
+ said.push(text);
789
+ }
768
790
  const grown = await takeGrowthNote(root).catch(() => null);
769
791
  if (grown)
770
792
  said.push(grown);
@@ -0,0 +1,122 @@
1
+ /**
2
+ * OS SINAIS DE PÁGINA FEITA POR IA - jornada "as camadas de design com fonte", E04 (24/10). Desenho aprovado em 16/10
3
+ * (o aviso do fim do turno, v4 de https://claude.ai/artifact/KLxJLPXaZokMjgQKBkcGtf).
4
+ *
5
+ * São só os sinais em que as fontes CONCORDAM, cada um com a fonte e o commit lidos - os mesmos do registro do site
6
+ * (`apps/web/src/lib/design-layers/registry.ts`) - e o que fazer no lugar. A checagem lê o código que o turno escreveu,
7
+ * não a tela: o que ela não consegue ler com segurança no código, ela não acusa.
8
+ *
9
+ * QUEM NÃO OUVE: um sistema que veio do código da pessoa (o `intent` do config, adopt/migrate). O desenho é dele: "sistema
10
+ * IMPORTADO: nada muda; ligar é dele" - o hábito do código dela vence a regra de fora.
11
+ */
12
+ const SOURCE = {
13
+ label: "taste-skill f624f37: on 16% of the strongest sites, 57% of the weakest",
14
+ gradientText: "impeccable 6af45ac and taste-skill f624f37: gradient text on 2% of the strongest sites, 13% of the weakest",
15
+ bounce: "impeccable 6af45ac and taste-skill f624f37 (motion.md: bounce and elastic easing are banned)",
16
+ sameFade: "taste-skill f624f37 (motion.md: the most cited motion tell) and design-motion-principles 4a9ca87",
17
+ };
18
+ const lineOf = (src, index) => src.slice(0, index).split("\n").length;
19
+ const excerpt = (s) => {
20
+ const one = s.replace(/\s+/g, " ").trim();
21
+ return one.length > 90 ? `${one.slice(0, 87)}...` : one;
22
+ };
23
+ /** O rótulo: um elemento curto em maiúsculas com espaçamento de letra, logo antes de um título. */
24
+ const LABEL = /<([a-z][\w.]*)\b[^>]*\bclassName=(?:"([^"]*)"|\{`([^`]*)`\})[^>]*>([^<{}]{1,200})<\/\1>\s*(?:\{\/\*[\s\S]*?\*\/\}\s*)?<(?:h[1-3]|Heading|DsHeading)\b/g;
25
+ /** O gradiente no texto: o fundo recortado no texto e um gradiente no mesmo lugar. */
26
+ const GRADIENT_TEXT_CLASS = /className=(?:"([^"]*)"|\{`([^`]*)`\})/g;
27
+ const GRADIENT_TEXT_STYLE = /background-clip:\s*text|backgroundClip:\s*["']text["']|WebkitBackgroundClip:\s*["']text["']/g;
28
+ /** O easing que quica: o do gsap pelo nome, e a curva que passa do fim. */
29
+ const BOUNCE_NAME = /\bease\s*:\s*["'](bounce|elastic|back)(?:\.\w+)?(?:\([^)]*\))?["']/g;
30
+ const OVERSHOOT = /cubic-bezier\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)/g;
31
+ /** A mesma entrada: cada seção marcada para entrar do mesmo jeito. */
32
+ const REVEAL = /\bdata-reveal\b|\bwhileInView=/g;
33
+ const SAME_FADE_AT = 4;
34
+ export function generatedTells(src) {
35
+ const tells = [];
36
+ for (const m of src.matchAll(LABEL)) {
37
+ const cls = m[2] ?? m[3] ?? "";
38
+ if (!/\buppercase\b/.test(cls) || !/\btracking-|letter-spacing/.test(cls))
39
+ continue;
40
+ /* O texto vem quebrado em linhas com recuo no JSX real ("Ceramics studio &amp; school", Kiln & Clay de 12/10): o
41
+ limite de rótulo é para o texto, não para o recuo. */
42
+ if (m[4].replace(/\s+/g, " ").trim().length > 48)
43
+ continue;
44
+ tells.push({
45
+ line: lineOf(src, m.index ?? 0),
46
+ what: "a small uppercase label above a heading",
47
+ excerpt: excerpt(`${m[4]}`),
48
+ instead: "put it in the heading or the first sentence, or drop it",
49
+ source: SOURCE.label,
50
+ });
51
+ }
52
+ for (const m of src.matchAll(GRADIENT_TEXT_CLASS)) {
53
+ const cls = m[1] ?? m[2] ?? "";
54
+ if (!/\bbg-clip-text\b/.test(cls) ||
55
+ !/\bbg-(gradient|linear|radial)-|\bfrom-/.test(cls))
56
+ continue;
57
+ tells.push({
58
+ line: lineOf(src, m.index ?? 0),
59
+ what: "gradient on text",
60
+ excerpt: excerpt(cls),
61
+ instead: "set the text in one colour from the system; let size and weight carry the emphasis",
62
+ source: SOURCE.gradientText,
63
+ });
64
+ }
65
+ for (const m of src.matchAll(GRADIENT_TEXT_STYLE)) {
66
+ const around = src.slice(Math.max(0, (m.index ?? 0) - 240), (m.index ?? 0) + 240);
67
+ if (!/gradient\(/.test(around))
68
+ continue;
69
+ tells.push({
70
+ line: lineOf(src, m.index ?? 0),
71
+ what: "gradient on text",
72
+ excerpt: excerpt(m[0]),
73
+ instead: "set the text in one colour from the system; let size and weight carry the emphasis",
74
+ source: SOURCE.gradientText,
75
+ });
76
+ }
77
+ for (const m of src.matchAll(BOUNCE_NAME)) {
78
+ tells.push({
79
+ line: lineOf(src, m.index ?? 0),
80
+ what: `a ${m[1]} easing`,
81
+ excerpt: excerpt(m[0]),
82
+ instead: "use the system's motion easing - things settle, they do not bounce",
83
+ source: SOURCE.bounce,
84
+ });
85
+ }
86
+ for (const m of src.matchAll(OVERSHOOT)) {
87
+ const y1 = Number(m[2]);
88
+ const y2 = Number(m[4]);
89
+ if (!(y1 > 1 || y1 < 0 || y2 > 1 || y2 < 0))
90
+ continue;
91
+ tells.push({
92
+ line: lineOf(src, m.index ?? 0),
93
+ what: "an easing that overshoots (it bounces past the end)",
94
+ excerpt: excerpt(m[0]),
95
+ instead: "use the system's motion easing - things settle, they do not bounce",
96
+ source: SOURCE.bounce,
97
+ });
98
+ }
99
+ const reveals = [...src.matchAll(REVEAL)];
100
+ if (reveals.length >= SAME_FADE_AT)
101
+ tells.push({
102
+ line: lineOf(src, reveals[0].index ?? 0),
103
+ what: `every section enters the same way (${reveals.length} marked to reveal)`,
104
+ excerpt: excerpt(reveals[0][0]),
105
+ instead: "give the entrance to the two or three sections that carry the story; the rest sit at rest",
106
+ source: SOURCE.sameFade,
107
+ });
108
+ return tells.sort((a, b) => a.line - b.line);
109
+ }
110
+ /** O bloco que o fim do turno entrega ao agente: por arquivo, o sinal, a linha, o que fazer no lugar e a fonte. */
111
+ export function tellsText(found) {
112
+ const total = found.reduce((n, f) => n + f.tells.length, 0);
113
+ if (total === 0)
114
+ return null;
115
+ const lines = [
116
+ `SynthesisUI · ${total} thing${total === 1 ? "" : "s"} on this page look${total === 1 ? "s" : ""} generated`,
117
+ ];
118
+ for (const { rel, tells } of found)
119
+ for (const t of tells)
120
+ lines.push("", ` ${rel}:${t.line} ${t.what}`, ` ${t.excerpt}`, ` -> ${t.instead}`, ` rule from ${t.source}`);
121
+ return lines.join("\n");
122
+ }
@@ -355,7 +355,13 @@
355
355
  * bloco do `CLAUDE.md` ensina a leitura da primeira tela em vez do `/sui-import-ds`; com o `rules.json` e o impeccable no
356
356
  * projeto, diz que o revisor dele é só da criação. Uma pasta escrita antes manda o agente importar e não fala do revisor.
357
357
  */
358
- export const MATERIALISER_SINCE = "0.16.508";
358
+ /**
359
+ * 0.16.508 -> 0.16.513 em 24/10, e o passo 1 dá **SIM** (a largura não vira espaço): o `init` declara
360
+ * `--max-width-<nome>: var(--container-<nome>)` para cada espaçamento do sistema com nome de tamanho de contêiner. Uma pasta
361
+ * escrita antes deixa `max-w-xl` virar `var(--spacing-xl)` - no Post Proof, 3rem e um título com uma palavra por linha.
362
+ * O conserto ficou numa branch local de 16/10 a 24/10 (jornada "o estado da arte do design"); 0.16.500 saiu sem ele.
363
+ */
364
+ export const MATERIALISER_SINCE = "0.16.513";
359
365
  /**
360
366
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
361
367
  *
@@ -592,7 +598,12 @@ export const COUNTED_DIFFERENTLY = "this run counts a value as named only when Y
592
598
  * E (E02, no mesmo PR) guarda o tamanho de título sem papel para a próxima leitura perguntar, e cobra o papel que a pessoa
593
599
  * escolheu no "não" (`decided` no rules.json): um hook pinado antes diz "papel novo" uma vez e nunca cobra.
594
600
  */
595
- export const CHECKER_SINCE = "0.16.506";
601
+ /**
602
+ * 0.16.506 -> 0.16.514 em 24/10 (jornada "as camadas de design com fonte", E04), e o passo 1 dá **SIM**: o fim do turno
603
+ * passa a acusar os sinais de página feita por IA em que as fontes concordam (o rótulo acima do título, o gradiente no
604
+ * texto, o easing que quica, a mesma entrada em toda seção - `generated-tells.ts`). Um hook pinado antes não os cobra.
605
+ */
606
+ export const CHECKER_SINCE = "0.16.514";
596
607
  /**
597
608
  * A ÚLTIMA VERSÃO EM QUE OS LEITORES PASSARAM A PRODUZIR UM CENSO DIFERENTE.
598
609
  *
@@ -79,6 +79,37 @@ function theirNameFor(ours) {
79
79
  return `--duration-${m[1]}`;
80
80
  return null;
81
81
  }
82
+ /**
83
+ * A LARGURA NÃO VIRA ESPAÇO (16/10, medido no experimento das camadas). No Tailwind v4, `max-w-<nome>` lê o
84
+ * `--spacing-<nome>` quando ele existe, antes do `--container-<nome>`: o Post Proof escreve `--spacing-xl: 3rem`, e o
85
+ * `max-w-xl` que o agente usava como largura de leitura (36rem) virava 3rem - títulos com uma palavra por linha.
86
+ * Declarar `--max-width-<nome>` faz o `max-w-*` voltar a ser largura; `px-xl`, `gap-xl` e o resto continuam no espaçamento
87
+ * do sistema (testado no Tailwind 4.3.3).
88
+ */
89
+ const CONTAINER_SIZES = new Set([
90
+ "3xs",
91
+ "2xs",
92
+ "xs",
93
+ "sm",
94
+ "md",
95
+ "lg",
96
+ "xl",
97
+ "2xl",
98
+ "3xl",
99
+ "4xl",
100
+ "5xl",
101
+ "6xl",
102
+ "7xl",
103
+ ]);
104
+ export function widthGuards(names) {
105
+ const out = [];
106
+ for (const n of names) {
107
+ const m = /^--spacing-(.+)$/.exec(n);
108
+ if (m && CONTAINER_SIZES.has(m[1]))
109
+ out.push(` --max-width-${m[1]}: var(--container-${m[1]});`);
110
+ }
111
+ return out;
112
+ }
82
113
  export async function writeNewProjectTheme(root, tokensCss, slug, version) {
83
114
  const sheets = await sheetsOf(root);
84
115
  const own = new Set();
@@ -116,6 +147,7 @@ export async function writeNewProjectTheme(root, tokensCss, slug, version) {
116
147
  open(`${slug} v${version}: the system's values under the names Tailwind turns into utilities. They are yours now - change a value here and every component follows. Written by \`init\`, because this project had no tokens of its own.`),
117
148
  "@theme {",
118
149
  ...lines,
150
+ ...widthGuards(system.keys()),
119
151
  "}",
120
152
  ...(next.length > 0 ? ["", ":root {", ...next, "}"] : []),
121
153
  end(slug),
@@ -286,7 +318,11 @@ async function readTheirs(root, slug) {
286
318
  * (`--container-*`), e o par vai para o `.lock`: a seção materializada escreve `var(--container-page)`, e a pessoa muda a
287
319
  * largura num lugar só. O valor é o MESMO do catálogo - `page-width-twin.spec.ts` compara os dois.
288
320
  */
289
- export const PAGE_WIDTH = { ours: "--ds-layout-page-width", theirs: "--container-page", value: "1440px" };
321
+ export const PAGE_WIDTH = {
322
+ ours: "--ds-layout-page-width",
323
+ theirs: "--container-page",
324
+ value: "1440px",
325
+ };
290
326
  function systemPairs(tokensCss, lookup = new Map()) {
291
327
  const { names, values } = tongueOf([], tokensCss);
292
328
  const out = [];
@@ -410,7 +446,13 @@ export async function writeBesideTheirs(root, tokensCss, slug, version, where) {
410
446
  begin(slug),
411
447
  open(`${slug} v${version}, beside your own tokens. The @theme adds only the names your project did not have; inside the data-theme selector below every value is the system's, including the names you already use, which keep their value everywhere else. Written by \`init\`, where you chose.`),
412
448
  ...(missing.length > 0
413
- ? ["@theme {", ...missing.map(([n, v]) => ` ${n}: ${v};`), "}", ""]
449
+ ? [
450
+ "@theme {",
451
+ ...missing.map(([n, v]) => ` ${n}: ${v};`),
452
+ ...widthGuards(missing.map(([n]) => n)),
453
+ "}",
454
+ "",
455
+ ]
414
456
  : []),
415
457
  `[data-theme="${slug}"] {`,
416
458
  ...scoped,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.512",
3
+ "version": "0.16.514",
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": {