synthesisui 0.16.53 → 0.16.55

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.
@@ -317,7 +317,25 @@ export async function takeCensus(root) {
317
317
  },
318
318
  propFiles: {},
319
319
  }));
320
- const merged = [...inventory, ...fromTypes];
320
+ /**
321
+ * A composed component that is ALSO defined keeps its usage and gains the
322
+ * axes its type declares - the declaration wins per axis, because it is the
323
+ * closed set the author designed.
324
+ *
325
+ * Without this the usage silently truncated the contract: `MetricCard` is
326
+ * composed once with `size="xsm"`, so its axis had ONE option and was dropped
327
+ * by the two-option rule, while its type declares four sizes and three
328
+ * variants (dono, 31/07). The component would have reached the platform with
329
+ * no contract at all, which is the exact thing reading definitions was for.
330
+ */
331
+ const declaredBy = new Map(defined.map((d) => [d.name, d]));
332
+ const enriched = inventory.map((c) => {
333
+ const d = declaredBy.get(c.name);
334
+ if (!d || Object.keys(d.axes).length === 0)
335
+ return c;
336
+ return { ...c, props: { ...c.props, ...d.axes } };
337
+ });
338
+ const merged = [...enriched, ...fromTypes];
321
339
  // The verdict travels WITH the payload: the platform reads one reading rather
322
340
  // than computing a second opinion from the same numbers, which is how two
323
341
  // implementations of the same judgement start disagreeing.
@@ -388,9 +406,13 @@ function summarize(c) {
388
406
  // The inventory, phase one: what this project composes. Printed, never
389
407
  // mapped - deciding that their `Pill` is our `ds-badge` is a judgement a
390
408
  // person approves, and it does not happen in a census.
391
- if (c.components && c.components.length > 0) {
392
- const withProps = c.components.filter((x) => Object.keys(x.props).length > 0).length;
393
- console.log(body(`${c.components.length} components composed here, ${withProps} of them with props written literally`));
409
+ // COMPOSED means composed. The payload also carries components that are only
410
+ // DECLARED (a library exports what it never uses), and counting those here
411
+ // made the line say 108 where 80 things are actually composed (dono, 31/07).
412
+ const composedOnly = (c.components ?? []).filter((x) => x.count > 0);
413
+ if (composedOnly.length > 0) {
414
+ const withProps = composedOnly.filter((x) => Object.keys(x.props).length > 0).length;
415
+ console.log(body(`${composedOnly.length} components composed here, ${withProps} of them with props written literally`));
394
416
  }
395
417
  if (c.totals.named > 0) {
396
418
  console.log(body(`${paint.strong(String(c.totals.named))} of those values ALREADY have a name in your system - ${c.totals.coverage}% of your design values come from it today`));
@@ -422,7 +444,7 @@ function printComponents(c) {
422
444
  if (list.length === 0)
423
445
  return;
424
446
  console.log("");
425
- const mine = list.filter((x) => !x.from);
447
+ const mine = list.filter((x) => !x.from && x.count > 0);
426
448
  const theirs = list.filter((x) => x.from);
427
449
  console.log(section("What this project composes"));
428
450
  if (theirs.length > 0) {
@@ -439,7 +461,7 @@ function printComponents(c) {
439
461
  if (mine.length > 12) {
440
462
  console.log(body(paint.faint(`(${mine.length - 12} more of yours)`)));
441
463
  }
442
- printCrosswalk(mine);
464
+ printCrosswalk(list.filter((x) => !x.from));
443
465
  printObserved(mine);
444
466
  printDefined(c, mine);
445
467
  }
@@ -468,7 +490,10 @@ function printDefined(census, used) {
468
490
  if (withAxes.length > 8) {
469
491
  console.log(body(paint.faint(` (${withAxes.length - 8} more)`)));
470
492
  }
471
- const crossed = reconcile(defined, used);
493
+ // Orphans are found by asking what has no USAGE, so this has to see the
494
+ // composed list alone: handing it the union made every declared component
495
+ // look used by itself, and 29 orphans became 1.
496
+ const crossed = reconcile(defined, used.filter((u) => u.count > 0));
472
497
  const dead = crossed.filter((r) => r.deadOptions.length > 0);
473
498
  const stray = crossed.filter((r) => r.undeclared.length > 0);
474
499
  const orphans = crossed.filter((r) => r.orphan);
@@ -238,6 +238,28 @@ export const TWIN_OVERLAP = 0.5;
238
238
  * the colour work learned twice: a ratio over a tiny set says nothing.
239
239
  */
240
240
  export const TWIN_MIN_SHARED = 2;
241
+ /**
242
+ * Axes that measure rather than mean.
243
+ *
244
+ * Every component has a size, and `sm|md|lg` is the same three words in all of
245
+ * them - so a shared size scale is a shared SCALE, never a shared purpose. It
246
+ * paired a metric card with a text input at 50% (dono, 31/07), which is the
247
+ * kind of finding that teaches a person to stop reading the list.
248
+ *
249
+ * `intent`, `tone`, `status` and their kin are what say what a thing IS, and a
250
+ * pair needs at least one of those in common before its sizes count for
251
+ * anything.
252
+ */
253
+ const DIMENSIONAL = new Set(["size", "density"]);
254
+ /**
255
+ * Values that are the absence of a choice.
256
+ *
257
+ * `default` and `neutral` appear on almost every axis and mean "nothing in
258
+ * particular", so sharing them is not agreement. With sizes excluded, a metric
259
+ * card and a text input still paired because both offer a `default` variant
260
+ * (dono, 31/07) - the same false positive one level down.
261
+ */
262
+ const UNCHOSEN = new Set(["neutral", "default", "none", "normal", "base"]);
241
263
  function sharedCount(a, b) {
242
264
  const va = new Set();
243
265
  const vb = new Set();
@@ -259,6 +281,29 @@ function sharedCount(a, b) {
259
281
  n += 1;
260
282
  return n;
261
283
  }
284
+ /** Shared values on an axis that MEANS something, not one that measures. */
285
+ function sharedMeaning(a, b) {
286
+ const pick = (x) => {
287
+ const out = new Set();
288
+ for (const [p, values] of Object.entries(x)) {
289
+ const axis = AXIS_SYNONYM[p.toLowerCase()];
290
+ if (!axis || DIMENSIONAL.has(axis))
291
+ continue;
292
+ for (const v of canonicalValues(values)) {
293
+ if (!UNCHOSEN.has(v))
294
+ out.add(v);
295
+ }
296
+ }
297
+ return out;
298
+ };
299
+ const va = pick(a);
300
+ const vb = pick(b);
301
+ let n = 0;
302
+ for (const v of va)
303
+ if (vb.has(v))
304
+ n += 1;
305
+ return n;
306
+ }
262
307
  export function crosswalk(components) {
263
308
  const mine = components.filter((c) => !c.from);
264
309
  // Two of THEIR components reading as the same catalogue entry is the
@@ -294,7 +339,8 @@ export function crosswalk(components) {
294
339
  overlap: axisOverlap(component.props, o.props),
295
340
  }))
296
341
  .filter((t) => t.overlap >= TWIN_OVERLAP &&
297
- sharedCount(component.props, mine.find((m) => m.name === t.name)?.props ?? {}) >= TWIN_MIN_SHARED)
342
+ sharedCount(component.props, mine.find((m) => m.name === t.name)?.props ?? {}) >= TWIN_MIN_SHARED &&
343
+ sharedMeaning(component.props, mine.find((m) => m.name === t.name)?.props ?? {}) >= 1)
298
344
  .sort((a, b) => b.overlap - a.overlap);
299
345
  if (canonical && CATALOGUE[canonical]) {
300
346
  const alsoClaiming = (claims.get(canonical) ?? []).filter((n) => n !== component.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.53",
3
+ "version": "0.16.55",
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": {