synthesisui 0.16.125 → 0.16.127

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.
@@ -874,7 +874,23 @@ export async function takeCensus(root, opts) {
874
874
  * because every declared option now looked used.
875
875
  */
876
876
  const declaredBy = new Map(defined.map((d) => [d.name, d]));
877
- const enriched = inventory.map((c) => {
877
+ const enriched = inventory.map((c0) => {
878
+ /**
879
+ * A NAME THIS SCOPE DECLARES IS THEIRS, whoever else imports the same identifier.
880
+ *
881
+ * The usage scan reads imports per FILE, so a name gets the package of the file that
882
+ * imported it. Their `Pill` does `import { Toggle } from "@base-ui/react/toggle"`, and
883
+ * that name collides with the `Toggle` atom they wrote one folder over: the entry came
884
+ * out carrying `from: "@base-ui/react/toggle"`, and `from` is what makes the crosswalk
885
+ * skip a component and `exclusiveContracts` drop it. Their Toggle - 12 declarations, 3
886
+ * parts, `size` and `checked` - was absent from the whole system (test50, dono, 04/08).
887
+ *
888
+ * `defined` is a DEFINITION: a function of theirs that returns JSX. A scope that merely
889
+ * re-exports a library component under the same name is not in it, so a real
890
+ * third-party keeps its package. This is law 14's ordering, one level up: what they
891
+ * declare breaks the tie before the crosswalk is asked.
892
+ */
893
+ const c = declaredWins(c0, declaredBy.has(c0.name));
878
894
  const d = declaredBy.get(c.name);
879
895
  const fromVariants = variantAxes.get(c.name);
880
896
  if (!d && !fromVariants)
@@ -1901,6 +1917,19 @@ function sayReach(c) {
1901
1917
  * (a return the scanner did not follow) must say nothing rather than borrow the
1902
1918
  * first node's package.
1903
1919
  */
1920
+ /**
1921
+ * A NAME THIS SCOPE DECLARES IS THEIRS - see the call site in `takeCensus` for what it cost.
1922
+ *
1923
+ * Kept as a function so the rule has a name and a test: the usage scan is per FILE, so an
1924
+ * identifier takes the package of whoever imported it, and one of their own components can
1925
+ * shadow one of their own names. `declared` is a DEFINITION of theirs, which is what makes
1926
+ * this a tie-break rather than a preference.
1927
+ */
1928
+ export function declaredWins(component, declared) {
1929
+ return component.from && declared
1930
+ ? { ...component, from: undefined }
1931
+ : component;
1932
+ }
1904
1933
  function rootPackage(tag, sketch) {
1905
1934
  if (!tag || !/^[A-Z]/.test(tag))
1906
1935
  return {};
@@ -125,6 +125,79 @@ function elementFor(name, recipe) {
125
125
  }
126
126
  /** Variant axes → typed props. An axis whose options ⊆ {true,false} is a
127
127
  * boolean prop; empty axes (no visual effect) are skipped. */
128
+ /**
129
+ * THE AXES A PART'S OWN LAYERS READ, which is where an imported recipe keeps them.
130
+ *
131
+ * `axesOf(part.variants)` is right for a recipe we generated and empty for one we READ: their
132
+ * conditional look arrives as `layers` with `when.variant`, never as a per-part `variants`
133
+ * map. So a generated part component wrote no `data-status`, the compiled rule
134
+ * `.ds-card-status-label[data-status="waiting"]` matched nothing, and the status colours of
135
+ * their card - the whole point of that component - appeared nowhere. 37 conditions across 7 of
136
+ * their components (test49, dono, 04/08).
137
+ *
138
+ * The option list comes from the COMPONENT's `variants`, because that is where a read recipe
139
+ * declares them; the part only names which axes it reacts to.
140
+ */
141
+ function layerAxes(part, variants) {
142
+ const named = new Set();
143
+ for (const layer of part.layers ?? [])
144
+ for (const key of Object.keys(layer.when?.variant ?? {}))
145
+ if (variants?.[key])
146
+ named.add(key);
147
+ /**
148
+ * BUILT HERE RATHER THAN THROUGH `axesOf`, and that is the whole reason this was invisible.
149
+ *
150
+ * `axesOf` keeps an option only when its style block carries declarations, which is right
151
+ * for a recipe we generated - there the option IS the styles. A recipe we READ declares
152
+ * `variants.status.waiting = {}` and puts every declaration in `layers`, so `axesOf`
153
+ * answered "no axes" for a component with four of them.
154
+ */
155
+ /**
156
+ * A CONDITION ON SOMETHING THAT IS NOT AN AXIS IS STILL A LOOK SOMEBODY HAS TO TRIGGER.
157
+ *
158
+ * Their chevron rotates while the card is closed, keyed on `isOpen` - state the component
159
+ * holds, so no axis and no option list. It becomes a boolean prop on the generated part, the
160
+ * same shape the usage snippet emits, because otherwise the rule ships and the chevron never
161
+ * turns.
162
+ */
163
+ const boolish = new Map();
164
+ for (const layer of part.layers ?? [])
165
+ for (const [key, option] of Object.entries(layer.when?.variant ?? {})) {
166
+ if (variants?.[key])
167
+ continue;
168
+ boolish.set(key, (boolish.get(key) ?? new Set()).add(String(option)));
169
+ }
170
+ const out = [];
171
+ for (const [key, seen] of boolish)
172
+ out.push({
173
+ key,
174
+ prop: camel(key),
175
+ attr: kebab(key),
176
+ boolean: true,
177
+ styledFalse: seen.has("false"),
178
+ options: [...seen],
179
+ });
180
+ for (const key of named) {
181
+ const options = Object.keys(variants?.[key] ?? {});
182
+ if (options.length === 0)
183
+ continue;
184
+ out.push({
185
+ key,
186
+ prop: camel(key),
187
+ attr: kebab(key),
188
+ boolean: options.every((o) => o === "true" || o === "false"),
189
+ styledFalse: options.includes("false"),
190
+ options,
191
+ });
192
+ }
193
+ return out;
194
+ }
195
+ /** The axes of a part: the ones it declares, plus the ones its layers read. */
196
+ function partAxesOf(part, variants) {
197
+ const own = axesOf(part.variants ?? {});
198
+ const keys = new Set(own.map((a) => a.key));
199
+ return [...own, ...layerAxes(part, variants).filter((a) => !keys.has(a.key))];
200
+ }
128
201
  function axesOf(variants) {
129
202
  const axes = [];
130
203
  for (const [axis, options] of Object.entries(variants ?? {})) {
@@ -736,7 +809,7 @@ localName = name) {
736
809
  const part = recipe.parts?.[partName];
737
810
  if (!part)
738
811
  return "";
739
- const partAxes = axesOf(part.variants ?? {});
812
+ const partAxes = partAxesOf(part, recipe.variants);
740
813
  const partComp = `${comp}${pascal(partName)}`;
741
814
  const { tag: partTag, attrs: partAttrs, voidEl: partVoid, } = partTagFor(part, formOf.get(partName));
742
815
  const partEl = asElement(partTag, partAttrs, partVoid);
@@ -873,7 +946,7 @@ ${orderedParts
873
946
  if (!part)
874
947
  return "";
875
948
  const partComp = `${comp}${pascal(partName)}`;
876
- const partAxes = axesOf(part.variants ?? {});
949
+ const partAxes = partAxesOf(part, recipe.variants);
877
950
  const { tag: partTag, attrs: partAttrs, voidEl: partVoid, } = partTagFor(part, formOf.get(partName));
878
951
  const partEl = asElement(partTag, partAttrs, partVoid);
879
952
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.125",
3
+ "version": "0.16.127",
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": {