synthesisui 0.16.480 → 0.16.481

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.
@@ -53,22 +53,92 @@ function prefixOf(own) {
53
53
  ]);
54
54
  return n > own.length / 2 && !generic.has(top) ? `--${top}-` : "--color-";
55
55
  }
56
- export async function planColourNames(root, slug, roleHex) {
56
+ /** O que cada papel pinta - o uso em que a troca vale. A principal vale em qualquer um. */
57
+ const KIND = {
58
+ primary: "any",
59
+ canvas: "background",
60
+ surface: "background",
61
+ foreground: "text",
62
+ muted: "text",
63
+ border: "border",
64
+ };
65
+ const kindOf = (prop) => /^(background|bg)/i.test(prop)
66
+ ? "background"
67
+ : /^color$/i.test(prop)
68
+ ? "text"
69
+ : /(border|outline)/i.test(prop)
70
+ ? "border"
71
+ : "other";
72
+ const SKIP_DIRS = new Set([
73
+ "node_modules",
74
+ ".next",
75
+ "dist",
76
+ "build",
77
+ "out",
78
+ "_synthesisui",
79
+ ".git",
80
+ "coverage",
81
+ ]);
82
+ /** As cores escritas à mão no código dele, com a propriedade que as pinta - CSS e objetos de estilo. */
83
+ async function usesIn(root, slug) {
84
+ const { readdir } = await import("node:fs/promises");
85
+ const { relative } = await import("node:path");
86
+ const out = [];
87
+ const walk = async (dir) => {
88
+ for (const e of await readdir(dir, { withFileTypes: true }).catch(() => [])) {
89
+ if (e.name.startsWith(".") || SKIP_DIRS.has(e.name))
90
+ continue;
91
+ const path = join(dir, e.name);
92
+ if (e.isDirectory())
93
+ await walk(path);
94
+ else if (/\.(css|scss|tsx|jsx)$/i.test(e.name)) {
95
+ const src = (await readFile(path, "utf8").catch(() => "")).replace(blockMarks.END_RE(slug), "\n");
96
+ const file = relative(root, path).split("\\").join("/");
97
+ for (const m of src.matchAll(/([a-zA-Z-]+)\s*:\s*["']?[^;{}\n"']*?(#[0-9a-fA-F]{6})\b/g))
98
+ if (!m[1].startsWith("--"))
99
+ out.push({
100
+ hex: m[2].toLowerCase(),
101
+ kind: kindOf(m[1]),
102
+ file,
103
+ });
104
+ }
105
+ }
106
+ };
107
+ await walk(root);
108
+ return out;
109
+ }
110
+ export async function planColourNames(root, slug, roleHex,
111
+ /** As cores de cada papel ANTES da escolha - a versão instalada antes do `add`. Sem ela, vale a cor nova. */
112
+ before = {}) {
57
113
  const v = await readVocabulary(root, slug);
58
114
  const prefix = prefixOf([...v.own.keys()]);
115
+ const uses = await usesIn(root, slug);
116
+ const low = (x) => (x ?? "").trim().toLowerCase();
117
+ const taken = new Set();
59
118
  const names = SIX_ROLES.filter((r) => roleHex[r]).map((role) => {
60
- const name = `${prefix}${WORD[role].word}`;
61
119
  const value = roleHex[role];
120
+ const old = low(before[role] ?? value);
121
+ /**
122
+ * O NOME DELE PRIMEIRO - o b1 declara `--accent: #c2410c`, e `--color-primary` com o mesmo valor ao lado seria o
123
+ * vocabulário dele duplicado pelo nosso. Vale o nome que segura a cor de hoje, ou a nova.
124
+ */
125
+ const his = [...v.own].find(([n, val]) => !taken.has(n) && (low(val) === old || low(val) === low(value)))?.[0];
126
+ const name = his ?? `${prefix}${WORD[role].word}`;
127
+ taken.add(name);
62
128
  const have = v.own.get(name);
129
+ const kind = KIND[role];
130
+ const painted = uses.some((u) => u.hex === old && (kind === "any" || u.kind === kind));
63
131
  return {
64
132
  role,
65
133
  name,
66
134
  value,
67
135
  job: WORD[role].job,
68
- exists: have != null && have.toLowerCase() === value.toLowerCase(),
136
+ exists: have != null && low(have) === low(value),
137
+ theirs: his != null,
138
+ was: painted ? old : null,
69
139
  };
70
140
  });
71
- return { file: v.global, prefix, names, css: v.css };
141
+ return { file: v.global, prefix, names, css: v.css, uses };
72
142
  }
73
143
  /** Escreve o bloco depois do sim dele - `ask` devolve a resposta; com o não, o arquivo fica como estava. */
74
144
  export async function writeColourNames(root, slug, plan, ask) {
@@ -94,11 +164,43 @@ export async function writeColourNames(root, slug, plan, ask) {
94
164
  await writeFile(path, `${base}\n${block}\n`, "utf8");
95
165
  return { written: true };
96
166
  }
97
- /** O PROMPT PRONTO - o que ele cola no agente. Cada valor à mão vira o nome; nenhuma cor nova, nenhum nome novo. */
167
+ /**
168
+ * O PROMPT PRONTO - o que ele cola no agente.
169
+ *
170
+ * A TROCA É PELA COR DE HOJE, e não pela nova: pela galeria, a cor nova não existe no código dele (medido em 28/09 com
171
+ * a Onyx no e06-galeria), e o que precisa mudar é o teal que ele escreve à mão. E É PELO USO: o branco do b2 é o fundo
172
+ * do cartão e o texto do botão ao mesmo tempo, e só o primeiro é a superfície.
173
+ */
98
174
  export function agentPrompt(plan) {
175
+ const where = {
176
+ any: "",
177
+ background: "as a background - ",
178
+ text: "as text - ",
179
+ border: "as a border - ",
180
+ other: "",
181
+ };
182
+ const lines = plan.names
183
+ .filter((n) => n.was)
184
+ .map((n) => ` ${n.was} -> var(${n.name}) (${where[KIND[n.role]]}${n.job})`);
185
+ const leave = [];
186
+ for (const n of plan.names) {
187
+ if (!n.was || KIND[n.role] === "any")
188
+ continue;
189
+ const claimed = new Set(plan.names.filter((m) => m.was === n.was).map((m) => KIND[m.role]));
190
+ const other = plan.uses.filter((u) => u.hex === n.was && !claimed.has(u.kind) && u.kind !== "other");
191
+ for (const kind of new Set(other.map((u) => u.kind))) {
192
+ const files = [
193
+ ...new Set(other.filter((u) => u.kind === kind).map((u) => u.file)),
194
+ ];
195
+ leave.push(` leave ${n.was} where it is ${kind} (${files.join(", ")}) - that is not the ${WORD[n.role].word}`);
196
+ }
197
+ }
99
198
  return [
100
199
  `Replace the colours written by hand in this project with the names declared in ${plan.file ?? "the global stylesheet"}:`,
101
- ...plan.names.map((n) => ` ${n.value} -> var(${n.name}) (${n.job})`),
102
- "Change only a value that is exactly one of these. Leave every other colour as it is, and do not add or invent a name or a colour - these six were chosen by the person.",
200
+ ...(lines.length > 0
201
+ ? lines
202
+ : [" (your code writes none of these by hand - nothing to replace)"]),
203
+ ...leave,
204
+ "Change a value only where it is used as the line says. Leave every other colour as it is, and do not add or invent a name or a colour - these six were chosen by the person.",
103
205
  ].join("\n");
104
206
  }
@@ -14,15 +14,20 @@ import { add } from "./add.js";
14
14
  export async function colours(asked, opts) {
15
15
  const root = opts.dir ?? process.cwd();
16
16
  const slug = asked ?? (await onlyInstalled(root));
17
+ const slugDir = join(root, "_synthesisui", "ds", slug);
18
+ /**
19
+ * AS CORES DE ANTES, lidas antes do `add` - é o que o código dele pinta hoje. Pela galeria, a cor nova não existe no
20
+ * código dele, e a troca que o prompt pede é da antiga (etapa 01 de "as cores voltam certas", 28/09).
21
+ */
22
+ const before = await installedRoles(slugDir);
17
23
  /** A versão publicada pelo "Use these colours" - a instalada aqui ainda é a de antes. */
18
24
  await add(slug, { registry: opts.registry, dir: root, setupHints: false });
19
- const slugDir = join(root, "_synthesisui", "ds", slug);
20
25
  const lock = JSON.parse(await readFile(join(slugDir, ".lock"), "utf8").catch(() => "{}"));
21
26
  const tokens = typeof lock.version === "number"
22
27
  ? await readFile(join(slugDir, `v${lock.version}`, "tokens.css"), "utf8").catch(() => "")
23
28
  : "";
24
29
  const roleHex = roleColours(tokens);
25
- const plan = await planColourNames(root, slug, roleHex);
30
+ const plan = await planColourNames(root, slug, roleHex, before);
26
31
  console.log("");
27
32
  console.log(section("Your six colours, back in your code"));
28
33
  if (!plan.file) {
@@ -31,7 +36,7 @@ export async function colours(asked, opts) {
31
36
  else {
32
37
  console.log(body(`These go into ${plan.file}, under names your code can use:`));
33
38
  for (const n of plan.names)
34
- console.log(body(` ${n.name}: ${n.value} ${n.exists ? "(already there)" : `- ${n.job}`}`));
39
+ console.log(body(` ${n.name}: ${n.value} ${n.exists ? "(already there, your name)" : n.theirs ? `(your name, new value) - ${n.job}` : `- ${n.job}`}`));
35
40
  }
36
41
  const result = await writeColourNames(root, slug, plan, () => opts.yes
37
42
  ? Promise.resolve(true)
@@ -73,6 +78,13 @@ export function roleColours(tokensCss) {
73
78
  }
74
79
  return out;
75
80
  }
81
+ /** As seis cores da versão que está instalada agora - `{}` quando ainda não há nenhuma. */
82
+ async function installedRoles(slugDir) {
83
+ const lock = JSON.parse(await readFile(join(slugDir, ".lock"), "utf8").catch(() => "{}"));
84
+ if (typeof lock.version !== "number")
85
+ return {};
86
+ return roleColours(await readFile(join(slugDir, `v${lock.version}`, "tokens.css"), "utf8").catch(() => ""));
87
+ }
76
88
  async function onlyInstalled(root) {
77
89
  const entries = await readdir(join(root, "_synthesisui", "ds"), {
78
90
  withFileTypes: true,
@@ -5209,6 +5209,8 @@ export async function runImport(opts) {
5209
5209
  // where the person is standing, and because a number they can check beats an
5210
5210
  // invitation they have to trust.
5211
5211
  const n = payload?.normalize;
5212
+ /** Com uma v2 para aprovar, o link é o lugar da decisão, e ele fica mesmo com a porta das cores. */
5213
+ const approveThere = Boolean(n?.decisions && n.decisions > 0);
5212
5214
  if (n?.decisions && n.decisions > 0) {
5213
5215
  console.log("");
5214
5216
  console.log(section("What a v2 would normalize"));
@@ -5223,11 +5225,15 @@ export async function runImport(opts) {
5223
5225
  console.log("");
5224
5226
  console.log(body("Nothing was changed. You approve it there:"));
5225
5227
  }
5226
- else if (payload?.url) {
5228
+ else if (payload?.url && !payload?.coloursDoor) {
5227
5229
  console.log("");
5228
5230
  console.log(body("Name it, see it, and govern it:"));
5229
5231
  }
5230
- if (payload?.url)
5232
+ /**
5233
+ * UM CONVITE SÓ (etapa 04 de "as cores voltam certas", 28/09): com a porta das cores, o próximo passo é escolher as
5234
+ * cores, e o link do tour competia com ele no fim da saída.
5235
+ */
5236
+ if (payload?.url && (approveThere || !payload?.coloursDoor))
5231
5237
  console.log(body(` ${paint.blue(payload.url)}`));
5232
5238
  console.log("");
5233
5239
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.480",
3
+ "version": "0.16.481",
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": {