synthesisui 0.16.50 → 0.16.51

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.
@@ -3,7 +3,7 @@ import { basename, join, relative } from "node:path";
3
3
  import { readToken, resolveRegistry } from "../config.js";
4
4
  import { isNearDuplicate } from "../doctor/color-distance.js";
5
5
  import { emptyTally, internalSpecifiers, scanComponentsInto, tallyToInventory, } from "../doctor/components-scan.js";
6
- import { crosswalk } from "../doctor/crosswalk.js";
6
+ import { crosswalk, observedRules } from "../doctor/crosswalk.js";
7
7
  import { diagnose, scanSource } from "../doctor/scan.js";
8
8
  import { parseSchemeBlocks } from "../doctor/scheme-blocks.js";
9
9
  import { buildTable } from "../doctor/tokens.js";
@@ -293,9 +293,14 @@ export async function takeCensus(root) {
293
293
  r.component.name,
294
294
  { bucket: r.bucket, canonical: r.canonical, because: r.because },
295
295
  ]));
296
+ const laws = new Map();
297
+ for (const r of observedRules(inventory)) {
298
+ laws.set(r.component, [...(laws.get(r.component) ?? []), r.text]);
299
+ }
296
300
  const components = inventory.map((c) => ({
297
301
  ...c,
298
302
  ...(verdicts.get(c.name) ?? {}),
303
+ ...(laws.has(c.name) ? { laws: laws.get(c.name) } : {}),
299
304
  }));
300
305
  const pkgRaw = await readFile(join(root, "package.json"), "utf8").catch(() => null);
301
306
  let name = null;
@@ -402,6 +407,29 @@ function printComponents(c) {
402
407
  console.log(body(paint.faint(`(${mine.length - 12} more of yours)`)));
403
408
  }
404
409
  printCrosswalk(mine);
410
+ printObserved(mine);
411
+ }
412
+ /**
413
+ * Laws their code already obeys, printed with the evidence that found them.
414
+ *
415
+ * Not applied and not sent as decisions - they ride along on the contract as
416
+ * `usage`, which is where a law about one component belongs and what the GUIDE
417
+ * and the build script already read.
418
+ */
419
+ function printObserved(mine) {
420
+ const rules = observedRules(mine);
421
+ if (rules.length === 0)
422
+ return;
423
+ console.log("");
424
+ console.log(section("Laws your code already obeys"));
425
+ console.log(body(paint.faint("A prop with one value across many files is not a choice being made - it is a rule nobody wrote down.")));
426
+ console.log("");
427
+ for (const r of rules.slice(0, 8)) {
428
+ console.log(body(` ${paint.strong(r.component)} ${paint.dim(r.text.replace(/^Always /, "always "))}`));
429
+ }
430
+ if (rules.length > 8) {
431
+ console.log(body(paint.faint(` (${rules.length - 8} more)`)));
432
+ }
405
433
  }
406
434
  /**
407
435
  * The crosswalk, printed and nothing else.
@@ -106,6 +106,7 @@ internal = []) {
106
106
  count: 0,
107
107
  files: new Set(),
108
108
  props: new Map(),
109
+ propFiles: new Map(),
109
110
  ...(owner ? { from: owner } : {}),
110
111
  };
111
112
  tally.set(name, hit);
@@ -122,6 +123,9 @@ internal = []) {
122
123
  const bucket = hit.props.get(prop) ?? new Map();
123
124
  bucket.set(value, (bucket.get(value) ?? 0) + 1);
124
125
  hit.props.set(prop, bucket);
126
+ const seenIn = hit.propFiles.get(prop) ?? new Set();
127
+ seenIn.add(file);
128
+ hit.propFiles.set(prop, seenIn);
125
129
  }
126
130
  // Bare booleans, minus anything already read as a valued prop.
127
131
  // Strip the WHOLE pair, name included: stripping only the value left
@@ -138,6 +142,9 @@ internal = []) {
138
142
  const bucket = hit.props.get(prop) ?? new Map();
139
143
  bucket.set("true", (bucket.get("true") ?? 0) + 1);
140
144
  hit.props.set(prop, bucket);
145
+ const seenIn = hit.propFiles.get(prop) ?? new Set();
146
+ seenIn.add(file);
147
+ hit.propFiles.set(prop, seenIn);
141
148
  }
142
149
  }
143
150
  }
@@ -149,6 +156,7 @@ export function tallyToInventory(tally, max = 80) {
149
156
  ...(v.from ? { from: v.from } : {}),
150
157
  count: v.count,
151
158
  files: v.files.size,
159
+ propFiles: Object.fromEntries([...v.propFiles.entries()].map(([p, files]) => [p, files.size])),
152
160
  props: Object.fromEntries([...v.props.entries()].map(([p, values]) => [
153
161
  p,
154
162
  [...values.entries()]
@@ -337,3 +337,41 @@ export function crosswalk(components) {
337
337
  })
338
338
  .sort((a, b) => b.component.files - a.component.files);
339
339
  }
340
+ /** Below this a repetition is a coincidence rather than a habit. */
341
+ export const RULE_MIN_FILES = 3;
342
+ export function observedRules(components) {
343
+ const out = [];
344
+ for (const c of components) {
345
+ if (c.from)
346
+ continue;
347
+ for (const [prop, values] of Object.entries(c.props)) {
348
+ if (values.length !== 1)
349
+ continue;
350
+ const value = values[0];
351
+ // A law is about a DESIGN AXIS or a flag. `marginBottom="1em"` and
352
+ // `textAlign="center"` are CSS escaping through a prop - a real finding,
353
+ // and a different one, but writing "always textAlign=center" as a law of
354
+ // the component is nonsense (dono, 31/07).
355
+ const isAxis = AXIS_SYNONYM[prop.toLowerCase()] != null;
356
+ if (!isAxis && value !== "true")
357
+ continue;
358
+ // The prop's OWN reach, never the component's: `Text` lives in 66 files
359
+ // and `textAlign` may appear in one of them, and the first version
360
+ // claimed all 66 agreed.
361
+ const files = c.propFiles?.[prop] ?? 0;
362
+ if (files < RULE_MIN_FILES)
363
+ continue;
364
+ out.push({
365
+ component: c.name,
366
+ prop,
367
+ value,
368
+ files,
369
+ count: c.count,
370
+ text: value === "true"
371
+ ? `Always set ${prop} - every one of the ${files} files that passes it does.`
372
+ : `Always ${prop}="${value}" - all ${files} files that pass it agree.`,
373
+ });
374
+ }
375
+ }
376
+ return out.sort((a, b) => b.files - a.files || b.count - a.count);
377
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.50",
3
+ "version": "0.16.51",
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": {