synthesisui 0.16.103 → 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.
@@ -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
@@ -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
- for (const m of html[1].matchAll(/([a-zA-Z-]+)\s*=\s*(?:["']([^"']*)["']|\{["']([^"']*)["']\})/g)) {
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
  }
@@ -331,6 +331,13 @@ census carries is the failure this pipeline keeps paying for. The sketch now run
331
331
  so a big component arrives WHOLE: \`TextEditor\` used to lose its editable region at node 60 and
332
332
  that single truncation is what forced a hand-read of the file (dono, 01/08).
333
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
+
334
341
  **Validate in ONE call, at the end.** Build every recipe in memory first, then send them all to
335
342
  \`validate_recipes\` - one round trip for the whole reading instead of one per component. A reader
336
343
  with 35 anatomies spent 35 turns asking the same question, and none of that was thinking. Only
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.103",
3
+ "version": "0.16.104",
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": {