synthesisui 0.16.91 → 0.16.93
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/import.js +50 -2
- package/dist/commands/mcp.js +88 -2
- package/dist/doctor/call-sites.js +52 -4
- package/dist/doctor/css-modules.js +1 -1
- package/dist/doctor/sketch.js +68 -0
- package/dist/doctor/transcribe.js +14 -0
- package/dist/skill-import.js +35 -0
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolveAnatomy, resolveFlatParts, safePartName, } from "../anatomy-read
|
|
|
4
4
|
import { readCredentials, readToken, resolveRegistry, sameRegistry, } from "../config.js";
|
|
5
5
|
import { architectureRule, describeArchitecture, describeChoice, detectArchitectures, } from "../doctor/architecture.js";
|
|
6
6
|
import { findBrokenRefs } from "../doctor/broken-refs.js";
|
|
7
|
-
import { nestingRules, propRules, readDefinitionProps, readNesting, } from "../doctor/call-sites.js";
|
|
7
|
+
import { nestingRules, propRules, readDefinitionProps, readNesting, readRuntime, } from "../doctor/call-sites.js";
|
|
8
8
|
import { asCatalogueTable, describeFallback, fetchCatalogue, } from "../doctor/catalogue-fetch.js";
|
|
9
9
|
import { describeClassStyle, detectClassStyle, } from "../doctor/class-style.js";
|
|
10
10
|
import { isNearDuplicate, isNeutral, lightness, } from "../doctor/color-distance.js";
|
|
@@ -18,6 +18,7 @@ import { describeConvention, describeRemainder, detectConventions, } from "../do
|
|
|
18
18
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
19
19
|
import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
|
|
20
20
|
import { describeSignals, emptySignals, finishSignals, readSignalsInto, } from "../doctor/signals.js";
|
|
21
|
+
import { sketchOf } from "../doctor/sketch.js";
|
|
21
22
|
import { buildTable } from "../doctor/tokens.js";
|
|
22
23
|
import { readInlineStyle, rootClasses, rootTag, transcribe, } from "../doctor/transcribe.js";
|
|
23
24
|
import { transcribeVariants } from "../doctor/variant-read.js";
|
|
@@ -292,6 +293,10 @@ export async function takeCensus(root, opts) {
|
|
|
292
293
|
* filtering them takes the vitrine from 271 to 29 (dono, 01/08).
|
|
293
294
|
*/
|
|
294
295
|
const screensOf = new Map();
|
|
296
|
+
/** Quem é dono do estado de cada componente, lido da definição. */
|
|
297
|
+
const runtimeOf = new Map();
|
|
298
|
+
/** Em quais projetos (--usage roots) cada componente aparece. */
|
|
299
|
+
const projectsOf = new Map();
|
|
295
300
|
/** Component → the axes its own `cva` declares. See `variant-read.ts`. */
|
|
296
301
|
const variantAxes = new Map();
|
|
297
302
|
/** How much came out of CSS Modules, for the coverage report. */
|
|
@@ -375,7 +380,11 @@ export async function takeCensus(root, opts) {
|
|
|
375
380
|
});
|
|
376
381
|
}
|
|
377
382
|
for (const d of found) {
|
|
378
|
-
|
|
383
|
+
const props = readDefinitionProps(d.name, src);
|
|
384
|
+
propsOf.set(d.name, props);
|
|
385
|
+
const runtime = readRuntime(props, src);
|
|
386
|
+
if (Object.keys(runtime).length > 0)
|
|
387
|
+
runtimeOf.set(d.name, runtime);
|
|
379
388
|
}
|
|
380
389
|
if (found.length > 0) {
|
|
381
390
|
countShape(src, found[0].name, shapes);
|
|
@@ -472,8 +481,16 @@ export async function takeCensus(root, opts) {
|
|
|
472
481
|
Object.keys(parts).length +
|
|
473
482
|
layers.length;
|
|
474
483
|
const tag = rootTag(src);
|
|
484
|
+
/**
|
|
485
|
+
* THE SKETCH travels with the look, so the reading phase never opens this
|
|
486
|
+
* file again: naming parts is a sentence over data the census already holds,
|
|
487
|
+
* and a file read whose content is here is a pipeline optimization failure -
|
|
488
|
+
* the owner's rule, adopted verbatim (dono, 01/08).
|
|
489
|
+
*/
|
|
490
|
+
const sketch = sketchOf(src);
|
|
475
491
|
if (size > 0 || tag) {
|
|
476
492
|
looks[found[0].name] = {
|
|
493
|
+
...(sketch.length > 0 ? { sketch } : {}),
|
|
477
494
|
...t,
|
|
478
495
|
base,
|
|
479
496
|
states,
|
|
@@ -589,6 +606,17 @@ export async function takeCensus(root, opts) {
|
|
|
589
606
|
usageSeen.add(rel);
|
|
590
607
|
usageProgress?.step(rel);
|
|
591
608
|
scanComponentsInto(usageTally, rel, src, [...shared, ...uInternal]);
|
|
609
|
+
// WHERE THE PAIRS LIVE. A library composes almost none of itself inside
|
|
610
|
+
// itself - `Button` goes inside `Modal` in the APP, not in the library
|
|
611
|
+
// source - so measuring nesting only on the defined loop reported zero
|
|
612
|
+
// companions on a 128-component library (e2e, 01/08).
|
|
613
|
+
readNesting(rel, src, nesting);
|
|
614
|
+
// Which PROJECT composes it - the credibility panel's number, per root.
|
|
615
|
+
for (const m of src.matchAll(/<([A-Z][A-Za-z0-9_]*)/g)) {
|
|
616
|
+
const at = projectsOf.get(m[1]) ?? new Set();
|
|
617
|
+
at.add(u.label);
|
|
618
|
+
projectsOf.set(m[1], at);
|
|
619
|
+
}
|
|
592
620
|
}
|
|
593
621
|
}
|
|
594
622
|
usageProgress?.done();
|
|
@@ -898,10 +926,30 @@ export async function takeCensus(root, opts) {
|
|
|
898
926
|
for (const pair of nestingRules(nesting, systemOwns)) {
|
|
899
927
|
laws.set(pair.component, [...(laws.get(pair.component) ?? []), pair.text]);
|
|
900
928
|
}
|
|
929
|
+
/**
|
|
930
|
+
* OS ENCAIXES, ESTRUTURADOS. nestingRules já media os pares e virava só prosa -
|
|
931
|
+
* prosa não é clicável nem consultável (dono, 01/08). O slug segue a mesma
|
|
932
|
+
* kebab-ização que o contrato usa.
|
|
933
|
+
*/
|
|
934
|
+
const companionsOf = new Map();
|
|
935
|
+
for (const pair of nestingRules(nesting, systemOwns)) {
|
|
936
|
+
const kebab = (n) => n.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
937
|
+
const list = companionsOf.get(pair.component) ?? [];
|
|
938
|
+
if (!list.includes(kebab(pair.child)))
|
|
939
|
+
list.push(kebab(pair.child));
|
|
940
|
+
companionsOf.set(pair.component, list);
|
|
941
|
+
}
|
|
901
942
|
const components = merged.map((c) => ({
|
|
902
943
|
...c,
|
|
903
944
|
...(verdicts.get(c.name) ?? {}),
|
|
904
945
|
...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
|
|
946
|
+
...(runtimeOf.has(c.name) ? { runtime: runtimeOf.get(c.name) } : {}),
|
|
947
|
+
...(companionsOf.has(c.name)
|
|
948
|
+
? { companions: companionsOf.get(c.name) }
|
|
949
|
+
: {}),
|
|
950
|
+
...(projectsOf.has(c.name)
|
|
951
|
+
? { projects: [...(projectsOf.get(c.name) ?? [])] }
|
|
952
|
+
: {}),
|
|
905
953
|
}));
|
|
906
954
|
const pkgRaw = await readFile(join(root, "package.json"), "utf8").catch(() => null);
|
|
907
955
|
let name = null;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { relative, resolve } from "node:path";
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { readToken, resolveRegistry } from "../config.js";
|
|
4
4
|
import { fileRequest } from "../doctor/requests.js";
|
|
5
5
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
@@ -340,6 +340,54 @@ async function validateRecipe(name, recipe) {
|
|
|
340
340
|
* a published package can be read by anyone, and it goes stale - the crosswalk's own copy
|
|
341
341
|
* had 26 names against a catalogue of 41.
|
|
342
342
|
*/
|
|
343
|
+
/**
|
|
344
|
+
* O CONTADOR DO AGENTE, fire-and-forget: a tela do UI Kit mostra "vezes que esta receita
|
|
345
|
+
* foi consultada pelo Agente", e este é o único ponto onde a consulta acontece de
|
|
346
|
+
* verdade. Nunca bloqueia a resposta: sem sessão ou sem rede, não conta e não quebra -
|
|
347
|
+
* telemetria que atrasa a ferramenta é telemetria que alguém desliga.
|
|
348
|
+
*/
|
|
349
|
+
function countAgentRead(root, component, action) {
|
|
350
|
+
void (async () => {
|
|
351
|
+
try {
|
|
352
|
+
const token = await readToken();
|
|
353
|
+
if (!token || !component)
|
|
354
|
+
return;
|
|
355
|
+
// O slug vem do .lock do sistema instalado - a mesma resolução do sync.
|
|
356
|
+
const dsDir = join(root, "_synthesisui", "ds");
|
|
357
|
+
let slug = null;
|
|
358
|
+
const entries = await readdir(dsDir, { withFileTypes: true }).catch(() => []);
|
|
359
|
+
for (const e of entries) {
|
|
360
|
+
if (!e.isDirectory())
|
|
361
|
+
continue;
|
|
362
|
+
const raw = await readFile(join(dsDir, e.name, ".lock"), "utf8").catch(() => "");
|
|
363
|
+
try {
|
|
364
|
+
const lock = JSON.parse(raw);
|
|
365
|
+
if (lock.slug) {
|
|
366
|
+
slug = lock.slug;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// lock ilegível - tenta o próximo
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (!slug)
|
|
375
|
+
return;
|
|
376
|
+
const base = resolveRegistry();
|
|
377
|
+
await fetch(`${base}/api/telemetry/agent-read`, {
|
|
378
|
+
method: "POST",
|
|
379
|
+
headers: {
|
|
380
|
+
Authorization: `Bearer ${token}`,
|
|
381
|
+
"content-type": "application/json",
|
|
382
|
+
},
|
|
383
|
+
body: JSON.stringify({ slug, component, action }),
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// offline não conta e não quebra nada
|
|
388
|
+
}
|
|
389
|
+
})();
|
|
390
|
+
}
|
|
343
391
|
async function askCatalogue(want, post) {
|
|
344
392
|
const token = await readToken();
|
|
345
393
|
if (!token) {
|
|
@@ -399,6 +447,42 @@ async function describeComponent(root, name) {
|
|
|
399
447
|
? "A molecule in your own ladder - built out of your atoms, so reach for those inside it."
|
|
400
448
|
: "An organism in your own ladder - a whole region of a screen, so it is more likely to be composed than to be composed into.");
|
|
401
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* O ESTADO, ANTES DO CÓDIGO. Um agente que não sabe quem é dono do estado inventa:
|
|
452
|
+
* um Toggle controlado ganha useState interno, um Input não-controlado ganha value
|
|
453
|
+
* sem onChange (dono, 01/08). Isto é a receita dizendo o comportamento.
|
|
454
|
+
*/
|
|
455
|
+
if (recipe.runtime) {
|
|
456
|
+
const r = recipe.runtime;
|
|
457
|
+
const lines = [];
|
|
458
|
+
if (r.stateOwner === "caller") {
|
|
459
|
+
lines.push("State: the CALLER owns it - controlled only. Wire the pair below; do not add internal state.");
|
|
460
|
+
}
|
|
461
|
+
else if (r.stateOwner === "self") {
|
|
462
|
+
lines.push("State: the component manages itself. Do not wire external state unless you need to read it.");
|
|
463
|
+
}
|
|
464
|
+
else if (r.stateOwner === "either") {
|
|
465
|
+
lines.push("State: both modes. Pass the controlled pair to own it, or the default* prop to seed it and let the component manage.");
|
|
466
|
+
}
|
|
467
|
+
for (const c of r.controlled ?? []) {
|
|
468
|
+
lines.push(` controlled: ${c.prop} + ${c.event}`);
|
|
469
|
+
}
|
|
470
|
+
if ((r.uncontrolled ?? []).length > 0) {
|
|
471
|
+
lines.push(` uncontrolled seed: ${(r.uncontrolled ?? []).join(", ")}`);
|
|
472
|
+
}
|
|
473
|
+
for (const n of r.notes ?? [])
|
|
474
|
+
lines.push(` ${n}`);
|
|
475
|
+
if (lines.length > 0)
|
|
476
|
+
out.push("", ...lines);
|
|
477
|
+
}
|
|
478
|
+
/** OS ENCAIXES - o que costuma ir junto, medido ou derivado dos layouts. */
|
|
479
|
+
if ((recipe.companions ?? []).length > 0) {
|
|
480
|
+
out.push("", `Goes with: ${(recipe.companions ?? []).join(", ")} - measured pairings; call describe_component on one before composing them together.`);
|
|
481
|
+
}
|
|
482
|
+
if (recipe.usageStats && recipe.usageStats.count > 0) {
|
|
483
|
+
const u = recipe.usageStats;
|
|
484
|
+
out.push("", `In use: ${u.count} instance${u.count === 1 ? "" : "s"} across ${u.files} file${u.files === 1 ? "" : "s"}${u.projects?.length ? ` in ${u.projects.join(", ")}` : ""}.`);
|
|
485
|
+
}
|
|
402
486
|
// WHAT IT SITS ON. 17 of 23 components in a real library carry no surface of
|
|
403
487
|
// their own, because the surface belongs to what they return.
|
|
404
488
|
const p = recipe.preview;
|
|
@@ -492,8 +576,10 @@ async function callTool(root, name, args) {
|
|
|
492
576
|
case "list_components":
|
|
493
577
|
return text(await listComponents(root));
|
|
494
578
|
case "describe_component":
|
|
579
|
+
countAgentRead(root, String(args.name ?? ""), "describe");
|
|
495
580
|
return text(await describeComponent(root, String(args.name ?? "")));
|
|
496
581
|
case "add_component":
|
|
582
|
+
countAgentRead(root, String(args.name ?? ""), "add");
|
|
497
583
|
return text(await addComponent(root, String(args.name ?? "")));
|
|
498
584
|
case "recipe_vocabulary":
|
|
499
585
|
return text(await recipeVocabulary());
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* version tried to consume the arrow, and `([^)]*)` swallowed the destructure it was
|
|
38
38
|
* trying to reach, so every arrow component read as declaring no props (spec, 01/08).
|
|
39
39
|
*/
|
|
40
|
-
const DESTRUCTURED = /(?:function\s+([A-Z][A-Za-z0-9_]*)\s*\(\s*\{([^}]*)\}|(?:const|let)\s+([A-Z][A-Za-z0-9_]*)\s*(?::[^=]*)?=\s*(?:function\s*)?\(\s*\{([^}]*)\})/g;
|
|
40
|
+
const DESTRUCTURED = /(?:function\s+([A-Z][A-Za-z0-9_]*)\s*\(\s*\{([^}]*)\}|(?:const|let)\s+([A-Z][A-Za-z0-9_]*)\s*(?::[^=]*)?=\s*(?:function\s*)?\(\s*\{([^}]*)\}|(?:const|let)\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:React\.)?(?:forwardRef|memo)(?:<[^>]*>)?\s*\(\s*(?:function\s*[A-Za-z0-9_]*\s*)?\(\s*\{([^}]*)\})/g;
|
|
41
41
|
/** `{...rest}` / `{...props}` on a JSX element - the forwarding, in the markup. */
|
|
42
42
|
const SPREAD_ONTO = /\{\s*\.\.\.\s*([A-Za-z_$][\w$]*)\s*\}/g;
|
|
43
43
|
/**
|
|
@@ -62,13 +62,16 @@ export function readDefinitionProps(name, source) {
|
|
|
62
62
|
*/
|
|
63
63
|
DESTRUCTURED.lastIndex = 0;
|
|
64
64
|
const matches = [...source.matchAll(DESTRUCTURED)];
|
|
65
|
-
|
|
65
|
+
// The third alternative is the forwardRef-inline idiom: a real Toggle is
|
|
66
|
+
// `const Toggle = React.forwardRef<…>(({ checked, onCheckedChange, … }, ref) =>`,
|
|
67
|
+
// and both earlier shapes read past it (test16, 01/08).
|
|
68
|
+
const exact = matches.filter((m) => (m[1] ?? m[3] ?? m[5]) === name);
|
|
66
69
|
const prefixed = matches.filter((m) => {
|
|
67
|
-
const found = m[1] ?? m[3];
|
|
70
|
+
const found = m[1] ?? m[3] ?? m[5];
|
|
68
71
|
return found !== name && found?.startsWith(name);
|
|
69
72
|
});
|
|
70
73
|
for (const m of exact.length > 0 ? exact : prefixed) {
|
|
71
|
-
const body = m[2] ?? m[4] ?? "";
|
|
74
|
+
const body = m[2] ?? m[4] ?? m[6] ?? "";
|
|
72
75
|
for (const raw of body.split(",")) {
|
|
73
76
|
const part = raw.trim();
|
|
74
77
|
if (!part)
|
|
@@ -187,3 +190,48 @@ export function propRules(name, read) {
|
|
|
187
190
|
}
|
|
188
191
|
return out;
|
|
189
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* WHO OWNS THE STATE - read off the definition, deterministically.
|
|
195
|
+
*
|
|
196
|
+
* The recipe said everything about the LOOK and nothing about behaviour, so an agent
|
|
197
|
+
* writing code in the client invented it: a controlled Toggle grew an internal
|
|
198
|
+
* useState, an uncontrolled Input got a value with no onChange (dono, 01/08). The
|
|
199
|
+
* patterns are machine-readable in the props themselves:
|
|
200
|
+
*
|
|
201
|
+
* checked + onChange the CONTROLLED pair - the caller owns the state
|
|
202
|
+
* defaultOpen, defaultValue the UNCONTROLLED seed - the component owns it
|
|
203
|
+
* useState in the body the component manages something itself
|
|
204
|
+
*
|
|
205
|
+
* `either` is both at once, which is exactly how a well-made input is written.
|
|
206
|
+
*/
|
|
207
|
+
export function readRuntime(read, source) {
|
|
208
|
+
const props = new Set(read.explicit);
|
|
209
|
+
const controlled = [];
|
|
210
|
+
for (const prop of read.explicit) {
|
|
211
|
+
if (/^(on[A-Z]|default[A-Z])/.test(prop))
|
|
212
|
+
continue;
|
|
213
|
+
const cap = prop[0].toUpperCase() + prop.slice(1);
|
|
214
|
+
// `checked`+`onChange` is the DOM's own spelling; `open`+`onOpenChange` is the
|
|
215
|
+
// primitive libraries'. Both are the same claim.
|
|
216
|
+
const event = [`on${cap}Change`, "onChange", `on${cap}`].find((e) => props.has(e));
|
|
217
|
+
if (event)
|
|
218
|
+
controlled.push({ prop, event });
|
|
219
|
+
}
|
|
220
|
+
const uncontrolled = read.explicit.filter((p) => /^default[A-Z]/.test(p));
|
|
221
|
+
const managesItself = /\buseState\s*[<(]/.test(source);
|
|
222
|
+
const out = {};
|
|
223
|
+
if (controlled.length > 0)
|
|
224
|
+
out.controlled = controlled;
|
|
225
|
+
if (uncontrolled.length > 0)
|
|
226
|
+
out.uncontrolled = uncontrolled;
|
|
227
|
+
if (controlled.length > 0 && (uncontrolled.length > 0 || managesItself)) {
|
|
228
|
+
out.stateOwner = "either";
|
|
229
|
+
}
|
|
230
|
+
else if (controlled.length > 0) {
|
|
231
|
+
out.stateOwner = "caller";
|
|
232
|
+
}
|
|
233
|
+
else if (managesItself || uncontrolled.length > 0) {
|
|
234
|
+
out.stateOwner = "self";
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ANATOMY SKETCH - the component's own markup, carried by the census so nobody
|
|
3
|
+
* reads the file twice.
|
|
4
|
+
*
|
|
5
|
+
* The owner's directive, verbatim in intent (dono, 01/08): the census is the ONLY
|
|
6
|
+
* source of truth about the project's tree; iterative per-layer sweeps are forbidden;
|
|
7
|
+
* and any file read whose content the census already carries is a pipeline
|
|
8
|
+
* optimization failure. The reading phase was the last consumer of raw files - it
|
|
9
|
+
* opened every component to see its JSX before naming parts, which is most of the
|
|
10
|
+
* tokens and most of the minutes of a run.
|
|
11
|
+
*
|
|
12
|
+
* The sketch is everything that reading extracted, minus the one thing that is
|
|
13
|
+
* genuinely a judgment: the NAMES. Tag, class string, depth, text, and the
|
|
14
|
+
* capitalised frontiers, per element, in document order. A reader holding this names
|
|
15
|
+
* `trigger`/`status-dot`/`panel` without opening anything - and naming is a sentence,
|
|
16
|
+
* not a sweep.
|
|
17
|
+
*
|
|
18
|
+
* GENERALIZED BY CONSTRUCTION: it reads JSX shape, not folder layout, so it works the
|
|
19
|
+
* same in an atomic library, a feature-foldered app, or a single flat directory.
|
|
20
|
+
*/
|
|
21
|
+
import { scanTags } from "./css-modules.js";
|
|
22
|
+
/** Past this a component is a page in disguise, and the gate already said no. */
|
|
23
|
+
const MAX_NODES = 60;
|
|
24
|
+
/** `className="..."` or className={"..."} / {`...`} with no interpolation. */
|
|
25
|
+
const CLASS_ATTR = /className=\{?["'`]([^"'`{}$]*)["'`]\}?/;
|
|
26
|
+
/**
|
|
27
|
+
* The sketch of one component file.
|
|
28
|
+
*
|
|
29
|
+
* Depth comes from the same tag scanner the CSS-module reader trusts (brace-counting,
|
|
30
|
+
* quote-aware, type-argument-proof). Text is captured only when it is a short literal
|
|
31
|
+
* sitting directly between an open and its close - dynamic children are the caller's,
|
|
32
|
+
* and the slot form already says so.
|
|
33
|
+
*/
|
|
34
|
+
export function sketchOf(source) {
|
|
35
|
+
const events = scanTags(source);
|
|
36
|
+
const out = [];
|
|
37
|
+
let depth = 0;
|
|
38
|
+
for (let i = 0; i < events.length; i++) {
|
|
39
|
+
const event = events[i];
|
|
40
|
+
if (event.kind === "close") {
|
|
41
|
+
depth = Math.max(0, depth - 1);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (out.length >= MAX_NODES)
|
|
45
|
+
break;
|
|
46
|
+
const node = { tag: event.tag, depth };
|
|
47
|
+
const cls = CLASS_ATTR.exec(event.body);
|
|
48
|
+
if (cls?.[1].trim())
|
|
49
|
+
node.classes = cls[1].trim();
|
|
50
|
+
// Literal text: the slice between this open and the next event, when it is prose.
|
|
51
|
+
const next = events[i + 1];
|
|
52
|
+
if (next) {
|
|
53
|
+
const between = source
|
|
54
|
+
.slice(event.at + event.tag.length, next.at)
|
|
55
|
+
.replace(/^[^>]*>/, "")
|
|
56
|
+
.trim();
|
|
57
|
+
if (between &&
|
|
58
|
+
between.length <= 60 &&
|
|
59
|
+
!between.includes("{") &&
|
|
60
|
+
!between.includes("<")) {
|
|
61
|
+
node.text = between;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
out.push(node);
|
|
65
|
+
depth += 1;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
@@ -610,6 +610,20 @@ export function readUtility(utility, declared) {
|
|
|
610
610
|
if (size)
|
|
611
611
|
return { property: axisProp, value: size };
|
|
612
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* THEIR OWN SPACING STEP IS A LIVE UTILITY IN v4: `gap-sm` compiles to
|
|
615
|
+
* `var(--spacing-sm)` the moment the theme declares it. A real Label uses exactly
|
|
616
|
+
* that, and the reading filed it under "known typos in their source" - the typo
|
|
617
|
+
* was ours (test15, 01/08). Their name first, the numeric formula second.
|
|
618
|
+
*/
|
|
619
|
+
const own = `--spacing-${sizeRest}`;
|
|
620
|
+
if (declared.has(own)) {
|
|
621
|
+
return {
|
|
622
|
+
property: spaceProp,
|
|
623
|
+
value: `{spacing.${sizeRest}}`,
|
|
624
|
+
token: own,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
613
627
|
const size = SPACING[sizeRest] ?? numericSpacing(sizeRest);
|
|
614
628
|
if (size)
|
|
615
629
|
return { property: spaceProp, value: size };
|
package/dist/skill-import.js
CHANGED
|
@@ -669,6 +669,41 @@ what shows while it loads a skeleton, a blur, or the surface colour
|
|
|
669
669
|
what shows when it is missing a missing asset must never look broken
|
|
670
670
|
\`\`\`
|
|
671
671
|
|
|
672
|
+
### The census is the ONLY source of truth - four universal rules
|
|
673
|
+
|
|
674
|
+
These hold in ANY project, whatever its folders, and they exist because a run spent most
|
|
675
|
+
of its tokens re-reading what the census already carried (dono, 01/08):
|
|
676
|
+
|
|
677
|
+
1. **No iterative sweeps by layer or folder.** Never walk atoms/, then molecules/, then
|
|
678
|
+
organisms/ reading files - the census walked everything once.
|
|
679
|
+
2. **Batch over the census's own data.** Everything you consume comes out of
|
|
680
|
+
\`_synthesisui/census.json\` - the looks, the sketch, the signals, the coverage.
|
|
681
|
+
3. **Metadata is one pass, already done.** Theme, fonts, page, libraries, architecture
|
|
682
|
+
arrive counted. You confirm in a sentence; you never re-derive.
|
|
683
|
+
4. **A file read whose content the census carries is a PIPELINE FAILURE.** Say so in
|
|
684
|
+
not-expressed.md when you catch yourself - that report is how the census grows.
|
|
685
|
+
|
|
686
|
+
### Name the parts from the SKETCH - the file is already in the census
|
|
687
|
+
|
|
688
|
+
Every component's look carries \`sketch\`: its own markup as data - tag, class string,
|
|
689
|
+
depth and text per element, in document order. That is everything you used to open the
|
|
690
|
+
file for, minus the one thing that is genuinely yours: the NAMES.
|
|
691
|
+
|
|
692
|
+
\`\`\`json
|
|
693
|
+
"sketch": [
|
|
694
|
+
{ "tag": "div", "depth": 0, "classes": "rounded-md border ..." },
|
|
695
|
+
{ "tag": "button", "depth": 1, "classes": "flex w-full ..." },
|
|
696
|
+
{ "tag": "span", "depth": 2, "classes": "size-2 rounded-full bg-success-500" },
|
|
697
|
+
{ "tag": "span", "depth": 2, "text": "Status" },
|
|
698
|
+
{ "tag": "ChevronDownIcon", "depth": 2 }
|
|
699
|
+
]
|
|
700
|
+
\`\`\`
|
|
701
|
+
|
|
702
|
+
Read that and write the anatomy: depth 1 is \`trigger\`, the dot is \`status-dot\`, the
|
|
703
|
+
capitalised tag is a frontier. **Do not open the component file** - if the sketch is
|
|
704
|
+
missing or visibly truncated (60-node cap), say so in not-expressed.md and only then
|
|
705
|
+
read, because that gap is the census's to close.
|
|
706
|
+
|
|
672
707
|
### What the CLI now reads for you, so do not spend a turn on it
|
|
673
708
|
|
|
674
709
|
**The rule under all of this: you are given EVIDENCE and asked to DECIDE.** You are never
|
package/package.json
CHANGED