synthesisui 0.16.89 → 0.16.90

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.
@@ -75,11 +75,19 @@ export function emptySignals() {
75
75
  enableSystem: null,
76
76
  prefersColorScheme: 0,
77
77
  htmlCarries: [],
78
+ page: [],
78
79
  },
79
80
  libraries: [],
80
81
  conflicts: [],
81
82
  };
82
83
  }
84
+ function addPagePaint(into, token, scheme, source) {
85
+ const found = into.theme.page.find((p) => p.token === token && p.scheme === scheme && p.source === source);
86
+ if (found)
87
+ found.count += 1;
88
+ else
89
+ into.theme.page.push({ token, scheme, source, count: 1 });
90
+ }
83
91
  const ANY_IMPORT = /(?:from\s+["']([^"'.][^"']*)["']|require\(\s*["']([^"'.][^"']*)["']\s*\))/g;
84
92
  /** The package a specifier belongs to: `@tiptap/react` and `motion/react` both keep the
85
93
  * part a manifest would name. */
@@ -110,6 +118,13 @@ export function readSignalsInto(into, imports, file, source) {
110
118
  const isStyle = /\.(css|scss|sass|less)$/i.test(file);
111
119
  if (isStyle) {
112
120
  into.theme.prefersColorScheme += (source.match(/prefers-color-scheme/g) ?? []).length;
121
+ /**
122
+ * `body { background: … }` in a global stylesheet is the page saying so itself -
123
+ * the strongest of the three page signals and the one a class sweep never finds.
124
+ */
125
+ for (const m of source.matchAll(/(?:^|\})\s*(?:html|body|:root)[^{]*\{[^}]*?background(?:-color)?\s*:\s*([^;}]+)/g)) {
126
+ addPagePaint(into, m[1].trim(), "light", "global-css");
127
+ }
113
128
  return;
114
129
  }
115
130
  if (!isCode)
@@ -152,6 +167,37 @@ export function readSignalsInto(into, imports, file, source) {
152
167
  }
153
168
  }
154
169
  }
170
+ /**
171
+ * THE PAGE PAINTS, from the markup. A `min-h-screen` container with a background is
172
+ * the page - that idiom is universal, which is what makes this generalize across
173
+ * folder layouts. `dark:bg-x` files under dark; `bg-x` under light.
174
+ */
175
+ for (const m of source.matchAll(/className=\{?["'`]([^"'`]+)["'`]/g)) {
176
+ const list = m[1].split(/\s+/);
177
+ const isViewport = list.some((c) => /^(?:min-h-screen|h-screen|min-h-dvh|h-dvh)$/.test(c));
178
+ if (isViewport) {
179
+ for (const c of list) {
180
+ const dark = /^dark:bg-([a-z][a-z0-9-]*)$/.exec(c);
181
+ const light = /^bg-([a-z][a-z0-9-]*)$/.exec(c);
182
+ if (dark)
183
+ addPagePaint(into, dark[1], "dark", "viewport-container");
184
+ else if (light &&
185
+ !/^(gradient|opacity|clip|cover|contain|center|fixed|local|scroll|none|auto|repeat|transparent|current|inherit)/.test(light[1])) {
186
+ addPagePaint(into, light[1], "light", "viewport-container");
187
+ }
188
+ }
189
+ }
190
+ }
191
+ for (const m of source.matchAll(/<(?:main|body)\b[^>]*className=\{?["'`]([^"'`]+)["'`]/g)) {
192
+ for (const c of m[1].split(/\s+/)) {
193
+ const dark = /^dark:bg-([a-z][a-z0-9-]*)$/.exec(c);
194
+ const light = /^bg-([a-z][a-z0-9-]*)$/.exec(c);
195
+ if (dark)
196
+ addPagePaint(into, dark[1], "dark", "main-or-body");
197
+ else if (light)
198
+ addPagePaint(into, light[1], "light", "main-or-body");
199
+ }
200
+ }
155
201
  ANY_IMPORT.lastIndex = 0;
156
202
  const seen = new Set();
157
203
  for (const m of source.matchAll(ANY_IMPORT)) {
@@ -267,6 +313,31 @@ export function describeSignals(s) {
267
313
  if (t.htmlCarries.length > 0) {
268
314
  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.`);
269
315
  }
316
+ /**
317
+ * The page, said as evidence with the SOURCE named - "2 full-viewport containers"
318
+ * is a claim somebody can weigh against "1 main". Strongest first per scheme.
319
+ */
320
+ const strength = {
321
+ "global-css": 3,
322
+ "main-or-body": 2,
323
+ "viewport-container": 1,
324
+ };
325
+ for (const scheme of ["dark", "light"]) {
326
+ const paints = t.page
327
+ .filter((p) => p.scheme === scheme)
328
+ .sort((a, b) => strength[b.source] - strength[a.source] || b.count - a.count);
329
+ if (paints.length === 0)
330
+ continue;
331
+ const label = {
332
+ "global-css": "body in global CSS",
333
+ "main-or-body": "on <main>/<body>",
334
+ "viewport-container": "full-viewport container",
335
+ };
336
+ out.push(`the ${scheme} page paints ${paints
337
+ .slice(0, 3)
338
+ .map((p) => `\`${p.token}\` (${p.count}x ${label[p.source]})`)
339
+ .join(", ")} - the canvas question, answered from the files rather than hunted.`);
340
+ }
270
341
  for (const family of ["primitives", "motion", "icons", "charts", "editors"]) {
271
342
  const found = s.libraries.filter((l) => l.family === family);
272
343
  if (found.length === 0)
@@ -251,12 +251,29 @@ html carries lang="en" data-theme="dark" - which is the ancestor every dark rule
251
251
  That is a real run. Your job is to CONFIRM OR CORRECT it, not to reproduce it - and a sweep
252
252
  would bring back a different slice than the next one, which is the worse property of the two.
253
253
 
254
+ **Confirmation costs ZERO reads.** A real run spent 7 pattern searches and 39 file reads to
255
+ "confirm" a theme the census had already counted (test14, 01/08) - that is re-deriving, not
256
+ confirming. The rule: the census numbers are TRUE unless something you already read while
257
+ doing other work contradicts them. Only on a contradiction do you open a file, and only the
258
+ one file the evidence names. If nothing contradicts, write the census's answer down and move
259
+ on - it costs one sentence.
260
+
254
261
  \`has: ["dark"]\` on a dark-only product is the right answer. It is better than filling a light
255
262
  slot on their behalf, and it makes adding light a deliberate act they take later.
256
263
 
257
264
  **\`roles\` - which of their values carries which meaning**, when their token names do not say.
258
265
  A project naming its neutrals by temperature (\`--color-darkgray-900\`) has told you nothing
259
- about which one is the page. Read a screen and see.
266
+ about which one is the page - and the census now answers that too, under "the page paints":
267
+
268
+ \`\`\`
269
+ the dark page paints darkgray-500 (2x on <main>/<body>, 2x full-viewport container)
270
+ the light page paints #EDF3FA (1x body in global CSS), lightgray-200 (2x viewport)
271
+ \`\`\`
272
+
273
+ Three countable sources, strongest first: \`body { background }\` in global CSS, a background
274
+ on \`<main>\`/\`<body>\`, and a full-viewport container (\`min-h-screen\` + a background IS the
275
+ page). **Do not hunt for the canvas** - pick from this evidence, and name the source when you
276
+ report it. When the sources disagree, that disagreement is itself the finding to surface.
260
277
 
261
278
  **\`fonts\` and \`concept\`** - the voice, and one paragraph on what this product is. The concept
262
279
  feeds every recommendation downstream, so a real one beats a generic one by a wide margin.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.89",
3
+ "version": "0.16.90",
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": {