synthesisui 0.16.62 → 0.16.65
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 +66 -0
- package/dist/doctor/broken-refs.js +75 -0
- package/dist/doctor/idiom.js +132 -0
- package/dist/skill-import.js +98 -19
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, dirname, join, relative } from "node:path";
|
|
3
3
|
import { readCredentials, readToken, resolveRegistry, sameRegistry, } from "../config.js";
|
|
4
|
+
import { findBrokenRefs } from "../doctor/broken-refs.js";
|
|
4
5
|
import { isNearDuplicate, isNeutral, lightness, } from "../doctor/color-distance.js";
|
|
5
6
|
import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
|
|
6
7
|
import { crosswalk, observedRules } from "../doctor/crosswalk.js";
|
|
7
8
|
import { reconcile, scanDefinitions, } from "../doctor/definitions-scan.js";
|
|
9
|
+
import { describeConvention, detectConventions, IDIOM_LABEL, } from "../doctor/idiom.js";
|
|
8
10
|
import { diagnose, scanSource } from "../doctor/scan.js";
|
|
9
11
|
import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
|
|
10
12
|
import { buildTable } from "../doctor/tokens.js";
|
|
@@ -273,11 +275,15 @@ export async function takeCensus(root) {
|
|
|
273
275
|
const tally = emptyTally();
|
|
274
276
|
const internal = await internalSpecifiers(root);
|
|
275
277
|
const defined = [];
|
|
278
|
+
// Kept for the reference check below, which needs every file at once: a name
|
|
279
|
+
// is only broken if NOTHING declares it, anywhere.
|
|
280
|
+
const sources = [];
|
|
276
281
|
for await (const file of walk(root)) {
|
|
277
282
|
const src = await readFile(file, "utf8").catch(() => "");
|
|
278
283
|
if (!src)
|
|
279
284
|
continue;
|
|
280
285
|
const rel = relative(root, file);
|
|
286
|
+
sources.push({ file: rel, source: src });
|
|
281
287
|
reports.push(scanSource(rel, src, table));
|
|
282
288
|
// Stories and tests compose components to SHOW them; counting those as the
|
|
283
289
|
// product's own composition is the same lie the colour census told before
|
|
@@ -368,10 +374,21 @@ export async function takeCensus(root) {
|
|
|
368
374
|
// an unreadable package.json costs the name, not the run
|
|
369
375
|
}
|
|
370
376
|
}
|
|
377
|
+
// Every name their CSS defines, from the same harvest the token table came
|
|
378
|
+
// from - so a reference is judged against what actually exists, not against
|
|
379
|
+
// the subset we managed to read as a ramp.
|
|
380
|
+
const declaredNames = new Set();
|
|
381
|
+
for (const m of css.matchAll(/(--[a-zA-Z0-9_-]+)\s*:/g)) {
|
|
382
|
+
declaredNames.add(m[1]);
|
|
383
|
+
}
|
|
384
|
+
const brokenRefs = findBrokenRefs(sources, declaredNames);
|
|
385
|
+
const conventions = detectConventions(sources);
|
|
371
386
|
return {
|
|
372
387
|
census: 1,
|
|
373
388
|
project: { name, stack: await detectStack(root) },
|
|
374
389
|
declared: Object.fromEntries(table.byName),
|
|
390
|
+
...(brokenRefs.length > 0 ? { brokenRefs } : {}),
|
|
391
|
+
...(conventions.length > 0 ? { conventions } : {}),
|
|
375
392
|
...(schemes.alt.size > 0
|
|
376
393
|
? { declaredAlt: Object.fromEntries(schemes.alt) }
|
|
377
394
|
: {}),
|
|
@@ -435,6 +452,55 @@ function summarize(c) {
|
|
|
435
452
|
console.log(body(`${paint.strong(v.value)} ${paint.faint(`${v.kind}, ${v.count}× in ${v.files} file${v.files === 1 ? "" : "s"}`)}`));
|
|
436
453
|
}
|
|
437
454
|
}
|
|
455
|
+
sayConventions(c);
|
|
456
|
+
sayBrokenRefs(c);
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* THEIR VOCABULARY, MEASURED. This section exists so every later finding can be
|
|
460
|
+
* phrased as "you already do X, and here you did not" instead of "adopt ours".
|
|
461
|
+
*/
|
|
462
|
+
function sayConventions(c) {
|
|
463
|
+
const list = c.conventions ?? [];
|
|
464
|
+
if (list.length === 0)
|
|
465
|
+
return;
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log(section("How you reference a value"));
|
|
468
|
+
for (const conv of list) {
|
|
469
|
+
console.log(body(describeConvention(conv)));
|
|
470
|
+
const off = conv.offIdiom.filter((o) => o.count > 0).slice(0, 3);
|
|
471
|
+
if (off.length > 0) {
|
|
472
|
+
console.log(body(paint.faint(` and ${off.map((o) => `${o.count} ${IDIOM_LABEL[o.idiom]}`).join(", ")}`)));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
console.log("");
|
|
476
|
+
console.log(body(paint.faint("Nothing here is a rule we brought. It is what your own files do most, so the rest can be named against it.")));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* NOT DRIFT - BROKEN. A `var()` no declaration answers renders nothing at all,
|
|
480
|
+
* so this goes above everything else the census has to say. It is also the one
|
|
481
|
+
* finding that needs no agreement with us: the convention being broken is
|
|
482
|
+
* theirs.
|
|
483
|
+
*/
|
|
484
|
+
function sayBrokenRefs(c) {
|
|
485
|
+
const broken = c.brokenRefs ?? [];
|
|
486
|
+
if (broken.length === 0)
|
|
487
|
+
return;
|
|
488
|
+
const uses = broken.reduce((n, b) => n + b.count, 0);
|
|
489
|
+
console.log("");
|
|
490
|
+
console.log(section("These resolve to nothing"));
|
|
491
|
+
console.log(body(`${uses} reference${uses === 1 ? "" : "s"} to ${broken.length} token${broken.length === 1 ? "" : "s"} your CSS never declares. A ${paint.strong("var()")} with no declaration paints no colour - not a different colour.`));
|
|
492
|
+
console.log("");
|
|
493
|
+
for (const b of broken.slice(0, 8)) {
|
|
494
|
+
const where = `${b.count}× in ${b.files} file${b.files === 1 ? "" : "s"}`;
|
|
495
|
+
console.log(body(b.meant
|
|
496
|
+
? `${paint.strong(`var(${b.name})`)} ${paint.faint(where)} - you declare ${paint.strong(b.meant)}`
|
|
497
|
+
: `${paint.strong(`var(${b.name})`)} ${paint.faint(`${where}, and nothing close is declared`)}`));
|
|
498
|
+
}
|
|
499
|
+
if (broken.length > 8) {
|
|
500
|
+
console.log(body(paint.faint(`(${broken.length - 8} more)`)));
|
|
501
|
+
}
|
|
502
|
+
console.log("");
|
|
503
|
+
console.log(body(paint.faint("Yours to fix in your own words - nothing here asks you to adopt ours.")));
|
|
438
504
|
}
|
|
439
505
|
/**
|
|
440
506
|
* The contract an agent needs, printed where an agent will read it.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOKENS REFERENCED AND NEVER DECLARED.
|
|
3
|
+
*
|
|
4
|
+
* Not drift, not taste - a variable that resolves to nothing. `var(--ocean-50)`
|
|
5
|
+
* in a project whose token is `--color-ocean-50` renders no colour at all, and
|
|
6
|
+
* the component ships with no background.
|
|
7
|
+
*
|
|
8
|
+
* Found by accident (dono, 01/08) while sizing something else: seven of nine
|
|
9
|
+
* references in one real `ImageUploader` were missing the `color-` prefix, and
|
|
10
|
+
* it is live. Nobody noticed because a missing background looks like a design
|
|
11
|
+
* choice.
|
|
12
|
+
*
|
|
13
|
+
* This is the highest-value thing a first read can say, and it costs nothing to
|
|
14
|
+
* find: it is arithmetic over two sets. It also needs no adoption of anything of
|
|
15
|
+
* ours - the convention being violated is THEIRS. A person can act on it in
|
|
16
|
+
* their own vocabulary, in the next commit, without agreeing to a single idea
|
|
17
|
+
* this product has (dono, 01/08: "a normalização será de acordo com cada
|
|
18
|
+
* projeto").
|
|
19
|
+
*/
|
|
20
|
+
/** `var(--x)`, including inside a Tailwind arbitrary value: `bg-[var(--x)]`. */
|
|
21
|
+
const VAR_REF = /var\(\s*(--[a-zA-Z0-9_-]+)/g;
|
|
22
|
+
/**
|
|
23
|
+
* PREFIXES A DECLARATION MAY CARRY that a reference dropped.
|
|
24
|
+
*
|
|
25
|
+
* Tailwind v4's `@theme` is the reason this happens at all: declaring
|
|
26
|
+
* `--color-ocean-50` publishes the variable under that full name AND the
|
|
27
|
+
* utility `bg-ocean-50`, so a person who reads their own utilities and then
|
|
28
|
+
* reaches for `var()` writes the utility's name and gets nothing.
|
|
29
|
+
*/
|
|
30
|
+
const NAMESPACES = [
|
|
31
|
+
"color",
|
|
32
|
+
"spacing",
|
|
33
|
+
"radius",
|
|
34
|
+
"font",
|
|
35
|
+
"text",
|
|
36
|
+
"shadow",
|
|
37
|
+
"ease",
|
|
38
|
+
"animate",
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Every reference no declaration answers, commonest first.
|
|
42
|
+
*
|
|
43
|
+
* `declared` is the set of names their own CSS defines, verbatim. Anything the
|
|
44
|
+
* scan could not attribute is left out rather than guessed at.
|
|
45
|
+
*/
|
|
46
|
+
export function findBrokenRefs(sources, declared) {
|
|
47
|
+
const seen = new Map();
|
|
48
|
+
for (const { file, source } of sources) {
|
|
49
|
+
for (const m of source.matchAll(VAR_REF)) {
|
|
50
|
+
const name = m[1];
|
|
51
|
+
if (declared.has(name))
|
|
52
|
+
continue;
|
|
53
|
+
const hit = seen.get(name) ?? { count: 0, files: new Set() };
|
|
54
|
+
hit.count += 1;
|
|
55
|
+
hit.files.add(file);
|
|
56
|
+
seen.set(name, hit);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const [name, hit] of seen) {
|
|
61
|
+
const bare = name.slice(2);
|
|
62
|
+
const meant = NAMESPACES.map((ns) => `--${ns}-${bare}`).find((c) => declared.has(c));
|
|
63
|
+
out.push({
|
|
64
|
+
name,
|
|
65
|
+
count: hit.count,
|
|
66
|
+
files: hit.files.size,
|
|
67
|
+
...(meant ? { meant } : {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// A broken reference somebody typed nine times is worse than one typed once,
|
|
71
|
+
// and one we can name a target for is actionable before one we cannot.
|
|
72
|
+
return out.sort((a, b) => Number(b.meant != null) - Number(a.meant != null) ||
|
|
73
|
+
b.count - a.count ||
|
|
74
|
+
a.name.localeCompare(b.name));
|
|
75
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HOW THIS PROJECT REFERENCES A VALUE - and it is not one answer per project.
|
|
3
|
+
*
|
|
4
|
+
* Measured on a real monorepo (dono, 01/08), scoping by project looked like
|
|
5
|
+
* divergence and was not: `packages/ui` read 52% `var()` and 39% utility, which
|
|
6
|
+
* is one directory holding `.tsx` and `.scss` in the same bucket. Split by
|
|
7
|
+
* LANGUAGE the picture is unambiguous:
|
|
8
|
+
*
|
|
9
|
+
* .tsx 80% tailwind utility
|
|
10
|
+
* .scss 66% css var()
|
|
11
|
+
* .css 67% css var()
|
|
12
|
+
*
|
|
13
|
+
* So the convention belongs to the language, the project is already chosen by
|
|
14
|
+
* `--scope`, and neither needs a person to pick it - it is measured. What a
|
|
15
|
+
* person gets instead is the useful half: what does NOT follow the idiom their
|
|
16
|
+
* own files overwhelmingly use.
|
|
17
|
+
*
|
|
18
|
+
* WHY THIS MATTERS MORE THAN OUR TOKENS (dono, 01/08). A report that says "use
|
|
19
|
+
* `{color.ocean.500}`" asks somebody to restructure a repo to suit us - in a
|
|
20
|
+
* shadcn project that is a migration with real incompatibility risk and a commit
|
|
21
|
+
* nobody wants to review. A report that says "you write `bg-ocean-500` in 80% of
|
|
22
|
+
* your JSX and here you wrote a hex" asks for nothing. Same decision, their
|
|
23
|
+
* vocabulary, a commit the size of the problem.
|
|
24
|
+
*
|
|
25
|
+
* It also explained the worst bug this census has found. `bg-[var(--ocean-50)]`
|
|
26
|
+
* is not merely a dropped `color-` prefix: it is the CSS idiom used inside the
|
|
27
|
+
* JSX idiom - a change of language mid-sentence - and the `.scss` beside it
|
|
28
|
+
* spells the same token correctly, because there that language is the right one.
|
|
29
|
+
*/
|
|
30
|
+
/** How each idiom reads, for the report. */
|
|
31
|
+
export const IDIOM_LABEL = {
|
|
32
|
+
"tailwind-utility": "Tailwind utility",
|
|
33
|
+
"css-var": "var(--token)",
|
|
34
|
+
"raw-hex": "raw hex",
|
|
35
|
+
"tailwind-arbitrary-var": "var() inside a utility",
|
|
36
|
+
"shadcn-hsl": "hsl(var(--token))",
|
|
37
|
+
};
|
|
38
|
+
const PATTERNS = [
|
|
39
|
+
// Order matters where two could match the same text: the shadcn wrapper and
|
|
40
|
+
// the arbitrary-value form are both `var()` and both more specific than it.
|
|
41
|
+
["shadcn-hsl", /hsl\(\s*var\(\s*--/g],
|
|
42
|
+
[
|
|
43
|
+
"tailwind-arbitrary-var",
|
|
44
|
+
/\b(?:bg|text|border|ring|fill|stroke|shadow|from|to|via)-\[var\(\s*--/g,
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"tailwind-utility",
|
|
48
|
+
/\b(?:bg|text|border|ring|fill|stroke|from|to|via)-(?:[a-z]+-)+\d{2,3}\b/g,
|
|
49
|
+
],
|
|
50
|
+
["css-var", /var\(\s*--/g],
|
|
51
|
+
["raw-hex", /#[0-9a-fA-F]{6}\b/g],
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Extensions worth reporting separately, because they are separate languages
|
|
55
|
+
* with separate correct answers. Anything else is counted and not split out.
|
|
56
|
+
*/
|
|
57
|
+
const LANGUAGES = new Set([".tsx", ".jsx", ".scss", ".css", ".less", ".sass"]);
|
|
58
|
+
/**
|
|
59
|
+
* A custom-property declaration, whose right-hand side is a DEFINITION.
|
|
60
|
+
* Deliberately not anchored to a line start: these are written inside `:root`,
|
|
61
|
+
* `@theme` and nested blocks at every indentation.
|
|
62
|
+
*/
|
|
63
|
+
const DECLARATION = /--[a-zA-Z0-9_-]+\s*:[^;}]*/g;
|
|
64
|
+
/**
|
|
65
|
+
* Count references per language and name the dominant idiom.
|
|
66
|
+
*
|
|
67
|
+
* A language with too few references to be conclusive is left out rather than
|
|
68
|
+
* given a convention on the strength of three sightings: claiming a project's
|
|
69
|
+
* convention from noise is worse than saying nothing, because every later
|
|
70
|
+
* finding inherits the claim.
|
|
71
|
+
*/
|
|
72
|
+
export function detectConventions(sources, minReferences = 20) {
|
|
73
|
+
const byLanguage = new Map();
|
|
74
|
+
for (const { file, source } of sources) {
|
|
75
|
+
const dot = file.lastIndexOf(".");
|
|
76
|
+
const ext = dot === -1 ? "" : file.slice(dot).toLowerCase();
|
|
77
|
+
if (!LANGUAGES.has(ext))
|
|
78
|
+
continue;
|
|
79
|
+
const counts = byLanguage.get(ext) ?? new Map();
|
|
80
|
+
// A DECLARATION IS NOT A REFERENCE. `--color-ocean-500: #059aed` is where a
|
|
81
|
+
// hex belongs, and counting it made a design system's own token file read as
|
|
82
|
+
// 87% raw hex - the worst drift in the repo, when it is the opposite: the one
|
|
83
|
+
// place every other value should be pointing at (dono, 01/08).
|
|
84
|
+
let rest = source.replace(DECLARATION, "");
|
|
85
|
+
// A more specific pattern claims its matches first, so the same text is
|
|
86
|
+
// never counted twice - `bg-[var(--x)]` is one arbitrary-value reference,
|
|
87
|
+
// not also a `var()` one.
|
|
88
|
+
for (const [idiom, re] of PATTERNS) {
|
|
89
|
+
const found = rest.match(re);
|
|
90
|
+
if (!found)
|
|
91
|
+
continue;
|
|
92
|
+
counts.set(idiom, (counts.get(idiom) ?? 0) + found.length);
|
|
93
|
+
rest = rest.replace(re, "");
|
|
94
|
+
}
|
|
95
|
+
byLanguage.set(ext, counts);
|
|
96
|
+
}
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const [language, counts] of byLanguage) {
|
|
99
|
+
const total = [...counts.values()].reduce((n, v) => n + v, 0);
|
|
100
|
+
if (total < minReferences)
|
|
101
|
+
continue;
|
|
102
|
+
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
103
|
+
const [idiom, top] = ranked[0];
|
|
104
|
+
out.push({
|
|
105
|
+
language,
|
|
106
|
+
idiom,
|
|
107
|
+
share: top / total,
|
|
108
|
+
total,
|
|
109
|
+
offIdiom: ranked.slice(1).map(([i, count]) => ({ idiom: i, count })),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return out.sort((a, b) => b.total - a.total);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* One line per language, in the report's voice and in THEIR vocabulary.
|
|
116
|
+
*
|
|
117
|
+
* A raw hex winning is NOT a convention. It is the absence of one, and saying
|
|
118
|
+
* "this language reaches for raw hex" dignifies it into a house style somebody
|
|
119
|
+
* might defend - when what the number means is that nothing there points at
|
|
120
|
+
* anything (dono, 01/08).
|
|
121
|
+
*/
|
|
122
|
+
export function describeConvention(c) {
|
|
123
|
+
const pct = Math.round(c.share * 100);
|
|
124
|
+
if (c.idiom === "raw-hex") {
|
|
125
|
+
return `${c.language} has no convention - ${pct}% of its ${c.total} references are values typed in place`;
|
|
126
|
+
}
|
|
127
|
+
return `${c.language} reaches for ${IDIOM_LABEL[c.idiom]} - ${pct}% of the ${c.total} references in it`;
|
|
128
|
+
}
|
|
129
|
+
/** True when this language's dominant answer is "no answer". */
|
|
130
|
+
export function hasConvention(c) {
|
|
131
|
+
return c.idiom !== "raw-hex";
|
|
132
|
+
}
|
package/dist/skill-import.js
CHANGED
|
@@ -166,29 +166,107 @@ about which one is the page. Read a screen and see.
|
|
|
166
166
|
**\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
|
|
167
167
|
feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
|
|
168
168
|
|
|
169
|
-
### 3.
|
|
169
|
+
### 3. Walk them through the decisions, one at a time
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
Four decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
|
172
|
+
question tool, one call per decision, so they pick instead of reading a wall and composing a
|
|
173
|
+
reply. A single block containing everything is a report, and a report gets read, not answered.
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
Every question carries **your recommendation first, marked as such**, and the evidence for it in
|
|
176
|
+
one line. They are choosing, not auditing you.
|
|
176
177
|
|
|
178
|
+
**3a. Source** - the folder you measure the system out of.
|
|
179
|
+
|
|
180
|
+
Options are the candidates the CLI already ranked, each with what it would produce, plus the
|
|
181
|
+
whole repo, plus *somewhere else* for a path they type:
|
|
182
|
+
|
|
183
|
+
\`\`\`
|
|
184
|
+
Where should the system come from?
|
|
185
|
+
|
|
186
|
+
packages/ui (recommended) 114 files · 47% of its values already named ·
|
|
187
|
+
92 tokens of their own · every app imports it
|
|
188
|
+
the whole repo 2797 files · 36% · the average of one dark app
|
|
189
|
+
and two light ones, which is a diagnosis and
|
|
190
|
+
not a system
|
|
191
|
+
apps/web-dashboard the heaviest consumer, if you want one app's
|
|
192
|
+
vocabulary rather than the shared one
|
|
193
|
+
somewhere else a path you name
|
|
194
|
+
\`\`\`
|
|
195
|
+
|
|
196
|
+
If they name a path you cannot use, say which of the two it is and offer the list again:
|
|
197
|
+
|
|
198
|
+
- **it does not exist**, or holds no files you can read
|
|
199
|
+
- **it holds no design values** - no colours, no radii, no tokens. A folder of server code or
|
|
200
|
+
config is not a design system, and measuring it produces a system of nothing.
|
|
201
|
+
|
|
202
|
+
**3b. Name** - suggest theirs, and let Enter take it:
|
|
203
|
+
|
|
204
|
+
\`\`\`
|
|
205
|
+
What should it be called? (SignalUI)
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
Say where the suggestion came from - \`Introduction.mdx\`, the package name, the folder. A name
|
|
209
|
+
they recognise is worth more than a clever one. The slug is derived once and never changes, so
|
|
210
|
+
this is the moment.
|
|
211
|
+
|
|
212
|
+
**3c. Primary** - and explain the role before offering the choice, because *primary* is our word
|
|
213
|
+
and not necessarily theirs:
|
|
214
|
+
|
|
215
|
+
\`\`\`
|
|
216
|
+
Primary is the colour that carries action - buttons, links, focus, the thing
|
|
217
|
+
you want pressed. Everything else in the system is measured for contrast
|
|
218
|
+
against it.
|
|
219
|
+
|
|
220
|
+
#059aed ocean-500 (recommended) your ColorPalette.mdx calls Ocean the
|
|
221
|
+
"Primary branding scale"
|
|
222
|
+
#1a4ed8 royal-blue-500 your docs label this one secondary
|
|
223
|
+
#ec4899 vivid-pink-500 half of your signature AI gradient
|
|
224
|
+
#4A90E2 blue-500 the most-painted blue in the folder,
|
|
225
|
+
but it sits after your own
|
|
226
|
+
/* End - Signal UI */ marker
|
|
177
227
|
\`\`\`
|
|
178
|
-
I'd create a system from packages/ui, called SignalUI, opening dark.
|
|
179
228
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
are the consumers; measuring all of them gave 36%, which
|
|
183
|
-
is the average of a light app and two dark ones)
|
|
184
|
-
called SignalUI their own name, from Introduction.mdx
|
|
185
|
-
brand #059aed their docs call Ocean the primary scale
|
|
186
|
-
opens dark data-theme="dark" in the root layout
|
|
229
|
+
Always name the hex AND the token, and always give the reason a value is *not* recommended when
|
|
230
|
+
frequency would have picked it. That last row is the whole point of asking.
|
|
187
231
|
|
|
188
|
-
|
|
232
|
+
**3d. Default theme** - which face it opens in:
|
|
233
|
+
|
|
234
|
+
\`\`\`
|
|
235
|
+
dark (recommended) data-theme="dark" in the root layout, next-themes
|
|
236
|
+
defaultTheme="dark", enableSystem={false}
|
|
237
|
+
light the other one, also real here
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
**Two is all there is** - a SynthesisUI system carries one palette and one alternate, and the
|
|
241
|
+
schema is \`"light" | "dark"\`. So this is not "which themes do you support", it is "which one do
|
|
242
|
+
docs and installs open in". If both are real, both get built from their own tokens; this picks
|
|
243
|
+
the face.
|
|
244
|
+
|
|
245
|
+
**3e. Canvas** - confirm, do not offer a list. The page is measured, not chosen:
|
|
246
|
+
|
|
247
|
+
\`\`\`
|
|
248
|
+
canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
|
|
249
|
+
(your sidebar sits a step deeper at darkgray-700)
|
|
189
250
|
\`\`\`
|
|
190
251
|
|
|
191
|
-
|
|
252
|
+
Say it and let them object. Offering five near-blacks to pick between is asking someone to
|
|
253
|
+
re-decide something their code already decided.
|
|
254
|
+
|
|
255
|
+
**3f. Fonts** - do not ask at all. Report and move on:
|
|
256
|
+
|
|
257
|
+
\`\`\`
|
|
258
|
+
fonts Figtree for both display and body - the only family declared
|
|
259
|
+
(--font-sans, and next/font Figtree in all three root layouts)
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
If a project genuinely declares two families, say which is which and why you paired them that
|
|
263
|
+
way. If it declares one, there is nothing to decide.
|
|
264
|
+
|
|
265
|
+
**A note on colour swatches.** You cannot paint a hex in this conversation, so the token name
|
|
266
|
+
carries the weight - \`ocean-500\` tells someone more than a square would. When they run
|
|
267
|
+
\`npx synthesisui import\` themselves in a terminal, the CLI paints them.
|
|
268
|
+
|
|
269
|
+
Then, and only after all four are answered:
|
|
192
270
|
|
|
193
271
|
\`\`\`
|
|
194
272
|
npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--registry <from step 0>]
|
|
@@ -196,10 +274,11 @@ npx synthesisui import --census _synthesisui/census.json --name "<the name>" [--
|
|
|
196
274
|
|
|
197
275
|
**This is the one real write.** Everything before it is on their disk and costs nothing to redo.
|
|
198
276
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
they learn where their system came from is when they open
|
|
277
|
+
Pass every answer explicitly - \`--name\`, \`--scope\` if they changed it, and the reading rewritten
|
|
278
|
+
with their primary and their theme. The CLI cannot ask anything when you are the one running it:
|
|
279
|
+
it has no terminal, so its own prompts are skipped by design. **Whatever you did not ask, nobody
|
|
280
|
+
asked.** Skip 3a and the first time they learn where their system came from is when they open
|
|
281
|
+
it.
|
|
203
282
|
|
|
204
283
|
If it refuses, it says which of the three it is: no session, an expired one, or a token issued
|
|
205
284
|
by a different host. All are recoverable from the census on disk; none need a re-measure.
|
package/package.json
CHANGED