synthesisui 0.16.102 → 0.16.104
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 -4
- package/dist/doctor/signals.js +59 -3
- package/dist/doctor/sketch.js +49 -3
- package/dist/skill-import.js +32 -23
- package/package.json +1 -1
package/dist/commands/import.js
CHANGED
|
@@ -636,6 +636,21 @@ export async function takeCensus(root, opts) {
|
|
|
636
636
|
}
|
|
637
637
|
usageSeen.add(rel);
|
|
638
638
|
usageProgress?.step(rel);
|
|
639
|
+
/**
|
|
640
|
+
* THE THEME IS SWITCHED WHERE THE APP IS, and this loop was walking right
|
|
641
|
+
* past the file that says so. Measured on a real monorepo (dono, 02/08):
|
|
642
|
+
* `packages/ui` declares 346 dark utilities across 58 files and NONE of the
|
|
643
|
+
* four switch fields; `apps/web-dashboard` declares `data-theme`,
|
|
644
|
+
* `defaultTheme="dark"`, `enableSystem={false}` and an `<html>` carrying both
|
|
645
|
+
* - so "which face does it open in" was unanswerable from the census and cost
|
|
646
|
+
* a hand read of a file this loop already had open.
|
|
647
|
+
*
|
|
648
|
+
* The scope is read first and wins on every field, so this only fills what a
|
|
649
|
+
* library legitimately does not have - and the label is recorded, because the
|
|
650
|
+
* system being measured here and the theme being switched there are two facts,
|
|
651
|
+
* not one average.
|
|
652
|
+
*/
|
|
653
|
+
readSignalsInto(signals, importTally, rel, src, u.label);
|
|
639
654
|
scanComponentsInto(usageTally, rel, src, [...shared, ...uInternal]);
|
|
640
655
|
// WHERE THE PAIRS LIVE. A library composes almost none of itself inside
|
|
641
656
|
// itself - `Button` goes inside `Modal` in the APP, not in the library
|
|
@@ -1645,8 +1660,21 @@ function rootPackage(tag, sketch) {
|
|
|
1645
1660
|
: {};
|
|
1646
1661
|
}
|
|
1647
1662
|
async function resolveReadParts(census, root) {
|
|
1648
|
-
|
|
1649
|
-
|
|
1663
|
+
/**
|
|
1664
|
+
* A READING IS OPTIONAL HERE. THE SKETCH IS NOT.
|
|
1665
|
+
*
|
|
1666
|
+
* This returned on line one when nobody had authored a reading, and a DERIVED
|
|
1667
|
+
* anatomy needs no reading at all - so the deriver shipped and never ran on the
|
|
1668
|
+
* path the owner actually uses. Measured on his own re-import (02/08): the census
|
|
1669
|
+
* carried `rootFrom` on 13 looks and `from` on 162 sketch nodes - the new fields
|
|
1670
|
+
* arrived - and `tree` on 0 of 36. Two components then read as empty contracts
|
|
1671
|
+
* and came back below the floor.
|
|
1672
|
+
*
|
|
1673
|
+
* "Medido 100%, convertido 0%", one week after the law that names it. The guard
|
|
1674
|
+
* is now about having something to convert, not about who asked.
|
|
1675
|
+
*/
|
|
1676
|
+
const read = census.reading?.components ?? {};
|
|
1677
|
+
if (!census.looks || Object.keys(census.looks).length === 0)
|
|
1650
1678
|
return;
|
|
1651
1679
|
const declared = new Map(Object.entries(census.declared));
|
|
1652
1680
|
// The manifest is read ONCE for the whole run: the version belongs in a rule's
|
|
@@ -1774,10 +1802,14 @@ async function resolveReadParts(census, root) {
|
|
|
1774
1802
|
for (const n of resolved.notes)
|
|
1775
1803
|
notes.add(n);
|
|
1776
1804
|
}
|
|
1777
|
-
const
|
|
1805
|
+
const authored = Object.keys(read).length;
|
|
1806
|
+
const total = authored || Object.keys(census.looks ?? {}).length;
|
|
1778
1807
|
if (named > 0 || shaped > 0) {
|
|
1779
1808
|
console.log("");
|
|
1780
|
-
console.log(body(
|
|
1809
|
+
console.log(body(
|
|
1810
|
+
// "Your reading" only when there WAS one - otherwise the names came off
|
|
1811
|
+
// their own markup, and claiming otherwise credits somebody's absent work.
|
|
1812
|
+
`${named} part${named === 1 ? "" : "s"} named across ${total} component${total === 1 ? "" : "s"}${authored > 0 ? " - your reading and their markup" : " - read off their own markup"}, so each previews as what it IS rather than as a text box`));
|
|
1781
1813
|
}
|
|
1782
1814
|
if (shaped > 0) {
|
|
1783
1815
|
console.log(body(`${shaped} of them arrived with a SHAPE - the parts nested as they nest in your code, so the preview is the component and not a row of labels`));
|
|
@@ -1889,6 +1921,20 @@ export async function runImport(opts) {
|
|
|
1889
1921
|
if (scope)
|
|
1890
1922
|
census.scope = scope;
|
|
1891
1923
|
}
|
|
1924
|
+
/**
|
|
1925
|
+
* DERIVE THE ANATOMY ON THE MEASURING PATH TOO.
|
|
1926
|
+
*
|
|
1927
|
+
* The send path resolves the reading above; a plain `--dry` measure never
|
|
1928
|
+
* reached that function, so the census on disk - the file the skill reads to
|
|
1929
|
+
* author its reading, and the file every one of our real-repo specs reads -
|
|
1930
|
+
* carried a sketch for every component and a tree for none. The person's own
|
|
1931
|
+
* re-import proved it: 36 sketches, 162 stamped nodes, 0 trees (dono, 02/08).
|
|
1932
|
+
*
|
|
1933
|
+
* Written into the census means the skill sees the shape it would otherwise
|
|
1934
|
+
* have to author from scratch, and can spend its judgement on the names.
|
|
1935
|
+
*/
|
|
1936
|
+
if (!opts.census)
|
|
1937
|
+
await resolveReadParts(census, root);
|
|
1892
1938
|
summarize(census);
|
|
1893
1939
|
// Only when nobody has scoped yet. Telling someone to scope to the folder
|
|
1894
1940
|
// they just scoped to reads as the tool not having noticed.
|
package/dist/doctor/signals.js
CHANGED
|
@@ -75,6 +75,7 @@ export function emptySignals() {
|
|
|
75
75
|
enableSystem: null,
|
|
76
76
|
prefersColorScheme: 0,
|
|
77
77
|
htmlCarries: [],
|
|
78
|
+
switchedIn: null,
|
|
78
79
|
page: [],
|
|
79
80
|
},
|
|
80
81
|
libraries: [],
|
|
@@ -113,7 +114,13 @@ export function libraryOf(specifier) {
|
|
|
113
114
|
* Fold one file into the signals. Called from the walk the census already does, so nothing
|
|
114
115
|
* here costs a second pass over the disk.
|
|
115
116
|
*/
|
|
116
|
-
export function readSignalsInto(into, imports, file, source
|
|
117
|
+
export function readSignalsInto(into, imports, file, source,
|
|
118
|
+
/**
|
|
119
|
+
* WHERE THIS FILE IS, when it is not the system's own folder - the `--usage`
|
|
120
|
+
* label. Present means "read for evidence", and the only thing it changes is
|
|
121
|
+
* that a switch found here is RECORDED AS FOUND HERE. See `switchedIn`.
|
|
122
|
+
*/
|
|
123
|
+
from) {
|
|
117
124
|
const isCode = /\.(tsx|jsx|ts|js|mjs|cjs|vue|svelte)$/i.test(file);
|
|
118
125
|
const isStyle = /\.(css|scss|sass|less)$/i.test(file);
|
|
119
126
|
if (isStyle) {
|
|
@@ -146,6 +153,16 @@ export function readSignalsInto(into, imports, file, source) {
|
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* THE SCOPE WINS, ALWAYS - `??=` is the whole rule. The system's own folder is
|
|
158
|
+
* read first, so a switch it declares is never overwritten by an app's; an app
|
|
159
|
+
* only fills what was null, which is the four fields a library legitimately does
|
|
160
|
+
* not have.
|
|
161
|
+
*/
|
|
162
|
+
const before = into.theme.attribute === null &&
|
|
163
|
+
into.theme.defaultTheme === null &&
|
|
164
|
+
into.theme.enableSystem === null &&
|
|
165
|
+
into.theme.htmlCarries.length === 0;
|
|
149
166
|
into.theme.attribute ??= THEME_ATTRIBUTE.exec(source)?.[1] ?? null;
|
|
150
167
|
into.theme.defaultTheme ??= DEFAULT_THEME.exec(source)?.[1] ?? null;
|
|
151
168
|
if (into.theme.enableSystem === null) {
|
|
@@ -160,12 +177,42 @@ export function readSignalsInto(into, imports, file, source) {
|
|
|
160
177
|
*/
|
|
161
178
|
const html = HTML_ATTRS.exec(source);
|
|
162
179
|
if (html) {
|
|
163
|
-
|
|
164
|
-
const attr = `${m[1]}="${m[2] ?? m[3] ?? ""}"`;
|
|
180
|
+
const carry = (attr) => {
|
|
165
181
|
if (!into.theme.htmlCarries.includes(attr)) {
|
|
166
182
|
into.theme.htmlCarries.push(attr);
|
|
167
183
|
}
|
|
184
|
+
};
|
|
185
|
+
for (const m of html[1].matchAll(/([a-zA-Z-]+)\s*=\s*(?:["']([^"']*)["']|\{["']([^"']*)["']\})/g)) {
|
|
186
|
+
carry(`${m[1]}="${m[2] ?? m[3] ?? ""}"`);
|
|
168
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* `style={{ colorScheme: "dark" }}` - the SECOND thing that decides what a dark
|
|
190
|
+
* rule matches, and the attribute pass above cannot see it: its value is not a
|
|
191
|
+
* quoted string sitting after an `=`, it is a property inside an object.
|
|
192
|
+
*
|
|
193
|
+
* It is on the owner's own root layout, right beside the `data-theme` the pass
|
|
194
|
+
* DID read, and it is what tells the browser which face to paint its own
|
|
195
|
+
* widgets - a scrollbar and a form control follow `color-scheme`, not a class
|
|
196
|
+
* (dono, 02/08).
|
|
197
|
+
*/
|
|
198
|
+
const scheme = /\bcolorScheme\s*:\s*["']([^"']+)["']/.exec(html[1]);
|
|
199
|
+
if (scheme)
|
|
200
|
+
carry(`colorScheme="${scheme[1]}"`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* WHOSE FILE ANSWERED IT. Stamped only when the switch was null before this file
|
|
204
|
+
* and is not null after, so the label names the file's ROOT that actually
|
|
205
|
+
* supplied it - and only for a `--usage` root, because the scope needs no
|
|
206
|
+
* attribution: it is the system by definition.
|
|
207
|
+
*/
|
|
208
|
+
if (from &&
|
|
209
|
+
into.theme.switchedIn === null &&
|
|
210
|
+
before &&
|
|
211
|
+
(into.theme.attribute !== null ||
|
|
212
|
+
into.theme.defaultTheme !== null ||
|
|
213
|
+
into.theme.enableSystem !== null ||
|
|
214
|
+
into.theme.htmlCarries.length > 0)) {
|
|
215
|
+
into.theme.switchedIn = from;
|
|
169
216
|
}
|
|
170
217
|
/**
|
|
171
218
|
* THE PAGE PAINTS, from the markup. A `min-h-screen` container with a background is
|
|
@@ -310,6 +357,15 @@ export function describeSignals(s) {
|
|
|
310
357
|
bits.push("with the OS allowed a vote");
|
|
311
358
|
out.push(`${bits.join(", ")}.`);
|
|
312
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* TWO PLACES, TWO FACTS. A library that declares 346 dark utilities and no
|
|
362
|
+
* switch is not a library missing something - the switch belongs to the app that
|
|
363
|
+
* mounts it. Saying WHERE keeps the sentence true and stops the reader from
|
|
364
|
+
* concluding the system declares a provider it does not have.
|
|
365
|
+
*/
|
|
366
|
+
if (t.switchedIn) {
|
|
367
|
+
out.push(`the system is measured where you scoped it; the theme is switched in \`${t.switchedIn}\` - that is where the attribute, the default and the \`<html>\` came from.`);
|
|
368
|
+
}
|
|
313
369
|
if (t.htmlCarries.length > 0) {
|
|
314
370
|
out.push(`\`<html>\` carries ${t.htmlCarries.map((a) => `\`${a}\``).join(" ")} - which is the ancestor every dark rule of theirs matches, and therefore the one ours must match.`);
|
|
315
371
|
}
|
package/dist/doctor/sketch.js
CHANGED
|
@@ -23,6 +23,50 @@ import { importMap, packageOfTag } from "./imports.js";
|
|
|
23
23
|
import { definitionSpan } from "./transcribe.js";
|
|
24
24
|
/** `{children}` / `{props.children}` - the caller's content, spelled either way. */
|
|
25
25
|
const CHILDREN = /\{\s*(?:props\.)?children\s*\}/;
|
|
26
|
+
/**
|
|
27
|
+
* THE TEXT THAT SITS DIRECTLY INSIDE ONE ELEMENT - its own gaps, not its
|
|
28
|
+
* children's contents.
|
|
29
|
+
*
|
|
30
|
+
* The first version of the slot check read only the span between an element's
|
|
31
|
+
* open tag and the next tag of any kind, so it saw `{children}` only when it was
|
|
32
|
+
* the FIRST thing inside. A real panel renders a header and THEN the caller's
|
|
33
|
+
* content, which is the mixed case the reader has a form for - and it was
|
|
34
|
+
* invisible. Found by driving `runImport` over a Base-UI dialog whose `{children}`
|
|
35
|
+
* follows its close button (02/08).
|
|
36
|
+
*
|
|
37
|
+
* Attribution matters as much as detection: `{children}` inside a CHILD is that
|
|
38
|
+
* child's slot, so the walk collects the gaps at this element's own level and
|
|
39
|
+
* skips whole subtrees.
|
|
40
|
+
*/
|
|
41
|
+
function directText(source, events, index) {
|
|
42
|
+
const open = events[index];
|
|
43
|
+
const openEnd = source.indexOf(">", open.at);
|
|
44
|
+
if (openEnd === -1)
|
|
45
|
+
return "";
|
|
46
|
+
let from = openEnd + 1;
|
|
47
|
+
const gaps = [];
|
|
48
|
+
let rel = 0;
|
|
49
|
+
for (let j = index + 1; j < events.length; j++) {
|
|
50
|
+
const event = events[j];
|
|
51
|
+
if (event.kind === "open") {
|
|
52
|
+
if (rel === 0)
|
|
53
|
+
gaps.push(source.slice(from, event.at));
|
|
54
|
+
rel += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
rel -= 1;
|
|
58
|
+
// Our own close: the last gap runs up to it, and the element ends here.
|
|
59
|
+
if (rel < 0) {
|
|
60
|
+
gaps.push(source.slice(from, event.at));
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (rel === 0) {
|
|
64
|
+
const closeEnd = source.indexOf(">", event.at);
|
|
65
|
+
from = closeEnd === -1 ? event.at : closeEnd + 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return gaps.join(" ");
|
|
69
|
+
}
|
|
26
70
|
/**
|
|
27
71
|
* Past this a component is a page in disguise, and the gate already said no.
|
|
28
72
|
*
|
|
@@ -177,10 +221,12 @@ export function sketchOf(source, name) {
|
|
|
177
221
|
!between.includes("<")) {
|
|
178
222
|
node.text = between;
|
|
179
223
|
}
|
|
180
|
-
// The caller's content, as a fact - see `SketchNode.slot`.
|
|
181
|
-
if (CHILDREN.test(between))
|
|
182
|
-
node.slot = true;
|
|
183
224
|
}
|
|
225
|
+
// The caller's content, as a fact - see `SketchNode.slot`. Read from the
|
|
226
|
+
// element's DIRECT text, so a `{children}` after a header still counts and one
|
|
227
|
+
// inside a child belongs to that child.
|
|
228
|
+
if (CHILDREN.test(directText(source, events, i)))
|
|
229
|
+
node.slot = true;
|
|
184
230
|
out.push(node);
|
|
185
231
|
depth += 1;
|
|
186
232
|
}
|
package/dist/skill-import.js
CHANGED
|
@@ -148,11 +148,11 @@ file to pick between. Nothing is sent by \`--dry\`. Read the file.
|
|
|
148
148
|
### 2. Walk them through the decisions - ALL of them, before any reading
|
|
149
149
|
|
|
150
150
|
**Every question this skill asks is answerable from the census you just took.** Source, name,
|
|
151
|
-
primary, theme, canvas,
|
|
152
|
-
|
|
151
|
+
primary, theme, canvas, architecture - the numbers behind all six are in the file on their disk,
|
|
152
|
+
and none of them needs the anatomy.
|
|
153
153
|
|
|
154
154
|
They used to be asked AFTER the reading, and that put six hundred lines of work between the
|
|
155
|
-
person's first answer and their second (dono, 01/08). A person who answers
|
|
155
|
+
person's first answer and their second (dono, 01/08). A person who answers six questions in
|
|
156
156
|
ninety seconds and then watches a bar has had a good four minutes; the same person interrupted
|
|
157
157
|
twice in the middle of a silence has had a bad twenty.
|
|
158
158
|
|
|
@@ -160,7 +160,7 @@ So: ask everything here, in one pass, and **do not come back**. If something you
|
|
|
160
160
|
the reading contradicts an answer, you do not reopen the question - you say so in the report at
|
|
161
161
|
the end and let them change it there.
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
Six decisions are theirs. **Ask them as separate questions with selectable options** - use your
|
|
164
164
|
question tool, one call per decision, so they pick instead of reading a wall and composing a
|
|
165
165
|
reply. A single block containing everything is a report, and a report gets read, not answered.
|
|
166
166
|
|
|
@@ -244,35 +244,37 @@ canvas #222326 darkgray-500 - what DashboardLayout paints on <main>
|
|
|
244
244
|
Say it and let them object. Offering five near-blacks to pick between is asking someone to
|
|
245
245
|
re-decide something their code already decided.
|
|
246
246
|
|
|
247
|
-
**2f. Our kit
|
|
248
|
-
|
|
249
|
-
has nothing to weigh and should never see the question.
|
|
247
|
+
**2f. Our kit** - do not ask. A project that already has a component library gets **theirs
|
|
248
|
+
alone**, and you say so in one line:
|
|
250
249
|
|
|
251
250
|
\`\`\`
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
add ours to yours (recommended) 68 components, and the 23 governed by your
|
|
256
|
-
own rules are the 23 you wrote
|
|
257
|
-
mine alone 37 components, all of them yours. Our
|
|
258
|
-
layouts that need a component you do not
|
|
259
|
-
have stay out too. Add any of ours later
|
|
260
|
-
from the library, one at a time
|
|
251
|
+
This system is yours alone - the 37 components you wrote, and none of ours.
|
|
252
|
+
Add any of ours later from the library, one at a time.
|
|
261
253
|
\`\`\`
|
|
262
254
|
|
|
263
|
-
Send it as \`"standards": "none"\` in the reading
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
Send it as \`"standards": "none"\` in the reading. Every time, with one exception below.
|
|
256
|
+
|
|
257
|
+
**Why this stopped being a question.** Somebody who points us at their own library is telling us
|
|
258
|
+
what their system is, and 45 components of ours landing beside 37 of theirs makes a system of 82
|
|
259
|
+
where only 37 are governed by anything they wrote - the number on their own screen stops being
|
|
260
|
+
about them. The kit is one click away in the library afterwards, so choosing wrong here costs a
|
|
261
|
+
click and choosing to ask costs a pause in the middle of their import.
|
|
262
|
+
|
|
263
|
+
**The exception, and it is not a preference.** An app with NO components of its own - \`--scope\`
|
|
264
|
+
pointed at an app rather than a library, and the CLI reported no component library - must NOT get
|
|
265
|
+
\`"standards": "none"\`: theirs alone plus nothing of theirs is a system with zero components. Leave
|
|
266
|
+
the field out there, exactly as before.
|
|
267
267
|
|
|
268
268
|
Two things to be straight about, because both are true and neither is obvious:
|
|
269
269
|
|
|
270
|
-
- **the foundations come from the seed
|
|
270
|
+
- **the foundations still come from the seed.** Their palette, their scales, their type - all
|
|
271
271
|
theirs - but the seven type slots, the easing and the neutral floor arrive from ours, because
|
|
272
272
|
the alternative to a type scale is no components at all rather than their components.
|
|
273
273
|
- **our layouts are written in our recipes.** A landing that composes \`button\`, \`card\` and
|
|
274
|
-
\`badge\` needs those to exist.
|
|
275
|
-
|
|
274
|
+
\`badge\` needs those to exist. Theirs alone keeps the layouts whose recipes they happen to have
|
|
275
|
+
and drops the rest - a layout pointing at a recipe nobody has renders as holes. Say that number
|
|
276
|
+
when the CLI reports it, so a shorter list of layouts is a fact they were told rather than one
|
|
277
|
+
they discover.
|
|
276
278
|
|
|
277
279
|
**2g. Which architecture has priority** - ask only when the CLI reported more than
|
|
278
280
|
one, which it does under "How this project is organised".
|
|
@@ -329,6 +331,13 @@ census carries is the failure this pipeline keeps paying for. The sketch now run
|
|
|
329
331
|
so a big component arrives WHOLE: \`TextEditor\` used to lose its editable region at node 60 and
|
|
330
332
|
that single truncation is what forced a hand-read of the file (dono, 01/08).
|
|
331
333
|
|
|
334
|
+
\`signals.theme\` includes the SWITCH - \`attribute\`, \`defaultTheme\`, \`enableSystem\` and what
|
|
335
|
+
\`<html>\` carries, \`colorScheme\` included - read from the \`--usage\` roots as well as the scope,
|
|
336
|
+
because a component library declares the dark utilities and the APP declares the switch. When it
|
|
337
|
+
came from an app, \`switchedIn\` names which one: say "the system is measured in \`packages/ui\`, the
|
|
338
|
+
theme is switched in \`apps/web-dashboard\`" rather than reporting that the library declares a
|
|
339
|
+
provider it does not have. Answering 2d never needs a layout file opened.
|
|
340
|
+
|
|
332
341
|
**Validate in ONE call, at the end.** Build every recipe in memory first, then send them all to
|
|
333
342
|
\`validate_recipes\` - one round trip for the whole reading instead of one per component. A reader
|
|
334
343
|
with 35 anatomies spent 35 turns asking the same question, and none of that was thinking. Only
|
package/package.json
CHANGED