synthesisui 0.16.280 → 0.16.282
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/commands/add.js +68 -13
- package/dist/commands/mcp.js +3 -3
- package/dist/fonts.js +23 -7
- package/dist/install-marks.js +1 -1
- package/dist/skill-configure.js +13 -0
- package/dist/skills.js +16 -0
- package/package.json +1 -1
package/dist/commands/add.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readdir, readFile, rm, writeFile, } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { syncClaudeMd } from "../claude-md.js";
|
|
4
4
|
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
@@ -126,16 +126,55 @@ async function readRootLock(path) {
|
|
|
126
126
|
}
|
|
127
127
|
const exists = (path) => access(path).then(() => true, () => false);
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
129
|
+
* ONDE AS ROTAS DELE MORAM DE VERDADE - e num monorepo elas não moram na raiz.
|
|
130
|
+
*
|
|
131
|
+
* Olhava só `app/` e `src/app/` na raiz. O `codelevel-monorepo` tem `apps/web/app` e
|
|
132
|
+
* `apps/landing/app`, então a busca voltava null, o `appDir` caía no default `"app"`, e TODO caminho
|
|
133
|
+
* impresso no setup apontava para uma pasta que não existe: `app/globals.css`, `app/layout.tsx`,
|
|
134
|
+
* `app/fonts.ts`. O cliente lê três instruções e nenhuma serve para o repositório dele.
|
|
135
|
+
*
|
|
136
|
+
* O que faz uma pasta ser raiz de app é o `layout` do App Router morar dentro dela - por isso o
|
|
137
|
+
* `apps/api` do Nest, que também é um workspace, não entra na lista.
|
|
138
|
+
*
|
|
139
|
+
* DEVOLVE TODAS, em ordem determinística: o setup é "once per app" e num monorepo isso é literal.
|
|
140
|
+
* Escolher uma calada seria acertar uma e errar a outra sem dizer qual.
|
|
132
141
|
*/
|
|
133
|
-
async function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
export async function detectAppDirs(root, pagesDir) {
|
|
143
|
+
const isAppRoot = async (dir) => {
|
|
144
|
+
if (!(await exists(join(root, dir))))
|
|
145
|
+
return false;
|
|
146
|
+
for (const ext of ["tsx", "jsx", "ts", "js"])
|
|
147
|
+
if (await exists(join(root, dir, `layout.${ext}`)))
|
|
148
|
+
return true;
|
|
149
|
+
return false;
|
|
150
|
+
};
|
|
151
|
+
const here = [pagesDir, `src/${pagesDir}`];
|
|
152
|
+
const nested = [];
|
|
153
|
+
for (const group of ["apps", "packages"]) {
|
|
154
|
+
let entries;
|
|
155
|
+
try {
|
|
156
|
+
entries = await readdir(join(root, group));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
for (const entry of entries.sort())
|
|
162
|
+
nested.push(`${group}/${entry}/${pagesDir}`, `${group}/${entry}/src/${pagesDir}`);
|
|
163
|
+
}
|
|
164
|
+
const found = [];
|
|
165
|
+
for (const dir of [...here, ...nested])
|
|
166
|
+
if (await isAppRoot(dir))
|
|
167
|
+
found.push(dir);
|
|
168
|
+
/**
|
|
169
|
+
* A RAIZ QUE EXISTE MAS NÃO TEM LAYOUT ainda é o melhor palpite de um app avulso - é o caso de um
|
|
170
|
+
* `create-next-app` no meio de uma migração. Sem isto, um projeto de app único perderia a detecção
|
|
171
|
+
* que já funcionava.
|
|
172
|
+
*/
|
|
173
|
+
if (found.length === 0)
|
|
174
|
+
for (const dir of here)
|
|
175
|
+
if (await exists(join(root, dir)))
|
|
176
|
+
return [dir];
|
|
177
|
+
return found;
|
|
139
178
|
}
|
|
140
179
|
/**
|
|
141
180
|
* Materializes a published DS into `_synthesisui/ds/<slug>/v<version>/`, points
|
|
@@ -355,10 +394,19 @@ export async function add(slug, opts) {
|
|
|
355
394
|
// Where the app actually lives (app/ vs src/app/) drives every printed
|
|
356
395
|
// path: the @import depth, the layout example, and where fonts.ts lands.
|
|
357
396
|
const projectConfig = await readProjectConfig(projectRoot);
|
|
358
|
-
const
|
|
359
|
-
|
|
397
|
+
const appDirs = await detectAppDirs(projectRoot, projectConfig.pagesDir);
|
|
398
|
+
/** Nenhuma pasta encontrada: os caminhos viram exemplo, e o texto abaixo diz isso. */
|
|
399
|
+
const appDir = appDirs[0] ?? projectConfig.pagesDir;
|
|
400
|
+
const appDirFound = appDirs.length > 0;
|
|
360
401
|
const importPrefix = "../".repeat(appDir.split("/").length);
|
|
361
402
|
console.log(section("One-time setup (once per app)"));
|
|
403
|
+
/**
|
|
404
|
+
* "ONCE PER APP" É LITERAL NUM MONOREPO, e calar os outros apps entrega metade da fiação. Os
|
|
405
|
+
* caminhos abaixo são de um só; sem esta linha o cliente conclui que o repositório inteiro está
|
|
406
|
+
* ligado quando só o primeiro está.
|
|
407
|
+
*/
|
|
408
|
+
if (appDirs.length > 1)
|
|
409
|
+
console.log(line(`(${appDirs.length} apps here: ${appDirs.join(", ")} - the paths below are for ${appDir}; repeat for the others.)`));
|
|
362
410
|
console.log(line(`1. Import the system in your GLOBAL stylesheet, e.g. ${appDir}/globals.css`));
|
|
363
411
|
console.log(line(` (the path is relative to that file - hence the leading ${importPrefix}):`));
|
|
364
412
|
console.log("");
|
|
@@ -434,7 +482,14 @@ export async function add(slug, opts) {
|
|
|
434
482
|
console.log(line(" Finish the wiring with two small edits:"));
|
|
435
483
|
}
|
|
436
484
|
else {
|
|
437
|
-
console.log(line(
|
|
485
|
+
console.log(line(appDirFound
|
|
486
|
+
? `3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`
|
|
487
|
+
: /**
|
|
488
|
+
* A FRASE DIZIA "already exists" PARA DOIS ESTADOS DIFERENTES, e um deles é o contrário
|
|
489
|
+
* disso: não achei a pasta do app. No monorepo dele o arquivo não existia e o terminal
|
|
490
|
+
* dizia que existia - o cliente não tem como saber que os caminhos acima são chute.
|
|
491
|
+
*/
|
|
492
|
+
`3. Load the type via next/font (I could not find your app folder from here, so the paths above are examples - create ${appDir}/fonts.ts wherever your app router lives):`));
|
|
438
493
|
console.log("");
|
|
439
494
|
console.log(snippet(nextFonts.fontsFile));
|
|
440
495
|
console.log("");
|
package/dist/commands/mcp.js
CHANGED
|
@@ -184,14 +184,14 @@ const TOOLS = [
|
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
name: "playbook",
|
|
187
|
-
description: "The skill playbooks (init, import, adapt, compose) - SERVED, not shipped, so they are always current and your context only carries the step you are on. Call with { skill } to get the framing and a table of contents; then fetch ONLY the chapter for your current step with { skill, section }. Never fetch more than the step needs. `compose` is the hierarchy to walk when this system has no recipe for what you were asked to build - the layers it DOES have, in order.",
|
|
187
|
+
description: "The skill playbooks (init, import, adapt, compose, configure) - SERVED, not shipped, so they are always current and your context only carries the step you are on. Call with { skill } to get the framing and a table of contents; then fetch ONLY the chapter for your current step with { skill, section }. Never fetch more than the step needs. `compose` is the hierarchy to walk when this system has no recipe for what you were asked to build - the layers it DOES have, in order.",
|
|
188
188
|
inputSchema: {
|
|
189
189
|
type: "object",
|
|
190
190
|
properties: {
|
|
191
191
|
skill: {
|
|
192
192
|
type: "string",
|
|
193
|
-
enum: ["init", "import", "adapt", "compose"],
|
|
194
|
-
description: "Which playbook. `compose` is the one for building something this system has no recipe for.",
|
|
193
|
+
enum: ["init", "import", "adapt", "compose", "configure"],
|
|
194
|
+
description: "Which playbook. `compose` is the one for building something this system has no recipe for; `configure` is the one for wiring an installed system into the app when its tokens are not reaching the browser.",
|
|
195
195
|
},
|
|
196
196
|
section: {
|
|
197
197
|
type: "string",
|
package/dist/fonts.js
CHANGED
|
@@ -12,6 +12,22 @@ const GENERIC_FAMILIES = new Set([
|
|
|
12
12
|
"inherit",
|
|
13
13
|
"initial",
|
|
14
14
|
]);
|
|
15
|
+
/**
|
|
16
|
+
* A PRIMEIRA FAMÍLIA DE UMA PILHA - `"Geist Mono", ui-monospace, monospace` é `Geist Mono`.
|
|
17
|
+
*
|
|
18
|
+
* O DOCUMENTO GUARDA A PILHA INTEIRA DE PROPÓSITO: é o que o código dele declara, e a lei 12 diz que
|
|
19
|
+
* o que ele declarou vence. O que não pode é a pilha virar NOME - e era o que acontecia aqui. O `add`
|
|
20
|
+
* do codelevel-ui imprimiu, em 22/08, um `fonts.ts` que não compila:
|
|
21
|
+
*
|
|
22
|
+
* export const mono = "Geist_Mono",_ui-monospace,_monospace({ // ← não é JavaScript
|
|
23
|
+
*
|
|
24
|
+
* e um href com `family="Geist+Mono",+ui-monospace,+monospace`, que o Google Fonts não resolve. Duas
|
|
25
|
+
* das quatro vagas do sistema dele saíam quebradas, e o cliente só descobriria colando o snippet.
|
|
26
|
+
*
|
|
27
|
+
* Um download pede UM nome de família; a pilha é o fallback do navegador e não viaja na URL. A mesma
|
|
28
|
+
* regra já existia em `read-unread.ts` na plataforma - existia num lugar e faltava neste.
|
|
29
|
+
*/
|
|
30
|
+
const firstFamily = (value) => (value.split(",")[0] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
15
31
|
/**
|
|
16
32
|
* TODA VAGA DE FONTE DO DOCUMENTO, dedup e sem os genéricos.
|
|
17
33
|
*
|
|
@@ -36,7 +52,7 @@ export function customFontFamilies(families) {
|
|
|
36
52
|
const seen = new Set();
|
|
37
53
|
const names = [];
|
|
38
54
|
for (const slot of familySlots(families)) {
|
|
39
|
-
const name = families[slot]
|
|
55
|
+
const name = firstFamily(families[slot] ?? "");
|
|
40
56
|
if (!name)
|
|
41
57
|
continue;
|
|
42
58
|
const key = name.toLowerCase();
|
|
@@ -68,17 +84,17 @@ export function googleFontsHref(families) {
|
|
|
68
84
|
export function nextFontSnippet(families, slug, appDir = "app") {
|
|
69
85
|
/** Toda vaga que o documento tem, não só as nossas três - ver `customFontFamilies`. */
|
|
70
86
|
const roles = familySlots(families).filter((role) => {
|
|
71
|
-
const name = families[role]
|
|
87
|
+
const name = firstFamily(families[role] ?? "");
|
|
72
88
|
return name && !GENERIC_FAMILIES.has(name.toLowerCase());
|
|
73
89
|
});
|
|
74
90
|
if (roles.length === 0)
|
|
75
91
|
return null;
|
|
76
|
-
const importName = (name) => name
|
|
92
|
+
const importName = (name) => firstFamily(name).replace(/ /g, "_");
|
|
77
93
|
const seen = new Map(); // family name -> const name
|
|
78
94
|
const importNames = [];
|
|
79
95
|
const consts = [];
|
|
80
96
|
for (const role of roles) {
|
|
81
|
-
const name = (families[role] ?? "")
|
|
97
|
+
const name = firstFamily(families[role] ?? "");
|
|
82
98
|
if (!seen.has(name)) {
|
|
83
99
|
seen.set(name, role);
|
|
84
100
|
importNames.push(importName(name));
|
|
@@ -101,13 +117,13 @@ export function nextFontSnippet(families, slug, appDir = "app") {
|
|
|
101
117
|
...consts,
|
|
102
118
|
];
|
|
103
119
|
const roleVar = (role) => {
|
|
104
|
-
const name = (families[role] ?? "")
|
|
120
|
+
const name = firstFamily(families[role] ?? "");
|
|
105
121
|
return `--font-ds-${seen.get(name)}`;
|
|
106
122
|
};
|
|
107
123
|
const layout = [
|
|
108
124
|
`// ${appDir}/layout.tsx`,
|
|
109
|
-
`import { ${[...new Set(roles.map((r) => seen.get((families[r] ?? "")
|
|
110
|
-
`<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get((families[r] ?? "")
|
|
125
|
+
`import { ${[...new Set(roles.map((r) => seen.get(firstFamily(families[r] ?? ""))))].join(", ")} } from "./fonts";`,
|
|
126
|
+
`<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get(firstFamily(families[r] ?? ""))}.variable}`))].join(" ")}\`}>`,
|
|
111
127
|
];
|
|
112
128
|
const css = [
|
|
113
129
|
`/* ${appDir}/globals.css - AFTER the tokens.css import */`,
|
package/dist/install-marks.js
CHANGED
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
* é sempre o bump deste PR - nunca o número que o `package.json` já carrega, porque alguém pode
|
|
129
129
|
* publicar no meio.
|
|
130
130
|
*/
|
|
131
|
-
export const MATERIALISER_SINCE = "0.16.
|
|
131
|
+
export const MATERIALISER_SINCE = "0.16.281";
|
|
132
132
|
/**
|
|
133
133
|
* A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
|
|
134
134
|
*
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* O STUB DA SKILL - o corpo é SERVIDO, não embarcado (R1, dono 16/08).
|
|
3
|
+
*
|
|
4
|
+
* Mesma doutrina das outras quatro: o frontmatter fica inteiro aqui, porque é a `description` que
|
|
5
|
+
* faz a skill ser invocada e ela não é segredo; a esteira mora em `apps/web/src/lib/ds/playbooks.ts`
|
|
6
|
+
* e chega pelo catalogue autenticado.
|
|
7
|
+
*
|
|
8
|
+
* O QUE ELA RESOLVE: um sistema instalado e sincronizado pode não mudar um pixel na tela dele. Até
|
|
9
|
+
* uma folha de estilo importar os tokens e um elemento carregar `data-ds`, nada chega ao navegador -
|
|
10
|
+
* e essa era a única parte da instalação que ninguém escrevia, só imprimia.
|
|
11
|
+
*/
|
|
12
|
+
export const CONFIGURE_SKILL_PATH = ".claude/skills/sui-configure-ds/SKILL.md";
|
|
13
|
+
export const CONFIGURE_SKILL = '---\nname: sui-configure-ds\ndescription: Wire an installed design system into the app so its tokens actually reach the browser - the stylesheet import, the data-ds scope, and the type. Use when a system is installed and the app does not look themed, when `doctor` reports "installed - but not wired up yet", or when somebody asks to set up / connect / configure the design system in their app ("configura o design system aqui", "why aren\'t the tokens working", "/sui-configure-ds"). Asks the doctor first and does nothing when the three checks already pass.\n---\n\n# Wiring the system into the app - served live\n\nThis playbook is served from the platform, not shipped in this file - it is\nalways current, and your context only carries the step you are on.\n\n1. Call the `playbook` tool on the `synthesisui` MCP server with\n { "skill": "configure" } - you get the framing and a table of contents.\n2. Fetch ONLY the chapter for the step you are on:\n { "skill": "configure", "section": "<id from the toc>" }. Never fetch more\n than the current step needs.\n3. Follow it exactly. When the step is done, fetch the next chapter.\n\nStart by running `npx synthesisui doctor`: the three lines it prints are what\ndecide the work, and three ticks means there is nothing to do.\n\nIf the tool answers that you are not signed in, run `npx synthesisui login`\nin the terminal and call it again. If the `synthesisui` MCP server is not\navailable at all, run `npx synthesisui connect`, restart the session, and\ninvoke this skill again.\n';
|
package/dist/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ADAPT_SKILL, ADAPT_SKILL_PATH } from "./skill-adapt.js";
|
|
2
2
|
import { COMPOSE_SKILL, COMPOSE_SKILL_PATH } from "./skill-compose.js";
|
|
3
|
+
import { CONFIGURE_SKILL, CONFIGURE_SKILL_PATH } from "./skill-configure.js";
|
|
3
4
|
import { IMPORT_SKILL, IMPORT_SKILL_PATH } from "./skill-import.js";
|
|
4
5
|
import { INIT_SKILL, INIT_SKILL_PATH } from "./skill-init.js";
|
|
5
6
|
/**
|
|
@@ -53,4 +54,19 @@ export const SKILLS = [
|
|
|
53
54
|
label: "/sui-compose",
|
|
54
55
|
what: "build what the system does not have, layer by layer",
|
|
55
56
|
},
|
|
57
|
+
/**
|
|
58
|
+
* A DE LIGAR O SISTEMA NO APP, e ela é a única aqui que fecha um passo que a esteira já sabia
|
|
59
|
+
* diagnosticar e não sabia resolver.
|
|
60
|
+
*
|
|
61
|
+
* O `add` IMPRIME a fiação, o `doctor` CONFERE a fiação com três ✓/✗, e nada a ESCREVE. Enquanto
|
|
62
|
+
* ela falta, os tokens não chegam ao navegador e o `doctor --fix` fica recusado de propósito - um
|
|
63
|
+
* fix num projeto sem fiação trocou 210 literais por variáveis que o navegador não resolvia e mudou
|
|
64
|
+
* a página (06/08).
|
|
65
|
+
*/
|
|
66
|
+
{
|
|
67
|
+
path: CONFIGURE_SKILL_PATH,
|
|
68
|
+
source: CONFIGURE_SKILL,
|
|
69
|
+
label: "/sui-configure-ds",
|
|
70
|
+
what: "the system wired into the app, so the tokens reach the browser",
|
|
71
|
+
},
|
|
56
72
|
];
|
package/package.json
CHANGED